diff --git a/CHANGELOG.md b/CHANGELOG.md index 370dee44b..f1c5df6e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.16 (2021-11-04) +# 5.7.16 (2021-11-07) ### 🐣新特性 * 【core 】 增加DateTime.toLocalDateTime @@ -35,6 +35,7 @@ * 【core 】 修复CompilerUtil.getFileManager参数没有使用的问题(issue#I4FIO6@Gitee) * 【core 】 修复NetUtil.isInRange的cidr判断问题(pr#1917@Github) * 【core 】 修复RegexPool中对URL正则匹配问题(issue#I4GRKD@Gitee) +* 【core 】 修复UrlQuery对于application/x-www-form-urlencoded问题(issue#1931@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java index 3cb1a51d5..71ae0ec8b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java @@ -179,6 +179,7 @@ public class PercentCodec implements Serializable { continue; } + // 兼容双字节的Unicode符处理(如部分emoji) byte[] ba = buf.toByteArray(); for (byte toEncode : ba) { // Converting each byte in the buffer diff --git a/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java b/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java index 5de8513f2..1b41f023e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java @@ -11,15 +11,9 @@ import cn.hutool.core.codec.PercentCodec; public class FormUrlencoded { /** - * query中的value
- * value不能包含"{@code &}",可以包含 "=" + * query中的value,默认除"-", "_", ".", "*"外都编码
+ * 这个类似于JDK提供的{@link java.net.URLEncoder} */ - public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(RFC3986.QUERY_PARAM_VALUE) - .setEncodeSpaceAsPlus(true).removeSafe('+'); - - /** - * query中的key
- * key不能包含"{@code &}" 和 "=" - */ - public static final PercentCodec QUERY_PARAM_NAME = QUERY_PARAM_VALUE.removeSafe('='); + public static final PercentCodec ALL = PercentCodec.of(RFC3986.UNRESERVED) + .removeSafe('~').addSafe('*').setEncodeSpaceAsPlus(true); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java index a40764932..39e563ea3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java @@ -73,7 +73,7 @@ public class RFC3986 { * query中的key
* key不能包含"{@code &}" 和 "=" */ - public static final PercentCodec QUERY_PARAM_NAME = QUERY_PARAM_VALUE.removeSafe('='); + public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.of(QUERY_PARAM_VALUE).removeSafe('='); /** * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java index f90609e0d..cbed10e1e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java @@ -13,6 +13,7 @@ import java.util.BitSet; /** * URL编码,数据内容的类型是 application/x-www-form-urlencoded。 + * TODO 6.x移除此类,使用PercentCodec代替(无法很好区分URL编码和www-form编码) * *
  * 1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不会被编码;
@@ -21,6 +22,7 @@ import java.util.BitSet;
  * 
* * @author looly + * @see cn.hutool.core.codec.PercentCodec */ public class URLEncoder implements Serializable { private static final long serialVersionUID = 1L; diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index 536b08c0d..2476f578c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -1,5 +1,6 @@ package cn.hutool.core.net.url; +import cn.hutool.core.codec.PercentCodec; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.IterUtil; import cn.hutool.core.convert.Convert; @@ -177,10 +178,28 @@ public class UrlQuery { *
  • 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
  • * * - * @param charset encode编码,null表示不做encode编码 + * @param charset encode编码,null表示不做encode编码 * @return URL查询字符串 */ public String build(Charset charset) { + return build(RFC3986.QUERY_PARAM_NAME, RFC3986.QUERY_PARAM_VALUE, charset); + } + + /** + * 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
    + * 对于{@code null}处理规则如下: + * + * + * @param keyCoder 键值对中键的编码器 + * @param valueCoder 键值对中值的编码器 + * @param charset encode编码,null表示不做encode编码 + * @return URL查询字符串 + * @since 5.7.16 + */ + public String build(PercentCodec keyCoder, PercentCodec valueCoder, Charset charset) { if (MapUtil.isEmpty(this.query)) { return StrUtil.EMPTY; } @@ -191,13 +210,13 @@ public class UrlQuery { for (Map.Entry entry : this.query) { name = entry.getKey(); if (null != name) { - if(sb.length() >0){ + if (sb.length() > 0) { sb.append("&"); } - sb.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)); + sb.append(keyCoder.encode(name, charset)); value = entry.getValue(); if (null != value) { - sb.append("=").append(RFC3986.QUERY_PARAM_VALUE.encode(value, charset)); + sb.append("=").append(valueCoder.encode(value, charset)); } } } @@ -213,8 +232,8 @@ public class UrlQuery { * 解析URL中的查询字符串
    * 规则见:https://url.spec.whatwg.org/#urlencoded-parsing * - * @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3 - * @param charset decode编码,null表示不做decode + * @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3 + * @param charset decode编码,null表示不做decode * @return this * @since 5.5.8 */ diff --git a/hutool-core/src/test/java/cn/hutool/core/net/FormUrlencodedTest.java b/hutool-core/src/test/java/cn/hutool/core/net/FormUrlencodedTest.java new file mode 100644 index 000000000..8006dbf9c --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/net/FormUrlencodedTest.java @@ -0,0 +1,17 @@ +package cn.hutool.core.net; + +import cn.hutool.core.util.CharsetUtil; +import org.junit.Assert; +import org.junit.Test; + +public class FormUrlencodedTest { + + @Test + public void encodeParamTest(){ + String encode = FormUrlencoded.ALL.encode("a+b", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a%2Bb", encode); + + encode = FormUrlencoded.ALL.encode("a b", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a+b", encode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/RFC3986Test.java b/hutool-core/src/test/java/cn/hutool/core/net/RFC3986Test.java new file mode 100644 index 000000000..c76f8134d --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/net/RFC3986Test.java @@ -0,0 +1,14 @@ +package cn.hutool.core.net; + +import cn.hutool.core.util.CharsetUtil; +import org.junit.Assert; +import org.junit.Test; + +public class RFC3986Test { + + @Test + public void encodeQueryTest(){ + final String encode = RFC3986.QUERY_PARAM_VALUE.encode("a=b", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a=b", encode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/URLEncoderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/URLEncoderTest.java deleted file mode 100644 index b59e0bd80..000000000 --- a/hutool-core/src/test/java/cn/hutool/core/net/URLEncoderTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.hutool.core.net; - -import cn.hutool.core.util.CharsetUtil; -import org.junit.Assert; -import org.junit.Test; - -public class URLEncoderTest { - - @Test - public void encodeTest(){ - String encode = URLEncoder.DEFAULT.encode("+", CharsetUtil.CHARSET_UTF_8); - Assert.assertEquals("+", encode); - - encode = URLEncoder.DEFAULT.encode(" ", CharsetUtil.CHARSET_UTF_8); - Assert.assertEquals("%20", encode); - } -} diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index e0135e77a..ae4e196f2 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -10,8 +10,10 @@ import cn.hutool.core.io.resource.MultiFileResource; import cn.hutool.core.io.resource.Resource; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.FormUrlencoded; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -1227,7 +1229,8 @@ public class HttpRequest extends HttpBase { * @since 5.3.2 */ private String getFormUrlEncoded() { - return HttpUtil.toParams(this.form, this.charset); + return UrlQuery.of(this.form) + .build(FormUrlencoded.ALL, FormUrlencoded.ALL, this.charset); } /** diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java index 2aabe1afe..c1cb54e9d 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java @@ -148,4 +148,5 @@ public class HttpRequestTest { HttpResponse execute = get.execute(); Console.log(execute.body()); } + }