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/
This commit is contained in:
Courtney Robinson 2013-01-30 17:02:33 +00:00 committed by Norman Maurer
parent b46760f93f
commit 2d9cc9f63b

View File

@ -31,9 +31,12 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.regex.Pattern;
import static io.netty.buffer.Unpooled.*; 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. * This encoder will help to encode Request for a FORM as POST.
*/ */
public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent> { public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent> {
/**
* 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<Pattern, String> percentEncodings = new HashMap<Pattern, String>();
static {
percentEncodings.put(Pattern.compile("\\*"), "%2A");
percentEncodings.put(Pattern.compile("\\+"), "%20");
percentEncodings.put(Pattern.compile("%7E"), "~");
}
/** /**
* Factory used to create InterfaceHttpData * Factory used to create InterfaceHttpData
*/ */
@ -89,6 +117,8 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
*/ */
private boolean headerFinalized; private boolean headerFinalized;
private final EncoderMode encoderMode;
/** /**
* *
* @param request * @param request
@ -102,7 +132,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
*/ */
public HttpPostRequestEncoder(FullHttpRequest request, boolean multipart) throws ErrorDataEncoderException { public HttpPostRequestEncoder(FullHttpRequest request, boolean multipart) throws ErrorDataEncoderException {
this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, multipart, this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, multipart,
HttpConstants.DEFAULT_CHARSET); HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738);
} }
/** /**
@ -120,7 +150,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
*/ */
public HttpPostRequestEncoder(HttpDataFactory factory, FullHttpRequest request, boolean multipart) public HttpPostRequestEncoder(HttpDataFactory factory, FullHttpRequest request, boolean multipart)
throws ErrorDataEncoderException { 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<HttpContent>
* True if the FORM is a ENCTYPE="multipart/form-data" * True if the FORM is a ENCTYPE="multipart/form-data"
* @param charset * @param charset
* the charset to use as default * the charset to use as default
* @param encoderMode
* the mode for the encoder to use. See {@link EncoderMode} for the details.
* @throws NullPointerException * @throws NullPointerException
* for request or charset or factory * for request or charset or factory
* @throws ErrorDataEncoderException * @throws ErrorDataEncoderException
* if the request is not a POST * if the request is not a POST
*/ */
public HttpPostRequestEncoder( public HttpPostRequestEncoder(
HttpDataFactory factory, FullHttpRequest request, boolean multipart, Charset charset) HttpDataFactory factory, FullHttpRequest request, boolean multipart, Charset charset,
EncoderMode encoderMode)
throws ErrorDataEncoderException { throws ErrorDataEncoderException {
if (factory == null) { if (factory == null) {
throw new NullPointerException("factory"); throw new NullPointerException("factory");
@ -163,6 +196,7 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
isLastChunkSent = false; isLastChunkSent = false;
isMultipart = multipart; isMultipart = multipart;
multipartHttpDatas = new ArrayList<InterfaceHttpData>(); multipartHttpDatas = new ArrayList<InterfaceHttpData>();
this.encoderMode = encoderMode;
if (isMultipart) { if (isMultipart) {
initDataMultipart(); initDataMultipart();
} }
@ -688,12 +722,19 @@ public class HttpPostRequestEncoder implements ChunkedMessageInput<HttpContent>
* @throws ErrorDataEncoderException * @throws ErrorDataEncoderException
* if the encoding is in error * 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) { if (s == null) {
return ""; return "";
} }
try { try {
return URLEncoder.encode(s, charset.name()); String encoded = URLEncoder.encode(s, charset.name());
if (encoderMode == EncoderMode.RFC3986) {
for (Map.Entry<Pattern, String> entry : percentEncodings.entrySet()) {
String replacement = entry.getValue();
encoded = entry.getKey().matcher(encoded).replaceAll(replacement);
}
}
return encoded;
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new ErrorDataEncoderException(charset.name(), e); throw new ErrorDataEncoderException(charset.name(), e);
} }