add UrlDecoder

This commit is contained in:
Looly 2020-04-16 01:13:58 +08:00
parent 8102b31373
commit ca7c407a1c
18 changed files with 1402 additions and 229 deletions

View File

@ -8,11 +8,15 @@
### 新特性
* 【core 】 ListUtil、MapUtil、CollUtil增加empty方法
* 【poi 】 调整别名策略clearHeaderAlias和addHeaderAlias同时清除aliasComparatorissue#828@Github
* 【core 】 修改StrUtil.equals逻辑改为contentEquals
* 【core 】 增加URLUtil.UrlDecoder
### Bug修复
* 【json 】 修复解析JSON字符串时配置无法传递问题
* 【core 】 修复ServletUtil.readCookieMap空指针问题issue#827@Github
* 【crypto 】 修复SM2中检查密钥导致的问题issue#I1EC47@Gitee
* 【core 】 修复TableMap.isEmpty判断问题
* 【http 】 修复编码后的URL传入导致二次编码的问题issue#I1EIMN@Gitee
-------------------------------------------------------------------------------------------------------------

View File

@ -2,7 +2,6 @@ package cn.hutool.core.map;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import java.io.Serializable;
@ -10,6 +9,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -24,7 +25,7 @@ import java.util.Set;
* @param <V> 值类型
* @author looly
*/
public class TableMap<K, V> implements Map<K, V>, Serializable {
public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Serializable {
private static final long serialVersionUID = 1L;
private final List<K> keys;
@ -58,7 +59,7 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
@Override
public boolean isEmpty() {
return ArrayUtil.isEmpty(keys);
return CollUtil.isEmpty(keys);
}
@Override
@ -159,13 +160,37 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
@SuppressWarnings("NullableProblems")
@Override
public Set<Map.Entry<K, V>> entrySet() {
HashSet<Map.Entry<K, V>> hashSet = new HashSet<>();
final Set<Map.Entry<K, V>> hashSet = new LinkedHashSet<>();
for (int i = 0; i < size(); i++) {
hashSet.add(new Entry<>(keys.get(i), values.get(i)));
}
return hashSet;
}
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new Iterator<Map.Entry<K, V>>() {
private final Iterator<K> keysIter = keys.iterator();
private final Iterator<V> valuesIter = values.iterator();
@Override
public boolean hasNext() {
return keysIter.hasNext() && valuesIter.hasNext();
}
@Override
public Map.Entry<K, V> next() {
return new Entry<>(keysIter.next(), valuesIter.next());
}
@Override
public void remove() {
keysIter.remove();
valuesIter.remove();
}
};
}
private static class Entry<K, V> implements Map.Entry<K, V> {
private final K key;

View File

@ -0,0 +1,73 @@
package cn.hutool.core.net;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import java.io.ByteArrayOutputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
/**
* URL解码数据内容的类型是 application/x-www-form-urlencoded
*
* <pre>
* 1. %20转换为空格 ;
* 2. "%xy"转换为文本形式,xy是两位16进制的数值;
* 3. 跳过不符合规范的%形式直接输出
* </pre>
*
* @author looly
*/
public class URLDecoder implements Serializable {
private static final long serialVersionUID = 1L;
private static final byte ESCAPE_CHAR = '%';
/**
* 解码
*
* @param str 包含URL编码后的字符串
* @param charset 编码
* @return 解码后的字符串
*/
public static String decode(String str, Charset charset) {
return StrUtil.str(decode(StrUtil.bytes(str, charset)), charset);
}
/**
* 解码
*
* @param bytes url编码的bytes
* @return 解码后的bytes
*/
public static byte[] decode(byte[] bytes) {
if (bytes == null) {
return null;
}
final ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length);
int b;
for (int i = 0; i < bytes.length; i++) {
b = bytes[i];
if (b == '+') {
buffer.write(CharUtil.SPACE);
} else if (b == ESCAPE_CHAR) {
if (i + 1 < bytes.length) {
final int u = CharUtil.digit16(bytes[i + 1]);
if (u >= 0 && i + 2 < bytes.length) {
final int l = CharUtil.digit16(bytes[i + 2]);
if (l >= 0) {
buffer.write((char) ((u << 4) + l));
i += 2;
continue;
}
}
}
// 跳过不符合规范的%形式
buffer.write(b);
} else {
buffer.write(b);
}
}
return buffer.toByteArray();
}
}

View File

@ -1,5 +1,8 @@
package cn.hutool.core.net;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.HexUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
@ -7,9 +10,6 @@ import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.BitSet;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.HexUtil;
/**
* URL编码数据内容的类型是 application/x-www-form-urlencoded
*
@ -17,7 +17,6 @@ import cn.hutool.core.util.HexUtil;
* 1.字符"a"-"z""A"-"Z""0"-"9"".""-""*""_" 都不会被编码;
* 2.将空格转换为%20 ;
* 3.将非文本内容转换成"%xy"的形式,xy是两位16进制的数值;
* 4.在每个 name=value 对之间放置 &amp; 符号
* </pre>
*
* @author looly,
@ -196,10 +195,8 @@ public class URLEncoder implements Serializable{
* @return 编码后的字符串
*/
public String encode(String path, Charset charset) {
int maxBytesPerChar = 10;
final StringBuilder rewrittenPath = new StringBuilder(path.length());
ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(buf, charset);
int c;
@ -221,9 +218,8 @@ public class URLEncoder implements Serializable{
}
byte[] ba = buf.toByteArray();
for (int j = 0; j < ba.length; j++) {
for (byte toEncode : ba) {
// Converting each byte in the buffer
byte toEncode = ba[j];
rewrittenPath.append('%');
HexUtil.appendHex(rewrittenPath, toEncode, false);
}

View File

@ -0,0 +1,495 @@
package cn.hutool.core.net.url;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.nio.charset.Charset;
/**
* URL 生成器格式形如
* <pre>
* [scheme:]scheme-specific-part[#fragment]
* [scheme:][//authority][path][?query][#fragment]
* [scheme:][//host:port][path][?query][#fragment]
* </pre>
*
* @author looly
* @see <a href="https://en.wikipedia.org/wiki/Uniform_Resource_Identifier">Uniform Resource Identifier</a>
* @since 5.3.1
*/
public final class UrlBuilder implements Serializable {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_SCHEME = "http";
/**
* 协议例如http
*/
private String scheme;
/**
* 主机例如127.0.0.1
*/
private String host;
/**
* 端口默认-1
*/
private int port = -1;
/**
* 路径例如/aa/bb/cc
*/
private UrlPath path;
/**
* 查询语句例如a=1&b=2
*/
private UrlQuery query;
/**
* 标识符例如#后边的部分
*/
private String fragment;
/**
* 编码用于URLEncode和URLDecode
*/
private Charset charset;
/**
* 使用URI构建UrlBuilder
*
* @param uri URI
* @param charset 编码用于URLEncode和URLDecode
* @return UrlBuilder
*/
public static UrlBuilder of(URI uri, Charset charset) {
return of(uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getRawQuery(), uri.getFragment(), charset);
}
/**
* 使用URL字符串构建UrlBuilder
*
* @param httpUrl URL字符串
* @param charset 编码用于URLEncode和URLDecode
* @return UrlBuilder
*/
public static UrlBuilder ofHttp(String httpUrl, Charset charset) {
Assert.notBlank(httpUrl, "Http url must be not blank!");
final int sepIndex = httpUrl.indexOf("://");
if (sepIndex < 0) {
httpUrl = "http://" + httpUrl.trim();
}
return of(httpUrl, charset);
}
/**
* 使用URL字符串构建UrlBuilder
*
* @param url URL字符串
* @param charset 编码用于URLEncode和URLDecode
* @return UrlBuilder
*/
public static UrlBuilder of(String url, Charset charset) {
Assert.notBlank(url, "Url must be not blank!");
return of(URLUtil.url(url.trim()), charset);
}
/**
* 使用URL构建UrlBuilder
*
* @param url URL
* @param charset 编码用于URLEncode和URLDecode
* @return UrlBuilder
*/
public static UrlBuilder of(URL url, Charset charset) {
return of(url.getProtocol(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef(), charset);
}
/**
* 构建UrlBuilder
*
* @param scheme 协议默认http
* @param host 主机例如127.0.0.1
* @param port 端口-1表示默认端口
* @param path 路径例如/aa/bb/cc
* @param query 查询例如a=1&b=2
* @param fragment 标识符例如#后边的部分
* @param charset 编码用于URLEncode和URLDecode
* @return UrlBuilder
*/
public static UrlBuilder of(String scheme, String host, int port, String path, String query, String fragment, Charset charset) {
return of(scheme, host, port, UrlPath.of(path, charset), UrlQuery.of(query, charset), fragment, charset);
}
/**
* 构建UrlBuilder
*
* @param scheme 协议默认http
* @param host 主机例如127.0.0.1
* @param port 端口-1表示默认端口
* @param path 路径例如/aa/bb/cc
* @param query 查询例如a=1&b=2
* @param fragment 标识符例如#后边的部分
* @param charset 编码用于URLEncode和URLDecode
* @return UrlBuilder
*/
public static UrlBuilder of(String scheme, String host, int port, UrlPath path, UrlQuery query, String fragment, Charset charset) {
return new UrlBuilder(scheme, host, port, path, query, fragment, charset);
}
/**
* 创建空的UrlBuilder
*
* @return UrlBuilder
*/
public static UrlBuilder create() {
return new UrlBuilder();
}
/**
* 构造
*/
public UrlBuilder() {
this.charset = CharsetUtil.CHARSET_UTF_8;
}
/**
* 构造
*
* @param scheme 协议默认http
* @param host 主机例如127.0.0.1
* @param port 端口-1表示默认端口
* @param path 路径例如/aa/bb/cc
* @param query 查询例如a=1&b=2
* @param fragment 标识符例如#后边的部分
* @param charset 编码用于URLEncode和URLDecode
*/
public UrlBuilder(String scheme, String host, int port, UrlPath path, UrlQuery query, String fragment, Charset charset) {
this.charset = charset;
this.scheme = scheme;
this.host = host;
this.port = port;
this.path = path;
this.query = query;
this.setFragment(fragment);
}
/**
* 获取协议例如http
*
* @return 协议例如http
*/
public String getScheme() {
return scheme;
}
/**
* 获取协议例如http如果用户未定义协议使用默认的http协议
*
* @return 协议例如http
*/
public String getSchemeWithDefault() {
return StrUtil.emptyToDefault(this.scheme, DEFAULT_SCHEME);
}
/**
* 设置协议例如http
*
* @param scheme 协议例如http
* @return this
*/
public UrlBuilder setScheme(String scheme) {
this.scheme = scheme;
return this;
}
/**
* 获取 主机例如127.0.0.1
*
* @return 主机例如127.0.0.1
*/
public String getHost() {
return host;
}
/**
* 设置主机例如127.0.0.1
*
* @param host 主机例如127.0.0.1
* @return this
*/
public UrlBuilder setHost(String host) {
this.host = host;
return this;
}
/**
* 获取端口默认-1
*
* @return 端口默认-1
*/
public int getPort() {
return port;
}
/**
* 设置端口默认-1
*
* @param port 端口默认-1
* @return this
*/
public UrlBuilder setPort(int port) {
this.port = port;
return this;
}
/**
* 获得authority部分
*
* @return authority部分
*/
public String getAuthority() {
return (port < 0) ? host : host + ":" + port;
}
/**
* 获取路径例如/aa/bb/cc
*
* @return 路径例如/aa/bb/cc
*/
public UrlPath getPath() {
return path;
}
/**
* 获得路径例如/aa/bb/cc
*
* @return 路径例如/aa/bb/cc
*/
public String getPathStr() {
return null == this.path ? StrUtil.SLASH : this.path.build(charset);
}
/**
* 设置路径例如/aa/bb/cc将覆盖之前所有的path相关设置
*
* @param path 路径例如/aa/bb/cc
* @return this
*/
public UrlBuilder setPath(UrlPath path) {
this.path = path;
return this;
}
/**
* 增加路径节点
*
* @param segment 路径节点
* @return this
*/
public UrlBuilder addPath(String segment) {
if (StrUtil.isBlank(segment)) {
return this;
}
if (null == this.path) {
this.path = new UrlPath();
}
this.path.add(segment);
return this;
}
/**
* 追加path节点
*
* @param segment path节点
* @return this
*/
public UrlBuilder appendPath(CharSequence segment) {
if (StrUtil.isEmpty(segment)) {
return this;
}
if (this.path == null) {
this.path = new UrlPath();
}
this.path.add(segment);
return this;
}
/**
* 获取查询语句例如a=1&b=2
*
* @return 查询语句例如a=1&b=2
*/
public UrlQuery getQuery() {
return query;
}
/**
* 获取查询语句例如a=1&b=2
*
* @return 查询语句例如a=1&b=2
*/
public String getQueryStr() {
return null == this.query ? null : this.query.build(this.charset);
}
/**
* 设置查询语句例如a=1&b=2将覆盖之前所有的query相关设置
*
* @param query 查询语句例如a=1&b=2
* @return this
*/
public UrlBuilder setQuery(UrlQuery query) {
this.query = query;
return this;
}
/**
* 添加查询项支持重复键
*
* @param key
* @param value
* @return this
*/
public UrlBuilder addQuery(String key, String value) {
if (StrUtil.isEmpty(key)) {
return this;
}
if (this.query == null) {
this.query = new UrlQuery();
}
this.query.add(key, value);
return this;
}
/**
* 获取标识符#后边的部分
*
* @return 标识符例如#后边的部分
*/
public String getFragment() {
return fragment;
}
/**
* 获取标识符#后边的部分
*
* @return 标识符例如#后边的部分
*/
public String getFragmentEncoded() {
return URLUtil.encodeAll(this.fragment, this.charset);
}
/**
* 设置标识符例如#后边的部分
*
* @param fragment 标识符例如#后边的部分
* @return this
*/
public UrlBuilder setFragment(String fragment) {
if (StrUtil.isEmpty(fragment)) {
this.fragment = null;
}
this.fragment = StrUtil.removePrefix(fragment, "#");
return this;
}
/**
* 获取编码用于URLEncode和URLDecode
*
* @return 编码
*/
public Charset getCharset() {
return charset;
}
/**
* 设置编码用于URLEncode和URLDecode
*
* @param charset 编码
* @return this
*/
public UrlBuilder setCharset(Charset charset) {
this.charset = charset;
return this;
}
/**
* 创建URL字符串
*
* @return URL字符串
*/
public String build() {
return toURL().toString();
}
/**
* 转换为{@link URL} 对象
*
* @return {@link URL}
*/
public URL toURL() {
return toURL(null);
}
/**
* 转换为{@link URL} 对象
*
* @param handler {@link URLStreamHandler}null表示默认
* @return {@link URL}
*/
public URL toURL(URLStreamHandler handler) {
final StringBuilder fileBuilder = new StringBuilder();
// path
fileBuilder.append(StrUtil.blankToDefault(getPathStr(), StrUtil.SLASH));
// query
final String query = getQueryStr();
if (StrUtil.isNotBlank(query)) {
fileBuilder.append('?').append(query);
}
// fragment
if (StrUtil.isNotBlank(this.fragment)) {
fileBuilder.append('#').append(getFragmentEncoded());
}
try {
return new URL(getSchemeWithDefault(), host, port, fileBuilder.toString(), handler);
} catch (MalformedURLException e) {
return null;
}
}
/**
* 转换为URI
*
* @return URI
*/
public URI toURI() {
try {
return new URI(
getSchemeWithDefault(),
getAuthority(),
getPathStr(),
getQueryStr(),
getFragmentEncoded());
} catch (URISyntaxException e) {
return null;
}
}
@Override
public String toString() {
return build();
}
}

View File

@ -0,0 +1,172 @@
package cn.hutool.core.net.url;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
/**
* URL中Path部分的封装
*
* @author looly
* @since 5.3.1
*/
public class UrlPath {
private List<String> segments;
private boolean withEngTag;
/**
* 构建UrlPath
*
* @param pathStr 初始化的路径字符串
* @param charset decode用的编码null表示不做decode
* @return {@link UrlPath}
*/
public static UrlPath of(String pathStr, Charset charset) {
final UrlPath urlPath = new UrlPath();
urlPath.parse(pathStr, charset);
return urlPath;
}
/**
* 是否path的末尾加 /
* @param withEngTag 是否path的末尾加 /
* @return this
*/
public UrlPath setWithEndTag(boolean withEngTag){
this.withEngTag = withEngTag;
return this;
}
/**
* 获取path的节点列表
*
* @return 节点列表
*/
public List<String> getSegments() {
return this.segments;
}
/**
* 获得指定节点
*
* @param index 节点位置
* @return 节点无节点或者越界返回null
*/
public String getSegment(int index) {
if (null == this.segments || index >= this.segments.size()) {
return null;
}
return this.segments.get(index);
}
/**
* 添加到path最后面
*/
public UrlPath add(CharSequence segment) {
add(segment, false);
return this;
}
/**
* 添加到path最前面
*/
public UrlPath addBefore(CharSequence segment) {
add(segment, true);
return this;
}
/**
* 解析path
*
* @param path 路径类似于aaa/bb/ccc
* @param charset decode编码null表示不解码
* @return this
*/
public UrlPath parse(String path, Charset charset) {
UrlPath urlPath = new UrlPath();
if (StrUtil.isNotEmpty(path)) {
path = path.trim();
final StringTokenizer tokenizer = new StringTokenizer(path, "/");
while (tokenizer.hasMoreTokens()) {
add(URLUtil.decode(tokenizer.nextToken(), charset));
}
}
return urlPath;
}
/**
* 构建path前面带'/'
*
* @param charset encode编码null表示不做encode
* @return 如果没有任何内容则返回空字符串""
*/
public String build(Charset charset) {
if (CollUtil.isEmpty(this.segments)) {
return StrUtil.EMPTY;
}
final StringBuilder builder = new StringBuilder();
for (String segment : segments) {
builder.append(CharUtil.SLASH).append(URLUtil.encodeAll(segment, charset));
}
if(withEngTag || StrUtil.isEmpty(builder)){
builder.append(CharUtil.SLASH);
}
return builder.toString();
}
@Override
public String toString() {
return build(null);
}
/**
* 增加节点
*
* @param segment 节点
* @param before 是否在前面添加
*/
private void add(CharSequence segment, boolean before) {
final String seg = fixSegment(segment);
if (null == seg) {
return;
}
if (this.segments == null) {
this.segments = new LinkedList<>();
}
if (before) {
this.segments.add(0, seg);
} else {
this.segments.add(seg);
}
}
/**
* 修正节点包括去掉前后的/去掉空白符
* @param segment 节点
* @return 修正后的节点
*/
private static String fixSegment(CharSequence segment) {
if (StrUtil.isEmpty(segment) || "/".contentEquals(segment)) {
return null;
}
String segmentStr = StrUtil.str(segment);
segmentStr = StrUtil.trim(segmentStr);
segmentStr = StrUtil.removePrefix(segmentStr, "/");
segmentStr = StrUtil.removeSuffix(segmentStr, "/");
segmentStr = StrUtil.trim(segmentStr);
return segmentStr;
}
}

View File

@ -0,0 +1,245 @@
package cn.hutool.core.net.url;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.TableMap;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
/**
* URL中查询字符串部分的封装类似于
* <pre>
* key1=v1&amp;key2=&amp;key3=v3
* </pre>
*
* @author looly
* @since 5.3.1
*/
public class UrlQuery {
private final TableMap<CharSequence, CharSequence> query;
/**
* 构建UrlQuery
*
* @param queryMap 初始化的查询键值对
* @return {@link UrlQuery}
*/
public static UrlQuery of(Map<? extends CharSequence, ?> queryMap) {
return new UrlQuery(queryMap);
}
/**
* 构建UrlQuery
*
* @param queryStr 初始化的查询字符串
* @param charset decode用的编码null表示不做decode
* @return {@link UrlQuery}
*/
public static UrlQuery of(String queryStr, Charset charset) {
final UrlQuery urlQuery = new UrlQuery();
urlQuery.parse(queryStr, charset);
return urlQuery;
}
/**
* 构造
*/
public UrlQuery() {
this(null);
}
/**
* 构造
*
* @param queryMap 初始化的查询键值对
*/
public UrlQuery(Map<? extends CharSequence, ?> queryMap) {
if(MapUtil.isNotEmpty(queryMap)) {
query = new TableMap<>(queryMap.size());
addAll(queryMap);
} else{
query = new TableMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY);
}
}
/**
* 增加键值对
*
* @param key
* @param value 集合和数组转换为逗号分隔形式
* @return this
*/
public UrlQuery add(CharSequence key, Object value) {
this.query.put(key, toStr(value));
return this;
}
/**
* 批量增加键值对
*
* @param queryMap query中的键值对
* @return this
*/
public UrlQuery addAll(Map<? extends CharSequence, ?> queryMap) {
if(MapUtil.isNotEmpty(queryMap)) {
queryMap.forEach(this::add);
}
return this;
}
/**
* 解析URL中的查询字符串
*
* @param queryStr 查询字符串类似于key1=v1&amp;key2=&amp;key3=v3
* @param charset decode编码null表示不做decode
* @return this
*/
public UrlQuery parse(String queryStr, Charset charset) {
if (StrUtil.isBlank(queryStr)) {
return this;
}
// 去掉Path部分
int pathEndPos = queryStr.indexOf('?');
if (pathEndPos > -1) {
queryStr = StrUtil.subSuf(queryStr, pathEndPos + 1);
if (StrUtil.isBlank(queryStr)) {
return this;
}
}
final int len = queryStr.length();
String name = null;
int pos = 0; // 未处理字符开始位置
int i; // 未处理字符结束位置
char c; // 当前字符
for (i = 0; i < len; i++) {
c = queryStr.charAt(i);
if (c == '=') { // 键值对的分界点
if (null == name) {
// name可以是""
name = queryStr.substring(pos, i);
}
pos = i + 1;
} else if (c == '&') { // 参数对的分界点
if (null == name && pos != i) {
// 对于像&a&这类无参数值的字符串我们将name为a的值设为""
addParam(queryStr.substring(pos, i), StrUtil.EMPTY, charset);
} else if (name != null) {
addParam(name, queryStr.substring(pos, i), charset);
name = null;
}
pos = i + 1;
}
}
// 处理结尾
if (pos != i) {
if (name == null) {
addParam(queryStr.substring(pos, i), StrUtil.EMPTY, charset);
} else {
addParam(name, queryStr.substring(pos, i), charset);
}
} else if (name != null) {
addParam(name, StrUtil.EMPTY, charset);
}
return this;
}
/**
* 获得查询的Map
*
* @return 查询的Map只读
*/
public Map<CharSequence, CharSequence> getQueryMap(){
return MapUtil.unmodifiable(this.query);
}
/**
* 获取查询值
* @param key
* @return
*/
public CharSequence get(CharSequence key){
if(MapUtil.isEmpty(this.query)){
return null;
}
return this.query.get(key);
}
/**
* 构建URL查询字符串即将key-value键值对转换为key1=v1&amp;key2=&amp;key3=v3形式
*
* @param charset encode编码null表示不做encode编码
* @return URL查询字符串
*/
public String build(Charset charset) {
if (MapUtil.isEmpty(this.query)) {
return StrUtil.EMPTY;
}
final StringBuilder sb = new StringBuilder();
boolean isFirst = true;
CharSequence key;
CharSequence value;
for (Map.Entry<CharSequence, CharSequence> entry : this.query) {
if (isFirst) {
isFirst = false;
} else {
sb.append("&");
}
key = entry.getKey();
if (StrUtil.isNotEmpty(key)) {
sb.append(URLUtil.encodeAll(StrUtil.str(key), charset)).append("=");
value = entry.getValue();
if (StrUtil.isNotEmpty(value)) {
sb.append(URLUtil.encodeAll(StrUtil.str(value), charset));
}
}
}
return sb.toString();
}
@Override
public String toString() {
return build(null);
}
/**
* 对象转换为字符串用于URL的Query中
*
* @param value
* @return 字符串
*/
private static String toStr(Object value) {
String result;
if (value instanceof Iterable) {
result = CollUtil.join((Iterable<?>) value, ",");
} else if (value instanceof Iterator) {
result = IterUtil.join((Iterator<?>) value, ",");
} else {
result = Convert.toStr(value);
}
return result;
}
/**
* 将键值对加入到值为List类型的Map中
*
* @param name key
* @param value value
* @param charset 编码
*/
private void addParam(String name, String value, Charset charset) {
name = URLUtil.decode(name, charset);
value = URLUtil.decode(value, charset);
this.query.put(name, value);
}
}

View File

@ -0,0 +1,7 @@
/**
* URL相关工具
*
* @author looly
* @since 5.3.1
*/
package cn.hutool.core.net.url;

View File

@ -332,4 +332,15 @@ public class CharUtil {
public static int getType(int c) {
return Character.getType(c);
}
/**
* 获取给定字符的16进制数值
*
* @param b 字符
* @return 16进制字符
* @since 5.3.1
*/
public static int digit16(int b) {
return Character.digit(b, 16);
}
}

View File

@ -2207,7 +2207,7 @@ public class StrUtil {
if (ignoreCase) {
return str1.toString().equalsIgnoreCase(str2.toString());
} else {
return str1.equals(str2);
return str1.toString().contentEquals(str2);
}
}

View File

@ -6,7 +6,9 @@ import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.net.URLEncoder;
import cn.hutool.core.net.url.UrlQuery;
import java.io.BufferedReader;
import java.io.File;
@ -18,44 +20,69 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLStreamHandler;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.jar.JarFile;
/**
* 统一资源定位符相关工具类
*
* @author xiaoleilu
*
*/
public class URLUtil {
/** 针对ClassPath路径的伪协议前缀兼容Spring: "classpath:" */
/**
* 针对ClassPath路径的伪协议前缀兼容Spring: "classpath:"
*/
public static final String CLASSPATH_URL_PREFIX = "classpath:";
/** URL 前缀表示文件: "file:" */
/**
* URL 前缀表示文件: "file:"
*/
public static final String FILE_URL_PREFIX = "file:";
/** URL 前缀表示jar: "jar:" */
/**
* URL 前缀表示jar: "jar:"
*/
public static final String JAR_URL_PREFIX = "jar:";
/** URL 前缀表示war: "war:" */
/**
* URL 前缀表示war: "war:"
*/
public static final String WAR_URL_PREFIX = "war:";
/** URL 协议表示文件: "file" */
/**
* URL 协议表示文件: "file"
*/
public static final String URL_PROTOCOL_FILE = "file";
/** URL 协议表示Jar文件: "jar" */
/**
* URL 协议表示Jar文件: "jar"
*/
public static final String URL_PROTOCOL_JAR = "jar";
/** URL 协议表示zip文件: "zip" */
/**
* URL 协议表示zip文件: "zip"
*/
public static final String URL_PROTOCOL_ZIP = "zip";
/** URL 协议表示WebSphere文件: "wsjar" */
/**
* URL 协议表示WebSphere文件: "wsjar"
*/
public static final String URL_PROTOCOL_WSJAR = "wsjar";
/** URL 协议表示JBoss zip文件: "vfszip" */
/**
* URL 协议表示JBoss zip文件: "vfszip"
*/
public static final String URL_PROTOCOL_VFSZIP = "vfszip";
/** URL 协议表示JBoss文件: "vfsfile" */
/**
* URL 协议表示JBoss文件: "vfsfile"
*/
public static final String URL_PROTOCOL_VFSFILE = "vfsfile";
/** URL 协议表示JBoss VFS资源: "vfs" */
/**
* URL 协议表示JBoss VFS资源: "vfs"
*/
public static final String URL_PROTOCOL_VFS = "vfs";
/** Jar路径以及内部文件路径的分界符: "!/" */
/**
* Jar路径以及内部文件路径的分界符: "!/"
*/
public static final String JAR_URL_SEPARATOR = "!/";
/** WAR路径及内部文件路径分界符 */
/**
* WAR路径及内部文件路径分界符
*/
public static final String WAR_URL_SEPARATOR = "*/";
/**
@ -71,7 +98,7 @@ public class URLUtil {
/**
* 通过一个字符串形式的URL地址创建URL对象
*
* @param url URL
* @param url URL
* @param handler {@link URLStreamHandler}
* @return URL对象
* @since 4.1.1
@ -111,7 +138,7 @@ public class URLUtil {
/**
* 将URL字符串转换为URL对象并做必要验证
*
* @param urlStr URL字符串
* @param urlStr URL字符串
* @param handler {@link URLStreamHandler}
* @return URL
* @since 4.1.9
@ -167,7 +194,7 @@ public class URLUtil {
/**
* 获得URL
*
* @param path 相对给定 class所在的路径
* @param path 相对给定 class所在的路径
* @param clazz 指定class
* @return URL
* @see ResourceUtil#getResource(String, Class)
@ -181,7 +208,7 @@ public class URLUtil {
*
* @param file URL对应的文件对象
* @return URL
* @exception UtilException MalformedURLException
* @throws UtilException MalformedURLException
*/
public static URL getURL(File file) {
Assert.notNull(file, "File is null !");
@ -197,7 +224,7 @@ public class URLUtil {
*
* @param files URL对应的文件对象
* @return URL
* @exception UtilException MalformedURLException
* @throws UtilException MalformedURLException
*/
public static URL[] getURLs(File... files) {
final URL[] urls = new URL[files.length];
@ -219,8 +246,8 @@ public class URLUtil {
* @return 域名的URI
* @since 4.6.9
*/
public static URI getHost(URL url){
if(null == url){
public static URI getHost(URL url) {
if (null == url) {
return null;
}
@ -234,12 +261,26 @@ public class URLUtil {
/**
* 补全相对路径
*
* @param baseUrl 基准URL
* @param baseUrl 基准URL
* @param relativePath 相对URL
* @return 相对路径
* @exception UtilException MalformedURLException
* @throws UtilException MalformedURLException
* @deprecated 拼写错误请使用{@link #completeUrl(String, String)}
*/
@Deprecated
public static String complateUrl(String baseUrl, String relativePath) {
return completeUrl(baseUrl, relativePath);
}
/**
* 补全相对路径
*
* @param baseUrl 基准URL
* @param relativePath 相对URL
* @return 相对路径
* @throws UtilException MalformedURLException
*/
public static String completeUrl(String baseUrl, String relativePath) {
baseUrl = normalize(baseUrl, false);
if (StrUtil.isBlank(baseUrl)) {
return null;
@ -260,7 +301,7 @@ public class URLUtil {
*
* @param url URL
* @return 编码后的URL
* @exception UtilException UnsupportedEncodingException
* @throws UtilException UnsupportedEncodingException
*/
public static String encodeAll(String url) {
return encodeAll(url, CharsetUtil.CHARSET_UTF_8);
@ -270,12 +311,15 @@ public class URLUtil {
* 编码URL<br>
* 将需要转换的内容ASCII码形式之外的内容用十六进制表示法转换出来并在之前加上%开头
*
* @param url URL
* @param charset 编码
* @param url URL
* @param charset 编码为null表示不编码
* @return 编码后的URL
* @exception UtilException UnsupportedEncodingException
* @throws UtilException UnsupportedEncodingException
*/
public static String encodeAll(String url, Charset charset) throws UtilException {
if (null == charset) {
return url;
}
try {
return java.net.URLEncoder.encode(url, charset.toString());
} catch (UnsupportedEncodingException e) {
@ -290,7 +334,7 @@ public class URLUtil {
*
* @param url URL
* @return 编码后的URL
* @exception UtilException UnsupportedEncodingException
* @throws UtilException UnsupportedEncodingException
* @since 3.1.2
*/
public static String encode(String url) throws UtilException {
@ -304,7 +348,7 @@ public class URLUtil {
*
* @param url URL
* @return 编码后的URL
* @exception UtilException UnsupportedEncodingException
* @throws UtilException UnsupportedEncodingException
* @since 3.1.2
*/
public static String encodeQuery(String url) throws UtilException {
@ -316,7 +360,7 @@ public class URLUtil {
* 将需要转换的内容ASCII码形式之外的内容用十六进制表示法转换出来并在之前加上%开头<br>
* 此方法用于URL自动编码类似于浏览器中键入地址自动编码对于像类似于/的字符不再编码
*
* @param url 被编码内容
* @param url 被编码内容
* @param charset 编码
* @return 编码后的字符
* @since 4.4.1
@ -336,7 +380,7 @@ public class URLUtil {
* 将需要转换的内容ASCII码形式之外的内容用十六进制表示法转换出来并在之前加上%开头<br>
* 此方法用于POST请求中的请求体自动编码转义大部分特殊字符
*
* @param url 被编码内容
* @param url 被编码内容
* @param charset 编码
* @return 编码后的字符
* @since 4.4.1
@ -356,10 +400,10 @@ public class URLUtil {
* 将需要转换的内容ASCII码形式之外的内容用十六进制表示法转换出来并在之前加上%开头<br>
* 此方法用于URL自动编码类似于浏览器中键入地址自动编码对于像类似于/的字符不再编码
*
* @param url URL
* @param url URL
* @param charset 编码
* @return 编码后的URL
* @exception UtilException UnsupportedEncodingException
* @throws UtilException UnsupportedEncodingException
*/
public static String encode(String url, String charset) throws UtilException {
if (StrUtil.isEmpty(url)) {
@ -373,10 +417,10 @@ public class URLUtil {
* 将需要转换的内容ASCII码形式之外的内容用十六进制表示法转换出来并在之前加上%开头<br>
* 此方法用于POST请求中的请求体自动编码转义大部分特殊字符
*
* @param url URL
* @param url URL
* @param charset 编码
* @return 编码后的URL
* @exception UtilException UnsupportedEncodingException
* @throws UtilException UnsupportedEncodingException
*/
public static String encodeQuery(String url, String charset) throws UtilException {
return encodeQuery(url, StrUtil.isBlank(charset) ? CharsetUtil.defaultCharset() : CharsetUtil.charset(charset));
@ -388,7 +432,7 @@ public class URLUtil {
*
* @param url URL
* @return 解码后的URL
* @exception UtilException UnsupportedEncodingException
* @throws UtilException UnsupportedEncodingException
* @since 3.1.2
*/
public static String decode(String url) throws UtilException {
@ -396,38 +440,32 @@ public class URLUtil {
}
/**
* 解码application/x-www-form-urlencoded字符
* 解码application/x-www-form-urlencoded字符<br>
* %开头的16进制表示的内容解码
*
* @param content 被解码内容
* @param charset 编码
* @param charset 编码null表示不解码
* @return 编码后的字符
* @since 4.4.1
*/
public static String decode(String content, Charset charset) {
if (null == charset) {
charset = CharsetUtil.defaultCharset();
return content;
}
return decode(content, charset.name());
return URLDecoder.decode(content, charset);
}
/**
* 解码URL<br>
* 解码application/x-www-form-urlencoded字符<br>
* %开头的16进制表示的内容解码
*
* @param url URL
* @param content URL
* @param charset 编码
* @return 解码后的URL
* @exception UtilException UnsupportedEncodingException
* @throws UtilException UnsupportedEncodingException
*/
public static String decode(String url, String charset) throws UtilException {
if (StrUtil.isEmpty(url)) {
return url;
}
try {
return URLDecoder.decode(url, charset);
} catch (UnsupportedEncodingException e) {
throw new UtilException(e, "Unsupported encoding: [{}]", charset);
}
public static String decode(String content, String charset) throws UtilException {
return decode(content, CharsetUtil.charset(charset));
}
/**
@ -435,7 +473,7 @@ public class URLUtil {
*
* @param uriStr URI路径
* @return path
* @exception UtilException 包装URISyntaxException
* @throws UtilException 包装URISyntaxException
*/
public static String getPath(String uriStr) {
URI uri;
@ -476,7 +514,7 @@ public class URLUtil {
*
* @param url URL
* @return URI
* @exception UtilException 包装URISyntaxException
* @throws UtilException 包装URISyntaxException
*/
public static URI toURI(URL url) throws UtilException {
return toURI(url, false);
@ -485,10 +523,10 @@ public class URLUtil {
/**
* 转URL为URI
*
* @param url URL
* @param url URL
* @param isEncode 是否编码参数中的特殊字符默认UTF-8编码
* @return URI
* @exception UtilException 包装URISyntaxException
* @throws UtilException 包装URISyntaxException
* @since 4.6.9
*/
public static URI toURI(URL url, boolean isEncode) throws UtilException {
@ -504,7 +542,7 @@ public class URLUtil {
*
* @param location 字符串路径
* @return URI
* @exception UtilException 包装URISyntaxException
* @throws UtilException 包装URISyntaxException
*/
public static URI toURI(String location) throws UtilException {
return toURI(location, false);
@ -516,11 +554,11 @@ public class URLUtil {
* @param location 字符串路径
* @param isEncode 是否编码参数中的特殊字符默认UTF-8编码
* @return URI
* @exception UtilException 包装URISyntaxException
* @throws UtilException 包装URISyntaxException
* @since 4.6.9
*/
public static URI toURI(String location, boolean isEncode) throws UtilException {
if(isEncode){
if (isEncode) {
location = encode(location);
}
try {
@ -590,7 +628,7 @@ public class URLUtil {
/**
* 获得Reader
*
* @param url {@link URL}
* @param url {@link URL}
* @param charset 编码
* @return {@link BufferedReader}
* @since 3.2.1
@ -636,7 +674,7 @@ public class URLUtil {
* 1. 多个/替换为一个
* </pre>
*
* @param url URL字符串
* @param url URL字符串
* @param isEncodePath 是否对URL中path部分的中文和特殊字符做转义不包括 http:, /和域名部分
* @return 标准化后的URL字符串
* @since 4.4.1
@ -663,7 +701,7 @@ public class URLUtil {
body = StrUtil.subPre(body, paramsSepIndex);
}
if(StrUtil.isNotEmpty(body)){
if (StrUtil.isNotEmpty(body)) {
// 去除开头的\或者/
//noinspection ConstantConditions
body = body.replaceAll("^[\\\\/]+", StrUtil.EMPTY);
@ -683,4 +721,21 @@ public class URLUtil {
}
return protocol + domain + StrUtil.nullToEmpty(path) + StrUtil.nullToEmpty(params);
}
/**
* 将Map形式的Form表单数据转换为Url参数形式<br>
* paramMap中如果key为空null和""会被忽略如果value为null会被做为空白符""<br>
* 会自动url编码键和值
*
* <pre>
* key1=v1&amp;key2=&amp;key3=v3
* </pre>
*
* @param paramMap 表单数据
* @param charset 编码编码为null表示不编码
* @return url参数
*/
public static String buildQuery(Map<String, ?> paramMap, Charset charset) {
return UrlQuery.of(paramMap).build(charset);
}
}

View File

@ -4,7 +4,6 @@ import cn.hutool.core.annotation.Alias;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.bean.copier.ValueProvider;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.map.MapUtil;
import lombok.Getter;
import lombok.Setter;
@ -154,7 +153,6 @@ public class BeanUtilTest {
person.setSlow(true);
Map<String, Object> map = BeanUtil.beanToMap(person);
Console.log(map);
Assert.assertEquals("sub名字", map.get("aliasSubName"));
}

View File

@ -0,0 +1,173 @@
package cn.hutool.core.net;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.CharsetUtil;
import org.junit.Assert;
import org.junit.Test;
public class UrlBuilderTest {
@Test
public void buildTest() {
String buildUrl = UrlBuilder.create().setHost("www.baidu.com").build();
Assert.assertEquals("http://www.baidu.com/", buildUrl);
}
@Test
public void testHost() {
String buildUrl = UrlBuilder.create()
.setScheme("https")
.setHost("www.baidu.com").build();
Assert.assertEquals("https://www.baidu.com/", buildUrl);
}
@Test
public void testHostPort() {
String buildUrl = UrlBuilder.create()
.setScheme("https")
.setHost("www.baidu.com")
.setPort(8080)
.build();
Assert.assertEquals("https://www.baidu.com:8080/", buildUrl);
}
@Test
public void testPathAndQuery() {
final String buildUrl = UrlBuilder.create()
.setScheme("https")
.setHost("www.baidu.com")
.addPath("/aaa").addPath("bbb")
.addQuery("ie", "UTF-8")
.addQuery("wd", "test")
.build();
Assert.assertEquals("https://www.baidu.com/aaa/bbb?ie=UTF-8&wd=test", buildUrl);
}
@Test
public void testQueryWithChinese() {
final String buildUrl = UrlBuilder.create()
.setScheme("https")
.setHost("www.baidu.com")
.addPath("/aaa").addPath("bbb")
.addQuery("ie", "UTF-8")
.addQuery("wd", "测试")
.build();
Assert.assertEquals("https://www.baidu.com/aaa/bbb?ie=UTF-8&wd=%E6%B5%8B%E8%AF%95", buildUrl);
}
@Test
public void testMultiQueryWithChinese() {
final String buildUrl = UrlBuilder.create()
.setScheme("https")
.setHost("www.baidu.com")
.addPath("/s")
.addQuery("ie", "UTF-8")
.addQuery("ie", "GBK")
.addQuery("wd", "测试")
.build();
Assert.assertEquals("https://www.baidu.com/s?ie=UTF-8&ie=GBK&wd=%E6%B5%8B%E8%AF%95", buildUrl);
}
@Test
public void testFragment() {
String buildUrl = new UrlBuilder()
.setScheme("https")
.setHost("www.baidu.com")
.setFragment("abc").build();
Assert.assertEquals("https://www.baidu.com/#abc", buildUrl);
}
@Test
public void testChineseFragment() {
String buildUrl = new UrlBuilder()
.setScheme("https")
.setHost("www.baidu.com")
.setFragment("测试").build();
Assert.assertEquals("https://www.baidu.com/#%E6%B5%8B%E8%AF%95", buildUrl);
}
@Test
public void testChineseFragmentWithPath() {
String buildUrl = new UrlBuilder()
.setScheme("https")
.setHost("www.baidu.com")
.addPath("/s")
.setFragment("测试").build();
Assert.assertEquals("https://www.baidu.com/s#%E6%B5%8B%E8%AF%95", buildUrl);
}
@Test
public void testChineseFragmentWithPathAndQuery() {
String buildUrl = new UrlBuilder()
.setScheme("https")
.setHost("www.baidu.com")
.addPath("/s")
.addQuery("wd", "test")
.setFragment("测试").build();
Assert.assertEquals("https://www.baidu.com/s?wd=test#%E6%B5%8B%E8%AF%95", buildUrl);
}
@Test
public void ofTest() {
final UrlBuilder builder = UrlBuilder.of("http://www.baidu.com/aaa/bbb/?a=1&b=2#frag1", CharsetUtil.CHARSET_UTF_8);
Assert.assertEquals("http", builder.getScheme());
Assert.assertEquals("www.baidu.com", builder.getHost());
Assert.assertEquals("aaa", builder.getPath().getSegment(0));
Assert.assertEquals("bbb", builder.getPath().getSegment(1));
Assert.assertEquals("1", builder.getQuery().get("a"));
Assert.assertEquals("2", builder.getQuery().get("b"));
Assert.assertEquals("frag1", builder.getFragment());
}
@Test
public void ofWithChineseTest() {
final UrlBuilder builder = UrlBuilder.ofHttp("www.baidu.com/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8);
Assert.assertEquals("http", builder.getScheme());
Assert.assertEquals("www.baidu.com", builder.getHost());
Assert.assertEquals("aaa", builder.getPath().getSegment(0));
Assert.assertEquals("bbb", builder.getPath().getSegment(1));
Assert.assertEquals("张三", builder.getQuery().get("a"));
Assert.assertEquals("李四", builder.getQuery().get("b"));
Assert.assertEquals("frag1", builder.getFragment());
}
@Test
public void ofWithBlankTest() {
final UrlBuilder builder = UrlBuilder.ofHttp(" www.baidu.com/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8);
Assert.assertEquals("http", builder.getScheme());
Assert.assertEquals("www.baidu.com", builder.getHost());
Assert.assertEquals("aaa", builder.getPath().getSegment(0));
Assert.assertEquals("bbb", builder.getPath().getSegment(1));
Assert.assertEquals("张三", builder.getQuery().get("a"));
Assert.assertEquals("李四", builder.getQuery().get("b"));
Assert.assertEquals("frag1", builder.getFragment());
}
@Test
public void ofSpecialTest() {
//测试不规范的或者无需解码的字符串是否成功解码
final UrlBuilder builder = UrlBuilder.ofHttp(" www.baidu.com/aaa/bbb/?a=张三&b=%%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8);
Assert.assertEquals("http", builder.getScheme());
Assert.assertEquals("www.baidu.com", builder.getHost());
Assert.assertEquals("aaa", builder.getPath().getSegment(0));
Assert.assertEquals("bbb", builder.getPath().getSegment(1));
Assert.assertEquals("张三", builder.getQuery().get("a"));
Assert.assertEquals("%四", builder.getQuery().get("b"));
Assert.assertEquals("frag1", builder.getFragment());
}
}

View File

@ -1,14 +1,14 @@
package cn.hutool.crypto.symmetric;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
/**
* AES加密算法实现<br>
* 高级加密标准英语Advanced Encryption Standard缩写AES在密码学中又称Rijndael加密法<br>
@ -105,7 +105,7 @@ public class AES extends SymmetricCrypto {
* @since 4.6.7
*/
public AES(Mode mode, Padding padding, SecretKey key, byte[] iv) {
this(mode, padding, key, ArrayUtil.isEmpty(iv) ? ((IvParameterSpec) null) : new IvParameterSpec(iv));
this(mode, padding, key, ArrayUtil.isEmpty(iv) ? null : new IvParameterSpec(iv));
}
/**
@ -153,7 +153,7 @@ public class AES extends SymmetricCrypto {
public AES(String mode, String padding, byte[] key, byte[] iv) {
this(mode, padding,//
SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), key),//
ArrayUtil.isEmpty(iv) ? ((IvParameterSpec) null) : new IvParameterSpec(iv));
ArrayUtil.isEmpty(iv) ? null : new IvParameterSpec(iv));
}
/**

View File

@ -12,11 +12,11 @@ import cn.hutool.core.io.resource.MultiResource;
import cn.hutool.core.io.resource.Resource;
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.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.cookie.GlobalCookieManager;
import cn.hutool.http.ssl.SSLSocketFactoryBuilder;
@ -96,7 +96,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
GlobalCookieManager.setCookieManager(null);
}
private String url;
private UrlBuilder url;
private URLStreamHandler urlHandler;
private Method method = Method.GET;
/**
@ -128,10 +128,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* 是否禁用缓存
*/
private boolean isDisableCache;
/**
* 是否对url中的参数进行编码
*/
private boolean encodeUrlParams;
/**
* 是否是REST请求模式
*/
@ -168,8 +164,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @param url URL
*/
public HttpRequest(String url) {
Assert.notBlank(url, "Param [url] can not be blank !");
this.url = URLUtil.normalize(url, true);
setUrl(url);
// 给定一个默认头信息
this.header(GlobalHeaders.INSTANCE.headers);
}
@ -265,7 +260,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @since 4.1.8
*/
public String getUrl() {
return url;
return url.toString();
}
/**
@ -276,7 +271,19 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @since 4.1.8
*/
public HttpRequest setUrl(String url) {
this.url = url;
this.url = UrlBuilder.ofHttp(url, this.charset);
return this;
}
/**
* 设置URL
*
* @param urlBuilder url字符串
* @return this
* @since 5.3.1
*/
public HttpRequest setUrl(UrlBuilder urlBuilder) {
this.url = urlBuilder;
return this;
}
@ -774,9 +781,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @param isEncodeUrlParams 是否对URL中的参数进行编码
* @return this
* @since 4.4.1
* @deprecated 编码自动完成无需设置
*/
@Deprecated
public HttpRequest setEncodeUrlParams(boolean isEncodeUrlParams) {
this.encodeUrlParams = isEncodeUrlParams;
return this;
}
@ -925,10 +933,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
public HttpResponse execute(boolean isAsync) {
// 初始化URL
urlWithParamIfGet();
// 编码URL
if (this.encodeUrlParams) {
this.url = HttpUtil.encodeParams(this.url, this.charset);
}
// 初始化 connection
initConnection();
@ -982,7 +986,8 @@ public class HttpRequest extends HttpBase<HttpRequest> {
this.httpConnection.disconnectQuietly();
}
this.httpConnection = HttpConnection.create(URLUtil.toUrlForHttp(this.url, this.urlHandler), this.proxy)//
this.httpConnection = HttpConnection
.create(this.url.toURL(this.urlHandler), this.proxy)//
.setMethod(this.method)//
.setHttpsInfo(this.hostnameVerifier, this.ssf)//
.setConnectTimeout(this.connectionTimeout)//
@ -1016,9 +1021,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
if (Method.GET.equals(method) && false == this.isRest) {
// 优先使用body形式的参数不存在使用form
if (ArrayUtil.isNotEmpty(this.bodyBytes)) {
this.url = HttpUtil.urlWithForm(this.url, StrUtil.str(this.bodyBytes, this.charset), this.charset, false);
this.url.getQuery().parse(StrUtil.str(this.bodyBytes, this.charset), this.charset);
} else {
this.url = HttpUtil.urlWithForm(this.url, this.form, this.charset, false);
this.url.getQuery().addAll(this.form);
}
}
}
@ -1047,7 +1052,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
if (responseCode != HttpURLConnection.HTTP_OK) {
if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_SEE_OTHER) {
this.url = httpConnection.header(Header.LOCATION);
setUrl(httpConnection.header(Header.LOCATION));
if (redirectCount < this.maxRedirectCount) {
redirectCount++;
return execute();

View File

@ -471,11 +471,11 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
*/
private String getFileNameFromDisposition() {
String fileName = null;
final String desposition = header(Header.CONTENT_DISPOSITION);
if (StrUtil.isNotBlank(desposition)) {
fileName = ReUtil.get("filename=\"(.*?)\"", desposition, 1);
final String disposition = header(Header.CONTENT_DISPOSITION);
if (StrUtil.isNotBlank(disposition)) {
fileName = ReUtil.get("filename=\"(.*?)\"", disposition, 1);
if (StrUtil.isBlank(fileName)) {
fileName = StrUtil.subAfter(desposition, "filename=", true);
fileName = StrUtil.subAfter(disposition, "filename=", true);
}
}
return fileName;

View File

@ -1,14 +1,12 @@
package cn.hutool.http;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
@ -23,13 +21,9 @@ import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
@ -390,44 +384,11 @@ public class HttpUtil {
* </pre>
*
* @param paramMap 表单数据
* @param charset 编码
* @param charset 编码null表示不encode键值对
* @return url参数
*/
public static String toParams(Map<String, ?> paramMap, Charset charset) {
if (CollectionUtil.isEmpty(paramMap)) {
return StrUtil.EMPTY;
}
if (null == charset) {// 默认编码为系统编码
charset = CharsetUtil.CHARSET_UTF_8;
}
final StringBuilder sb = new StringBuilder();
boolean isFirst = true;
String key;
Object value;
String valueStr;
for (Entry<String, ?> item : paramMap.entrySet()) {
if (isFirst) {
isFirst = false;
} else {
sb.append("&");
}
key = item.getKey();
value = item.getValue();
if (value instanceof Iterable) {
value = CollUtil.join((Iterable<?>) value, ",");
} else if (value instanceof Iterator) {
value = IterUtil.join((Iterator<?>) value, ",");
}
valueStr = Convert.toStr(value);
if (StrUtil.isNotEmpty(key)) {
sb.append(URLUtil.encodeAll(key, charset)).append("=");
if (StrUtil.isNotEmpty(valueStr)) {
sb.append(URLUtil.encodeAll(valueStr, charset));
}
}
}
return sb.toString();
return URLUtil.buildQuery(paramMap, charset);
}
/**
@ -437,7 +398,7 @@ public class HttpUtil {
* <p>注意此方法只能标准化整个URL并不适合于单独编码参数值</p>
*
* @param urlWithParams url和参数可以包含url本身也可以单独参数
* @param charset 编码
* @param charset 编码
* @return 编码后的url和参数
* @since 4.0.1
*/
@ -457,10 +418,10 @@ public class HttpUtil {
// 无参数返回url
return urlPart;
}
} else if(false == StrUtil.contains(urlWithParams, '=')){
} else if (false == StrUtil.contains(urlWithParams, '=')) {
// 无参数的URL
return urlWithParams;
}else {
} else {
// 无URL的参数
paramPart = urlWithParams;
}
@ -536,8 +497,10 @@ public class HttpUtil {
* @param charset 字符集
* @return 参数Map
* @since 4.0.2
* @deprecated 请使用 {@link #decodeParamMap(String, Charset)}
*/
public static HashMap<String, String> decodeParamMap(String paramsStr, String charset) {
@Deprecated
public static Map<String, String> decodeParamMap(String paramsStr, String charset) {
return decodeParamMap(paramsStr, CharsetUtil.charset(charset));
}
@ -549,15 +512,12 @@ public class HttpUtil {
* @return 参数Map
* @since 5.2.6
*/
public static HashMap<String, String> decodeParamMap(String paramsStr, Charset charset) {
final Map<String, List<String>> paramsMap = decodeParams(paramsStr, charset);
final HashMap<String, String> result = MapUtil.newHashMap(paramsMap.size());
List<String> valueList;
for (Entry<String, List<String>> entry : paramsMap.entrySet()) {
valueList = entry.getValue();
result.put(entry.getKey(), CollUtil.isEmpty(valueList) ? null : valueList.get(0));
public static Map<String, String> decodeParamMap(String paramsStr, Charset charset) {
final Map<CharSequence, CharSequence> queryMap = UrlQuery.of(paramsStr, charset).getQueryMap();
if (MapUtil.isEmpty(queryMap)) {
return MapUtil.empty();
}
return result;
return Convert.toMap(String.class, String.class, queryMap);
}
/**
@ -580,56 +540,17 @@ public class HttpUtil {
* @since 5.2.6
*/
public static Map<String, List<String>> decodeParams(String paramsStr, Charset charset) {
if (StrUtil.isBlank(paramsStr)) {
return Collections.emptyMap();
final Map<CharSequence, CharSequence> queryMap = UrlQuery.of(paramsStr, charset).getQueryMap();
if (MapUtil.isEmpty(queryMap)) {
return MapUtil.empty();
}
// 去掉Path部分
int pathEndPos = paramsStr.indexOf('?');
if (pathEndPos > -1) {
paramsStr = StrUtil.subSuf(paramsStr, pathEndPos + 1);
if (StrUtil.isBlank(paramsStr)) {
return Collections.emptyMap();
}
}
final int len = paramsStr.length();
final Map<String, List<String>> params = new LinkedHashMap<>();
String name = null;
int pos = 0; // 未处理字符开始位置
int i; // 未处理字符结束位置
char c; // 当前字符
for (i = 0; i < len; i++) {
c = paramsStr.charAt(i);
if (c == '=') { // 键值对的分界点
if (null == name) {
// name可以是""
name = paramsStr.substring(pos, i);
}
pos = i + 1;
} else if (c == '&') { // 参数对的分界点
if (null == name && pos != i) {
// 对于像&a&这类无参数值的字符串我们将name为a的值设为""
addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset);
} else if (name != null) {
addParam(params, name, paramsStr.substring(pos, i), charset);
name = null;
}
pos = i + 1;
}
}
// 处理结尾
if (pos != i) {
if (name == null) {
addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset);
} else {
addParam(params, name, paramsStr.substring(pos, i), charset);
}
} else if (name != null) {
addParam(params, name, StrUtil.EMPTY, charset);
}
queryMap.forEach((key, value) -> {
final List<String> values = params.computeIfAbsent(StrUtil.str(key), k -> new ArrayList<>(1));
// 一般是一个参数
values.add(StrUtil.str(value));
});
return params;
}
@ -826,26 +747,7 @@ public class HttpUtil {
* @return {@link SimpleServer}
* @since 5.2.6
*/
public static SimpleServer createServer(int port){
public static SimpleServer createServer(int port) {
return new SimpleServer(port);
}
// ----------------------------------------------------------------------------------------- Private method start
/**
* 将键值对加入到值为List类型的Map中
*
* @param params 参数
* @param name key
* @param value value
* @param charset 编码
*/
private static void addParam(Map<String, List<String>> params, String name, String value, Charset charset) {
name = URLUtil.decode(name, charset);
value = URLUtil.decode(value, charset);
final List<String> values = params.computeIfAbsent(name, k -> new ArrayList<>(1));
// 一般是一个参数
values.add(value);
}
// ----------------------------------------------------------------------------------------- Private method start end
}

View File

@ -11,6 +11,7 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -18,7 +19,7 @@ import java.util.Map;
public class HttpUtilTest {
@Test
@Ignore
// @Ignore
public void postTest() {
String result = HttpUtil.createPost("api.uhaozu.com/goods/description/1120448506").charset(CharsetUtil.UTF_8).execute().body();
Console.log(result);
@ -71,6 +72,17 @@ public class HttpUtilTest {
Console.log(str);
}
@Test
@Ignore
public void getTest5() {
String url2 = "http://storage.chancecloud.com.cn/20200413_%E7%B2%A4B12313_386.pdf";
ByteArrayOutputStream os2 = new ByteArrayOutputStream();
HttpUtil.download(url2, os2, false);
url2 = "http://storage.chancecloud.com.cn/20200413_粤B12313_386.pdf";
HttpUtil.download(url2, os2, false);
}
@Test
@Ignore
public void get12306Test() {