【问题标题】:Is there a JDK class to do HTML encoding (but not URL encoding)?是否有一个 JDK 类来进行 HTML 编码(但不是 URL 编码)?
【发布时间】:2010-10-13 22:25:12
【问题描述】:

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

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

【问题讨论】:

    标签: html java html-encode


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 总的来说,我同意你的看法。但是我正在为生产中的东西添加额外的诊断输出。当添加新的第 3 方依赖项时,律师会参与其中。它并不像你想象的那么琐碎。否则我不会问这个问题!
      • 让哲学远离stackoverflow :) 每个人都有重写代码的理由。
      • 通常,这是对那些编写代码但不知道代码具体做什么的人的建议。从来没有听过这样的建议让我成为一名开发人员——我的意思是,这就是我学习和改进的方式。
      • 除非该项目应该在昨天完成并且您必须同时处理其他 3 个项目。有时需要考虑现实世界的限制,而自己动手通常是引入更多错误(并因此花费更多时间)的可靠方法。
      • "'不是另一个依赖'不是决定选择/写什么的一个很好的基础。" - 我不同意。这种心态是大多数 Java 应用程序如此臃肿的主要原因。
      【解决方案3】:

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

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

      查看JavaDoc

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

      【讨论】:

      • 正如我在对另一个答案的评论中所说,添加依赖项 NOT 就像在某处放置 JAR 一样简单。律师需要检查第 3 方 JAR 的许可证,需要更改安装程序,等等。这并不总是微不足道的。
      • 我也不喜欢单一方法的依赖。
      • 请注意您上面的方法签名是错误的。 HTML 应该有一个小写的 tml String escaped = StringEscapeUtils.escapeHtml(stringToEscape);
      • 是否可以只转义特殊字符?
      • 在 3.6 中已弃用。请改用 org.apache.commons.text.StringEscapeUtils。
      【解决方案4】:

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

      很遗憾,Java API 中没有这种常用的东西。

      【讨论】:

        【解决方案5】:

        一个简单的方法似乎是这个:

        /**
         * 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);
                    }
                }
        

        【讨论】:

        • 感谢它正是我所需要的。
        • 我认为你也应该检查一下 & - 不是 38 吗?
        • 这会起作用,但它不符合规范。必须将以下内容编码为它们指定的实体,而不是表示字符数字代码: < " -> " 和 & -> &
        • 你也忘记了撇号。这就是当存在有效的现有解决方案时,永远不要编写自己的安全性(转义 HTML 通常与安全性相关,想想 XSS)代码的原因。喜欢HtmlUtils.htmlEscape(String)
        • 仅供参考:您的示例已针对另一个问题 stackoverflow.com/a/25228492/1049542 进行了重写,并附有重要说明“放大器丢失了”
        【解决方案6】:

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

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

        除此之外,我还遇到了无法引入外部库的问题,至少没有一定的繁文缛节。

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

        下面是它的样子,但最新版本总是可以在this 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:保留连续的空白。

        【讨论】:

        • 什么许可证适用于您答案中的代码?是公共领域吗?
        • @Zen。请参阅更新的答案,特别是 Gist 的链接。
        【解决方案7】:

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

        也许这会有所帮助。

        【讨论】:

          猜你喜欢
          • 2018-08-18
          • 2023-04-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-08
          • 2016-03-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多