From 01e310a6e708b20fb59826bcf65dfd74f80aae4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=B2=BE=E5=8D=8E?= <842761733@qq.com> Date: Fri, 29 Nov 2019 14:41:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E9=A2=84=E8=A7=88UR?= =?UTF-8?q?L=E7=89=B9=E6=AE=8A=E5=AD=97=E4=BD=93=E4=BD=BF=E7=94=A8Hutool?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/cn/keking/hutool/HexUtil.java | 388 ++++++++++++++++++ .../main/java/cn/keking/hutool/StrUtil.java | 283 +++++++++++++ .../java/cn/keking/hutool/URLEncoder.java | 232 +++++++++++ .../main/java/cn/keking/hutool/URLUtil.java | 70 ++++ .../java/cn/keking/utils/DownloadUtils.java | 91 +--- 5 files changed, 975 insertions(+), 89 deletions(-) create mode 100644 jodconverter-web/src/main/java/cn/keking/hutool/HexUtil.java create mode 100644 jodconverter-web/src/main/java/cn/keking/hutool/StrUtil.java create mode 100644 jodconverter-web/src/main/java/cn/keking/hutool/URLEncoder.java create mode 100644 jodconverter-web/src/main/java/cn/keking/hutool/URLUtil.java diff --git a/jodconverter-web/src/main/java/cn/keking/hutool/HexUtil.java b/jodconverter-web/src/main/java/cn/keking/hutool/HexUtil.java new file mode 100644 index 00000000..34d75f8d --- /dev/null +++ b/jodconverter-web/src/main/java/cn/keking/hutool/HexUtil.java @@ -0,0 +1,388 @@ +package cn.keking.hutool; + +import java.awt.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * 十六进制(简写为hex或下标16)在数学中是一种逢16进1的进位制,一般用数字0到9和字母A到F表示(其中:A~F即10~15)。
+ * 例如十进制数57,在二进制写作111001,在16进制写作39。
+ * 像java,c这样的语言为了区分十六进制和十进制数值,会在十六进制数的前面加上 0x,比如0x20是十进制的32,而不是十进制的20
+ *

+ * 参考:https://my.oschina.net/xinxingegeya/blog/287476 + * + * @author Looly + */ +public class HexUtil { + + /** + * 用于建立十六进制字符的输出的小写字符数组 + */ + private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + /** + * 用于建立十六进制字符的输出的大写字符数组 + */ + private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + /** + * 判断给定字符串是否为16进制数
+ * 如果是,需要使用对应数字类型对象的decode方法解码
+ * 例如:{@code Integer.decode}方法解码int类型的16进制数字 + * + * @param value 值 + * @return 是否为16进制 + */ + public static boolean isHexNumber(String value) { + final int index = (value.startsWith("-") ? 1 : 0); + if (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index)) { + try { + Long.decode(value); + } catch (NumberFormatException e) { + return false; + } + return true; + } else { + return false; + } + } + + // ---------------------------------------------------------------------------------------------------- encode + + /** + * 将字节数组转换为十六进制字符数组 + * + * @param data byte[] + * @return 十六进制char[] + */ + public static char[] encodeHex(byte[] data) { + return encodeHex(data, true); + } + + /** + * 将字节数组转换为十六进制字符数组 + * + * @param str 字符串 + * @param charset 编码 + * @return 十六进制char[] + */ + public static char[] encodeHex(String str, Charset charset) { + return encodeHex(StrUtil.bytes(str, charset), true); + } + + /** + * 将字节数组转换为十六进制字符数组 + * + * @param data byte[] + * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 + * @return 十六进制char[] + */ + public static char[] encodeHex(byte[] data, boolean toLowerCase) { + return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + /** + * 将字节数组转换为十六进制字符串 + * + * @param data byte[] + * @return 十六进制String + */ + public static String encodeHexStr(byte[] data) { + return encodeHexStr(data, true); + } + + /** + * 将字节数组转换为十六进制字符串,结果为小写 + * + * @param data 被编码的字符串 + * @param charset 编码 + * @return 十六进制String + */ + public static String encodeHexStr(String data, Charset charset) { + return encodeHexStr(StrUtil.bytes(data, charset), true); + } + + /** + * 将字节数组转换为十六进制字符串,结果为小写,默认编码是UTF-8 + * + * @param data 被编码的字符串 + * @return 十六进制String + */ + public static String encodeHexStr(String data) { + return encodeHexStr(data, StandardCharsets.UTF_8); + } + + /** + * 将字节数组转换为十六进制字符串 + * + * @param data byte[] + * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 + * @return 十六进制String + */ + public static String encodeHexStr(byte[] data, boolean toLowerCase) { + return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + // ---------------------------------------------------------------------------------------------------- decode + + /** + * 将十六进制字符数组转换为字符串,默认编码UTF-8 + * + * @param hexStr 十六进制String + * @return 字符串 + */ + public static String decodeHexStr(String hexStr) { + return decodeHexStr(hexStr, StandardCharsets.UTF_8); + } + + /** + * 将十六进制字符数组转换为字符串 + * + * @param hexStr 十六进制String + * @param charset 编码 + * @return 字符串 + */ + public static String decodeHexStr(String hexStr, Charset charset) { + if (StrUtil.isEmpty(hexStr)) { + return hexStr; + } + return decodeHexStr(hexStr.toCharArray(), charset); + } + + /** + * 将十六进制字符数组转换为字符串 + * + * @param hexData 十六进制char[] + * @param charset 编码 + * @return 字符串 + */ + public static String decodeHexStr(char[] hexData, Charset charset) { + return StrUtil.str(decodeHex(hexData), charset); + } + + /** + * 将十六进制字符数组转换为字节数组 + * + * @param hexData 十六进制char[] + * @return byte[] + * @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度,将抛出运行时异常 + */ + public static byte[] decodeHex(char[] hexData) { + + int len = hexData.length; + + if ((len & 0x01) != 0) { + throw new RuntimeException("Odd number of characters."); + } + + byte[] out = new byte[len >> 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; j < len; i++) { + int f = toDigit(hexData[j], j) << 4; + j++; + f = f | toDigit(hexData[j], j); + j++; + out[i] = (byte) (f & 0xFF); + } + + return out; + } + + /** + * 将十六进制字符串解码为byte[] + * + * @param hexStr 十六进制String + * @return byte[] + */ + public static byte[] decodeHex(String hexStr) { + if (StrUtil.isEmpty(hexStr)) { + return null; + } + return decodeHex(hexStr.toCharArray()); + } + + // ---------------------------------------------------------------------------------------- Color + + /** + * 将{@link Color}编码为Hex形式 + * + * @param color {@link Color} + * @return Hex字符串 + * @since 3.0.8 + */ + public static String encodeColor(Color color) { + return encodeColor(color, "#"); + } + + /** + * 将{@link Color}编码为Hex形式 + * + * @param color {@link Color} + * @param prefix 前缀字符串,可以是#、0x等 + * @return Hex字符串 + * @since 3.0.8 + */ + public static String encodeColor(Color color, String prefix) { + final StringBuilder builder = new StringBuilder(prefix); + String colorHex; + colorHex = Integer.toHexString(color.getRed()); + if (1 == colorHex.length()) { + builder.append('0'); + } + builder.append(colorHex); + colorHex = Integer.toHexString(color.getGreen()); + if (1 == colorHex.length()) { + builder.append('0'); + } + builder.append(colorHex); + colorHex = Integer.toHexString(color.getBlue()); + if (1 == colorHex.length()) { + builder.append('0'); + } + builder.append(colorHex); + return builder.toString(); + } + + /** + * 将Hex颜色值转为 + * + * @param hexColor 16进制颜色值,可以以#开头,也可以用0x开头 + * @return {@link Color} + * @since 3.0.8 + */ + public static Color decodeColor(String hexColor) { + return Color.decode(hexColor); + } + + /** + * 将指定int值转换为Unicode字符串形式,常用于特殊字符(例如汉字)转Unicode形式
+ * 转换的字符串如果u后不足4位,则前面用0填充,例如: + * + *

+	 * '我' =》\u4f60
+	 * 
+ * + * @param value int值,也可以是char + * @return Unicode表现形式 + */ + public static String toUnicodeHex(int value) { + final StringBuilder builder = new StringBuilder(6); + + builder.append("\\u"); + String hex = toHex(value); + int len = hex.length(); + if (len < 4) { + builder.append("0000", 0, 4 - len);// 不足4位补0 + } + builder.append(hex); + + return builder.toString(); + } + + /** + * 将指定char值转换为Unicode字符串形式,常用于特殊字符(例如汉字)转Unicode形式
+ * 转换的字符串如果u后不足4位,则前面用0填充,例如: + * + *
+	 * '我' =》\u4f60
+	 * 
+ * + * @param ch char值 + * @return Unicode表现形式 + * @since 4.0.1 + */ + public static String toUnicodeHex(char ch) { + return "\\u" +// + DIGITS_LOWER[(ch >> 12) & 15] +// + DIGITS_LOWER[(ch >> 8) & 15] +// + DIGITS_LOWER[(ch >> 4) & 15] +// + DIGITS_LOWER[(ch) & 15]; + } + + /** + * 转为16进制字符串 + * + * @param value int值 + * @return 16进制字符串 + * @since 4.4.1 + */ + public static String toHex(int value) { + return Integer.toHexString(value); + } + + /** + * 转为16进制字符串 + * + * @param value int值 + * @return 16进制字符串 + * @since 4.4.1 + */ + public static String toHex(long value) { + return Long.toHexString(value); + } + + /** + * 将byte值转为16进制并添加到{@link StringBuilder}中 + * + * @param builder {@link StringBuilder} + * @param b byte + * @param toLowerCase 是否使用小写 + * @since 4.4.1 + */ + public static void appendHex(StringBuilder builder, byte b, boolean toLowerCase) { + final char[] toDigits = toLowerCase ? DIGITS_LOWER : DIGITS_UPPER; + + int high = (b & 0xf0) >>> 4;//高位 + int low = b & 0x0f;//低位 + builder.append(toDigits[high]); + builder.append(toDigits[low]); + } + + // ---------------------------------------------------------------------------------------- Private method start + + /** + * 将字节数组转换为十六进制字符串 + * + * @param data byte[] + * @param toDigits 用于控制输出的char[] + * @return 十六进制String + */ + private static String encodeHexStr(byte[] data, char[] toDigits) { + return new String(encodeHex(data, toDigits)); + } + + /** + * 将字节数组转换为十六进制字符数组 + * + * @param data byte[] + * @param toDigits 用于控制输出的char[] + * @return 十六进制char[] + */ + private static char[] encodeHex(byte[] data, char[] toDigits) { + final int len = data.length; + final char[] out = new char[len << 1];//len*2 + // two characters from the hex value. + for (int i = 0, j = 0; i < len; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4];// 高位 + out[j++] = toDigits[0x0F & data[i]];// 低位 + } + return out; + } + + /** + * 将十六进制字符转换成一个整数 + * + * @param ch 十六进制char + * @param index 十六进制字符在字符数组中的位置 + * @return 一个整数 + * @throws RuntimeException 当ch不是一个合法的十六进制字符时,抛出运行时异常 + */ + private static int toDigit(char ch, int index) { + int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index); + } + return digit; + } + // ---------------------------------------------------------------------------------------- Private method end +} \ No newline at end of file diff --git a/jodconverter-web/src/main/java/cn/keking/hutool/StrUtil.java b/jodconverter-web/src/main/java/cn/keking/hutool/StrUtil.java new file mode 100644 index 00000000..91d62c5b --- /dev/null +++ b/jodconverter-web/src/main/java/cn/keking/hutool/StrUtil.java @@ -0,0 +1,283 @@ +package cn.keking.hutool; + +import java.nio.charset.Charset; + +/** + * 字符串工具类 + * + * @author xiaoleilu + * + */ +public class StrUtil { + + public static final String EMPTY = ""; + + /** + * 是否空白符
+ * 空白符包括空格、制表符、全角空格和不间断空格
+ * + * @see Character#isWhitespace(int) + * @see Character#isSpaceChar(int) + * @param c 字符 + * @return 是否空白符 + * @since 4.0.10 + */ + public static boolean isBlankChar(int c) { + return Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\ufeff' || c == '\u202a'; + } + + /** + * 是否空白符
+ * 空白符包括空格、制表符、全角空格和不间断空格
+ * + * @param c 字符 + * @return 是否空白符 + * @see Character#isWhitespace(int) + * @see Character#isSpaceChar(int) + * @since 4.0.10 + */ + public static boolean isBlankChar(char c) { + return isBlankChar((int) c); + } + + /** + * 字符串是否为空白 空白的定义如下:
+ * 1、为null
+ * 2、为不可见字符(如空格)
+ * 3、""
+ * + * @param str 被检测的字符串 + * @return 是否为空 + */ + public static boolean isBlank(CharSequence str) { + int length; + + if ((str == null) || ((length = str.length()) == 0)) { + return true; + } + + for (int i = 0; i < length; i++) { + // 只要有一个非空字符即为非空字符串 + if (false == isBlankChar(str.charAt(i))) { + return false; + } + } + + return true; + } + + /** + * 字符串是否为空,空的定义如下:
+ * 1、为null
+ * 2、为""
+ * + * @param str 被检测的字符串 + * @return 是否为空 + */ + public static boolean isEmpty(CharSequence str) { + return str == null || str.length() == 0; + } + + /** + * 编码字符串 + * + * @param str 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 编码后的字节码 + */ + public static byte[] bytes(CharSequence str, Charset charset) { + if (str == null) { + return null; + } + + if (null == charset) { + return str.toString().getBytes(); + } + return str.toString().getBytes(charset); + } + + /** + * {@link CharSequence} 转为字符串,null安全 + * + * @param cs {@link CharSequence} + * @return 字符串 + */ + public static String str(CharSequence cs) { + return null == cs ? null : cs.toString(); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 解码后的字符串 + */ + public static String str(byte[] data, Charset charset) { + if (data == null) { + return null; + } + + if (null == charset) { + return new String(data); + } + return new String(data, charset); + } + + /** + * 改进JDK subString
+ * index从0开始计算,最后一个字符为-1
+ * 如果from和to位置一样,返回 ""
+ * 如果from或to为负数,则按照length从后向前数位置,如果绝对值大于字符串长度,则from归到0,to归到length
+ * 如果经过修正的index中from大于to,则互换from和to example:
+ * abcdefgh 2 3 =》 c
+ * abcdefgh 2 -3 =》 cde
+ * + * @param str String + * @param fromIndex 开始的index(包括) + * @param toIndex 结束的index(不包括) + * @return 字串 + */ + public static String sub(CharSequence str, int fromIndex, int toIndex) { + if (isEmpty(str)) { + return str(str); + } + int len = str.length(); + + if (fromIndex < 0) { + fromIndex = len + fromIndex; + if (fromIndex < 0) { + fromIndex = 0; + } + } else if (fromIndex > len) { + fromIndex = len; + } + + if (toIndex < 0) { + toIndex = len + toIndex; + if (toIndex < 0) { + toIndex = len; + } + } else if (toIndex > len) { + toIndex = len; + } + + if (toIndex < fromIndex) { + int tmp = fromIndex; + fromIndex = toIndex; + toIndex = tmp; + } + + if (fromIndex == toIndex) { + return EMPTY; + } + + return str.toString().substring(fromIndex, toIndex); + } + + /** + * 切割指定位置之前部分的字符串 + * + * @param string 字符串 + * @param toIndex 切割到的位置(不包括) + * @return 切割后的剩余的前半部分字符串 + */ + public static String subPre(CharSequence string, int toIndex) { + return sub(string, 0, toIndex); + } + + /** + * 切割指定位置之后部分的字符串 + * + * @param string 字符串 + * @param fromIndex 切割开始的位置(包括) + * @return 切割后后剩余的后半部分字符串 + */ + public static String subSuf(CharSequence string, int fromIndex) { + if (isEmpty(string)) { + return null; + } + return sub(string, fromIndex, string.length()); + } + + /** + * 指定范围内查找指定字符 + * + * @param str 字符串 + * @param searchChar 被查找的字符 + * @param start 起始位置,如果小于0,从0开始查找 + * @param end 终止位置,如果超过str.length()则默认查找到字符串末尾 + * @return 位置 + */ + public static int indexOf(final CharSequence str, char searchChar, int start, int end) { + final int len = str.length(); + if (start < 0 || start > len) { + start = 0; + } + if (end > len || end < 0) { + end = len; + } + for (int i = start; i < end; i++) { + if (str.charAt(i) == searchChar) { + return i; + } + } + return -1; + } + + /** + * 指定范围内查找指定字符 + * + * @param str 字符串 + * @param searchChar 被查找的字符 + * @param start 起始位置,如果小于0,从0开始查找 + * @return 位置 + */ + public static int indexOf(final CharSequence str, char searchChar, int start) { + if (str instanceof String) { + return ((String) str).indexOf(searchChar, start); + } else { + return indexOf(str, searchChar, start, -1); + } + } + + /** + * 指定范围内查找指定字符 + * + * @param str 字符串 + * @param searchChar 被查找的字符 + * @return 位置 + */ + public static int indexOf(final CharSequence str, char searchChar) { + return indexOf(str, searchChar, 0); + } + + /** + * 如果字符串是null,则返回指定默认字符串,否则返回字符串本身。 + * + *
+	 * nullToDefault(null, "default")  = "default"
+	 * nullToDefault("", "default")    = ""
+	 * nullToDefault("  ", "default")  = "  "
+	 * nullToDefault("bat", "default") = "bat"
+	 * 
+ * + * @param str 要转换的字符串 + * @param defaultStr 默认字符串 + * + * @return 字符串本身或指定的默认字符串 + */ + public static String nullToDefault(CharSequence str, String defaultStr) { + return (str == null) ? defaultStr : str.toString(); + } + + /** + * 当给定字符串为null时,转换为Empty + * + * @param str 被转换的字符串 + * @return 转换后的字符串 + */ + public static String nullToEmpty(CharSequence str) { + return nullToDefault(str, EMPTY); + } +} diff --git a/jodconverter-web/src/main/java/cn/keking/hutool/URLEncoder.java b/jodconverter-web/src/main/java/cn/keking/hutool/URLEncoder.java new file mode 100644 index 00000000..ccb2b92a --- /dev/null +++ b/jodconverter-web/src/main/java/cn/keking/hutool/URLEncoder.java @@ -0,0 +1,232 @@ +package cn.keking.hutool; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.util.BitSet; + +/** + * URL编码,数据内容的类型是 application/x-www-form-urlencoded。 + * + *
+ * 1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不会被编码;
+ * 2.将空格转换为%20 ;
+ * 3.将非文本内容转换成"%xy"的形式,xy是两位16进制的数值;
+ * 4.在每个 name=value 对之间放置 & 符号。
+ * 
+ * + * @author looly, + * + */ +public class URLEncoder implements Serializable{ + private static final long serialVersionUID = 1L; + + // --------------------------------------------------------------------------------------------- Static method start + /** + * 默认{@link URLEncoder}
+ * 默认的编码器针对URI路径编码,定义如下: + * + *
+	 * pchar = unreserved(不处理) / pct-encoded / sub-delims(子分隔符) / ":" / "@"
+	 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+	 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
+	 * 
+ */ + public static final URLEncoder DEFAULT = createDefault(); + + /** + * 用于查询语句的{@link URLEncoder}
+ * 编码器针对URI路径编码,定义如下: + * + *
+	 * 0x20 ' ' =》 '+' 
+	 * 0x2A, 0x2D, 0x2E, 0x30 to 0x39, 0x41 to 0x5A, 0x5F, 0x61 to 0x7A as-is 
+	 * '*', '-', '.', '0' to '9', 'A' to 'Z', '_', 'a' to 'z' Also '=' and '&' 不编码
+	 * 其它编码为 %nn 形式
+	 * 
+ * + * 详细见:https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm + */ + public static final URLEncoder QUERY = createQuery(); + + /** + * 创建默认{@link URLEncoder}
+ * 默认的编码器针对URI路径编码,定义如下: + * + *
+	 * pchar = unreserved(不处理) / pct-encoded / sub-delims(子分隔符) / ":" / "@"
+	 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+	 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
+	 * 
+ * + * @return {@link URLEncoder} + */ + public static URLEncoder createDefault() { + final URLEncoder encoder = new URLEncoder(); + encoder.addSafeCharacter('-'); + encoder.addSafeCharacter('.'); + encoder.addSafeCharacter('_'); + encoder.addSafeCharacter('~'); + // Add the sub-delims + encoder.addSafeCharacter('!'); + encoder.addSafeCharacter('$'); + encoder.addSafeCharacter('&'); + encoder.addSafeCharacter('\''); + encoder.addSafeCharacter('('); + encoder.addSafeCharacter(')'); + encoder.addSafeCharacter('*'); + encoder.addSafeCharacter('+'); + encoder.addSafeCharacter(','); + encoder.addSafeCharacter(';'); + encoder.addSafeCharacter('='); + // Add the remaining literals + encoder.addSafeCharacter(':'); + encoder.addSafeCharacter('@'); + // Add '/' so it isn't encoded when we encode a path + encoder.addSafeCharacter('/'); + + return encoder; + } + + /** + * 创建用于查询语句的{@link URLEncoder}
+ * 编码器针对URI路径编码,定义如下: + * + *
+	 * 0x20 ' ' =》 '+' 
+	 * 0x2A, 0x2D, 0x2E, 0x30 to 0x39, 0x41 to 0x5A, 0x5F, 0x61 to 0x7A as-is 
+	 * '*', '-', '.', '0' to '9', 'A' to 'Z', '_', 'a' to 'z' Also '=' and '&' 不编码
+	 * 其它编码为 %nn 形式
+	 * 
+ * + * 详细见:https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm + * + * @return {@link URLEncoder} + */ + public static URLEncoder createQuery() { + final URLEncoder encoder = new URLEncoder(); + // Special encoding for space + encoder.setEncodeSpaceAsPlus(true); + // Alpha and digit are safe by default + // Add the other permitted characters + encoder.addSafeCharacter('*'); + encoder.addSafeCharacter('-'); + encoder.addSafeCharacter('.'); + encoder.addSafeCharacter('_'); + encoder.addSafeCharacter('='); + encoder.addSafeCharacter('&'); + + return encoder; + } + // --------------------------------------------------------------------------------------------- Static method end + + /** 存放安全编码 */ + private final BitSet safeCharacters; + /** 是否编码空格为+ */ + private boolean encodeSpaceAsPlus = false; + + /** + * 构造
+ * + * [a-zA-Z0-9]默认不被编码 + */ + public URLEncoder() { + this(new BitSet(256)); + + for (char i = 'a'; i <= 'z'; i++) { + addSafeCharacter(i); + } + for (char i = 'A'; i <= 'Z'; i++) { + addSafeCharacter(i); + } + for (char i = '0'; i <= '9'; i++) { + addSafeCharacter(i); + } + } + + /** + * 构造 + * + * @param safeCharacters 安全字符,安全字符不被编码 + */ + private URLEncoder(BitSet safeCharacters) { + this.safeCharacters = safeCharacters; + } + + /** + * 增加安全字符
+ * 安全字符不被编码 + * + * @param c 字符 + */ + public void addSafeCharacter(char c) { + safeCharacters.set(c); + } + + /** + * 移除安全字符
+ * 安全字符不被编码 + * + * @param c 字符 + */ + public void removeSafeCharacter(char c) { + safeCharacters.clear(c); + } + + /** + * 是否将空格编码为+ + * + * @param encodeSpaceAsPlus 是否将空格编码为+ + */ + public void setEncodeSpaceAsPlus(boolean encodeSpaceAsPlus) { + this.encodeSpaceAsPlus = encodeSpaceAsPlus; + } + + /** + * 将URL中的字符串编码为%形式 + * + * @param path 需要编码的字符串 + * @param charset 编码 + * + * @return 编码后的字符串 + */ + public String encode(String path, Charset charset) { + + int maxBytesPerChar = 10; + final StringBuilder rewrittenPath = new StringBuilder(path.length()); + ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar); + OutputStreamWriter writer = new OutputStreamWriter(buf, charset); + + int c; + for (int i = 0; i < path.length(); i++) { + c = path.charAt(i); + if (safeCharacters.get(c)) { + rewrittenPath.append((char) c); + } else if (encodeSpaceAsPlus && c == ' ') { + // 对于空格单独处理 + rewrittenPath.append('+'); + } else { + // convert to external encoding before hex conversion + try { + writer.write((char) c); + writer.flush(); + } catch (IOException e) { + buf.reset(); + continue; + } + + byte[] ba = buf.toByteArray(); + for (int j = 0; j < ba.length; j++) { + // Converting each byte in the buffer + byte toEncode = ba[j]; + rewrittenPath.append('%'); + HexUtil.appendHex(rewrittenPath, toEncode, false); + } + buf.reset(); + } + } + return rewrittenPath.toString(); + } +} diff --git a/jodconverter-web/src/main/java/cn/keking/hutool/URLUtil.java b/jodconverter-web/src/main/java/cn/keking/hutool/URLUtil.java new file mode 100644 index 00000000..98a0dac1 --- /dev/null +++ b/jodconverter-web/src/main/java/cn/keking/hutool/URLUtil.java @@ -0,0 +1,70 @@ +package cn.keking.hutool; + +import java.nio.charset.StandardCharsets; + +/** + * 统一资源定位符相关工具类 + * + * @author xiaoleilu + * + */ +public class URLUtil { + + /** + * 标准化URL字符串,包括: + * + *
+	 * 1. 多个/替换为一个
+	 * 
+ * + * @param url URL字符串 + * @return 标准化后的URL字符串 + */ + public static String normalize(String url) { + return normalize(url, false); + } + + /** + * 标准化URL字符串,包括: + * + *
+	 * 1. 多个/替换为一个
+	 * 
+ * + * @param url URL字符串 + * @param isEncodeBody 是否对URL中body部分的中文和特殊字符做转义(不包括http:和/) + * @return 标准化后的URL字符串 + * @since 4.4.1 + */ + public static String normalize(String url, boolean isEncodeBody) { + if (StrUtil.isBlank(url)) { + return url; + } + final int sepIndex = url.indexOf("://"); + String pre; + String body; + if (sepIndex > 0) { + pre = StrUtil.subPre(url, sepIndex + 3); + body = StrUtil.subSuf(url, sepIndex + 3); + } else { + pre = "http://"; + body = url; + } + + final int paramsSepIndex = StrUtil.indexOf(body, '?'); + String params = null; + if (paramsSepIndex > 0) { + params = StrUtil.subSuf(body, paramsSepIndex); + body = StrUtil.subPre(body, paramsSepIndex); + } + + // 去除开头的\或者/ + body = body.replaceAll("^[\\\\/]+", StrUtil.EMPTY); + // 替换多个\或/为单个/ + body = body.replace("\\", "/").replaceAll("//+", "/"); + if (isEncodeBody) { + body = URLEncoder.DEFAULT.encode(body, StandardCharsets.UTF_8); + } + return pre + body + StrUtil.nullToEmpty(params); + } +} \ No newline at end of file diff --git a/jodconverter-web/src/main/java/cn/keking/utils/DownloadUtils.java b/jodconverter-web/src/main/java/cn/keking/utils/DownloadUtils.java index 26e721e6..21ba2e2d 100644 --- a/jodconverter-web/src/main/java/cn/keking/utils/DownloadUtils.java +++ b/jodconverter-web/src/main/java/cn/keking/utils/DownloadUtils.java @@ -1,6 +1,7 @@ package cn.keking.utils; import cn.keking.config.ConfigConstants; +import cn.keking.hutool.URLUtil; import cn.keking.model.FileAttribute; import cn.keking.model.ReturnResponse; import org.slf4j.Logger; @@ -29,11 +30,6 @@ public class DownloadUtils { private static final String URL_PARAM_FTP_CONTROL_ENCODING = "ftp.control.encoding"; /** - * 一开始测试的时候发现有些文件没有下载下来,而有些可以;当时也是郁闷了好一阵,但是最终还是不得解 - * 再次测试的时候,通过前台对比url发现,原来参数中有+号特殊字符存在,但是到后之后却变成了空格,突然恍然大悟 - * 应该是转义出了问题,url转义中会把+号当成空格来计算,所以才会出现这种情况,遂想要通过整体替换空格为加号,因为url - * 中的参数部分是不会出现空格的,但是文件名中就不好确定了,所以只对url参数部分做替换 - * 注: 针对URLEncoder.encode(s,charset)会将空格转成+的情况需要做下面的替换工作 * @param fileAttribute * @return */ @@ -43,12 +39,7 @@ public class DownloadUtils { ReturnResponse response = new ReturnResponse<>(0, "下载成功!!!", ""); URL url = null; try { - urlAddress = replacePlusMark(urlAddress); - urlAddress = encodeUrlParam(urlAddress); - // 因为tomcat不能处理'+'号,所以讲'+'号替换成'%20%' - // 也不能处理空格 - urlAddress = urlAddress.replaceAll("\\+", "%20"); - urlAddress = urlAddress.replaceAll(" ", "%20"); + urlAddress = URLUtil.normalize(urlAddress, true); url = new URL(urlAddress); } catch (MalformedURLException e) { e.printStackTrace(); @@ -105,84 +96,6 @@ public class DownloadUtils { } } - /** - * 注:可能是原来因为前端通过encodeURI来编码的,因为通过encodeURI编码+会被转成+号(亦即没有转), - * 而通过encodeURIComponent则会转成%2B,这样URLDecoder是可以正确处理的,所以也就没有必要在这里替换了 - * 转换url参数部分的空格为加号(因为在url编解码的过程中出现+转为空格的情况) - * @param urlAddress - * @return - */ - private String replacePlusMark(String urlAddress) { - if (urlAddress.contains("?")) { - String nonParamStr = urlAddress.substring(0,urlAddress.indexOf("?") + 1); - String paramStr = urlAddress.substring(nonParamStr.length()); - return nonParamStr + paramStr.replace(" ", "+"); - } - return urlAddress; - } - - /** - * 对最有一个路径进行转码 - * @param urlAddress - * http://192.168.2.111:8013/demo/Handle中文.zip - * http://192.168.2.111:8013/download?id=1&filename=中文.zip - * @return - */ - - private String encodeUrlParam(String urlAddress){ - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < urlAddress.length(); i++) { - char c = urlAddress.charAt(i); - if (c >= 0 && c <= 255) { - sb.append(c); - } else { - byte[] b; - try { - //指定需要的编码类型 - b = String.valueOf(c).getBytes("utf-8"); - } catch (Exception ex) { - System.out.println(ex); - b = new byte[0]; - } - for (int j = 0; j < b.length; j++) { - int k = b[j]; - if (k < 0) { - k += 256; - } - sb.append("%" + Integer.toHexString(k).toUpperCase()); - } - } - } - return sb.toString(); - } - - - - /** - * 因为jodConvert2.1不支持ms2013版本的office转换,这里偷懒,尝试看改一下文件类型,让jodConvert2.1去 - * 处理ms2013,看结果如何,如果问题很大的话只能采取其他方式,如果没有问题,暂时使用该版本来转换 - * @param type - * @return - */ - private String dealWithMS2013(String type) { - String newType = null; - switch (type){ - case "docx": - newType = "doc"; - break; - case "xlsx": - newType = "doc"; - break; - case "pptx": - newType = "ppt"; - break; - default: - newType = type; - break; - } - return newType; - } - /** * 转换文本文件编码为utf8 * 探测源文件编码,探测到编码切不为utf8则进行转码