32

我当然熟悉java.net.URLEncoderandjava.net.URLDecoder类。但是,我只需要 HTML 样式的编码。(我不想' '替换为'+'等)。我不知道任何只做 HTML 编码的 JDK 内置类。有吗?我知道其他选择(例如Jakarta Commons Lang 'StringEscapeUtils',但我不想在我需要的项目中添加另一个外部依赖项。

我希望最近的 JDK(又名 5 或 6)中添加了一些我不知道的东西。否则我必须自己动手。

4

7 回答 7

45

没有内置的 JDK 类来执行此操作,但它是 Jakarta commons-lang 库的一部分。

String escaped = StringEscapeUtils.escapeHtml3(stringToEscape);
String escaped = StringEscapeUtils.escapeHtml4(stringToEscape);

查看JavaDoc

添加依赖项通常就像将 jar 放到某个地方一样简单,并且 commons-lang 有很多有用的实用程序,因此通常值得将其加入。

于 2009-03-17T21:52:33.397 回答
14

一种简单的方法似乎是这样的:

/**
 * HTML encode of UTF8 string i.e. symbols with code more than 127 aren't encoded
 * Use Apache Commons Text StringEscapeUtils if it is possible
 *
 * <pre>
 * escapeHtml("\tIt's timeto hack & fun\r<script>alert(\"PWNED\")</script>")
 *    .equals("&#9;It&#39;s time to hack &amp; fun&#13;&lt;script&gt;alert(&quot;PWNED&quot;)&lt;/script&gt;")
 * </pre>
 */
public static String escapeHtml(String rawHtml) {
    int rawHtmlLength = rawHtml.length();
    // add 30% for additional encodings
    int capacity = (int) (rawHtmlLength * 1.3);
    StringBuilder sb = new StringBuilder(capacity);
    for (int i = 0; i < rawHtmlLength; i++) {
        char ch = rawHtml.charAt(i);
        if (ch == '<') {
            sb.append("&lt;");
        } else if (ch == '>') {
            sb.append("&gt;");
        } else if (ch == '"') {
            sb.append("&quot;");
        } else if (ch == '&') {
            sb.append("&amp;");
        } else if (ch < ' ' || ch == '\'') {
            // non printable ascii symbols escaped as numeric entity
            // single quote ' in html doesn't have &apos; so show it as numeric entity &#39;
            sb.append("&#").append((int)ch).append(';');
        } else {
            // any non ASCII char i.e. upper than 127 is still UTF
            sb.append(ch);
        }
    }
    return sb.toString();
}

但是,如果您确实需要转义所有非 ASCII 符号,即您将以 7 位编码传输编码文本,则将最后一个 else 替换为:

        } else {
            // encode non ASCII characters if needed
            int c = (ch & 0xFFFF);
            if (c > 127) {
                sb.append("&#").append(c).append(';');
            } else {
                sb.append(ch);
            }
        }
于 2012-01-12T15:53:33.030 回答
10

显然,答案是“不”。不幸的是,在这种情况下,我不得不做一些事情并且无法为其添加新的外部依赖项——在短期内。我同意大家的观点,即使用 Commons Lang 是最好的长期解决方案。一旦我可以将新库添加到项目中,这就是我将采用的方法。

很遗憾,这种常用的东西不在 Java API 中。

于 2009-09-09T16:27:54.367 回答
5

我发现我审查过的所有现有解决方案(库)都存在以下一个或几个问题:

  • 他们没有在 Javadoc 中准确地告诉您它们所替换的内容。
  • 它们逃逸太多了……这使得 HTML 更难阅读。
  • 它们不会记录返回值何时可以安全使用(安全用于 HTML 实体?用于 HTML 属性?等)
  • 它们没有针对速度进行优化。
  • 它们没有避免双重转义的功能(不要转义已经转义的内容)
  • 他们将单引号替换为&apos; (错误!)

除此之外,我还遇到了无法引入外部库的问题,至少在没有一定数量的繁文缛节的情况下是这样。

所以,我推出了自己的。有罪的。

下面是它的样子,但最新版本总是可以在这个 gist中找到。

/**
 * HTML string utilities
 */
public class SafeHtml {

    /**
     * Escapes a string for use in an HTML entity or HTML attribute.
     * 
     * <p>
     * The returned value is always suitable for an HTML <i>entity</i> but only
     * suitable for an HTML <i>attribute</i> if the attribute value is inside
     * double quotes. In other words the method is not safe for use with HTML
     * attributes unless you put the value in double quotes like this:
     * <pre>
     *    &lt;div title="value-from-this-method" &gt; ....
     * </pre>
     * Putting attribute values in double quotes is always a good idea anyway.
     * 
     * <p>The following characters will be escaped:
     * <ul>
     *   <li>{@code &} (ampersand) -- replaced with {@code &amp;}</li>
     *   <li>{@code <} (less than) -- replaced with {@code &lt;}</li>
     *   <li>{@code >} (greater than) -- replaced with {@code &gt;}</li>
     *   <li>{@code "} (double quote) -- replaced with {@code &quot;}</li>
     *   <li>{@code '} (single quote) -- replaced with {@code &#39;}</li>
     *   <li>{@code /} (forward slash) -- replaced with {@code &#47;}</li>
     * </ul>
     * It is not necessary to escape more than this as long as the HTML page
     * <a href="https://en.wikipedia.org/wiki/Character_encodings_in_HTML">uses
     * a Unicode encoding</a>. (Most web pages uses UTF-8 which is also the HTML5
     * recommendation.). Escaping more than this makes the HTML much less readable.
     * 
     * @param s the string to make HTML safe
     * @param avoidDoubleEscape avoid double escaping, which means for example not 
     *     escaping {@code &lt;} one more time. Any sequence {@code &....;}, as explained in
     *     {@link #isHtmlCharEntityRef(java.lang.String, int) isHtmlCharEntityRef()}, will not be escaped.
     * 
     * @return a HTML safe string 
     */
    public static String htmlEscape(String s, boolean avoidDoubleEscape) {
        if (s == null || s.length() == 0) {
            return s;
        }
        StringBuilder sb = new StringBuilder(s.length()+16);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
                case '&':
                    // Avoid double escaping if already escaped
                    if (avoidDoubleEscape && (isHtmlCharEntityRef(s, i))) {
                        sb.append('&');
                    } else {
                        sb.append("&amp;");
                    }
                    break;
                case '<':
                    sb.append("&lt;");
                    break;
                case '>':
                    sb.append("&gt;");
                    break;
                case '"':
                    sb.append("&quot;"); 
                    break;
                case '\'':
                    sb.append("&#39;"); 
                    break;
                case '/':
                    sb.append("&#47;"); 
                    break;
                default:
                    sb.append(c);
            }
        }
        return sb.toString();
  }

  /**
   * Checks if the value at {@code index} is a HTML entity reference. This
   * means any of :
   * <ul>
   *   <li>{@code &amp;} or {@code &lt;} or {@code &gt;} or {@code &quot;} </li>
   *   <li>A value of the form {@code &#dddd;} where {@code dddd} is a decimal value</li>
   *   <li>A value of the form {@code &#xhhhh;} where {@code hhhh} is a hexadecimal value</li>
   * </ul>
   * @param str the string to test for HTML entity reference.
   * @param index position of the {@code '&'} in {@code str}
   * @return 
   */
  public static boolean isHtmlCharEntityRef(String str, int index)  {
      if (str.charAt(index) != '&') {
          return false;
      }
      int indexOfSemicolon = str.indexOf(';', index + 1);
      if (indexOfSemicolon == -1) { // is there a semicolon sometime later ?
          return false;
      }
      if (!(indexOfSemicolon > (index + 2))) {   // is the string actually long enough
          return false;
      }
      if (followingCharsAre(str, index, "amp;")
              || followingCharsAre(str, index, "lt;")
              || followingCharsAre(str, index, "gt;")
              || followingCharsAre(str, index, "quot;")) {
          return true;
      }
      if (str.charAt(index+1) == '#') {
          if (str.charAt(index+2) == 'x' || str.charAt(index+2) == 'X') {
              // It's presumably a hex value
              if (str.charAt(index+3) == ';') {
                  return false;
              }
              for (int i = index+3; i < indexOfSemicolon; i++) {
                  char c = str.charAt(i);
                  if (c >= 48 && c <=57) {  // 0 -- 9
                      continue;
                  }
                  if (c >= 65 && c <=70) {   // A -- F
                      continue;
                  }
                  if (c >= 97 && c <=102) {   // a -- f
                      continue;
                  }
                  return false;  
              }
              return true;   // yes, the value is a hex string
          } else {
              // It's presumably a decimal value
              for (int i = index+2; i < indexOfSemicolon; i++) {
                  char c = str.charAt(i);
                  if (c >= 48 && c <=57) {  // 0 -- 9
                      continue;
                  }
                  return false;
              }
              return true; // yes, the value is decimal
          }
      }
      return false;
  } 


  /**
   * Tests if the chars following position <code>startIndex</code> in string
   * <code>str</code> are that of <code>nextChars</code>.
   * 
   * <p>Optimized for speed. Otherwise this method would be exactly equal to
   * {@code (str.indexOf(nextChars, startIndex+1) == (startIndex+1))}.
   *
   * @param str
   * @param startIndex
   * @param nextChars
   * @return 
   */  
  private static boolean followingCharsAre(String str, int startIndex, String nextChars)  {
      if ((startIndex + nextChars.length()) < str.length()) {
          for(int i = 0; i < nextChars.length(); i++) {
              if ( nextChars.charAt(i) != str.charAt(startIndex+i+1)) {
                  return false;
              }
          }
          return true;
      } else {
          return false;
      }
  }
}

TODO:保留连续的空白。

于 2016-05-22T20:08:14.510 回答
1

请不要自己动手。使用 Jakarta Commons Lang。它经过测试并证明可以工作。除非必须,否则不要编写代码。“不是在这里发明的”或“不是另一个依赖项”不是决定选择/写什么的很好的基础。

于 2009-03-17T19:59:52.093 回答
0

不,我建议使用您提到的 StringEscapeUtils,或者例如 JTidy(http://jtidy.sourceforge.net/multiproject/jtidyservlet/apidocs/org/w3c/tidy/servlet/util/HTMLEncode.html)。

于 2009-03-17T19:57:27.277 回答
-1

我会建议使用 org.springframework.web.util.HtmlUtils.htmlEscape(String input)

可能这会有所帮助。

于 2017-07-28T10:15:56.753 回答