Aynı çıktı üreten JavaScript'in encodeURIC bileşenine eşdeğer Java mı?


92

Tırnak işaretlerini, boşlukları ve "egzotik" Unicode karakterlerini içeren bir dizeyi kodlayacak ve JavaScript'in encodeURIComponent işleviyle aynı çıktıyı üretecek bir şey bulmaya çalışan çeşitli Java kod parçalarını deniyorum .

İşkence test dizim: "A" B ± "

Firebug'a aşağıdaki JavaScript ifadesini girersem:

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

-Sonra şunu alırım:

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

İşte küçük test Java programım:

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

- Bu programın çıktıları:

URLEncoder.encode% 22A% 22 + B +% C2% B1 +% 22 döndürür
getBytes "A" B ± "döndürür

Kapat, ama puro yok! Java kullanarak bir UTF-8 dizesini kodlamanın en iyi yolu nedir, böylece JavaScript'le aynı çıktıyı üretir encodeURIComponent?

DÜZENLEME: Java 1.4 kullanıyorum, kısa süre içinde Java 5'e geçiyorum.

Yanıtlar:


63

Uygulama farklılıklarına baktığımda şunu görüyorum:

MDC açıkencodeURIComponent() :

  • değişmez karakterler (normal ifade gösterimi): [-a-zA-Z0-9._*~'()!]

Java 1.5.0 belgeleriURLEncoder :

  • değişmez karakterler (normal ifade gösterimi): [-a-zA-Z0-9._*]
  • boşluk karakteri " "artı işaretine dönüştürülür "+".

Temel olarak, istenen sonucu elde etmek için kullanın URLEncoder.encode(s, "UTF-8")ve sonra bazı son işlemler yapın:

  • tüm oluşumlarını "+"ile değiştir"%20"
  • "%xx"herhangi bir [~'()!]geri dönüşü temsil eden tüm olayları değişmez karşıt parçalarına değiştirin

Keşke basit bir dilde "[~ '()!]' Den herhangi birini temsil eden tüm"% xx "oluşumlarını değişmez karşıt parçalarına geri döndür" yazmış olsaydınız. :( minik kafam bunu anlayamıyor .......
Shailendra Singh Rajawat

1
@Shailendra veya veya veya veya [~'()!]anlamına gelir . :) Yine de normal ifadenin temellerini öğrenmenizi tavsiye ederim. (Ayrıca en az iki yanıt ilgili Java kodunu gösterdiğinden bu "~""'""("")""!"
konuyu genişletmedim

3
Tüm oluşumlarını "+"ile değiştirmek , URI yollarındaki yasal bir karakter gibi (sorgu dizesinde olmasa "%20"da) potansiyel olarak yıkıcıdır "+". Örneğin, "a + b c" şu şekilde kodlanmalıdır "a+b%20c"; bu çözüm onu "a%20b%20c". Bunun yerine kullanın new URI(null, null, value, null).getRawPath().
Chris Nitchie

@ChrisNitchie Sorunun amacı bu değildi. Soru, "Java eşdeğer çıktıyı üreten JavaScript'in kodURIC bileşenine eşdeğer mi?" , "Generic Java encode-URI-component function" değil mi? .
Tomalak

118

Sonunda bulduğum sınıf bu:

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
Bir ipucu eklemek. Android 4.4'te ayrıca değiştirmemiz gerektiğini buldum, bu da %0AAndroid girişinde bir dönüş anahtarı anlamına geliyor, yoksa js'yi çökertecek.
Aloong


1
@Aloong Değiştir derken ne demek istiyorsun "%0A"? Yerine hangi karakter girecek? Sadece boş dizge ""mi?
HendraWD

15

Java 6 ile birlikte gelen javascript motorunu kullanarak:


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

Çıktı:% 22A% 22% 20B% 20% c2% b1% 20% 22

Durum farklı ama istediğine daha yakın.


Ah, özür dilerim ... Java 1.4'te Java 5'e geçiyorum sorusuna birazdan bahsetmeliydim!
John Topley

3
Tek çözüm javascript ise Rhino'yu deneyebilirsiniz, ancak bu küçük problem için çok fazla.
Ravi Wallau

3
Java 6 kullanıyor olsa bile, bu çözümün en üstte YOL olduğunu düşünüyorum. Javascript yöntemini doğrudan çağırmanın bir yolunu aradığını sanmıyorum, sadece onu taklit etmenin bir yolunu.
Outlaw Programmer

1
Olabilir. Sizin için işe yarayacak hiçbir şey bulamazsanız, en kolay çözümün kendi kaçış fonksiyonunuzu yazmak olacağını düşünüyorum. StringEscapeUtils sınıfından (Jakarta Commons Lang) bazı yöntemleri kopyalayın ve ihtiyaçlarınıza göre yeniden uygulayın.
Ravi Wallau

2
Bu gerçekten işe yarıyor ve eğer performans konusunda endişelenmiyorsan ... Bence iyi.
2rs2ts

8

Kullanıyorum java.net.URI#getRawPath(), örneğin

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

Değeri fixedolacak a+b%20c.htmlne istediğinizi olan.

URLEncoder.encode()Çıktısının sonradan işlenmesi, URI'de olması gereken tüm artıları ortadan kaldıracaktır . Örneğin

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

verecek a%20b%20c.htmlşekilde yorumlanır hangi a b c.html.


Bunun en iyi cevap olması gerektiğini düşündükten sonra, birkaç dosya adıyla pratikte denedim ve biri kiril karakterli en az ikisinde başarısız oldu. Yani hayır, belli ki bu yeterince iyi test edilmedi.
AsGoodAsItGets

şu gibi dizeler için çalışmaz: http://a+b c.htmlbir hata atar
balazs

5

EncodeURIComponent'in kendi versiyonunu buldum, çünkü yayınlanan çözümün bir sorunu var, eğer String'de kodlanması gereken bir + varsa, bir boşluğa dönüştürülecek.

İşte benim sınıfım:

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

İyi bir çözüm için teşekkürler! Diğerleri tamamen ... verimsiz görünüyor, IMO. Belki de bugünün donanımında BitSet olmadan daha da iyi olurdu. Veya 0 ... 127 için iki sabit kodlu uzun.
Jonas N

URLEncoder.encode("+", "UTF-8");verimleri "%2B"çözüm yüzden, uygun URL kodlama, özür dilerim, tamamen gereksiz. Neden yeryüzünde URLEncoder.encodeboşluklar açılmıyor %20beni aşıyor.
2rs2ts


1

Java.net.URI sınıfını şu şekilde başarıyla kullandım:

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

Hayır, bu yaklaşım tam anlamıyla başarılı değil, ancak nispeten sorun yok. Yine de sorunlarınız var. Örneğin, kardinal karakter # java% 23 olarak kodlayacak javascript onu kodlamayacaktır. Bakınız: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Javascript espace değil. AZ az 0-9; , /? : @ & = + $ - _. ! ~ * '() # Ve bunlardan bazıları için java uzayacak.
99Sono

Şu ifade ile bir UNIT testi yapmak iyi bir şey: '' 'Dize karakterleriJavascriptDoesNotEspace = "A-Za-z0-9;, /?: @ & = + $ -_.! ~ *' () #"; kardinal tek aykırı değerdir. Dolayısıyla, yukarıdaki algoritmayı javascript ile uyumlu hale getirmek için düzeltmek önemsizdir.
99Sono

1

Bu, Ravi Wallau'nun çözümüne açık bir örnektir:

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

Çıktı: https://www.website.com/document/Tom%20%26%20Jerry%20Manuscript.pdf

Aynı zamanda Loren Shqipognja'nın bir String değişkeninin nasıl geçirileceğine dair yorumlarındaki asılı soruyu da yanıtlıyor encodeURIComponent(). Yöntem bir scriptEngine.eval()döndürür Object, bu nedenle String.valueOf()diğer yöntemler arasında String'e dönüştürülebilir .


1

benim için bu işe yaradı:

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

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

veya farklı bir UriBuilder ile

import javax.ws.rs.core.UriBuilder;

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

Kanımca, manuel olarak sonradan işlem yapmak yerine standart bir kitaplık kullanmak daha iyi bir fikirdir. Ayrıca @Chris yanıtı iyi görünüyordu, ancak " http: // a + b c.html" gibi url'ler için çalışmıyor


1
Standart kitaplığı kullanmak iyidir ... ... orta düzey yazılım değilseniz ve standart bir kitaplığın farklı bir sürümüne bağlı değilseniz ve sonra kodunuzu kullanan herkes bağımlılıklarla uğraşmak zorunda kalır ve sonra hiçbir şeyin bozulmamasını umar ...
Ajax

Bu çözüm işe yararsa harika olur, ancak istekle aynı şekilde davranmaz encodeURIComponent. sonuç encodeURIComponentiçin döner , ancak öneriniz geri döner . Bundan başka soru ve cevaplarda birçok kez bahsedildiğini biliyorum, ancak insanlar körü körüne güvenmeden önce burada belirtilmelidir. ?& %3F%26%20%3F%26+
Philipp

1

Kullandığım şey bu:

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

RFC 3986'ya göre, ayrılmamış karakter olmayan her karakteri yüzde kodlayarak Javascript'in ötesine geçer .


Bu karşıt dönüşümdür:

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

Guava kitaplığında PercentEscaper vardır:

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

"-_. *" güvenli karakterlerdir

false, PercentEscaper'ın boşluktan '+' değil '% 20' ile kaçacağını söylüyor


0

Kullandığım String encodedUrl = new URI(null, url, null).toASCIIString(); kodlamak URL'ler için. Kullandığımdaki mevcut parametrelerden sonra parametre eklemek urliçinUriComponentsBuilder

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.