Java equivalente a encodeURIComponent di JavaScript che produce un output identico?


92

Ho sperimentato vari bit di codice Java cercando di trovare qualcosa che codificherà una stringa contenente virgolette, spazi e caratteri Unicode "esotici" e produrrà un output identico alla funzione encodeURIComponent di JavaScript .

La stringa del mio test di tortura è: "A" B ± "

Se inserisco la seguente istruzione JavaScript in Firebug:

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

—Poi ottengo:

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

Ecco il mio piccolo programma Java di prova:

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"));
  }
}

—Questo programma produce:

URLEncoder.encode restituisce% 22A% 22 + B +% C2% B1 +% 22
getBytes restituisce "A" B ± "

Vicino, ma niente sigaro! Qual è il modo migliore per codificare una stringa UTF-8 utilizzando Java in modo che produca lo stesso output di JavaScript encodeURIComponent?

EDIT: sto usando Java 1.4 per passare a Java 5 a breve.

Risposte:


63

Guardando le differenze di implementazione, vedo che:

MDC suencodeURIComponent() :

  • caratteri letterali (rappresentazione regex): [-a-zA-Z0-9._*~'()!]

Documentazione Java 1.5.0 suURLEncoder :

  • caratteri letterali (rappresentazione regex): [-a-zA-Z0-9._*]
  • il carattere spazio " "viene convertito in un segno più "+".

Quindi, in pratica, per ottenere il risultato desiderato, usa URLEncoder.encode(s, "UTF-8")e poi fai un po 'di post-elaborazione:

  • sostituire tutte le occorrenze di "+"con"%20"
  • sostituire tutte le occorrenze di "%xx"rappresentare qualsiasi di [~'()!]indietro alle loro controparti letterali

Vorrei che avessi scritto "Sostituisci tutte le occorrenze di"% xx "che rappresenta uno qualsiasi di [~ '()!] Nelle loro controparti letterali" in un linguaggio semplice. :( la mia testolina non è in grado di capirlo .......
Shailendra Singh Rajawat

1
@Shailendra [~'()!]significa "~"o "'"o "("o ")"o "!". :) Tuttavia, consiglio di imparare anche le basi delle espressioni regolari. (Inoltre non ho ampliato questo
aspetto

3
La sostituzione di tutte le occorrenze di "+"with "%20"è potenzialmente distruttiva, poiché "+"è un carattere legale nei percorsi URI (sebbene non nella stringa di query). Ad esempio, "a + b c" dovrebbe essere codificato come "a+b%20c"; questa soluzione lo convertirà in "a%20b%20c". Invece, usa new URI(null, null, value, null).getRawPath().
Chris Nitchie

@ChrisNitchie Non era questo il punto della domanda. La domanda era "Java equivalente all'encodeURIComponent di JavaScript che produce un output identico?" , non "Funzione del componente URI di codifica Java generico?" .
Tomalak

118

Questa è la classe che mi è venuta alla fine:

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();
  }
}

5
Aggiunta di un suggerimento. In Android 4.4 ho scoperto che dobbiamo anche sostituire, il %0Ache significa una chiave di ritorno nell'input di Android, altrimenti si bloccherà il js.
Aloong


1
@Aloong Cosa intendi per sostituire "%0A"? Quale personaggio sarebbe il sostituto? È solo una stringa vuota ""?
HendraWD

15

Utilizzando il motore javascript fornito con 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 ± \"'))");
    }
}

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

Il caso è diverso ma è più vicino a quello che vuoi.


Ah, scusa ... avrei dovuto menzionare nella domanda che sono su Java 1.4 per passare a Java 5 a breve!
John Topley

3
Se javascript è l'unica soluzione puoi provare Rhino, ma è troppo solo per questo piccolo problema.
Ravi Wallau

3
Anche se stava usando Java 6, penso che questa soluzione sia MOLTO sopra le righe. Non credo che stia cercando un modo per invocare direttamente il metodo javascript, solo un modo per emularlo.
Programmatore fuorilegge

1
Può essere. Penso che la soluzione più semplice sarebbe scrivere la tua funzione di escape se non riesci a trovare nulla che faccia il trucco per te. Basta copiare un metodo dalla classe StringEscapeUtils (Jakarta Commons Lang) e reimplementarlo secondo le proprie esigenze.
Ravi Wallau

2
Funziona davvero, e se non sei preoccupato per le prestazioni ... penso che sia buono.
2rs2ts

8

Io uso java.net.URI#getRawPath(), ad es

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

Il valore di fixedsarà a+b%20c.html, che è quello che vuoi.

La post-elaborazione dell'output di URLEncoder.encode()cancellerà tutti i vantaggi che dovrebbero essere nell'URI. Per esempio

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

ti darà a%20b%20c.html, che verrà interpretato come a b c.html.


Dopo aver pensato che questa dovrebbe essere la risposta migliore, l'ho provata in pratica con pochi nomi di file e ha fallito in almeno due, uno con caratteri cirillici. Quindi no, questo ovviamente non è stato testato abbastanza bene.
AsGoodAsItGets il

non funziona per stringhe come http://a+b c.html
:, genererà

5

Ho ideato la mia versione di encodeURIComponent, perché la soluzione pubblicata ha un problema, se c'era un + presente nella stringa, che dovrebbe essere codificata, verrà convertita in uno spazio.

Quindi ecco la mia 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 };
        }
    }
}

Grazie per una buona soluzione! Gli altri sembrano totalmente ... inefficienti, IMO. Forse sarebbe ancora meglio senza il BitSet sull'hardware di oggi. O due long hardcoded per 0 ... 127.
Jonas N

URLEncoder.encode("+", "UTF-8");produce "%2B", che è la corretta codifica dell'URL, quindi la tua soluzione è, mi scuso, totalmente inutile. Perché diavolo URLEncoder.encodenon trasforma gli spazi in %20è oltre me.
2rs2ts


1

Ho utilizzato con successo la classe java.net.URI in questo modo:

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;
}

No, non è completamente riuscito questo approccio, ma è relativamente ok. Hai ancora problemi però. Ad esempio, il carattere cardinale # java codificherà in% 23 javascript non lo codificherà. Vedi: developer.mozilla.org/it-IT/docs/Web/JavaScript/Reference/… Javascript non espace. AZ az 0-9; , /? : @ & = + $ - _. ! ~ * '() # E per alcuni di questi java espace.
99Sono

La cosa buona è fare un test UNIT con la seguente espressione: '' 'String charactersJavascriptDoesNotEspace = "A-Za-z0-9;, /?: @ & = + $ -_.! ~ *' () #"; '' 'il cardinale è l'unico valore anomalo. Quindi correggere l'algoritmo sopra per renderlo compatibile con javascript è banale.
99Sono

1

Questo è un semplice esempio della soluzione di 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();
    }
}

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

Risponde anche alla domanda in sospeso nei commenti di Loren Shqipognja su come passare una variabile String a encodeURIComponent(). Il metodo scriptEngine.eval()restituisce un Object, quindi può essere convertito in String tramite String.valueOf()altri metodi.


1

per me questo ha funzionato:

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

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

o con un altro UriBuilder

import javax.ws.rs.core.UriBuilder;

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

A mio parere, l'utilizzo di una libreria standard è un'idea migliore piuttosto che la post-elaborazione manuale. Anche la risposta di @Chris sembrava buona, ma non funziona per gli URL, come " http: // a + b c.html"


1
Usare la libreria standard è buono ... ... a meno che tu non sia middleware e dipenda da una versione diversa di una libreria standard, e quindi chiunque utilizzi il tuo codice deve giocherellare con le dipendenze, e poi sperare che nulla si interrompa ...
Ajax

Sarebbe bello se questa soluzione funzionasse, ma non si comporta allo stesso modo della richiesta encodeURIComponent. encodeURIComponentrestituisce ?& il risultato %3F%26%20, ma il tuo suggerimento ritorna %3F%26+. So che questo è menzionato più volte in altre domande e risposte, ma dovrebbe essere menzionato qui, prima che le persone si fidino ciecamente.
Philipp

1

Questo è quello che sto usando:

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();
}

Va oltre Javascript codificando in percentuale ogni carattere che non è un carattere non riservato secondo RFC 3986 .


Questa è la conversione opposta:

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();
}


0

La libreria Guava ha PercentEscaper:

Escaper percentEscaper = new PercentEscaper("-_.*", false);

"-_. *" sono caratteri sicuri

false dice PercentEscaper per sfuggire allo spazio con "% 20", non "+"


0

Ho usato String encodedUrl = new URI(null, url, null).toASCIIString(); per codificare gli URL. Per aggiungere parametri dopo quelli esistenti nel file urlusoUriComponentsBuilder

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.