web-dev-qa-db-fra.com

Existe-t-il une classe JDK pour effectuer le codage HTML (mais pas le codage URL)?

Je connais bien sûr le Java.net.URLEncoder et Java.net.URLDecoder Des classes. Cependant, je n'ai besoin que d'un encodage de style HTML. (Je ne veux pas ' ' remplacé par '+', etc). Je ne connais aucun JDK intégré dans la classe qui ne fera que du codage HTML. Est-ce qu'il y a un? Je connais d'autres choix (par exemple, Jakarta Commons Lang 'StringEscapeUtils' , mais je ne veux pas ajouter une autre dépendance externe au projet où j'en ai besoin).

J'espère que quelque chose a été ajouté à un JDK récent (alias 5 ou 6) qui fera cela que je ne connais pas. Sinon, je dois rouler le mien.

31
Eddie

Apparemment, la réponse est "non". C'était malheureusement un cas où je devais faire quelque chose et ne pouvait pas ajouter une nouvelle dépendance externe pour cela - à court terme. Je suis d'accord avec tout le monde que l'utilisation de Commons Lang est la meilleure solution à long terme. C'est ce que j'irai avec une fois que je pourrai ajouter une nouvelle bibliothèque au projet.

Il est dommage que quelque chose d'une telle utilisation courante ne se trouve pas dans l'API Java.

9
Eddie

Il n'y a pas de JDK construit en classe pour cela, mais il fait partie de la bibliothèque Jakarta commons-lang.

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

Découvrez JavaDoc

Ajouter la dépendance est généralement aussi simple que de laisser tomber le pot quelque part, et commons-lang a tellement d'utilitaires utiles qu'il vaut souvent la peine de l'avoir à bord.

45
johnmcase

Un moyen simple semble être celui-ci:

public static String encodeHTML(String s)
{
    StringBuffer out = new StringBuffer();
    for(int i=0; i<s.length(); i++)
    {
        char c = s.charAt(i);
        if(c > 127 || c=='"' || c=='<' || c=='>')
        {
           out.append("&#"+(int)c+";");
        }
        else
        {
            out.append(c);
        }
    }
    return out.toString();
}

Source: http://forums.thedailywtf.com/forums/p/2806/72054.aspx#72054

13
Rawton Evolekam

J'ai constaté que toutes les solutions existantes (bibliothèques) que j'ai examinées souffraient d'un ou plusieurs des problèmes ci-dessous:

  • Ils ne vous disent pas exactement dans le Javadoc ce qu'ils remplacent.
  • Ils s'échappent trop ... ce qui rend le HTML beaucoup plus difficile à lire.
  • Ils ne documentent pas lorsque la valeur retournée est sûre à utiliser (sûre à utiliser pour une entité HTML ?, pour un attribut HTML ?, etc.)
  • Ils ne sont pas optimisés pour la vitesse.
  • Ils n'ont pas de fonction pour éviter les doubles échappements (n'échappent pas à ce qui est déjà échappé)
  • Ils remplacent les guillemets simples par &apos; (faux!)

En plus de cela, j'ai également eu le problème de ne pas pouvoir apporter une bibliothèque externe, du moins pas sans une certaine quantité de paperasse.

J'ai donc roulé le mien. Coupable.

Voici à quoi il ressemble, mais la dernière version peut toujours être trouvée dans 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: Conserver les espaces consécutifs.

4
peterh

Veuillez ne pas rouler le vôtre Utilisez Jakarta Commons Lang. Il est testé et éprouvé pour fonctionner. N'écrivez pas de code avant de le faire. "Pas inventé ici" ou "Pas une autre dépendance" n'est pas une très bonne base pour décider quoi choisir/écrire.

1
bitboxer

Non, je recommanderais d'utiliser les StringEscapeUtils que vous avez mentionnés, ou par exemple JTidy ( http://jtidy.sourceforge.net/multiproject/jtidyservlet/apidocs/org/w3c/tidy/servlet/util/HTMLEncode.html ).

0
simon