Java tương đương với mã hóa của JavaScript


92

Tôi đã thử nghiệm với nhiều đoạn mã Java khác nhau để cố gắng tìm ra thứ gì đó sẽ mã hóa một chuỗi chứa dấu ngoặc kép, dấu cách và các ký tự Unicode "kỳ lạ" và tạo ra đầu ra giống với hàm encodeURIComponent của JavaScript .

Chuỗi kiểm tra tra tấn của tôi là: "A" B ± "

Nếu tôi nhập câu lệnh JavaScript sau vào Firebug:

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

—Sau đó tôi nhận được:

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

Đây là chương trình Java thử nghiệm nhỏ của tôi:

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

—Chương trình này xuất ra:

URLEncoder.encode trả về% 22A% 22 + B +% C2% B1 +% 22
getBytes trả về "A" B ± "

Đóng, nhưng không có xì gà! Cách tốt nhất để mã hóa chuỗi UTF-8 bằng Java để nó tạo ra đầu ra giống như JavaScript là encodeURIComponentgì?

CHỈNH SỬA: Tôi đang sử dụng Java 1.4 sẽ sớm chuyển sang Java 5.

Câu trả lời:


63

Nhìn vào sự khác biệt về triển khai, tôi thấy rằng:

MDC trênencodeURIComponent() :

  • ký tự chữ (biểu diễn regex): [-a-zA-Z0-9._*~'()!]

Tài liệu Java 1.5.0 vềURLEncoder :

  • ký tự chữ (biểu diễn regex): [-a-zA-Z0-9._*]
  • ký tự khoảng trắng " "được chuyển đổi thành một dấu cộng "+".

Vì vậy, về cơ bản, để có được kết quả mong muốn, hãy sử dụng URLEncoder.encode(s, "UTF-8")và sau đó thực hiện một số xử lý hậu kỳ:

  • thay thế tất cả các lần xuất hiện "+"bằng"%20"
  • thay thế tất cả các lần xuất hiện "%xx"đại diện cho bất kỳ phần nào [~'()!]trở lại các bộ phận theo nghĩa đen của chúng

Tôi ước gì bạn đã viết "Thay thế tất cả các lần xuất hiện của"% xx "đại diện cho bất kỳ [~ '()!] Trở lại các phần đối nghĩa theo nghĩa đen của chúng" bằng một số ngôn ngữ đơn giản. :( cái đầu nhỏ xíu của tôi không thể hiểu được nó .......
Shailendra Singh Rajawat

1
@Shailendra [~'()!]có nghĩa là "~"hoặc "'"hoặc "("hoặc ")"hoặc "!". :) Mặc dù vậy, tôi cũng khuyên bạn nên học những điều cơ bản về regex. (Tôi cũng không mở rộng về điều đó vì ít nhất hai câu trả lời khác hiển thị mã Java tương ứng.)
Tomalak

3
Việc thay thế tất cả các lần xuất hiện "+"bằng "%20"có khả năng phá hủy, cũng như "+"một ký tự hợp pháp trong các đường dẫn URI (mặc dù không có trong chuỗi truy vấn). Ví dụ: "a + b c" nên được mã hóa thành "a+b%20c"; giải pháp này sẽ chuyển đổi nó thành "a%20b%20c". Thay vào đó, hãy sử dụng new URI(null, null, value, null).getRawPath().
Chris Nitchie

@ChrisNitchie Đó không phải là mấu chốt của câu hỏi. Câu hỏi là "Java tương đương với mã hóaURIComponent của JavaScript tạo ra đầu ra giống hệt nhau?" , không phải "Hàm thành phần mã hóa-URI chung của Java?" .
Tomalak

117

Cuối cùng thì đây là lớp tôi đã nghĩ ra:

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
Thêm một mẹo. Trong Android 4.4, tôi thấy rằng chúng ta cũng cần phải thay thế %0Acó nghĩa là một phím quay lại trong đầu vào Android, nếu không nó sẽ làm hỏng js.
Aloong

Bạn có trang trải mọi thứ tại đây: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
kamaci

1
@Aloong Bạn có nghĩa là gì khi thay thế "%0A"? Nhân vật nào sẽ được thay thế? Nó chỉ là chuỗi rỗng ""?
HendraWD

15

Sử dụng công cụ javascript được vận chuyển với 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 ± \"'))");
    }
}

Sản lượng:% 22A% 22% 20B% 20% c2% b1% 20% 22

Trường hợp khác nhau nhưng nó gần với những gì bạn muốn.


À, xin lỗi ... đáng lẽ tôi phải đề cập trong câu hỏi rằng tôi đang sử dụng Java 1.4 để chuyển sang Java 5 trong thời gian ngắn!
John Topley ngày

3
Nếu javascript là giải pháp duy nhất bạn có thể thử Rhino, nhưng nó quá nhiều chỉ cho vấn đề nhỏ này.
Ravi Wallau ngày

3
Ngay cả khi anh ấy đang sử dụng Java 6, tôi nghĩ giải pháp này là CÁCH vượt trội. Tôi không nghĩ anh ấy đang tìm cách gọi trực tiếp phương thức javascript, chỉ là một cách để mô phỏng nó.
Lập trình viên ngoài vòng pháp luật ngày

1
Có lẽ. Tôi nghĩ giải pháp đơn giản nhất là viết hàm thoát của riêng bạn nếu bạn không thể tìm thấy bất kỳ thứ gì phù hợp với bạn. Chỉ cần sao chép một số phương thức từ lớp StringEscapeUtils (Jakarta Commons Lang) và thực hiện lại nó theo nhu cầu của bạn.
Ravi Wallau

2
Điều này thực sự hiệu quả, và nếu bạn không lo lắng về hiệu suất ... tôi nghĩ nó tốt.
2rs2ts

8

Tôi sử dụng java.net.URI#getRawPath(), ví dụ

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

Giá trị của fixedý chí a+b%20c.htmllà những gì bạn muốn.

Hậu xử lý đầu ra của URLEncoder.encode()sẽ xóa bất kỳ điểm cộng nào được cho là có trong URI. Ví dụ

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

sẽ cung cấp cho bạn a%20b%20c.html, sẽ được hiểu là a b c.html.


Sau khi nghĩ rằng đây là câu trả lời tốt nhất, tôi đã thử nó trên thực tế với một vài tên tệp và nó không thành công trong ít nhất hai, một có ký tự cyrillic. Vì vậy, không, điều này rõ ràng là chưa được kiểm tra đủ tốt.
AsGoodAsItGets

không hoạt động đối với các chuỗi như http://a+b c.html
:,

5

Tôi đã nghĩ ra phiên bản encodeURIComponent của riêng mình, vì giải pháp đã đăng có một vấn đề, nếu có dấu + trong Chuỗi, cần được mã hóa, nó sẽ được chuyển đổi thành khoảng trắng.

Đây là lớp học của tôi:

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

Cảm ơn vì một giải pháp tốt! Những cái khác trông hoàn toàn ... không hiệu quả, IMO. Có lẽ sẽ tốt hơn nếu không có BitSet trên phần cứng ngày nay. Hoặc hai long được mã hóa cứng cho 0 ... 127.
Jonas N

URLEncoder.encode("+", "UTF-8");kết quả "%2B", đó là mã hóa URL thích hợp, vì vậy, giải pháp của bạn là, tôi xin lỗi, hoàn toàn không cần thiết. Tại sao trên trái đất URLEncoder.encodekhông biến không gian thành %20ngoài tôi.
2rs2ts


1

Tôi đã sử dụng thành công lớp java.net.URI như vậy:

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

Không, cách tiếp cận này không hoàn toàn thành công, nhưng nó tương đối ổn. Bạn vẫn có vấn đề. Ví dụ: ký tự chính # java sẽ mã hóa thành% 23 javascript sẽ không mã hóa nó. Xem: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Javascript không espace. AZ az 0-9; , /? : @ & = + $ - _. ! ~ * '() # Và đối với một số java này sẽ xóa dấu cách.
99Sono

Điều tốt là thực hiện kiểm tra UNIT với biểu thức sau: '' 'Chuỗi ký tựJavascriptDoesNotEspace = "A-Za-z0-9;, /?: @ & = + $ -_.! ~ *' () #"; '' hồng y là người ngoại lệ duy nhất. Vì vậy, việc sửa chữa thuật toán ở trên để làm cho nó tương thích với javascript là chuyện nhỏ.
99Sono

1

Đây là một ví dụ đơn giản về giải pháp của 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();
    }
}

Đầu ra: https://www.website.com/document/Tom%20%26%20Jerry%20Manuscript.pdf

Nó cũng trả lời câu hỏi treo trong phần bình luận của Loren Shqipognja về cách chuyển một biến Chuỗi sang encodeURIComponent(). Phương thức scriptEngine.eval()trả về một Object, vì vậy nó có thể được chuyển đổi thành Chuỗi thông qua String.valueOf()giữa các phương thức khác.


1

đối với tôi điều này đã hoạt động:

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

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

hoặc với một UriBuilder khác

import javax.ws.rs.core.UriBuilder;

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

Theo tôi, sử dụng một thư viện tiêu chuẩn là một ý tưởng tốt hơn là xử lý bài viết theo cách thủ công. Ngoài ra, câu trả lời của @Chris có vẻ tốt, nhưng nó không hoạt động đối với các url, như " http: // a + b c.html"


1
Sử dụng thư viện tiêu chuẩn là tốt ... ... trừ khi bạn là người trung gian và phụ thuộc vào một phiên bản khác của thư viện tiêu chuẩn, và sau đó bất kỳ ai sử dụng mã của bạn phải loay hoay với các phụ thuộc và sau đó hy vọng không có gì bị hỏng ...
Ajax

Sẽ rất tuyệt nếu giải pháp này hoạt động, nhưng nó không hoạt động giống như yêu cầu encodeURIComponent. encodeURIComponenttrả về ?& kết quả %3F%26%20, nhưng đề xuất của bạn trả về %3F%26+. Tôi biết điều này được đề cập nhiều lần trong các câu hỏi và câu trả lời khác, nhưng nên được đề cập ở đây, trước khi mọi người tin tưởng một cách mù quáng.
Philipp

1

Đây là những gì tôi đang sử dụng:

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

Nó vượt xa Javascript bằng cách mã hóa phần trăm mọi ký tự không phải là ký tự chưa được lưu trữ theo RFC 3986 .


Đây là chuyển đổi hỗn hợp:

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

Thư viện ổi có PercentEscaper:

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

"-_. *" là các ký tự an toàn

false nói PercentEscaper để thoát không gian bằng '% 20', không phải '+'


0

Tôi đã sử dụng String encodedUrl = new URI(null, url, null).toASCIIString(); để mã hóa url. Để thêm các tham số sau những tham số hiện có trong urltôi sử dụngUriComponentsBuilder

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.