From 1edbc0de80cf867018f56dfa5ba38f399a5a4f5a Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 8 Apr 2021 18:12:10 +0800 Subject: [PATCH] fix plus encode bug --- CHANGELOG.md | 1 + .../java/cn/hutool/core/net/URLDecoder.java | 64 ++++++++++++++++++- .../cn/hutool/core/net/url/UrlBuilder.java | 21 ++++++ .../java/cn/hutool/core/net/url/UrlPath.java | 7 +- .../java/cn/hutool/core/util/URLUtil.java | 21 +++++- .../cn/hutool/core/net/URLEncoderTest.java | 17 +++++ .../cn/hutool/core/net/UrlBuilderTest.java | 7 ++ .../cn/hutool/core/net/UrlDecoderTest.java | 12 ++++ .../java/cn/hutool/core/util/URLUtilTest.java | 14 ++-- .../main/java/cn/hutool/http/ContentType.java | 2 +- .../main/java/cn/hutool/http/HttpRequest.java | 3 +- 11 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/net/URLEncoderTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/net/UrlDecoderTest.java 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)); } /**