fix plus encode bug

This commit is contained in:
Looly 2021-04-08 18:12:10 +08:00
parent 6b0b729c68
commit 1edbc0de80
11 changed files with 151 additions and 18 deletions

View File

@ -22,6 +22,7 @@
* 【core 】 修复Validator.isUrl()传空返回trueissue#I3ETTY@Gitee * 【core 】 修复Validator.isUrl()传空返回trueissue#I3ETTY@Gitee
* 【db 】 修复数据库driver根据url的判断识别错误问题issue#I3EWBI@Gitee * 【db 】 修复数据库driver根据url的判断识别错误问题issue#I3EWBI@Gitee
* 【json 】 修复JSONStrFormatter换行多余空行问题issue#I3FA8B@Gitee * 【json 】 修复JSONStrFormatter换行多余空行问题issue#I3FA8B@Gitee
* 【core 】 修复UrlPath中的+被转义为空格%20的问题issue#1501@Github
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -23,24 +23,84 @@ public class URLDecoder implements Serializable {
private static final byte ESCAPE_CHAR = '%'; private static final byte ESCAPE_CHAR = '%';
/**
* 解码不对+解码
* <pre>
* 1. %20转换为空格 ;
* 2. "%xy"转换为文本形式,xy是两位16进制的数值;
* 3. 跳过不符合规范的%形式直接输出
* </pre>
*
* @param str 包含URL编码后的字符串
* @param charset 编码
* @return 解码后的字符串
*/
public static String decodeForPath(String str, Charset charset) {
return decode(str, charset, false);
}
/** /**
* 解码 * 解码
* <pre>
* 1. +%20转换为空格 ;
* 2. "%xy"转换为文本形式,xy是两位16进制的数值;
* 3. 跳过不符合规范的%形式直接输出
* </pre>
* *
* @param str 包含URL编码后的字符串 * @param str 包含URL编码后的字符串
* @param charset 编码 * @param charset 编码
* @return 解码后的字符串 * @return 解码后的字符串
*/ */
public static String decode(String str, Charset charset) { public static String decode(String str, Charset charset) {
return StrUtil.str(decode(StrUtil.bytes(str, charset)), charset); return decode(str, charset, true);
} }
/** /**
* 解码 * 解码
* <pre>
* 1. %20转换为空格 ;
* 2. "%xy"转换为文本形式,xy是两位16进制的数值;
* 3. 跳过不符合规范的%形式直接输出
* </pre>
*
* @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);
}
/**
* 解码
* <pre>
* 1. +%20转换为空格 ;
* 2. "%xy"转换为文本形式,xy是两位16进制的数值;
* 3. 跳过不符合规范的%形式直接输出
* </pre>
* *
* @param bytes url编码的bytes * @param bytes url编码的bytes
* @return 解码后的bytes * @return 解码后的bytes
*/ */
public static byte[] decode(byte[] bytes) { public static byte[] decode(byte[] bytes) {
return decode(bytes, true);
}
/**
* 解码
* <pre>
* 1. %20转换为空格 ;
* 2. "%xy"转换为文本形式,xy是两位16进制的数值;
* 3. 跳过不符合规范的%形式直接输出
* </pre>
*
* @param bytes url编码的bytes
* @param isPlusToSpace 是否+转换为空格
* @return 解码后的bytes
* @since 5.6.3
*/
public static byte[] decode(byte[] bytes, boolean isPlusToSpace) {
if (bytes == null) { if (bytes == null) {
return null; return null;
} }
@ -49,7 +109,7 @@ public class URLDecoder implements Serializable {
for (int i = 0; i < bytes.length; i++) { for (int i = 0; i < bytes.length; i++) {
b = bytes[i]; b = bytes[i];
if (b == '+') { if (b == '+') {
buffer.write(CharUtil.SPACE); buffer.write(isPlusToSpace ? CharUtil.SPACE : b);
} else if (b == ESCAPE_CHAR) { } else if (b == ESCAPE_CHAR) {
if (i + 1 < bytes.length) { if (i + 1 < bytes.length) {
final int u = CharUtil.digit16(bytes[i + 1]); final int u = CharUtil.digit16(bytes[i + 1]);

View File

@ -82,6 +82,17 @@ public final class UrlBuilder implements Serializable {
return ofHttp(httpUrl, null); 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协议对待 * 使用URL字符串构建UrlBuilder当传入的URL没有协议时按照http协议对待
* *
@ -99,6 +110,16 @@ public final class UrlBuilder implements Serializable {
return of(httpUrl, charset); 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 * 使用URL字符串构建UrlBuilder
* *

View File

@ -2,6 +2,7 @@ package cn.hutool.core.net.url;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil; import cn.hutool.core.util.URLUtil;
@ -97,8 +98,6 @@ public class UrlPath {
* @return this * @return this
*/ */
public UrlPath parse(String path, Charset charset) { public UrlPath parse(String path, Charset charset) {
UrlPath urlPath = new UrlPath();
if (StrUtil.isNotEmpty(path)) { if (StrUtil.isNotEmpty(path)) {
// 原URL中以/结尾则这个规则需保留issue#I1G44J@Gitee // 原URL中以/结尾则这个规则需保留issue#I1G44J@Gitee
if(StrUtil.endWith(path, CharUtil.SLASH)){ if(StrUtil.endWith(path, CharUtil.SLASH)){
@ -108,11 +107,11 @@ public class UrlPath {
path = fixPath(path); path = fixPath(path);
final List<String> split = StrUtil.split(path, '/'); final List<String> split = StrUtil.split(path, '/');
for (String seg : split) { for (String seg : split) {
addInternal(URLUtil.decode(seg, charset), false); addInternal(URLDecoder.decodeForPath(seg, charset), false);
} }
} }
return urlPath; return this;
} }
/** /**

View File

@ -134,7 +134,7 @@ public class URLUtil {
* @return URL * @return URL
* @since 5.5.2 * @since 5.5.2
*/ */
public static URI getStringURI(CharSequence content){ public static URI getStringURI(CharSequence content) {
final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); final String contentStr = StrUtil.addPrefixIfNot(content, "string:///");
return URI.create(contentStr); return URI.create(contentStr);
} }
@ -467,6 +467,23 @@ public class URLUtil {
return URLDecoder.decode(content, charset); return URLDecoder.decode(content, charset);
} }
/**
* 解码application/x-www-form-urlencoded字符<br>
* %开头的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字符<br> * 解码application/x-www-form-urlencoded字符<br>
* %开头的16进制表示的内容解码 * %开头的16进制表示的内容解码

View File

@ -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);
}
}

View File

@ -18,6 +18,13 @@ public class UrlBuilderTest {
Assert.assertEquals("http://www.hutool.cn/", buildUrl); 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 @Test
public void testHost() { public void testHost() {
String buildUrl = UrlBuilder.create() String buildUrl = UrlBuilder.create()

View File

@ -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));
}
}

View File

@ -77,7 +77,7 @@ public enum ContentType {
} }
/** /**
* 是否为默认Content-Type默认包括<code>null</code>和application/x-www-form-urlencoded * 是否为默认Content-Type默认包括{@code null}和application/x-www-form-urlencoded
* *
* @param contentType 内容类型 * @param contentType 内容类型
* @return 是否为默认Content-Type * @return 是否为默认Content-Type

View File

@ -12,7 +12,6 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.body.MultipartBody; import cn.hutool.http.body.MultipartBody;
@ -157,7 +156,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @param url URL * @param url URL
*/ */
public HttpRequest(String url) { public HttpRequest(String url) {
this(UrlBuilder.ofHttp(url, CharsetUtil.CHARSET_UTF_8)); this(UrlBuilder.ofHttp(url));
} }
/** /**