/* * Copyright (c) 2002-2012 Alibaba Group Holding Limited. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.citrus.util; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.BitSet; import com.alibaba.citrus.util.i18n.LocaleUtil; import com.alibaba.citrus.util.internal.Entities; /** * 字符串转义工具类,能将字符串转换成适应 Java、Java Script、HTML、XML、和SQL语句的形式。 * * @author Michael Zhou */ public class StringEscapeUtil { // ========================================================================== // Java和JavaScript。 // ========================================================================== /** * 按Java的规则对字符串进行转义。 * <p> * 将双引号和控制字符转换成<code>'\\'</code>开头的形式,例如tab制表符将被转换成<code>\t</code>。 * </p> * <p> * Java和JavaScript字符串的唯一差别是,JavaScript必须对单引号进行转义,而Java不需要。 * </p> * <p> * 例如:字符串:<code>He didn't say, "Stop!"</code>被转换成 * <code>He didn't say, \"Stop!\"</code> * </p> * * @param str 要转义的字符串 * @return 转义后的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> */ public static String escapeJava(String str) { return escapeJavaStyleString(str, false, false); } /** * 按Java的规则对字符串进行转义。 * <p> * 将双引号和控制字符转换成<code>'\\'</code>开头的形式,例如tab制表符将被转换成<code>\t</code>。 * </p> * <p> * Java和JavaScript字符串的唯一差别是,JavaScript必须对单引号进行转义,而Java不需要。 * </p> * <p> * 例如:字符串:<code>He didn't say, "Stop!"</code>被转换成 * <code>He didn't say, \"Stop!\"</code> * </p> * * @param str 要转义的字符串 * @param strict 是否以严格的方式编码字符串 * @return 转义后的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> */ public static String escapeJava(String str, boolean strict) { return escapeJavaStyleString(str, false, strict); } /** * 按Java的规则对字符串进行转义。 * <p> * 将双引号和控制字符转换成<code>'\\'</code>开头的形式,例如tab制表符将被转换成<code>\t</code>。 * </p> * <p> * Java和JavaScript字符串的唯一差别是,JavaScript必须对单引号进行转义,而Java不需要。 * </p> * <p> * 例如:字符串:<code>He didn't say, "Stop!"</code>被转换成 * <code>He didn't say, \"Stop!\"</code> * </p> * * @param str 要转义的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void escapeJava(String str, Appendable out) throws IOException { escapeJavaStyleString(str, false, out, false); } /** * 按Java的规则对字符串进行转义。 * <p> * 将双引号和控制字符转换成<code>'\\'</code>开头的形式,例如tab制表符将被转换成<code>\t</code>。 * </p> * <p> * Java和JavaScript字符串的唯一差别是,JavaScript必须对单引号进行转义,而Java不需要。 * </p> * <p> * 例如:字符串:<code>He didn't say, "Stop!"</code>被转换成 * <code>He didn't say, \"Stop!\"</code> * </p> * * @param str 要转义的字符串 * @param out 输出流 * @param strict 是否以严格的方式编码字符串 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void escapeJava(String str, Appendable out, boolean strict) throws IOException { escapeJavaStyleString(str, false, out, strict); } /** * 按JavaScript的规则对字符串进行转义。 * <p> * 将双引号、单引号和控制字符转换成<code>'\\'</code>开头的形式,例如tab制表符将被转换成<code>\t</code>。 * </p> * <p> * Java和JavaScript字符串的唯一差别是,JavaScript必须对单引号进行转义,而Java不需要。 * </p> * <p> * 例如:字符串:<code>He didn't say, "Stop!"</code>被转换成 * <code>He didn\'t say, \"Stop!\"</code> * </p> * * @param str 要转义的字符串 * @return 转义后的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> */ public static String escapeJavaScript(String str) { return escapeJavaStyleString(str, true, false); } /** * 按JavaScript的规则对字符串进行转义。 * <p> * 将双引号、单引号和控制字符转换成<code>'\\'</code>开头的形式,例如tab制表符将被转换成<code>\t</code>。 * </p> * <p> * Java和JavaScript字符串的唯一差别是,JavaScript必须对单引号进行转义,而Java不需要。 * </p> * <p> * 例如:字符串:<code>He didn't say, "Stop!"</code>被转换成 * <code>He didn\'t say, \"Stop!\"</code> * </p> * * @param str 要转义的字符串 * @param strict 是否以严格的方式编码字符串 * @return 转义后的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> */ public static String escapeJavaScript(String str, boolean strict) { return escapeJavaStyleString(str, true, strict); } /** * 按JavaScript的规则对字符串进行转义。 * <p> * 将双引号、单引号和控制字符转换成<code>'\\'</code>开头的形式,例如tab制表符将被转换成<code>\t</code>。 * </p> * <p> * Java和JavaScript字符串的唯一差别是,JavaScript必须对单引号进行转义,而Java不需要。 * </p> * <p> * 例如:字符串:<code>He didn't say, "Stop!"</code>被转换成 * <code>He didn\'t say, \"Stop!\"</code> * </p> * * @param str 要转义的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void escapeJavaScript(String str, Appendable out) throws IOException { escapeJavaStyleString(str, true, out, false); } /** * 按JavaScript的规则对字符串进行转义。 * <p> * 将双引号、单引号和控制字符转换成<code>'\\'</code>开头的形式,例如tab制表符将被转换成<code>\t</code>。 * </p> * <p> * Java和JavaScript字符串的唯一差别是,JavaScript必须对单引号进行转义,而Java不需要。 * </p> * <p> * 例如:字符串:<code>He didn't say, "Stop!"</code>被转换成 * <code>He didn\'t say, \"Stop!\"</code> * </p> * * @param str 要转义的字符串 * @param out 输出流 * @param strict 是否以严格的方式编码字符串 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void escapeJavaScript(String str, Appendable out, boolean strict) throws IOException { escapeJavaStyleString(str, true, out, strict); } /** * 按Java或JavaScript的规则对字符串进行转义。 * * @param str 要转义的字符串 * @param javascript 是否对单引号和slash进行转义 * @param strict 是否以严格的方式编码字符串 * @return 转义后的字符串 */ private static String escapeJavaStyleString(String str, boolean javascript, boolean strict) { if (str == null) { return null; } try { StringBuilder out = new StringBuilder(str.length() * 2); if (escapeJavaStyleString(str, javascript, out, strict)) { return out.toString(); } return str; } catch (IOException e) { return str; // StringBuilder不可能发生这个异常 } } /** * 按Java或JavaScript的规则对字符串进行转义。 * * @param str 要转义的字符串 * @param javascript 是否对单引号和slash进行转义 * @param out 输出流 * @param strict 是否以严格的方式编码字符串 * @return 如果字符串没有变化,则返回<code>false</code> */ private static boolean escapeJavaStyleString(String str, boolean javascript, Appendable out, boolean strict) throws IOException { boolean needToChange = false; if (out == null) { throw new IllegalArgumentException("The Appendable must not be null"); } if (str == null) { return needToChange; } int length = str.length(); for (int i = 0; i < length; i++) { char ch = str.charAt(i); if (ch < 32) { switch (ch) { case '\b': out.append('\\'); out.append('b'); break; case '\n': out.append('\\'); out.append('n'); break; case '\t': out.append('\\'); out.append('t'); break; case '\f': out.append('\\'); out.append('f'); break; case '\r': out.append('\\'); out.append('r'); break; default: if (ch > 0xf) { out.append("\\u00" + Integer.toHexString(ch).toUpperCase()); } else { out.append("\\u000" + Integer.toHexString(ch).toUpperCase()); } break; } // 设置改变标志 needToChange = true; } else if (strict && ch > 0xff) { if (ch > 0xfff) { out.append("\\u").append(Integer.toHexString(ch).toUpperCase()); } else { out.append("\\u0").append(Integer.toHexString(ch).toUpperCase()); } // 设置改变标志 needToChange = true; } else { switch (ch) { case '\'': case '/': // 注意:对于javascript,对/进行escape是重要的安全措施。 if (javascript) { out.append('\\'); // 设置改变标志 needToChange = true; } out.append(ch); break; case '"': out.append('\\'); out.append('"'); // 设置改变标志 needToChange = true; break; case '\\': out.append('\\'); out.append('\\'); // 设置改变标志 needToChange = true; break; default: out.append(ch); break; } } } return needToChange; } /** * 按Java的规则对字符串进行反向转义。 * <p> * <code>'\\'</code>开头的形式转换成相应的字符,例如<code>\t</code>将被转换成tab制表符 * </p> * <p> * 如果转义符不能被识别,它将被保留不变。 * </p> * * @param str 不包含转义字符的字符串 * @return 恢复成未转义的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> */ public static String unescapeJava(String str) { return unescapeJavaStyleString(str); } /** * 按Java的规则对字符串进行反向转义。 * <p> * <code>'\\'</code>开头的形式转换成相应的字符,例如<code>\t</code>将被转换成tab制表符 * </p> * <p> * 如果转义符不能被识别,它将被保留不变。 * </p> * * @param str 包含转义字符的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void unescapeJava(String str, Appendable out) throws IOException { unescapeJavaStyleString(str, out); } /** * 按JavaScript的规则对字符串进行反向转义。 * <p> * <code>'\\'</code>开头的形式转换成相应的字符,例如<code>\t</code>将被转换成tab制表符 * </p> * <p> * 如果转义符不能被识别,它将被保留不变。 * </p> * * @param str 包含转义字符的字符串 * @return 恢复成未转义的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> */ public static String unescapeJavaScript(String str) { return unescapeJavaStyleString(str); } /** * 按Java的规则对字符串进行反向转义。 * <p> * <code>'\\'</code>开头的形式转换成相应的字符,例如<code>\t</code>将被转换成tab制表符 * </p> * <p> * 如果转义符不能被识别,它将被保留不变。 * </p> * * @param str 包含转义字符的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void unescapeJavaScript(String str, Appendable out) throws IOException { unescapeJavaStyleString(str, out); } /** * 按Java的规则对字符串进行反向转义。 * <p> * <code>'\\'</code>开头的形式转换成相应的字符,例如<code>\t</code>将被转换成tab制表符 * </p> * <p> * 如果转义符不能被识别,它将被保留不变。 * </p> * * @param str 包含转义字符的字符串 * @return 不包含转义字符的字符串 */ private static String unescapeJavaStyleString(String str) { if (str == null) { return null; } try { StringBuilder out = new StringBuilder(str.length()); if (unescapeJavaStyleString(str, out)) { return out.toString(); } return str; } catch (IOException e) { return str; // StringBuilder不可能发生这个异常 } } /** * 按Java的规则对字符串进行反向转义。 * <p> * <code>'\\'</code>开头的形式转换成相应的字符,例如<code>\t</code>将被转换成tab制表符 * </p> * <p> * 如果转义符不能被识别,它将被保留不变。 * </p> * * @param str 包含转义字符的字符串 * @param out 输出流 * @return 如果字符串没有变化,则返回<code>false</code> * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ private static boolean unescapeJavaStyleString(String str, Appendable out) throws IOException { boolean needToChange = false; if (out == null) { throw new IllegalArgumentException("The Appendable must not be null"); } if (str == null) { return needToChange; } int length = str.length(); StringBuilder unicode = new StringBuilder(4); boolean hadSlash = false; boolean inUnicode = false; for (int i = 0; i < length; i++) { char ch = str.charAt(i); if (inUnicode) { unicode.append(ch); if (unicode.length() == 4) { String unicodeStr = unicode.toString(); try { int value = Integer.parseInt(unicodeStr, 16); out.append((char) value); unicode.setLength(0); inUnicode = false; hadSlash = false; // 设置改变标志 needToChange = true; } catch (NumberFormatException e) { out.append("\\u" + unicodeStr); } } continue; } if (hadSlash) { hadSlash = false; switch (ch) { case '\\': out.append('\\'); // 设置改变标志 needToChange = true; break; case '\'': out.append('\''); // 设置改变标志 needToChange = true; break; case '\"': out.append('"'); // 设置改变标志 needToChange = true; break; case 'r': out.append('\r'); // 设置改变标志 needToChange = true; break; case 'f': out.append('\f'); // 设置改变标志 needToChange = true; break; case 't': out.append('\t'); // 设置改变标志 needToChange = true; break; case 'n': out.append('\n'); // 设置改变标志 needToChange = true; break; case 'b': out.append('\b'); // 设置改变标志 needToChange = true; break; case 'u': { inUnicode = true; break; } default: out.append(ch); break; } continue; } else if (ch == '\\') { hadSlash = true; continue; } out.append(ch); } if (hadSlash) { out.append('\\'); } return needToChange; } // ========================================================================== // HTML和XML。 // ========================================================================== /** * 根据HTML的规则,将字符串中的部分字符转换成实体编码。 * <p> * 例如:<code>"bread" & "butter"</code>将被转换成 * <tt>&quot;bread&quot; &amp; * &quot;butter&quot;</tt>. * </p> * <p> * 支持所有HTML 4.0 entities。 * </p> * * @param str 要转义的字符串 * @return 用实体编码转义的字符串,如果原字串为<code>null</code>,则返回<code>null</code> * @see <a * href="http://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO * Entities</a> * @see <a href="http://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character * Entities for ISO Latin-1</a> * @see <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">HTML * 4.0 Character entity references</a> * @see <a href="http://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01 * Character References</a> * @see <a * href="http://www.w3.org/TR/html401/charset.html#code-position">HTML * 4.01 Code positions</a> */ public static String escapeHtml(String str) { return escapeEntities(Entities.HTML40_MODIFIED, str); } /** * 根据HTML的规则,将字符串中的部分字符转换成实体编码。 * <p> * 例如:<code>"bread" & "butter"</code>将被转换成 * <tt>&quot;bread&quot; &amp; * &quot;butter&quot;</tt>. * </p> * <p> * 支持所有HTML 4.0 entities。 * </p> * * @param str 要转义的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 * @see <a * href="http://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO * Entities</a> * @see <a href="http://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character * Entities for ISO Latin-1</a> * @see <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">HTML * 4.0 Character entity references</a> * @see <a href="http://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01 * Character References</a> * @see <a * href="http://www.w3.org/TR/html401/charset.html#code-position">HTML * 4.01 Code positions</a> */ public static void escapeHtml(String str, Appendable out) throws IOException { escapeEntities(Entities.HTML40_MODIFIED, str, out); } /** * 根据XML的规则,将字符串中的部分字符转换成实体编码。 * <p> * 例如:<code>"bread" & "butter"</code>将被转换成 * <tt>&quot;bread&quot; &amp; * &quot;butter&quot;</tt>. * </p> * <p> * 只转换4种基本的XML实体:<code>gt</code>、<code>lt</code>、<code>quot</code>和 * <code>amp</code>。 不支持DTD或外部实体。 * </p> * * @param str 要转义的字符串 * @return 用实体编码转义的字符串,如果原字串为<code>null</code>,则返回<code>null</code> */ public static String escapeXml(String str) { return escapeEntities(Entities.XML, str); } /** * 根据XML的规则,将字符串中的部分字符转换成实体编码。 * <p> * 例如:<code>"bread" & "butter"</code>将被转换成 * <tt>&quot;bread&quot; &amp; * &quot;butter&quot;</tt>. * </p> * <p> * 只转换4种基本的XML实体:<code>gt</code>、<code>lt</code>、<code>quot</code>和 * <code>amp</code>。 不支持DTD或外部实体。 * </p> * * @param str 要转义的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void escapeXml(String str, Appendable out) throws IOException { escapeEntities(Entities.XML, str, out); } /** * 根据指定的规则,将字符串中的部分字符转换成实体编码。 * * @param entities 实体集合 * @param str 要转义的字符串 * @return 用实体编码转义的字符串,如果原字串为<code>null</code>,则返回<code>null</code> */ public static String escapeEntities(Entities entities, String str) { if (str == null) { return null; } try { StringBuilder out = new StringBuilder(str.length()); if (escapeEntitiesInternal(entities, str, out)) { return out.toString(); } return str; } catch (IOException e) { return str; // StringBuilder不可能发生这个异常 } } /** * 根据指定的规则,将字符串中的部分字符转换成实体编码。 * * @param entities 实体集合 * @param str 要转义的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void escapeEntities(Entities entities, String str, Appendable out) throws IOException { escapeEntitiesInternal(entities, str, out); } /** * 按HTML的规则对字符串进行反向转义,支持HTML 4.0中的所有实体,以及unicode实体如<code>&#12345;</code> * 。 * <p> * 例如:"&lt;Fran&ccedil;ais&gt;"将被转换成"<Français>" * </p> * <p> * 如果实体不能被识别,它将被保留不变。 * </p> * * @param str 不包含转义字符的字符串 * @return 恢复成未转义的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> */ public static String unescapeHtml(String str) { return unescapeEntities(Entities.HTML40, str); } /** * 按HTML的规则对字符串进行反向转义,支持HTML 4.0中的所有实体,以及unicode实体如<code>&#12345;</code> * 。 * <p> * 例如:"&lt;Fran&ccedil;ais&gt;"将被转换成"<Français>" * </p> * <p> * 如果实体不能被识别,它将被保留不变。 * </p> * * @param str 包含转义字符的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void unescapeHtml(String str, Appendable out) throws IOException { unescapeEntities(Entities.HTML40, str, out); } /** * 按XML的规则对字符串进行反向转义,支持unicode实体如<code>&#12345;</code>。 * <p> * 例如:"&lt;Fran&ccedil;ais&gt;"将被转换成"<Français>" * </p> * <p> * 如果实体不能被识别,它将被保留不变。 * </p> * * @param str 不包含转义字符的字符串 * @return 恢复成未转义的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> */ public static String unescapeXml(String str) { return unescapeEntities(Entities.XML, str); } /** * 按XML的规则对字符串进行反向转义,支持unicode实体如<code>&#12345;</code>。 * <p> * 例如:"&lt;Fran&ccedil;ais&gt;"将被转换成"<Français>" * </p> * <p> * 如果实体不能被识别,它将被保留不变。 * </p> * * @param str 不包含转义字符的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void unescapeXml(String str, Appendable out) throws IOException { unescapeEntities(Entities.XML, str, out); } /** * 按指定的规则对字符串进行反向转义。 * * @param entities 实体集合 * @param str 不包含转义字符的字符串 * @return 恢复成未转义的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> */ public static String unescapeEntities(Entities entities, String str) { if (str == null) { return null; } try { StringBuilder out = new StringBuilder(str.length()); if (unescapeEntitiesInternal(entities, str, out)) { return out.toString(); } return str; } catch (IOException e) { return str; // StringBuilder不可能发生这个异常 } } /** * 按指定的规则对字符串进行反向转义。 * <p> * 如果实体不能被识别,它将被保留不变。 * </p> * * @param entities 实体集合 * @param str 不包含转义字符的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ public static void unescapeEntities(Entities entities, String str, Appendable out) throws IOException { unescapeEntitiesInternal(entities, str, out); } /** * 将字符串中的部分字符转换成实体编码。 * * @param entities 实体集合 * @param str 要转义的字符串 * @param out 字符输出流,不能为<code>null</code> * @return 如果字符串没有变化,则返回<code>false</code> * @throws IllegalArgumentException 如果<code>entities</code>或输出流为 * <code>null</code> * @throws IOException 如果输出失败 */ private static boolean escapeEntitiesInternal(Entities entities, String str, Appendable out) throws IOException { boolean needToChange = false; if (entities == null) { throw new IllegalArgumentException("The Entities must not be null"); } if (out == null) { throw new IllegalArgumentException("The Appendable must not be null"); } if (str == null) { return needToChange; } for (int i = 0; i < str.length(); ++i) { char ch = str.charAt(i); String entityName = entities.getEntityName(ch); if (entityName == null) { out.append(ch); } else { out.append('&'); out.append(entityName); out.append(';'); // 设置改变标志 needToChange = true; } } return needToChange; } /** * 将字符串中的已定义实体和unicode实体如<code>&#12345;</code>转换成相应的unicode字符。 * <p> * 未定义的实体将保留不变。 * </p> * * @param entities 实体集合,如果为<code>null</code>,则只转换<code>&#number</code> * 实体。 * @param str 包含转义字符的字符串 * @param out 字符输出流,不能为<code>null</code> * @return 如果字符串没有变化,则返回<code>false</code> * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 */ private static boolean unescapeEntitiesInternal(Entities entities, String str, Appendable out) throws IOException { boolean needToChange = false; if (out == null) { throw new IllegalArgumentException("The Appendable must not be null"); } if (str == null) { return needToChange; } for (int i = 0; i < str.length(); ++i) { char ch = str.charAt(i); if (ch == '&') { // 查找&xxxx; int semi = str.indexOf(';', i + 1); if (semi == -1 || i + 1 >= semi - 1) { out.append(ch); continue; } // 如果是&#xxxxx; if (str.charAt(i + 1) == '#') { int firstCharIndex = i + 2; int radix = 10; if (firstCharIndex >= semi - 1) { out.append(ch); out.append('#'); i++; continue; } char firstChar = str.charAt(firstCharIndex); if (firstChar == 'x' || firstChar == 'X') { firstCharIndex++; radix = 16; if (firstCharIndex >= semi - 1) { out.append(ch); out.append('#'); i++; continue; } } try { int entityValue = Integer.parseInt(str.substring(firstCharIndex, semi), radix); out.append((char) entityValue); // 设置改变标志 needToChange = true; } catch (NumberFormatException e) { out.append(ch); out.append('#'); i++; continue; } } else { String entityName = str.substring(i + 1, semi); int entityValue = -1; if (entities != null) { entityValue = entities.getEntityValue(entityName); } if (entityValue == -1) { out.append('&'); out.append(entityName); out.append(';'); } else { out.append((char) entityValue); // 设置改变标志 needToChange = true; } } i = semi; } else { out.append(ch); } } return needToChange; } // ========================================================================== // SQL语句。 // ========================================================================== /** * 按SQL语句的规则对字符串进行转义。 * <p> * 例如: * <p/> * <pre> * statement.executeQuery("SELECT * FROM MOVIES WHERE TITLE='" + StringEscapeUtil.escapeSql("McHale's Navy") + "'"); * </pre> * <p/> * </p> * <p> * 目前,此方法只将单引号转换成两个单引号:<code>"McHale's Navy"</code>转换成<code>"McHale''s * Navy"</code>。不处理字符串中包含的<code>%</code>和<code>_</code>字符。 * </p> * * @param str 要转义的字符串 * @return 转义后的字符串,如果原字符串为<code>null</code>,则返回<code>null</code> * @see <a href="http://www.jguru.com/faq/view.jsp?EID=8881">faq</a> */ public static String escapeSql(String str) { return StringUtil.replace(str, "'", "''"); } /** * 按SQL语句的规则对字符串进行转义。 * <p> * 例如: * <p/> * <pre> * statement.executeQuery("SELECT * FROM MOVIES WHERE TITLE='" + StringEscapeUtil.escapeSql("McHale's Navy") + "'"); * </pre> * <p/> * </p> * <p> * 目前,此方法只将单引号转换成两个单引号:<code>"McHale's Navy"</code>转换成<code>"McHale''s * Navy"</code>。不处理字符串中包含的<code>%</code>和<code>_</code>字符。 * </p> * * @param str 要转义的字符串 * @param out 输出流 * @throws IllegalArgumentException 如果输出流为<code>null</code> * @throws IOException 如果输出失败 * @see <a href="http://www.jguru.com/faq/view.jsp?EID=8881">faq</a> */ public static void escapeSql(String str, Appendable out) throws IOException { if (out == null) { throw new IllegalArgumentException("The Appendable must not be null"); } String result = StringUtil.replace(str, "'", "''"); if (result != null) { out.append(result); } } // ========================================================================== // URL/URI encoding/decoding。 // 根据RFC2396:http://www.ietf.org/rfc/rfc2396.txt // ========================================================================== /** "Alpha" characters from RFC 2396. */ private static final BitSet ALPHA = new BitSet(256); static { for (int i = 'a'; i <= 'z'; i++) { ALPHA.set(i); } for (int i = 'A'; i <= 'Z'; i++) { ALPHA.set(i); } } /** "Alphanum" characters from RFC 2396. */ private static final BitSet ALPHANUM = new BitSet(256); static { ALPHANUM.or(ALPHA); for (int i = '0'; i <= '9'; i++) { ALPHANUM.set(i); } } /** "Mark" characters from RFC 2396. */ private static final BitSet MARK = new BitSet(256); static { MARK.set('-'); MARK.set('_'); MARK.set('.'); MARK.set('!'); MARK.set('~'); MARK.set('*'); MARK.set('\''); MARK.set('('); MARK.set(')'); } /** "Reserved" characters from RFC 2396. */ private static final BitSet RESERVED = new BitSet(256); static { RESERVED.set(';'); RESERVED.set('/'); RESERVED.set('?'); RESERVED.set(':'); RESERVED.set('@'); RESERVED.set('&'); RESERVED.set('='); RESERVED.set('+'); RESERVED.set('$'); RESERVED.set(','); } /** "Unreserved" characters from RFC 2396. */ private static final BitSet UNRESERVED = new BitSet(256); static { UNRESERVED.or(ALPHANUM); UNRESERVED.or(MARK); } /** 将一个数字转换成16进制的转换表。 */ private static char[] HEXADECIMAL = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /** * 将指定字符串编码成<code>application/x-www-form-urlencoded</code>格式。 * <p> * 除了RFC2396中的<code>unreserved</code>字符之外的所有字符,都将被转换成URL编码<code>%xx</code>。 * 根据RFC2396,<code>unreserved</code>的定义如下: * <p/> * <pre> * <![CDATA * unreserved = alphanum | mark * alphanum = 大小写英文字母 | 数字 * mark = "-" | "_" | "." | "!" | "˜" | "*" | "'" | "(" | ")" * ]]> * </pre> * <p/> * </p> * <p> * 警告:该方法使用当前线程默认的字符编码来编码URL,因此该方法在不同的上下文中可能会产生不同的结果。 * </p> * * @param str 要编码的字符串,可以是<code>null</code> * @return URL编码后的字符串 */ public static String escapeURL(String str) { try { return escapeURLInternal(str, null, true); } catch (UnsupportedEncodingException e) { return str; // 不可能发生这个异常 } } /** * 将指定字符串编码成<code>application/x-www-form-urlencoded</code>格式。 * <p> * 除了RFC2396中的<code>unreserved</code>字符之外的所有字符,都将被转换成URL编码<code>%xx</code>。 * 根据RFC2396,<code>unreserved</code>的定义如下: * <p/> * <pre> * <![CDATA * unreserved = alphanum | mark * alphanum = 大小写英文字母 | 数字 * mark = "-" | "_" | "." | "!" | "˜" | "*" | "'" | "(" | ")" * ]]> * </pre> * <p/> * </p> * <p> * 该方法使用指定的字符编码来编码URL。 * </p> * * @param str 要编码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用系统默认编码 * @return URL编码后的字符串 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 */ public static String escapeURL(String str, String encoding) throws UnsupportedEncodingException { return escapeURLInternal(str, encoding, true); } /** * 将指定字符串编码成<code>application/x-www-form-urlencoded</code>格式。 * <p> * 如果指定参数<code>strict</code>为<code>true</code>,则按严格的方式编码URL。 除了RFC2396中的 * <code>unreserved</code>字符之外的所有字符,都将被转换成URL编码<code>%xx</code>。 根据RFC2396, * <code>unreserved</code>的定义如下: * <p/> * <pre> * <![CDATA * unreserved = alphanum | mark * alphanum = 大小写英文字母 | 数字 * mark = "-" | "_" | "." | "!" | "˜" | "*" | "'" | "(" | ")" * ]]> * </pre> * <p/> * </p> * <p> * 如果指定参数<code>strict</code>为<code>false</code>,则使用宽松的方式编码URL。 * 除了控制字符、空白字符以及RFC2396中的<code>reserved</code>字符之外的所有字符,都将被保留不变。 * 根据RFC2396,只有控制字符、空白字符以及符合下列<code>reserved</code>定义的字符才被转换成 * <code>%xx</code>格式: * <p/> * <pre> * <![CDATA * reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," * ]]> * </pre> * <p/> * </p> * <p> * 该方法使用指定的字符编码来编码URL。 * </p> * * @param str 要编码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用当前线程默认的编码 * @param strict 是否以严格的方式编码URL * @return URL编码后的字符串 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 */ public static String escapeURL(String str, String encoding, boolean strict) throws UnsupportedEncodingException { return escapeURLInternal(str, encoding, strict); } /** * 将指定字符串编码成<code>application/x-www-form-urlencoded</code>格式。 * <p> * 除了RFC2396中的<code>unreserved</code>字符之外的所有字符,都将被转换成URL编码<code>%xx</code>。 * 根据RFC2396,<code>unreserved</code>的定义如下: * <p/> * <pre> * <![CDATA * unreserved = alphanum | mark * alphanum = 大小写英文字母 | 数字 * mark = "-" | "_" | "." | "!" | "˜" | "*" | "'" | "(" | ")" * ]]> * </pre> * <p/> * </p> * <p> * 该方法使用指定的字符编码来编码URL。 * </p> * * @param str 要编码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用系统默认编码 * @param out 输出到指定字符流 * @throws IOException 如果输出到<code>out</code>失败 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 * @throws IllegalArgumentException <code>out</code>为<code>null</code> */ public static void escapeURL(String str, String encoding, Appendable out) throws IOException { escapeURLInternal(str, encoding, out, true); } /** * 将指定字符串编码成<code>application/x-www-form-urlencoded</code>格式。 * <p> * 如果指定参数<code>strict</code>为<code>true</code>,则按严格的方式编码URL。 除了RFC2396中的 * <code>unreserved</code>字符之外的所有字符,都将被转换成URL编码<code>%xx</code>。 根据RFC2396, * <code>unreserved</code>的定义如下: * <p/> * <pre> * <![CDATA * unreserved = alphanum | mark * alphanum = 大小写英文字母 | 数字 * mark = "-" | "_" | "." | "!" | "˜" | "*" | "'" | "(" | ")" * ]]> * </pre> * <p/> * </p> * <p> * 如果指定参数<code>strict</code>为<code>false</code>,则使用宽松的方式编码URL。 * 除了控制字符、空白字符以及RFC2396中的<code>reserved</code>字符之外的所有字符,都将被保留不变。 * 根据RFC2396,只有控制字符、空白字符以及符合下列<code>reserved</code>定义的字符才被转换成 * <code>%xx</code>格式: * <p/> * <pre> * <![CDATA * reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," * ]]> * </pre> * <p/> * </p> * <p> * 该方法使用指定的字符编码来编码URL。 * </p> * * @param str 要编码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用系统默认编码 * @param out 输出到指定字符流 * @param strict 是否以严格的方式编码URL * @throws IOException 如果输出到<code>out</code>失败 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 * @throws IllegalArgumentException <code>out</code>为<code>null</code> */ public static void escapeURL(String str, String encoding, Appendable out, boolean strict) throws IOException { escapeURLInternal(str, encoding, out, strict); } /** * 将指定字符串编码成<code>application/x-www-form-urlencoded</code>格式。 * * @param str 要编码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用系统默认编码 * @param strict 是否以严格的方式编码URL * @return URL编码后的字符串 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 */ private static String escapeURLInternal(String str, String encoding, boolean strict) throws UnsupportedEncodingException { if (str == null) { return null; } try { StringBuilder out = new StringBuilder(64); if (escapeURLInternal(str, encoding, out, strict)) { return out.toString(); } return str; } catch (UnsupportedEncodingException e) { throw e; } catch (IOException e) { return str; // StringBuilder不可能发生这个异常 } } /** * 将指定字符串编码成<code>application/x-www-form-urlencoded</code>格式。 * * @param str 要编码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用系统默认编码 * @param strict 是否以严格的方式编码URL * @param out 输出流 * @return 如果字符串被改变,则返回<code>true</code> * @throws IOException 如果输出到<code>out</code>失败 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 * @throws IllegalArgumentException <code>out</code>为<code>null</code> */ private static boolean escapeURLInternal(String str, String encoding, Appendable out, boolean strict) throws IOException { if (encoding == null) { encoding = LocaleUtil.getContext().getCharset().name(); } boolean needToChange = false; if (out == null) { throw new IllegalArgumentException("The Appendable must not be null"); } if (str == null) { return needToChange; } char[] charArray = str.toCharArray(); int length = charArray.length; for (int i = 0; i < length; i++) { int ch = charArray[i]; if (isSafeCharacter(ch, strict)) { // “安全”的字符,直接输出 out.append((char) ch); } else if (ch == ' ') { // 特殊情况:空格(0x20)转换成'+' out.append('+'); // 设置改变标志 needToChange = true; } else { // 对ch进行URL编码。 // 首先按指定encoding取得该字符的字节码。 byte[] bytes = String.valueOf((char) ch).getBytes(encoding); for (byte toEscape : bytes) { out.append('%'); int low = toEscape & 0x0F; int high = (toEscape & 0xF0) >> 4; out.append(HEXADECIMAL[high]); out.append(HEXADECIMAL[low]); } // 设置改变标志 needToChange = true; } } return needToChange; } /** * 判断指定字符是否是“安全”的,这个字符将不被转换成URL编码。 * * @param ch 要判断的字符 * @param strict 是否以严格的方式编码 * @return 如果是“安全”的,则返回<code>true</code> */ private static boolean isSafeCharacter(int ch, boolean strict) { if (strict) { return UNRESERVED.get(ch); } else { return ch > ' ' && !RESERVED.get(ch) && !Character.isWhitespace((char) ch); } } /** * 解码<code>application/x-www-form-urlencoded</code>格式的字符串。 * <p> * 警告:该方法使用系统字符编码来解码URL,因此该方法在不同的系统中可能会产生不同的结果。 * </p> * * @param str 要解码的字符串,可以是<code>null</code> * @return URL解码后的字符串 */ public static String unescapeURL(String str) { try { return unescapeURLInternal(str, null); } catch (UnsupportedEncodingException e) { return str; // 不可能发生这个异常 } } /** * 解码<code>application/x-www-form-urlencoded</code>格式的字符串。 * * @param str 要解码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用系统默认编码 * @return URL解码后的字符串 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 */ public static String unescapeURL(String str, String encoding) throws UnsupportedEncodingException { return unescapeURLInternal(str, encoding); } /** * 解码<code>application/x-www-form-urlencoded</code>格式的字符串。 * * @param str 要解码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用系统默认编码 * @param out 输出流 * @throws IOException 如果输出到<code>out</code>失败 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 * @throws IllegalArgumentException <code>out</code>为<code>null</code> */ public static void unescapeURL(String str, String encoding, Appendable out) throws IOException { unescapeURLInternal(str, encoding, out); } /** * 解码<code>application/x-www-form-urlencoded</code>格式的字符串。 * * @param str 要解码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用系统默认编码 * @return URL解码后的字符串 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 */ private static String unescapeURLInternal(String str, String encoding) throws UnsupportedEncodingException { if (str == null) { return null; } try { StringBuilder out = new StringBuilder(str.length()); if (unescapeURLInternal(str, encoding, out)) { return out.toString(); } return str; } catch (UnsupportedEncodingException e) { throw e; } catch (IOException e) { return str; // StringBuilder不可能发生这个异常 } } /** * 解码<code>application/x-www-form-urlencoded</code>格式的字符串。 * * @param str 要解码的字符串,可以是<code>null</code> * @param encoding 输出字符编码,如果为<code>null</code>,则使用系统默认编码 * @param out 输出流 * @return 如果字符串被改变,则返回<code>true</code> * @throws IOException 如果输出到<code>out</code>失败 * @throws UnsupportedEncodingException 如果指定的<code>encoding</code>为非法的 * @throws IllegalArgumentException <code>out</code>为<code>null</code> */ private static boolean unescapeURLInternal(String str, String encoding, Appendable out) throws IOException { if (encoding == null) { encoding = LocaleUtil.getContext().getCharset().name(); } boolean needToChange = false; if (out == null) { throw new IllegalArgumentException("The Appendable must not be null"); } byte[] buffer = null; int pos = 0; int startIndex = 0; char[] charArray = str.toCharArray(); int length = charArray.length; for (int i = 0; i < length; i++) { int ch = charArray[i]; if (ch < 256) { // 读取连续的字节,并将它按指定编码转换成字符。 if (buffer == null) { buffer = new byte[length - i]; // 最长只需要length - i } if (pos == 0) { startIndex = i; } switch (ch) { case '+': // 将'+'转换成' ' buffer[pos++] = ' '; // 设置改变标志 needToChange = true; break; case '%': if (i + 2 < length) { try { byte b = (byte) Integer.parseInt(str.substring(i + 1, i + 3), 16); buffer[pos++] = b; i += 2; // 设置改变标志 needToChange = true; } catch (NumberFormatException e) { // 如果%xx不是合法的16进制数,则原样输出 buffer[pos++] = (byte) ch; } } else { buffer[pos++] = (byte) ch; } break; default: // 写到bytes中,到时一起输出。 buffer[pos++] = (byte) ch; break; } } else { // 先将buffer中的字节串转换成字符串。 if (pos > 0) { String s = new String(buffer, 0, pos, encoding); out.append(s); if (!needToChange && !s.equals(new String(charArray, startIndex, pos))) { needToChange = true; } pos = 0; } // 如果ch是ISO-8859-1以外的字符,直接输出即可 out.append((char) ch); } } // 先将buffer中的字节串转换成字符串。 if (pos > 0) { String s = new String(buffer, 0, pos, encoding); out.append(s); if (!needToChange && !s.equals(new String(charArray, startIndex, pos))) { needToChange = true; } pos = 0; } return needToChange; } }