Java entspricht der encodeURIComponent von JavaScript, die identische Ausgaben erzeugt?

89

Ich habe mit verschiedenen Teilen von Java-Code experimentiert, um etwas zu finden, das eine Zeichenfolge mit Anführungszeichen, Leerzeichen und "exotischen" Unicode-Zeichen codiert und eine Ausgabe erzeugt, die mit der Funktion encodeURIComponent von JavaScript identisch ist.

Mein Folterteststring lautet: "A" B ± "

Wenn ich in Firebug die folgende JavaScript-Anweisung eingebe:

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

- Dann bekomme ich:

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

Hier ist mein kleines Test-Java-Programm:

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

- Dieses Programm gibt Folgendes aus:

URLEncoder.encode gibt% 22A% 22 + B +% C2% B1 +% 22 zurück
getBytes gibt "A" B ± "zurück.

Nah dran, aber keine Zigarre! Was ist der beste Weg, um eine UTF-8-Zeichenfolge mit Java so zu codieren, dass sie dieselbe Ausgabe wie JavaScript erzeugt encodeURIComponent?

BEARBEITEN: Ich verwende Java 1.4 und wechsle in Kürze zu Java 5.

John Topley
quelle

Antworten:

62

Wenn ich mir die Implementierungsunterschiede ansehe, sehe ich Folgendes:

MDC einencodeURIComponent() :

  • Literalzeichen (Regex-Darstellung): [-a-zA-Z0-9._*~'()!]

Java 1.5.0 Dokumentation zuURLEncoder :

  • Literalzeichen (Regex-Darstellung): [-a-zA-Z0-9._*]
  • Das Leerzeichen " "wird in ein Pluszeichen umgewandelt "+".

Um das gewünschte Ergebnis zu erzielen, verwenden Sie im Grunde genommen eine URLEncoder.encode(s, "UTF-8")Nachbearbeitung und führen Sie diese durch:

  • Ersetzen Sie alle Vorkommen von "+"durch"%20"
  • Ersetzen Sie alle Vorkommnisse, in denen Sie "%xx"einen Teil [~'()!]Ihrer wörtlichen Gegenstücke darstellen
Tomalak
quelle
Ich wünschte, Sie hätten in einer einfachen Sprache "Ersetzen Sie alle Vorkommen von"% xx ", die eines von [~ '()!] Zurückstellen, zu ihren wörtlichen Gegenstücken" geschrieben. :( mein winziger Kopf kann es nicht verstehen .......
Shailendra Singh Rajawat
1
@Shailendra [~'()!]bedeutet "~"oder "'"oder "("oder ")"oder "!". :) Ich empfehle jedoch auch, die Regex-Grundlagen zu lernen. (Ich habe das auch nicht erweitert, da mindestens zwei andere Antworten den jeweiligen Java-Code zeigen.)
Tomalak
3
Das Ersetzen aller Vorkommen von "+"durch "%20"ist möglicherweise destruktiv, ebenso "+"wie ein zulässiges Zeichen in URI-Pfaden (jedoch nicht in der Abfragezeichenfolge). Zum Beispiel sollte "a + b c" codiert werden als "a+b%20c"; Diese Lösung würde es in konvertieren "a%20b%20c". Verwenden Sie stattdessen new URI(null, null, value, null).getRawPath().
Chris Nitchie
@ ChrisNitchie Das war nicht der Punkt der Frage. Die Frage lautete: "Java entspricht der encodeURIComponent von JavaScript, die identische Ausgaben erzeugt." , nicht "Generische Java-Codierungs-URI-Komponentenfunktion?" .
Tomalak
116

Dies ist die Klasse, die ich mir am Ende ausgedacht habe:

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();
  }
}
John Topley
quelle
5
Tipp hinzufügen. In Android 4.4 habe ich festgestellt, dass wir auch ersetzen müssen, %0Awas eine Eingabetaste in der Android-Eingabe bedeutet, sonst stürzt das js ab.
Aloong
Behandeln
1
@Aloong Was meinst du mit ersetzen "%0A"? Welcher Charakter wäre der Ersatz? Ist es nur eine leere Zeichenfolge ""?
HendraWD
15

Verwenden der mit Java 6 gelieferten Javascript-Engine:


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

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

Der Fall ist anders, aber näher an dem, was Sie wollen.

Ravi Wallau
quelle
Ah, sorry ... Ich hätte in der Frage erwähnen sollen, dass ich auf Java 1.4 bin und in Kürze auf Java 5 umsteige!
John Topley
3
Wenn Javascript die einzige Lösung ist, können Sie Rhino ausprobieren, aber es ist zu viel, nur für dieses kleine Problem.
Ravi Wallau
3
Selbst wenn er Java 6 verwendet hat, denke ich, dass diese Lösung übertrieben ist. Ich glaube nicht, dass er nach einer Möglichkeit sucht, die Javascript-Methode direkt aufzurufen, sondern nur nach einer Möglichkeit, sie zu emulieren.
Outlaw Programmer
1
Vielleicht. Ich denke, die einfachste Lösung wäre, eine eigene Escape-Funktion zu schreiben, wenn Sie nichts finden, was den Trick für Sie erledigt. Kopieren Sie einfach eine Methode aus der StringEscapeUtils-Klasse (Jakarta Commons Lang) und implementieren Sie sie entsprechend Ihren Anforderungen.
Ravi Wallau
2
Das funktioniert tatsächlich, und wenn Sie sich keine Sorgen um die Leistung machen ... Ich denke, es ist gut.
2rs2ts
8

Ich benutze java.net.URI#getRawPath()zB

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

Der Wert von fixedwird sein a+b%20c.html, was Sie wollen.

Post-Verarbeitung der Ausgabe von URLEncoder.encode()werden alle Pluspunkte auszulöschen, die angeblich in der URI sein. Beispielsweise

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

wird Ihnen geben a%20b%20c.html, was als interpretiert wird a b c.html.

Chris Nitchie
quelle
Nachdem ich dachte, dass dies die beste Antwort sein sollte, versuchte ich es in der Praxis mit ein paar Dateinamen, und es schlug in mindestens zwei fehl, einer mit kyrillischen Zeichen. Also, nein, das wurde offensichtlich nicht gut genug getestet.
AsGoodAsItGets
funktioniert nicht für Strings wie : http://a+b c.html, es wird einen Fehler
auslösen
4

Ich habe meine eigene Version der encodeURIComponent entwickelt, da die veröffentlichte Lösung ein Problem hat: Wenn in der Zeichenfolge ein + vorhanden ist, das codiert werden soll, wird sie in ein Leerzeichen konvertiert.

Also hier ist meine Klasse:

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 };
        }
    }
}
Joe Mill
quelle
Danke für eine gute Lösung! Die anderen sehen total ... ineffizient aus, IMO. Vielleicht wäre es ohne das BitSet auf der heutigen Hardware sogar noch besser. Oder zwei fest codierte Longs für 0 ... 127.
Jonas N
URLEncoder.encode("+", "UTF-8");ergibt "%2B", was die richtige URL-Codierung ist, also ist Ihre Lösung, ich entschuldige mich, völlig unnötig. Warum um alles in der Welt?URLEncoder.encode der Welt keine Räume werden, %20ist mir ein Rätsel.
2rs2ts
1

Ich habe die Klasse java.net.URI erfolgreich wie folgt verwendet:

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;
}
Mike Bryant
quelle
1

Dies ist ein einfaches Beispiel für die Lösung von 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();
    }
}

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

Es beantwortet auch die hängende Frage in den Kommentaren von Loren Shqipognja, wie eine String-Variable übergeben werden soll encodeURIComponent(). Die Methode scriptEngine.eval()gibt ein zurück Object, sodass sie String.valueOf()unter anderem über String konvertiert werden kann .

Silber
quelle
1

bei mir hat das geklappt:

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

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

oder mit einem anderen UriBuilder

import javax.ws.rs.core.UriBuilder;

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

Meiner Meinung nach ist die Verwendung einer Standardbibliothek eine bessere Idee als die manuelle Nachbearbeitung. Auch die Antwort von @Chris sah gut aus, funktioniert aber nicht für URLs wie " http: // a + b c.html".

Balazs
quelle
1
Die Verwendung der Standardbibliothek ist gut ... ... es sei denn, Sie sind Middleware und von einer anderen Version einer Standardbibliothek abhängig. Dann muss jeder, der Ihren Code verwendet, mit Abhängigkeiten herumspielen und hoffen, dass nichts kaputt geht ...
Ajax
Wäre toll, wenn diese Lösung funktionieren würde, aber sie verhält sich nicht so wie die Anfrage encodeURIComponent. encodeURIComponentgibt ?& das Ergebnis zurück %3F%26%20, aber Ihr Vorschlag kehrt zurück %3F%26+. Ich weiß, dass dies in anderen Fragen und Antworten mehrfach erwähnt wird, sollte aber hier erwähnt werden, bevor die Leute ihm blind vertrauen.
Philipp
1

Folgendes verwende ich:

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

Es geht über Javascript hinaus, indem jedes Zeichen, das gemäß RFC 3986 kein nicht reserviertes Zeichen ist, prozentual codiert wird .


Dies ist die entgegengesetzte Umwandlung:

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();
}
Nuno Cruces
quelle
0

Die Guavenbibliothek hat PercentEscaper:

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

"-_. *" sind sichere Zeichen

false sagt PercentEscaper, dass er mit '% 20' und nicht mit '+' dem Leerzeichen entkommen soll.

Aliaksei Nikuliak
quelle
0

Ich habe String encodedUrl = new URI(null, url, null).toASCIIString(); URLs verschlüsselt. Um Parameter nach den vorhandenen in der von urlmir verwendeten hinzuzufügenUriComponentsBuilder

AlexN
quelle