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}处理规则如下:
+ *
+ * - 如果key为{@code null},则这个键值对忽略
+ * - 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
+ *
+ *
+ * @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());
}
+
}