fix escape bug

This commit is contained in:
Looly 2021-06-24 07:53:21 +08:00
parent a97e967ecd
commit 949c7a856e
14 changed files with 166 additions and 36 deletions

View File

@ -3,11 +3,13 @@
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.7.3 (2021-06-20) # 5.7.3 (2021-06-24)
### 🐣新特性 ### 🐣新特性
* 【core 】 增加Convert.toSet方法issue#I3XFG2@Gitee
### 🐞Bug修复 ### 🐞Bug修复
* 【json 】 修复XML转义字符的问题issue#I3XH09@Gitee
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -576,7 +577,7 @@ public class Convert {
* @param <T> 元素类型 * @param <T> 元素类型
* @param elementType 集合中元素类型 * @param elementType 集合中元素类型
* @param value 被转换的值 * @param value 被转换的值
* @return {@link List} * @return {@link ArrayList}
* @since 4.1.20 * @since 4.1.20
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -584,6 +585,20 @@ public class Convert {
return (List<T>) toCollection(ArrayList.class, elementType, value); return (List<T>) toCollection(ArrayList.class, elementType, value);
} }
/**
* 转换为HashSet
*
* @param <T> 元素类型
* @param elementType 集合中元素类型
* @param value 被转换的值
* @return {@link HashSet}
* @since 5.7.3
*/
@SuppressWarnings("unchecked")
public static <T> Set<T> toSet(Class<T> elementType, Object value) {
return (Set<T>) toCollection(HashSet.class, elementType, value);
}
/** /**
* 转换为Map * 转换为Map
* *

View File

@ -1,7 +1,6 @@
package cn.hutool.core.text.escape; package cn.hutool.core.text.escape;
import cn.hutool.core.text.replacer.LookupReplacer; import cn.hutool.core.text.replacer.LookupReplacer;
import cn.hutool.core.text.replacer.ReplacerChain;
/** /**
* HTML4的ESCAPE * HTML4的ESCAPE
@ -10,16 +9,9 @@ import cn.hutool.core.text.replacer.ReplacerChain;
* @author looly * @author looly
* *
*/ */
public class Html4Escape extends ReplacerChain { public class Html4Escape extends XmlEscape {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
protected static final String[][] BASIC_ESCAPE = { //
{ "\"", "&quot;" }, // " - double-quote
{ "&", "&amp;" }, // & - ampersand
{ "<", "&lt;" }, // < - less-than
{ ">", "&gt;" }, // > - greater-than
};
protected static final String[][] ISO8859_1_ESCAPE = { // protected static final String[][] ISO8859_1_ESCAPE = { //
{ "\u00A0", "&nbsp;" }, // non-breaking space { "\u00A0", "&nbsp;" }, // non-breaking space
{ "\u00A1", "&iexcl;" }, // inverted exclamation mark { "\u00A1", "&iexcl;" }, // inverted exclamation mark
@ -317,7 +309,7 @@ public class Html4Escape extends ReplacerChain {
}; };
public Html4Escape() { public Html4Escape() {
addChain(new LookupReplacer(BASIC_ESCAPE)); super();
addChain(new LookupReplacer(ISO8859_1_ESCAPE)); addChain(new LookupReplacer(ISO8859_1_ESCAPE));
addChain(new LookupReplacer(HTML40_EXTENDED_ESCAPE)); addChain(new LookupReplacer(HTML40_EXTENDED_ESCAPE));
} }

View File

@ -1,7 +1,6 @@
package cn.hutool.core.text.escape; package cn.hutool.core.text.escape;
import cn.hutool.core.text.replacer.LookupReplacer; import cn.hutool.core.text.replacer.LookupReplacer;
import cn.hutool.core.text.replacer.ReplacerChain;
/** /**
* HTML4的UNESCAPE * HTML4的UNESCAPE
@ -9,20 +8,15 @@ import cn.hutool.core.text.replacer.ReplacerChain;
* @author looly * @author looly
* *
*/ */
public class Html4Unescape extends ReplacerChain { public class Html4Unescape extends XmlUnescape {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
protected static final String[][] BASIC_UNESCAPE = InternalEscapeUtil.invert(Html4Escape.BASIC_ESCAPE);
protected static final String[][] ISO8859_1_UNESCAPE = InternalEscapeUtil.invert(Html4Escape.ISO8859_1_ESCAPE); protected static final String[][] ISO8859_1_UNESCAPE = InternalEscapeUtil.invert(Html4Escape.ISO8859_1_ESCAPE);
protected static final String[][] HTML40_EXTENDED_UNESCAPE = InternalEscapeUtil.invert(Html4Escape.HTML40_EXTENDED_ESCAPE); protected static final String[][] HTML40_EXTENDED_UNESCAPE = InternalEscapeUtil.invert(Html4Escape.HTML40_EXTENDED_ESCAPE);
// issue#1118
protected static final String[][] OTHER_UNESCAPE = new String[][]{new String[]{"&apos;", "'"}};
public Html4Unescape() { public Html4Unescape() {
addChain(new LookupReplacer(BASIC_UNESCAPE)); super();
addChain(new LookupReplacer(ISO8859_1_UNESCAPE)); addChain(new LookupReplacer(ISO8859_1_UNESCAPE));
addChain(new LookupReplacer(HTML40_EXTENDED_UNESCAPE)); addChain(new LookupReplacer(HTML40_EXTENDED_UNESCAPE));
addChain(new LookupReplacer(OTHER_UNESCAPE));
addChain(new NumericEntityUnescaper());
} }
} }

View File

@ -0,0 +1,38 @@
package cn.hutool.core.text.escape;
import cn.hutool.core.text.replacer.LookupReplacer;
import cn.hutool.core.text.replacer.ReplacerChain;
/**
* XML特殊字符转义<br>
* https://stackoverflow.com/questions/1091945/what-characters-do-i-need-to-escape-in-xml-documents<br>
*
* <pre>
* &amp; (ampersand) 替换为 &amp;amp;
* &lt; (less than) 替换为 &amp;lt;
* &gt; (greater than) 替换为 &amp;gt;
* &quot; (double quote) 替换为 &amp;quot;
* &apos; (single quote / apostrophe) 替换为 &amp;apos;
* </pre>
*
* @author looly
* @since 5.7.2
*/
public class XmlEscape extends ReplacerChain {
private static final long serialVersionUID = 1L;
protected static final String[][] BASIC_ESCAPE = { //
{"'", "&apos;"}, // " - single-quote
{"\"", "&quot;"}, // " - double-quote
{"&", "&amp;"}, // & - ampersand
{"<", "&lt;"}, // < - less-than
{">", "&gt;"}, // > - greater-than
};
/**
* 构造
*/
public XmlEscape() {
addChain(new LookupReplacer(BASIC_ESCAPE));
}
}

View File

@ -0,0 +1,21 @@
package cn.hutool.core.text.escape;
import cn.hutool.core.text.replacer.LookupReplacer;
import cn.hutool.core.text.replacer.ReplacerChain;
/**
* XML的UNESCAPE
*
* @author looly
* @since 5.7.2
*/
public class XmlUnescape extends ReplacerChain {
private static final long serialVersionUID = 1L;
protected static final String[][] BASIC_UNESCAPE = InternalEscapeUtil.invert(XmlEscape.BASIC_ESCAPE);
public XmlUnescape() {
addChain(new LookupReplacer(BASIC_UNESCAPE));
addChain(new NumericEntityUnescaper());
}
}

View File

@ -1,10 +1,10 @@
package cn.hutool.core.text.replacer; package cn.hutool.core.text.replacer;
import java.io.Serializable;
import cn.hutool.core.lang.Replacer; import cn.hutool.core.lang.Replacer;
import cn.hutool.core.text.StrBuilder; import cn.hutool.core.text.StrBuilder;
import java.io.Serializable;
/** /**
* 抽象字符串替换类<br> * 抽象字符串替换类<br>
* 通过实现replace方法实现局部替换逻辑 * 通过实现replace方法实现局部替换逻辑
@ -28,18 +28,18 @@ public abstract class StrReplacer implements Replacer<CharSequence>, Serializabl
@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 builder = 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, builder);
if (0 == consumed) { if (0 == consumed) {
//0表示未处理或替换任何字符原样输出本字符并从下一个字符继续 //0表示未处理或替换任何字符原样输出本字符并从下一个字符继续
strBuillder.append(t.charAt(pos)); builder.append(t.charAt(pos));
pos++; pos++;
} }
pos += consumed; pos += consumed;
} }
return strBuillder; return builder;
} }
} }

View File

@ -3,6 +3,8 @@ package cn.hutool.core.util;
import cn.hutool.core.lang.Filter; 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;
import cn.hutool.core.text.escape.XmlEscape;
import cn.hutool.core.text.escape.XmlUnescape;
/** /**
* 转义和反转义工具类Escape / Unescape<br> * 转义和反转义工具类Escape / Unescape<br>
@ -24,6 +26,37 @@ public class EscapeUtil {
|| StrUtil.contains(NOT_ESCAPE_CHARS, c) || StrUtil.contains(NOT_ESCAPE_CHARS, c)
); );
/**
* 转义XML中的特殊字符<br>
* <pre>
* &amp; (ampersand) 替换为 &amp;amp;
* &lt; (less than) 替换为 &amp;lt;
* &gt; (greater than) 替换为 &amp;gt;
* &quot; (double quote) 替换为 &amp;quot;
* &apos; (single quote / apostrophe) 替换为 &amp;apos;
* </pre>
*
* @param xml XML文本
* @return 转义后的文本
* @since 5.7.2
*/
public static String escapeXml(CharSequence xml) {
XmlEscape escape = new XmlEscape();
return escape.replace(xml).toString();
}
/**
* 反转义XML中的特殊字符
*
* @param xml XML文本
* @return 转义后的文本
* @since 5.7.2
*/
public static String unescapeXml(CharSequence xml) {
XmlUnescape unescape = new XmlUnescape();
return unescape.replace(xml).toString();
}
/** /**
* 转义HTML4中的特殊字符 * 转义HTML4中的特殊字符
* *

View File

@ -986,7 +986,7 @@ public class XmlUtil {
* @since 4.0.8 * @since 4.0.8
*/ */
public static String escape(String string) { public static String escape(String string) {
return EscapeUtil.escape(string); return EscapeUtil.escapeHtml4(string);
} }
/** /**
@ -998,7 +998,7 @@ public class XmlUtil {
* @since 5.0.6 * @since 5.0.6
*/ */
public static String unescape(String string) { public static String unescape(String string) {
return EscapeUtil.unescape(string); return EscapeUtil.unescapeHtml4(string);
} }
/** /**

View File

@ -39,9 +39,9 @@ public class EscapeUtilTest {
} }
@Test @Test
public void escapeSinleQuotesTest(){ public void escapeSingleQuotesTest(){
String str = "'some text with single quotes'"; String str = "'some text with single quotes'";
final String s = EscapeUtil.escapeHtml4(str); final String s = EscapeUtil.escapeHtml4(str);
Assert.assertEquals(str, s); Assert.assertEquals("&apos;some text with single quotes&apos;", s);
} }
} }

View File

@ -287,4 +287,11 @@ public class XmlUtilTest {
String format = XmlUtil.toStr(xml,"GBK",true); String format = XmlUtil.toStr(xml,"GBK",true);
Console.log(format); Console.log(format);
} }
@Test
public void escapeTest(){
String a = "<>";
final String escape = XmlUtil.escape(a);
Console.log(escape);
}
} }

View File

@ -19,9 +19,24 @@ public enum Padding {
* 0补码即不满block长度时使用0填充 * 0补码即不满block长度时使用0填充
*/ */
ZeroPadding, ZeroPadding,
/**
* This padding for block ciphers is described in 5.2 Block Encryption Algorithms in the W3C's "XML Encryption Syntax and Processing" document.
*/
ISO10126Padding, ISO10126Padding,
/**
* Optimal Asymmetric Encryption Padding scheme defined in PKCS1
*/
OAEPPadding, OAEPPadding,
/**
* The padding scheme described in PKCS #1, used with the RSA algorithm
*/
PKCS1Padding, PKCS1Padding,
/**
* The padding scheme described in RSA Laboratories, "PKCS #5: Password-Based Encryption Standard," version 1.5, November 1993.
*/
PKCS5Padding, PKCS5Padding,
/**
* The padding scheme defined in the SSL Protocol Version 3.0, November 18, 1996, section 5.2.3.2 (CBC block cipher)
*/
SSL3Padding SSL3Padding
} }

View File

@ -331,11 +331,11 @@ public class XML {
if (i > 0) { if (i > 0) {
sb.append('\n'); sb.append('\n');
} }
sb.append(EscapeUtil.escapeHtml4(val.toString())); sb.append(EscapeUtil.escapeXml(val.toString()));
i++; i++;
} }
} else { } else {
sb.append(EscapeUtil.escapeHtml4(value.toString())); sb.append(EscapeUtil.escapeXml(value.toString()));
} }
// Emit an array of similar keys // Emit an array of similar keys
@ -377,7 +377,7 @@ public class XML {
} }
if (object.getClass().isArray()) { if (ArrayUtil.isArray(object)) {
object = new JSONArray(object); object = new JSONArray(object);
} }
@ -392,10 +392,12 @@ public class XML {
return sb.toString(); return sb.toString();
} }
String string = EscapeUtil.escapeHtml4(object.toString()); String string = EscapeUtil.escapeXml(object.toString());
return (tagName == null) ? return (tagName == null) ?
"\"" + string + "\"" : (string.length() == 0) ? "<" + tagName + "/>" "\"" + string + "\"" : (string.length() == 0) ? "<" + tagName + "/>"
: "<" + tagName + ">" + string + "</" + tagName + ">"; : "<" + tagName + ">" + string + "</" + tagName + ">";
} }
} }

View File

@ -14,4 +14,15 @@ public class XMLTest {
Assert.assertEquals("<aaa>你好</aaa><键2>test</键2>", s); Assert.assertEquals("<aaa>你好</aaa><键2>test</键2>", s);
} }
@Test
public void escapeTest(){
String xml = "<a>•</a>";
JSONObject jsonObject = XML.toJSONObject(xml);
Assert.assertEquals("{\"a\":\"\"}", jsonObject.toString());
String xml2 = XML.toXml(JSONUtil.parseObj(jsonObject));
Assert.assertEquals(xml, xml2);
}
} }