fix escape bug

This commit is contained in:
Looly 2020-02-28 14:22:16 +08:00
parent 83d6428db8
commit fbfb124f11
5 changed files with 86 additions and 28 deletions

View File

@ -7,8 +7,10 @@
### 新特性 ### 新特性
* 【poi 】 Excel合并单元格读取同一个值不再为空 * 【poi 】 Excel合并单元格读取同一个值不再为空
* 【core 】 增加EscapeUtil.escapeAllissue#758@Github
### Bug修复 ### Bug修复
* 【core 】 修复EscapeUtil.escape转义错误issue#758@Github
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -29,6 +29,7 @@ public class ReplacerChain extends StrReplacer implements Chain<StrReplacer, Rep
} }
} }
@SuppressWarnings("NullableProblems")
@Override @Override
public Iterator<StrReplacer> iterator() { public Iterator<StrReplacer> iterator() {
return replacers.iterator(); return replacers.iterator();

View File

@ -8,31 +8,32 @@ import cn.hutool.core.text.StrBuilder;
/** /**
* 抽象字符串替换类<br> * 抽象字符串替换类<br>
* 通过实现replace方法实现局部替换逻辑 * 通过实现replace方法实现局部替换逻辑
* *
* @author looly * @author looly
* @since 4.1.5 * @since 4.1.5
*/ */
public abstract class StrReplacer implements Replacer<CharSequence>, Serializable{ public abstract class StrReplacer implements Replacer<CharSequence>, Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
* 抽象的字符串替换方法通过传入原字符串和当前位置执行替换逻辑返回处理或替换的字符串长度部分 * 抽象的字符串替换方法通过传入原字符串和当前位置执行替换逻辑返回处理或替换的字符串长度部分
*
* @param str 被处理的字符串 * @param str 被处理的字符串
* @param pos 当前位置 * @param pos 当前位置
* @param out 输出 * @param out 输出
* @return 处理的原字符串长度0表示跳过此字符 * @return 处理的原字符串长度0表示跳过此字符
*/ */
protected abstract int replace(CharSequence str, int pos, StrBuilder out); protected abstract int replace(CharSequence str, int pos, StrBuilder out);
@Override @Override
public CharSequence replace(CharSequence t) { public CharSequence replace(CharSequence t) {
final int len = t.length(); final int len = t.length();
final StrBuilder strBuillder = StrBuilder.create(len); final StrBuilder strBuillder = StrBuilder.create(len);
int pos = 0;//当前位置 int pos = 0;//当前位置
int consumed;//处理过的字符数 int consumed;//处理过的字符数
while(pos < len) { while (pos < len) {
consumed = replace(t, pos, strBuillder); consumed = replace(t, pos, strBuillder);
if(0 == consumed) { if (0 == consumed) {
//0表示未处理或替换任何字符原样输出本字符并从下一个字符继续 //0表示未处理或替换任何字符原样输出本字符并从下一个字符继续
strBuillder.append(t.charAt(pos)); strBuillder.append(t.charAt(pos));
pos++; pos++;

View File

@ -1,5 +1,6 @@
package cn.hutool.core.util; package cn.hutool.core.util;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.text.escape.Html4Escape; import cn.hutool.core.text.escape.Html4Escape;
import cn.hutool.core.text.escape.Html4Unescape; import cn.hutool.core.text.escape.Html4Unescape;
@ -7,57 +8,87 @@ import cn.hutool.core.text.escape.Html4Unescape;
* 转义和反转义工具类Escape / Unescape<br> * 转义和反转义工具类Escape / Unescape<br>
* escape采用ISO Latin字符集对指定的字符串进行编码<br> * escape采用ISO Latin字符集对指定的字符串进行编码<br>
* 所有的空格符标点符号特殊字符以及其他非ASCII字符都将被转化成%xx格式的字符编码(xx等于该字符在字符集表里面的编码的16进制数字) * 所有的空格符标点符号特殊字符以及其他非ASCII字符都将被转化成%xx格式的字符编码(xx等于该字符在字符集表里面的编码的16进制数字)
* *
* @author xiaoleilu * @author xiaoleilu
*/ */
public class EscapeUtil { public class EscapeUtil {
/**
* 不转义的符号编码
*/
private static final String NOT_ESCAPE_CHARS = "*@-_+./";
private static final Filter<Character> JS_ESCAPE_FILTER = c -> false == (
Character.isDigit(c)
|| Character.isLowerCase(c)
|| Character.isUpperCase(c)
|| StrUtil.contains(NOT_ESCAPE_CHARS, c)
);
/** /**
* 转义HTML4中的特殊字符 * 转义HTML4中的特殊字符
* *
* @param html HTML文本 * @param html HTML文本
* @return 转义后的文本 * @return 转义后的文本
* @since 4.1.5 * @since 4.1.5
*/ */
public static String escapeHtml4(String html) { public static String escapeHtml4(CharSequence html) {
Html4Escape escape = new Html4Escape(); Html4Escape escape = new Html4Escape();
return escape.replace(html).toString(); return escape.replace(html).toString();
} }
/** /**
* 反转义HTML4中的特殊字符 * 反转义HTML4中的特殊字符
* *
* @param html HTML文本 * @param html HTML文本
* @return 转义后的文本 * @return 转义后的文本
* @since 4.1.5 * @since 4.1.5
*/ */
public static String unescapeHtml4(String html) { public static String unescapeHtml4(CharSequence html) {
Html4Unescape unescape = new Html4Unescape(); Html4Unescape unescape = new Html4Unescape();
return unescape.replace(html).toString(); return unescape.replace(html).toString();
} }
/** /**
* Escape编码Unicode<br> * Escape编码Unicode等同于JS的escape()方法<br>
* 该方法不会对 ASCII 字母和数字进行编码也不会对下面这些 ASCII 标点符号进行编码 * @ - _ + . / 其他所有的字符都会被转义序列替换 * 该方法不会对 ASCII 字母和数字进行编码也不会对下面这些 ASCII 标点符号进行编码 * @ - _ + . / <br>
* * 其他所有的字符都会被转义序列替换
*
* @param content 被转义的内容 * @param content 被转义的内容
* @return 编码后的字符串 * @return 编码后的字符串
*/ */
public static String escape(String content) { public static String escape(CharSequence content) {
if (StrUtil.isBlank(content)) { return escape(content, JS_ESCAPE_FILTER);
return content; }
/**
* Escape编码Unicode<br>
* 该方法不会对 ASCII 字母和数字进行编码其他所有的字符都会被转义序列替换
*
* @param content 被转义的内容
* @return 编码后的字符串
*/
public static String escapeAll(CharSequence content) {
return escape(content, c -> true);
}
/**
* Escape编码Unicode<br>
* 该方法不会对 ASCII 字母和数字进行编码其他所有的字符都会被转义序列替换
*
* @param content 被转义的内容
* @param filter 编码过滤器对于过滤器中accept为false的字符不做编码
* @return 编码后的字符串
*/
public static String escape(CharSequence content, Filter<Character> filter) {
if (StrUtil.isEmpty(content)) {
return StrUtil.str(content);
} }
int i; final StringBuilder tmp = new StringBuilder(content.length() * 6);
char j; char j;
StringBuilder tmp = new StringBuilder(); for (int i = 0; i < content.length(); i++) {
tmp.ensureCapacity(content.length() * 6);
for (i = 0; i < content.length(); i++) {
j = content.charAt(i); j = content.charAt(i);
if (false == filter.accept(j)) {
if (Character.isDigit(j) || Character.isLowerCase(j) || Character.isUpperCase(j)) {
tmp.append(j); tmp.append(j);
} else if (j < 256) { } else if (j < 256) {
tmp.append("%"); tmp.append("%");
@ -75,7 +106,7 @@ public class EscapeUtil {
/** /**
* Escape解码 * Escape解码
* *
* @param content 被转义的内容 * @param content 被转义的内容
* @return 解码后的字符串 * @return 解码后的字符串
*/ */
@ -115,7 +146,7 @@ public class EscapeUtil {
/** /**
* 安全的unescape文本当文本不是被escape的时候返回原文 * 安全的unescape文本当文本不是被escape的时候返回原文
* *
* @param content 内容 * @param content 内容
* @return 解码后的字符串如果解码失败返回原字符串 * @return 解码后的字符串如果解码失败返回原字符串
*/ */

View File

@ -12,6 +12,29 @@ public class EscapeUtilTest {
String result = EscapeUtil.unescapeHtml4("&#25391;&#33633;&#22120;&#31867;&#22411;"); String result = EscapeUtil.unescapeHtml4("&#25391;&#33633;&#22120;&#31867;&#22411;");
Assert.assertEquals("振荡器类型", result); Assert.assertEquals("振荡器类型", result);
String escape = EscapeUtil.escapeHtml4("*@-_+./(123你好)");
Assert.assertEquals("*@-_+./(123你好)", escape);
}
@Test
public void escapeTest(){
String str = "*@-_+./(123你好)ABCabc";
String escape = EscapeUtil.escape(str);
Assert.assertEquals("*@-_+./%28123%u4f60%u597d%29ABCabc", escape);
String unescape = EscapeUtil.unescape(escape);
Assert.assertEquals(str, unescape);
}
@Test
public void escapeAllTest(){
String str = "*@-_+./(123你好)ABCabc";
String escape = EscapeUtil.escapeAll(str);
Assert.assertEquals("%2a%40%2d%5f%2b%2e%2f%28%31%32%33%u4f60%u597d%29%41%42%43%61%62%63", escape);
String unescape = EscapeUtil.unescape(escape);
Assert.assertEquals(str, unescape);
} }
} }