web-dev-qa-db-fra.com

Équivalent Java du composant encodeURIComponent de JavaScript produisant une sortie identique?

J'ai essayé différents types de code Java pour essayer de créer quelque chose qui encoderait une chaîne contenant des guillemets, des espaces et des caractères Unicode "exotiques" et produirait une sortie identique à la fonction encodeURIComponent de JavaScript.

Ma chaîne de test de torture est: "A" B ± "

Si j'entre l'énoncé JavaScript suivant dans Firebug:

encodeURIComponent('"A" B ± "');

—Alors je reçois:

"%22A%22%20B%20%C2%B1%20%22"

Voici mon petit programme de test Java:

import Java.io.UnsupportedEncodingException;
import Java.net.URLEncoder;

public class EncodingTest
{
  public static void main(String[] args) throws UnsupportedEncodingException
  {
    String s = "\"A\" B ± \"";
    System.out.println("URLEncoder.encode returns "
      + URLEncoder.encode(s, "UTF-8"));

    System.out.println("getBytes returns "
      + new String(s.getBytes("UTF-8"), "ISO-8859-1"));
  }
}

—Les résultats de ce programme:

URLEncoder.encode renvoie% 22A% 22 + B +% C2% B1 +% 22 
 GetBytes renvoie "A" B ± "

Proche, mais pas de cigare! Quel est le meilleur moyen d’encoder une chaîne UTF-8 en utilisant Java afin qu’elle produise le même résultat que la variable encodeURIComponent de JavaScript?

EDIT: J'utilise Java 1.4 pour passer à Java 5 sous peu.

78
John Topley

En regardant les différences de mise en œuvre, je constate que:

MDC on encodeURIComponent() :

  • caractères littéraux (représentation regex): [-a-zA-Z0-9._*~'()!]

Documentation Java 1.5.0 sur URLEncoder :

  • caractères littéraux (représentation regex): [-a-zA-Z0-9._*]
  • le caractère espace " " est converti en un signe plus "+"

Donc, fondamentalement, pour obtenir le résultat souhaité, utilisez URLEncoder.encode(s, "UTF-8") puis effectuez du post-traitement:

  • remplace toutes les occurrences de "+" par "%20"
  • remplace toutes les occurrences de "%xx" représentant l'une quelconque de [~'()!] dans leurs contre-parties littérales
50
Tomalak

C’est le cours que j’ai eu à la fin:

import Java.io.UnsupportedEncodingException;
import Java.net.URLDecoder;
import Java.net.URLEncoder;

/**
 * Utility class for JavaScript compatible UTF-8 encoding and decoding.
 * 
 * @see http://stackoverflow.com/questions/607176/Java-equivalent-to-javascripts-encodeuricomponent-that-produces-identical-output
 * @author John Topley 
 */
public class EncodingUtil
{
  /**
   * Decodes the passed UTF-8 String using an algorithm that's compatible with
   * JavaScript's <code>decodeURIComponent</code> function. Returns
   * <code>null</code> if the String is <code>null</code>.
   *
   * @param s The UTF-8 encoded String to be decoded
   * @return the decoded String
   */
  public static String decodeURIComponent(String s)
  {
    if (s == null)
    {
      return null;
    }

    String result = null;

    try
    {
      result = URLDecoder.decode(s, "UTF-8");
    }

    // This exception should never occur.
    catch (UnsupportedEncodingException e)
    {
      result = s;  
    }

    return result;
  }

  /**
   * Encodes the passed String as UTF-8 using an algorithm that's compatible
   * with JavaScript's <code>encodeURIComponent</code> function. Returns
   * <code>null</code> if the String is <code>null</code>.
   * 
   * @param s The String to be encoded
   * @return the encoded String
   */
  public static String encodeURIComponent(String s)
  {
    String result = null;

    try
    {
      result = URLEncoder.encode(s, "UTF-8")
                         .replaceAll("\\+", "%20")
                         .replaceAll("\\%21", "!")
                         .replaceAll("\\%27", "'")
                         .replaceAll("\\%28", "(")
                         .replaceAll("\\%29", ")")
                         .replaceAll("\\%7E", "~");
    }

    // This exception should never occur.
    catch (UnsupportedEncodingException e)
    {
      result = s;
    }

    return result;
  }  

  /**
   * Private constructor to prevent this class from being instantiated.
   */
  private EncodingUtil()
  {
    super();
  }
}
104
John Topley

Utilisation du moteur javascript fourni avec Java 6:


import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class Wow
{
    public static void main(String[] args) throws Exception
    {
        ScriptEngineManager factory = new ScriptEngineManager();
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        engine.eval("print(encodeURIComponent('\"A\" B ± \"'))");
    }
}

Rendement:% 22A% 22% 20B% 20% c2% b1% 20% 22

Le cas est différent mais c'est plus proche de ce que vous voulez.

12
Ravi Wallau

J'utilise Java.net.URI#getRawPath(), par exemple.

String s = "a+b c.html";
String fixed = new URI(null, null, s, null).getRawPath();

La valeur de fixed sera a+b%20c.html, comme vous le souhaitez.

Le post-traitement de la sortie de URLEncoder.encode() effacera tous les avantages qui sont supposés être dans l'URI. Par exemple

URLEncoder.encode("a+b c.html").replaceAll("\\+", "%20");

vous donnera a%20b%20c.html, qui sera interprété comme a b c.html.

5
Chris Nitchie

Je suis venu avec ma propre version de la encodeURIComponent, parce que la solution affichée a un problème, s'il y avait un + présent dans la chaîne, qui devrait être encodé, il sera converti en un espace. 

Alors voici ma classe:

import Java.io.UnsupportedEncodingException;
import Java.util.BitSet;

public final class EscapeUtils
{
    /** used for the encodeURIComponent function */
    private static final BitSet dontNeedEncoding;

    static
    {
        dontNeedEncoding = new BitSet(256);

        // a-z
        for (int i = 97; i <= 122; ++i)
        {
            dontNeedEncoding.set(i);
        }
        // A-Z
        for (int i = 65; i <= 90; ++i)
        {
            dontNeedEncoding.set(i);
        }
        // 0-9
        for (int i = 48; i <= 57; ++i)
        {
            dontNeedEncoding.set(i);
        }

        // '()*
        for (int i = 39; i <= 42; ++i)
        {
            dontNeedEncoding.set(i);
        }
        dontNeedEncoding.set(33); // !
        dontNeedEncoding.set(45); // -
        dontNeedEncoding.set(46); // .
        dontNeedEncoding.set(95); // _
        dontNeedEncoding.set(126); // ~
    }

    /**
     * A Utility class should not be instantiated.
     */
    private EscapeUtils()
    {

    }

    /**
     * Escapes all characters except the following: alphabetic, decimal digits, - _ . ! ~ * ' ( )
     * 
     * @param input
     *            A component of a URI
     * @return the escaped URI component
     */
    public static String encodeURIComponent(String input)
    {
        if (input == null)
        {
            return input;
        }

        StringBuilder filtered = new StringBuilder(input.length());
        char c;
        for (int i = 0; i < input.length(); ++i)
        {
            c = input.charAt(i);
            if (dontNeedEncoding.get(c))
            {
                filtered.append(c);
            }
            else
            {
                final byte[] b = charToBytesUTF(c);

                for (int j = 0; j < b.length; ++j)
                {
                    filtered.append('%');
                    filtered.append("0123456789ABCDEF".charAt(b[j] >> 4 & 0xF));
                    filtered.append("0123456789ABCDEF".charAt(b[j] & 0xF));
                }
            }
        }
        return filtered.toString();
    }

    private static byte[] charToBytesUTF(char c)
    {
        try
        {
            return new String(new char[] { c }).getBytes("UTF-8");
        }
        catch (UnsupportedEncodingException e)
        {
            return new byte[] { (byte) c };
        }
    }
}
4
Joe Mill

J'ai proposé une autre implémentation documentée à l'adresse http://blog.sangupta.com/2010/05/encodeuricomponent-and.html . L'implémentation peut également gérer les octets Unicode.

3
sangupta

Voici un exemple simple de la solution de Ravi Wallau:

public String buildSafeURL(String partialURL, String documentName)
        throws ScriptException {
    ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
    ScriptEngine scriptEngine = scriptEngineManager
            .getEngineByName("JavaScript");

    String urlSafeDocumentName = String.valueOf(scriptEngine
            .eval("encodeURIComponent('" + documentName + "')"));
    String safeURL = partialURL + urlSafeDocumentName;

    return safeURL;
}

public static void main(String[] args) {
    EncodeURIComponentDemo demo = new EncodeURIComponentDemo();
    String partialURL = "https://www.website.com/document/";
    String documentName = "Tom & Jerry Manuscript.pdf";

    try {
        System.out.println(demo.buildSafeURL(partialURL, documentName));
    } catch (ScriptException se) {
        se.printStackTrace();
    }
}

Sortie: https://www.website.com/document/Tom%20%26%20Jerry%20Manuscript.pdf

Il répond également à la question pendante dans les commentaires de Loren Shqipognja sur la manière de passer une variable String à encodeURIComponent(). La méthode scriptEngine.eval() renvoie une Object, de sorte qu'elle peut être convertie en String via String.valueOf(), entre autres méthodes.

1
silver

C'est ce que j'utilise:

private static final String HEX = "0123456789ABCDEF";

public static String encodeURIComponent(String str) {
    if (str == null) return null;

    byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
    StringBuilder builder = new StringBuilder(bytes.length);

    for (byte c : bytes) {
        if (c >= 'a' ? c <= 'z' || c == '~' :
            c >= 'A' ? c <= 'Z' || c == '_' :
            c >= '0' ? c <= '9' :  c == '-' || c == '.')
            builder.append((char)c);
        else
            builder.append('%')
                   .append(HEX.charAt(c >> 4 & 0xf))
                   .append(HEX.charAt(c & 0xf));
    }

    return builder.toString();
}

Cela va au-delà de Javascript en codant pour cent tous les caractères qui ne sont pas des caractères non réservés selon RFC 3986 .


C'est la conversion opposée:

public static String decodeURIComponent(String str) {
    if (str == null) return null;

    int length = str.length();
    byte[] bytes = new byte[length / 3];
    StringBuilder builder = new StringBuilder(length);

    for (int i = 0; i < length; ) {
        char c = str.charAt(i);
        if (c != '%') {
            builder.append(c);
            i += 1;
        } else {
            int j = 0;
            do {
                char h = str.charAt(i + 1);
                char l = str.charAt(i + 2);
                i += 3;

                h -= '0';
                if (h >= 10) {
                    h |= ' ';
                    h -= 'a' - '0';
                    if (h >= 6) throw new IllegalArgumentException();
                    h += 10;
                }

                l -= '0';
                if (l >= 10) {
                    l |= ' ';
                    l -= 'a' - '0';
                    if (l >= 6) throw new IllegalArgumentException();
                    l += 10;
                }

                bytes[j++] = (byte)(h << 4 | l);
                if (i >= length) break;
                c = str.charAt(i);
            } while (c == '%');
            builder.append(new String(bytes, 0, j, UTF_8));
        }
    }

    return builder.toString();
}
1
Nuno Cruces

pour moi cela a fonctionné:

import org.Apache.http.client.utils.URIBuilder;

String encodedString = new URIBuilder()
  .setParameter("i", stringToEncode)
  .build()
  .getRawQuery() // output: i=encodedString
  .substring(2);

ou avec un autre UriBuilder

import javax.ws.rs.core.UriBuilder;

String encodedString = UriBuilder.fromPath("")
  .queryParam("i", stringToEncode)
  .toString()   // output: ?i=encodedString
  .substring(3);

À mon avis, utiliser une bibliothèque standard est une meilleure idée plutôt que de post-traiter manuellement. La réponse de @Chris semblait également bonne, mais elle ne fonctionne pas pour les URL, comme " http: // a + b c.html"

0
balazs

J'ai utilisé String encodedUrl = new URI(null, url, null).toASCIIString(); pour encoder les URL. Pour ajouter des paramètres après ceux existants dans la url j'utilise UriComponentsBuilder

0
AlexN

J'ai utilisé avec succès la classe Java.net.URI comme ceci:

public static String uriEncode(String string) {
    String result = string;
    if (null != string) {
        try {
            String scheme = null;
            String ssp = string;
            int es = string.indexOf(':');
            if (es > 0) {
                scheme = string.substring(0, es);
                ssp = string.substring(es + 1);
            }
            result = (new URI(scheme, ssp, null)).toString();
        } catch (URISyntaxException usex) {
            // ignore and use string that has syntax error
        }
    }
    return result;
}
0
Mike Bryant

J'ai trouvé la classe PercentEscaper dans la bibliothèque client google-http-Java, qui peut être utilisée pour implémenter assez facilement encoderURIComponent.

PercentEscaper from javadoc de google-http-Java-clientaccueil de google-http-Java-client

0
honzajde