Skip to content

Commit a22041c

Browse files
committed
Fix form url encoding when charset is not UTF-8, close #1444
Motivation: form urlencoding doesn’t properly honor charset. It uses it for converting the bytes while those are supposed to be already in the US-ASCII range. It should be using it the first encode into bytes, which should be then escaped. Modifications: Use current optimized code for UTF-8 and fall back to URLEncoder for other charsets. Results: Proper encoding when charset is different from UTF-8, eg GBK
1 parent a740990 commit a22041c

File tree

2 files changed

+68
-14
lines changed

2 files changed

+68
-14
lines changed

client/src/main/java/org/asynchttpclient/util/HttpUtils.java

+25-10
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
*/
1313
package org.asynchttpclient.util;
1414

15-
import static java.nio.charset.StandardCharsets.ISO_8859_1;
15+
import static java.nio.charset.StandardCharsets.*;
1616
import static org.asynchttpclient.util.MiscUtils.isNonEmpty;
1717

18+
import java.io.UnsupportedEncodingException;
19+
import java.net.URLEncoder;
1820
import java.nio.ByteBuffer;
1921
import java.nio.charset.Charset;
2022
import java.util.List;
@@ -82,28 +84,41 @@ public static boolean followRedirect(AsyncHttpClientConfig config, Request reque
8284
return request.getFollowRedirect() != null ? request.getFollowRedirect() : config.isFollowRedirect();
8385
}
8486

85-
private static StringBuilder urlEncodeFormParams0(List<Param> params) {
87+
public static ByteBuffer urlEncodeFormParams(List<Param> params, Charset charset) {
88+
return StringUtils.charSequence2ByteBuffer(urlEncodeFormParams0(params, charset), US_ASCII);
89+
}
90+
91+
private static StringBuilder urlEncodeFormParams0(List<Param> params, Charset charset) {
8692
StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder();
8793
for (Param param : params) {
88-
encodeAndAppendFormParam(sb, param.getName(), param.getValue());
94+
encodeAndAppendFormParam(sb, param.getName(), param.getValue(), charset);
8995
}
9096
sb.setLength(sb.length() - 1);
9197
return sb;
9298
}
9399

94-
public static ByteBuffer urlEncodeFormParams(List<Param> params, Charset charset) {
95-
return StringUtils.charSequence2ByteBuffer(urlEncodeFormParams0(params), charset);
96-
}
97-
98-
private static void encodeAndAppendFormParam(final StringBuilder sb, final CharSequence name, final CharSequence value) {
99-
Utf8UrlEncoder.encodeAndAppendFormElement(sb, name);
100+
private static void encodeAndAppendFormParam(StringBuilder sb, String name, String value, Charset charset) {
101+
encodeAndAppendFormField(sb, name, charset);
100102
if (value != null) {
101103
sb.append('=');
102-
Utf8UrlEncoder.encodeAndAppendFormElement(sb, value);
104+
encodeAndAppendFormField(sb, value, charset);
103105
}
104106
sb.append('&');
105107
}
106108

109+
private static void encodeAndAppendFormField(StringBuilder sb, String field, Charset charset) {
110+
if (charset.equals(UTF_8)) {
111+
Utf8UrlEncoder.encodeAndAppendFormElement(sb, field);
112+
} else {
113+
try {
114+
// TODO there's probably room for perf improvements
115+
sb.append(URLEncoder.encode(field, charset.name()));
116+
} catch (UnsupportedEncodingException e) {
117+
// can't happen, as Charset was already resolved
118+
}
119+
}
120+
}
121+
107122
public static String hostHeader(Request request, Uri uri) {
108123
String virtualHost = request.getVirtualHost();
109124
if (virtualHost != null)

client/src/test/java/org/asynchttpclient/util/HttpUtilsTest.java

+43-4
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,23 @@
1313
*/
1414
package org.asynchttpclient.util;
1515

16+
import static java.nio.charset.StandardCharsets.*;
1617
import static org.testng.Assert.*;
18+
import io.netty.buffer.ByteBuf;
19+
import io.netty.buffer.Unpooled;
1720

21+
import java.net.URLEncoder;
22+
import java.nio.ByteBuffer;
23+
import java.nio.charset.CharacterCodingException;
1824
import java.nio.charset.Charset;
19-
import java.nio.charset.StandardCharsets;
25+
import java.util.ArrayList;
26+
import java.util.List;
2027

2128
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
2229
import org.asynchttpclient.Dsl;
30+
import org.asynchttpclient.Param;
2331
import org.asynchttpclient.Request;
32+
import org.asynchttpclient.netty.util.ByteBufUtils;
2433
import org.asynchttpclient.uri.Uri;
2534
import org.testng.annotations.Test;
2635

@@ -92,19 +101,19 @@ public void testIsSameBaseUrlReturnsTrueWhenOneUriHasDefaultPort() {
92101
@Test
93102
public void testParseCharsetWithoutQuotes() {
94103
Charset charset = HttpUtils.parseCharset("Content-type: application/json; charset=utf-8");
95-
assertEquals(charset, StandardCharsets.UTF_8, "parseCharset returned wrong Charset");
104+
assertEquals(charset, UTF_8, "parseCharset returned wrong Charset");
96105
}
97106

98107
@Test
99108
public void testParseCharsetWithSingleQuotes() {
100109
Charset charset = HttpUtils.parseCharset("Content-type: application/json; charset='utf-8'");
101-
assertEquals(charset, StandardCharsets.UTF_8, "parseCharset returned wrong Charset");
110+
assertEquals(charset, UTF_8, "parseCharset returned wrong Charset");
102111
}
103112

104113
@Test
105114
public void testParseCharsetWithDoubleQuotes() {
106115
Charset charset = HttpUtils.parseCharset("Content-type: application/json; charset=\"utf-8\"");
107-
assertEquals(charset, StandardCharsets.UTF_8, "parseCharset returned wrong Charset");
116+
assertEquals(charset, UTF_8, "parseCharset returned wrong Charset");
108117
}
109118

110119
@Test
@@ -160,4 +169,34 @@ public void testGetFollowRedirectPriorityGivenToRequest() {
160169
boolean followRedirect = HttpUtils.followRedirect(config, request);
161170
assertFalse(followRedirect, "Follow redirect value set in request should be given priority");
162171
}
172+
173+
private void formUrlEncoding(Charset charset) throws Exception {
174+
String key = "key";
175+
String value = "中文";
176+
List<Param> params = new ArrayList<>();
177+
params.add(new Param(key, value));
178+
ByteBuffer ahcBytes = HttpUtils.urlEncodeFormParams(params, charset);
179+
String ahcString = toUsAsciiString(ahcBytes);
180+
String jdkString = key + "=" + URLEncoder.encode(value, charset.name());
181+
assertEquals(ahcString, jdkString);
182+
}
183+
184+
@Test
185+
public void formUrlEncodingShouldSupportUtf8Charset() throws Exception {
186+
formUrlEncoding(UTF_8);
187+
}
188+
189+
@Test
190+
public void formUrlEncodingShouldSupportNonUtf8Charset() throws Exception {
191+
formUrlEncoding(Charset.forName("GBK"));
192+
}
193+
194+
private static String toUsAsciiString(ByteBuffer buf) throws CharacterCodingException {
195+
ByteBuf bb = Unpooled.wrappedBuffer(buf);
196+
try {
197+
return ByteBufUtils.byteBuf2String(US_ASCII, bb);
198+
} finally {
199+
bb.release();
200+
}
201+
}
163202
}

0 commit comments

Comments
 (0)