From 2d9cc9f63beb5d0c381320ba18ae7b573d2612ee Mon Sep 17 00:00:00 2001 From: Courtney Robinson Date: Wed, 30 Jan 2013 17:02:33 +0000 Subject: [PATCH] Allow to specify the mode the encoder uses for form params. This allows it to be used with OAUTH The OAuth 1 spec has small deviations from UrlEncoder.encode's output. + Percent encodes the parameters + Added tests to verify + See relevant OAuth section http://oauth.net/core/1.0/#encoding_parameters + Detailed explanation http://hueniverse.com/oauth/guide/authentication/ --- .../multipart/HttpPostRequestEncoder.java | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java index 763fe6a487..bc794a3154 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java @@ -31,9 +31,12 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.ListIterator; +import java.util.Map; import java.util.Random; +import java.util.regex.Pattern; import static io.netty.buffer.Unpooled.*; @@ -41,6 +44,31 @@ import static io.netty.buffer.Unpooled.*; * This encoder will help to encode Request for a FORM as POST. */ public class HttpPostRequestEncoder implements ChunkedMessageInput { + + /** + * Different modes to use to encode form data. + */ + public enum EncoderMode { + /** + * Legacy mode which should work for most. It is known to not work with OAUTH. For OAUTH use + * {@link EncoderMode#RFC3986}. The W3C form recommentations this for submitting post form data. + */ + RFC1738, + + /** + * Mode which is more new and is used for OAUTH + */ + RFC3986 + } + + private static final Map percentEncodings = new HashMap(); + + static { + percentEncodings.put(Pattern.compile("\\*"), "%2A"); + percentEncodings.put(Pattern.compile("\\+"), "%20"); + percentEncodings.put(Pattern.compile("%7E"), "~"); + } + /** * Factory used to create InterfaceHttpData */ @@ -89,6 +117,8 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput */ private boolean headerFinalized; + private final EncoderMode encoderMode; + /** * * @param request @@ -102,7 +132,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput */ public HttpPostRequestEncoder(FullHttpRequest request, boolean multipart) throws ErrorDataEncoderException { this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, multipart, - HttpConstants.DEFAULT_CHARSET); + HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738); } /** @@ -120,7 +150,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput */ public HttpPostRequestEncoder(HttpDataFactory factory, FullHttpRequest request, boolean multipart) throws ErrorDataEncoderException { - this(factory, request, multipart, HttpConstants.DEFAULT_CHARSET); + this(factory, request, multipart, HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738); } /** @@ -133,13 +163,16 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput * True if the FORM is a ENCTYPE="multipart/form-data" * @param charset * the charset to use as default + * @param encoderMode + * the mode for the encoder to use. See {@link EncoderMode} for the details. * @throws NullPointerException * for request or charset or factory * @throws ErrorDataEncoderException * if the request is not a POST */ public HttpPostRequestEncoder( - HttpDataFactory factory, FullHttpRequest request, boolean multipart, Charset charset) + HttpDataFactory factory, FullHttpRequest request, boolean multipart, Charset charset, + EncoderMode encoderMode) throws ErrorDataEncoderException { if (factory == null) { throw new NullPointerException("factory"); @@ -163,6 +196,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput isLastChunkSent = false; isMultipart = multipart; multipartHttpDatas = new ArrayList(); + this.encoderMode = encoderMode; if (isMultipart) { initDataMultipart(); } @@ -688,12 +722,19 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput * @throws ErrorDataEncoderException * if the encoding is in error */ - private static String encodeAttribute(String s, Charset charset) throws ErrorDataEncoderException { + private String encodeAttribute(String s, Charset charset) throws ErrorDataEncoderException { if (s == null) { return ""; } try { - return URLEncoder.encode(s, charset.name()); + String encoded = URLEncoder.encode(s, charset.name()); + if (encoderMode == EncoderMode.RFC3986) { + for (Map.Entry entry : percentEncodings.entrySet()) { + String replacement = entry.getValue(); + encoded = entry.getKey().matcher(encoded).replaceAll(replacement); + } + } + return encoded; } catch (UnsupportedEncodingException e) { throw new ErrorDataEncoderException(charset.name(), e); }