diff --git a/CHANGELOG.md b/CHANGELOG.md
index 842a1a27d..f1da89249 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
* 【core 】 修复Validator.isUrl()传空返回true(issue#I3ETTY@Gitee)
* 【db 】 修复数据库driver根据url的判断识别错误问题(issue#I3EWBI@Gitee)
* 【json 】 修复JSONStrFormatter换行多余空行问题(issue#I3FA8B@Gitee)
+* 【core 】 修复UrlPath中的+被转义为空格%20的问题(issue#1501@Github)
-------------------------------------------------------------------------------------------------------------
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java
index d2aac260d..19e0dd2ff 100644
--- a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java
+++ b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java
@@ -23,24 +23,84 @@ public class URLDecoder implements Serializable {
private static final byte ESCAPE_CHAR = '%';
+ /**
+ * 解码,不对+解码
+ *
+ * 1. 将%20转换为空格 ;
+ * 2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
+ * 3. 跳过不符合规范的%形式,直接输出
+ *
+ *
+ * @param str 包含URL编码后的字符串
+ * @param charset 编码
+ * @return 解码后的字符串
+ */
+ public static String decodeForPath(String str, Charset charset) {
+ return decode(str, charset, false);
+ }
+
/**
* 解码
+ *
+ * 1. 将+和%20转换为空格 ;
+ * 2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
+ * 3. 跳过不符合规范的%形式,直接输出
+ *
*
* @param str 包含URL编码后的字符串
* @param charset 编码
* @return 解码后的字符串
*/
public static String decode(String str, Charset charset) {
- return StrUtil.str(decode(StrUtil.bytes(str, charset)), charset);
+ return decode(str, charset, true);
}
/**
* 解码
+ *
+ * 1. 将%20转换为空格 ;
+ * 2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
+ * 3. 跳过不符合规范的%形式,直接输出
+ *
+ *
+ * @param str 包含URL编码后的字符串
+ * @param isPlusToSpace 是否+转换为空格
+ * @param charset 编码
+ * @return 解码后的字符串
+ */
+ public static String decode(String str, Charset charset, boolean isPlusToSpace) {
+ return StrUtil.str(decode(StrUtil.bytes(str, charset), isPlusToSpace), charset);
+ }
+
+ /**
+ * 解码
+ *
+ * 1. 将+和%20转换为空格 ;
+ * 2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
+ * 3. 跳过不符合规范的%形式,直接输出
+ *
*
* @param bytes url编码的bytes
* @return 解码后的bytes
*/
public static byte[] decode(byte[] bytes) {
+ return decode(bytes, true);
+ }
+
+ /**
+ * 解码
+ *
+ * 1. 将%20转换为空格 ;
+ * 2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
+ * 3. 跳过不符合规范的%形式,直接输出
+ *
+ *
+ * @param bytes url编码的bytes
+ * @param isPlusToSpace 是否+转换为空格
+ * @return 解码后的bytes
+ * @since 5.6.3
+ */
+ public static byte[] decode(byte[] bytes, boolean isPlusToSpace) {
if (bytes == null) {
return null;
}
@@ -49,7 +109,7 @@ public class URLDecoder implements Serializable {
for (int i = 0; i < bytes.length; i++) {
b = bytes[i];
if (b == '+') {
- buffer.write(CharUtil.SPACE);
+ buffer.write(isPlusToSpace ? CharUtil.SPACE : b);
} else if (b == ESCAPE_CHAR) {
if (i + 1 < bytes.length) {
final int u = CharUtil.digit16(bytes[i + 1]);
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java
index 6da825fd7..4549cad4b 100644
--- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java
+++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java
@@ -82,6 +82,17 @@ public final class UrlBuilder implements Serializable {
return ofHttp(httpUrl, null);
}
+ /**
+ * 使用URL字符串构建UrlBuilder,当传入的URL没有协议时,按照http协议对待,编码默认使用UTF-8
+ *
+ * @param httpUrl URL字符串
+ * @return UrlBuilder
+ * @since 5.6.3
+ */
+ public static UrlBuilder ofHttp(String httpUrl) {
+ return ofHttp(httpUrl, CharsetUtil.CHARSET_UTF_8);
+ }
+
/**
* 使用URL字符串构建UrlBuilder,当传入的URL没有协议时,按照http协议对待。
*
@@ -99,6 +110,16 @@ public final class UrlBuilder implements Serializable {
return of(httpUrl, charset);
}
+ /**
+ * 使用URL字符串构建UrlBuilder,默认使用UTF-8编码
+ *
+ * @param url URL字符串
+ * @return UrlBuilder
+ */
+ public static UrlBuilder of(String url) {
+ return of(url, CharsetUtil.CHARSET_UTF_8);
+ }
+
/**
* 使用URL字符串构建UrlBuilder
*
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java
index e3e0632d3..748275e04 100644
--- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java
+++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java
@@ -2,6 +2,7 @@ package cn.hutool.core.net.url;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
@@ -97,8 +98,6 @@ public class UrlPath {
* @return this
*/
public UrlPath parse(String path, Charset charset) {
- UrlPath urlPath = new UrlPath();
-
if (StrUtil.isNotEmpty(path)) {
// 原URL中以/结尾,则这个规则需保留,issue#I1G44J@Gitee
if(StrUtil.endWith(path, CharUtil.SLASH)){
@@ -108,11 +107,11 @@ public class UrlPath {
path = fixPath(path);
final List split = StrUtil.split(path, '/');
for (String seg : split) {
- addInternal(URLUtil.decode(seg, charset), false);
+ addInternal(URLDecoder.decodeForPath(seg, charset), false);
}
}
- return urlPath;
+ return this;
}
/**
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java
index 780c2f710..78c7df9eb 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java
@@ -134,7 +134,7 @@ public class URLUtil {
* @return URL
* @since 5.5.2
*/
- public static URI getStringURI(CharSequence content){
+ public static URI getStringURI(CharSequence content) {
final String contentStr = StrUtil.addPrefixIfNot(content, "string:///");
return URI.create(contentStr);
}
@@ -467,6 +467,23 @@ public class URLUtil {
return URLDecoder.decode(content, charset);
}
+ /**
+ * 解码application/x-www-form-urlencoded字符
+ * 将%开头的16进制表示的内容解码。
+ *
+ * @param content 被解码内容
+ * @param charset 编码,null表示不解码
+ * @param isPlusToSpace 是否+转换为空格
+ * @return 编码后的字符
+ * @since 5.6.3
+ */
+ public static String decode(String content, Charset charset, boolean isPlusToSpace) {
+ if (null == charset) {
+ return content;
+ }
+ return URLDecoder.decode(content, charset, isPlusToSpace);
+ }
+
/**
* 解码application/x-www-form-urlencoded字符
* 将%开头的16进制表示的内容解码。
@@ -705,7 +722,7 @@ public class URLUtil {
*
* @param url URL字符串
* @param isEncodePath 是否对URL中path部分的中文和特殊字符做转义(不包括 http:, /和域名部分)
- * @param replaceSlash 是否替换url body中的 //
+ * @param replaceSlash 是否替换url body中的 //
* @return 标准化后的URL字符串
* @since 5.5.5
*/
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
new file mode 100644
index 000000000..b59e0bd80
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/net/URLEncoderTest.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 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-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java
index c96601c33..917910870 100644
--- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java
@@ -18,6 +18,13 @@ public class UrlBuilderTest {
Assert.assertEquals("http://www.hutool.cn/", buildUrl);
}
+ @Test
+ public void buildTest2() {
+ // path中的+不做处理
+ String buildUrl = UrlBuilder.ofHttp("http://www.hutool.cn/+8618888888888", CharsetUtil.CHARSET_UTF_8).build();
+ Assert.assertEquals("http://www.hutool.cn/+8618888888888", buildUrl);
+ }
+
@Test
public void testHost() {
String buildUrl = UrlBuilder.create()
diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlDecoderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlDecoderTest.java
new file mode 100644
index 000000000..906b5784a
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlDecoderTest.java
@@ -0,0 +1,12 @@
+package cn.hutool.core.net;
+
+import cn.hutool.core.util.CharsetUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UrlDecoderTest {
+ @Test
+ public void decodeForPathTest(){
+ Assert.assertEquals("+", URLDecoder.decodeForPath("+", CharsetUtil.CHARSET_UTF_8));
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/URLUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/URLUtilTest.java
index a561803cd..e04aa3d9a 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/URLUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/URLUtilTest.java
@@ -9,7 +9,7 @@ import java.net.URL;
/**
* URLUtil单元测试
- *
+ *
* @author looly
*
*/
@@ -26,24 +26,24 @@ public class URLUtilTest {
normalize = URLUtil.normalize(url);
Assert.assertEquals("http://www.hutool.cn//aaa/bbb", normalize);
}
-
+
@Test
public void normalizeTest2() {
String url = "http://www.hutool.cn//aaa/\\bbb?a=1&b=2";
String normalize = URLUtil.normalize(url);
Assert.assertEquals("http://www.hutool.cn//aaa//bbb?a=1&b=2", normalize);
-
+
url = "www.hutool.cn//aaa/bbb?a=1&b=2";
normalize = URLUtil.normalize(url);
Assert.assertEquals("http://www.hutool.cn//aaa/bbb?a=1&b=2", normalize);
}
-
+
@Test
public void normalizeTest3() {
String url = "http://www.hutool.cn//aaa/\\bbb?a=1&b=2";
String normalize = URLUtil.normalize(url, true);
Assert.assertEquals("http://www.hutool.cn//aaa//bbb?a=1&b=2", normalize);
-
+
url = "www.hutool.cn//aaa/bbb?a=1&b=2";
normalize = URLUtil.normalize(url, true);
Assert.assertEquals("http://www.hutool.cn//aaa/bbb?a=1&b=2", normalize);
@@ -59,7 +59,7 @@ public class URLUtilTest {
String normalize = URLUtil.normalize("http://[fe80::8f8:2022:a603:d180]:9439", true);
Assert.assertEquals(url, normalize);
}
-
+
@Test
public void formatTest() {
String url = "//www.hutool.cn//aaa/\\bbb?a=1&b=2";
@@ -81,7 +81,7 @@ public class URLUtilTest {
String encode = URLUtil.encode(body);
Assert.assertEquals("366466%20-%20%E5%89%AF%E6%9C%AC.jpg", encode);
Assert.assertEquals(body, URLUtil.decode(encode));
-
+
String encode2 = URLUtil.encodeQuery(body);
Assert.assertEquals("366466+-+%E5%89%AF%E6%9C%AC.jpg", encode2);
}
diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java
index b60259b71..d6d998267 100644
--- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java
+++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java
@@ -77,7 +77,7 @@ public enum ContentType {
}
/**
- * 是否为默认Content-Type,默认包括null
和application/x-www-form-urlencoded
+ * 是否为默认Content-Type,默认包括{@code null}和application/x-www-form-urlencoded
*
* @param contentType 内容类型
* @return 是否为默认Content-Type
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 5b4b4910c..a4986b809 100644
--- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java
+++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java
@@ -12,7 +12,6 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.body.MultipartBody;
@@ -157,7 +156,7 @@ public class HttpRequest extends HttpBase {
* @param url URL
*/
public HttpRequest(String url) {
- this(UrlBuilder.ofHttp(url, CharsetUtil.CHARSET_UTF_8));
+ this(UrlBuilder.ofHttp(url));
}
/**