web-dev-qa-db-ja.com

JavaでのHTTP URLアドレスのエンコード

私のJavaスタンドアローンアプリケーションはユーザからURL(ファイルを指す)を取得します、そしてそれをヒットしてダウンロードする必要があります。私が直面している問題は、HTTP URLアドレスを正しくエンコードできないことです。

例:

URL:  http://search.barnesandnoble.com/booksearch/first book.pdf

Java.net.URLEncoder.encode(url.toString(), "ISO-8859-1");

私を返します:

http%3A%2F%2Fsearch.barnesandnoble.com%2Fbooksearch%2Ffirst+book.pdf

しかし、私が欲しいのは

http://search.barnesandnoble.com/booksearch/first%20book.pdf

(スペースは%20に置き換えられました)

URLEncoderはHTTP URLをエンコードするようには設計されていないと思います。JavaDocには「HTMLフォームエンコード用のユーティリティクラス」と書かれています。

350
Sudhakar R

Java.net.URI クラスが役に立ちます。あなたが見つけるURLのドキュメンテーションで

URIクラスは特定の状況でそのコンポーネントフィールドのエスケープを実行することに注意してください。 URLのエンコードとデコードを管理するための推奨される方法は、URIを使用することです。

次のように、コンストラクタの1つを複数の引数で使用します。

URI uri = new URI(
    "http", 
    "search.barnesandnoble.com", 
    "/booksearch/first book.pdf",
    null);
URL url = uri.toURL();
//or String request = uri.toString();

(URIの単一引数コンストラクタは不正な文字をエスケープしません)


不正な文字だけが上記のコードによってエスケープされます - それは非ASCII文字をエスケープしません(fatihのコメントを参照)。
toASCIIStringメソッドはUS-ASCII文字のみを含む文字列を取得するために使用できます。

URI uri = new URI(
    "http", 
    "search.barnesandnoble.com", 
    "/booksearch/é",
    null);
String request = uri.toASCIIString();

http://www.google.com/ig/api?weather=São Pauloのようなクエリを含むURLの場合は、5パラメータバージョンのコンストラクタを使用します。

URI uri = new URI(
        "http", 
        "www.google.com", 
        "/ig/api",
        "weather=São Paulo",
        null);
String request = uri.toASCIIString();
289

上記の答えのほとんどは間違っていることに注意してください。

URLEncoderクラスは、名前にもかかわらず、ここにある必要はありません。 Sunがこのクラスに非常に迷惑な名前を付けたのは残念です。 URLEncoderは、URL自体をエンコードするためではなく、パラメータとしてデータを渡すためのものです。

言い換えれば、"http://search.barnesandnoble.com/booksearch/first book.pdf"はURLです。パラメータは、たとえば"http://search.barnesandnoble.com/booksearch/first book.pdf?parameter1=this&param2=that"になります。パラメータはURLEncoderを使用するものです。

次の2つの例は、2つの違いを強調しています。

以下はHTTP標準に従って間違ったパラメータを生成します。アンパサンド(&)とプラス(+)が間違ってエンコードされるように注意します。

uri = new URI("http", null, "www.google.com", 80, 
"/help/me/book name+me/", "MY CRZY QUERY! +&+ :)", null);

// URI: http://www.google.com:80/help/me/book%20name+me/?MY%20CRZY%20QUERY!%20+&+%20:)

以下は、クエリが正しくエンコードされた正しいパラメータを生成します。スペース、アンパーサンド、およびプラス記号に注意してください。

uri = new URI("http", null, "www.google.com", 80, "/help/me/book name+me/", URLEncoder.encode("MY CRZY QUERY! +&+ :)", "UTF-8"), null);

// URI: http://www.google.com:80/help/me/book%20name+me/?MY+CRZY+QUERY%2521+%252B%2526%252B+%253A%2529
85
Matt

ここでは、Androidユーザー向けの提案を1つ追加します。これを行うことで、外部ライブラリを入手する必要がなくなります。また、上記の回答のいくつかで提案されているすべての検索/置換文字の解決策は危険であり、避けるべきです。

これを試してみてください。

String urlStr = "http://abc.dev.domain.com/0007AC/ads/800x480 15sec h.264.mp4";
URL url = new URL(urlStr);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
url = uri.toURL();

この特定のURLでは、リクエストに使用できるようにスペースをエンコードする必要があります。

これは、Androidクラスで利用可能ないくつかの機能を利用します。 1つ目は、URLクラスがURLを適切なコンポーネントに分割できるため、文字列の検索や置換作業を行う必要がないということです。次に、このアプローチでは、単一の文字列からではなくコンポーネントを介してURIを構築するときに、コンポーネントを適切にエスケープするというURIクラス機能を利用します。

この方法の利点は、有効なURL文字列を取得して、それを特別な知識を必要とせずに機能させることができることです。

76
Craig B

私が開発したソリューションは他よりもずっと安定しています。

public class URLParamEncoder {

    public static String encode(String input) {
        StringBuilder resultStr = new StringBuilder();
        for (char ch : input.toCharArray()) {
            if (isUnsafe(ch)) {
                resultStr.append('%');
                resultStr.append(toHex(ch / 16));
                resultStr.append(toHex(ch % 16));
            } else {
                resultStr.append(ch);
            }
        }
        return resultStr.toString();
    }

    private static char toHex(int ch) {
        return (char) (ch < 10 ? '0' + ch : 'A' + ch - 10);
    }

    private static boolean isUnsafe(char ch) {
        if (ch > 128 || ch < 0)
            return true;
        return " %$&+,/:;=?@<>#%".indexOf(ch) >= 0;
    }

}
48
fmucar

URLがある場合は、このメソッドにurl.toString()を渡すことができます。最初のデコード。二重エンコードを避けるためです(例えば、スペースのエンコードは%20に、パーセント記号のエンコードは%25になります。したがって、ダブルエンコードはスペースを%2520に変換します)。次に、上で説明したようにURIを使用し、URLのすべての部分を追加します(クエリパラメータを削除しないようにします)。

public URL convertToURLEscapingIllegalCharacters(String string){
    try {
        String decodedURL = URLDecoder.decode(string, "UTF-8");
        URL url = new URL(decodedURL);
        URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef()); 
        return uri.toURL(); 
    } catch (Exception ex) {
        ex.printStackTrace();
        return null;
    }
}
35
Scott Izu

ええ、URLエンコーディングはその文字列をエンコードして、最終的な宛先へのURLで正しく渡されるようにします。たとえば、 http://stackoverflow.com?url=http://yyy.com を持つことはできませんでした。パラメータをUrlEncodingすると、そのパラメータ値が修正されます。

だから私はあなたのための2つの選択肢があります:

  1. ドメインとは別のパスにアクセスできますか?もしそうなら、あなたは単にパスをUrlEncodeすることができるかもしれません。ただし、そうでない場合は、オプション2が適しています。

  2. Commons-httpclient-3.1を入手してください。これはクラスURIUtilを持ちます。

    System.out.println(URIUtil.encodePath( " http://example.com/x y"、 "ISO-8859-1")) ;

これはURIのパス部分のみをエンコードするので、探しているものを正確に出力します。

参考までに、このメソッドが実行時に機能するにはcommons-codecとcommons-loggingが必要です。

26
Nathan Feger

Nitpicking:定義により空白文字を含む文字列はURIではありません。それで、あなたが探しているものは、RFC 3986の セクション2.1 で定義されたURIエスケープを実装するコードです。

11
Julian Reschke

残念ながら、org.Apache.commons.httpclient.util.URIUtilは非推奨であり、replacement org.Apache.commons.codec.net.URLCodecは実際のURLではなくフォーム投稿に適したコーディングを行います。それで、私は自分自身の関数を書かなければなりませんでした、それは単一のコンポーネントを使います(?と&を持つクエリ文字列全体には適していません)。

public static String encodeURLComponent(final String s)
{
  if (s == null)
  {
    return "";
  }

  final StringBuilder sb = new StringBuilder();

  try
  {
    for (int i = 0; i < s.length(); i++)
    {
      final char c = s.charAt(i);

      if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
          ((c >= '0') && (c <= '9')) ||
          (c == '-') ||  (c == '.')  || (c == '_') || (c == '~'))
      {
        sb.append(c);
      }
      else
      {
        final byte[] bytes = ("" + c).getBytes("UTF-8");

        for (byte b : bytes)
        {
          sb.append('%');

          int upper = (((int) b) >> 4) & 0xf;
          sb.append(Integer.toHexString(upper).toUpperCase(Locale.US));

          int lower = ((int) b) & 0xf;
          sb.append(Integer.toHexString(lower).toUpperCase(Locale.US));
        }
      }
    }

    return sb.toString();
  }
  catch (UnsupportedEncodingException uee)
  {
    throw new RuntimeException("UTF-8 unsupported!?", uee);
  }
}
11
Jeff Tsay

誰かが自分のプロジェクトに依存関係を追加したくない場合は、これらの関数が役に立つかもしれません。

URLの「パス」部分をここに渡します。あなたはおそらく完全なURLをパラメータとして渡したくないでしょう(クエリ文字列は異なるエスケープなどを必要とします)。

/**
 * Percent-encodes a string so it's suitable for use in a URL Path (not a query string / form encode, which uses + for spaces, etc)
 */
public static String percentEncode(String encodeMe) {
    if (encodeMe == null) {
        return "";
    }
    String encoded = encodeMe.replace("%", "%25");
    encoded = encoded.replace(" ", "%20");
    encoded = encoded.replace("!", "%21");
    encoded = encoded.replace("#", "%23");
    encoded = encoded.replace("$", "%24");
    encoded = encoded.replace("&", "%26");
    encoded = encoded.replace("'", "%27");
    encoded = encoded.replace("(", "%28");
    encoded = encoded.replace(")", "%29");
    encoded = encoded.replace("*", "%2A");
    encoded = encoded.replace("+", "%2B");
    encoded = encoded.replace(",", "%2C");
    encoded = encoded.replace("/", "%2F");
    encoded = encoded.replace(":", "%3A");
    encoded = encoded.replace(";", "%3B");
    encoded = encoded.replace("=", "%3D");
    encoded = encoded.replace("?", "%3F");
    encoded = encoded.replace("@", "%40");
    encoded = encoded.replace("[", "%5B");
    encoded = encoded.replace("]", "%5D");
    return encoded;
}

/**
 * Percent-decodes a string, such as used in a URL Path (not a query string / form encode, which uses + for spaces, etc)
 */
public static String percentDecode(String encodeMe) {
    if (encodeMe == null) {
        return "";
    }
    String decoded = encodeMe.replace("%21", "!");
    decoded = decoded.replace("%20", " ");
    decoded = decoded.replace("%23", "#");
    decoded = decoded.replace("%24", "$");
    decoded = decoded.replace("%26", "&");
    decoded = decoded.replace("%27", "'");
    decoded = decoded.replace("%28", "(");
    decoded = decoded.replace("%29", ")");
    decoded = decoded.replace("%2A", "*");
    decoded = decoded.replace("%2B", "+");
    decoded = decoded.replace("%2C", ",");
    decoded = decoded.replace("%2F", "/");
    decoded = decoded.replace("%3A", ":");
    decoded = decoded.replace("%3B", ";");
    decoded = decoded.replace("%3D", "=");
    decoded = decoded.replace("%3F", "?");
    decoded = decoded.replace("%40", "@");
    decoded = decoded.replace("%5B", "[");
    decoded = decoded.replace("%5D", "]");
    decoded = decoded.replace("%25", "%");
    return decoded;
}

そしてテスト:

@Test
public void testPercentEncode_Decode() {
    assertEquals("", percentDecode(percentEncode(null)));
    assertEquals("", percentDecode(percentEncode("")));

    assertEquals("!", percentDecode(percentEncode("!")));
    assertEquals("#", percentDecode(percentEncode("#")));
    assertEquals("$", percentDecode(percentEncode("$")));
    assertEquals("@", percentDecode(percentEncode("@")));
    assertEquals("&", percentDecode(percentEncode("&")));
    assertEquals("'", percentDecode(percentEncode("'")));
    assertEquals("(", percentDecode(percentEncode("(")));
    assertEquals(")", percentDecode(percentEncode(")")));
    assertEquals("*", percentDecode(percentEncode("*")));
    assertEquals("+", percentDecode(percentEncode("+")));
    assertEquals(",", percentDecode(percentEncode(",")));
    assertEquals("/", percentDecode(percentEncode("/")));
    assertEquals(":", percentDecode(percentEncode(":")));
    assertEquals(";", percentDecode(percentEncode(";")));

    assertEquals("=", percentDecode(percentEncode("=")));
    assertEquals("?", percentDecode(percentEncode("?")));
    assertEquals("@", percentDecode(percentEncode("@")));
    assertEquals("[", percentDecode(percentEncode("[")));
    assertEquals("]", percentDecode(percentEncode("]")));
    assertEquals(" ", percentDecode(percentEncode(" ")));

    // Get a little complex
    assertEquals("[]]", percentDecode(percentEncode("[]]")));
    assertEquals("a=d%*", percentDecode(percentEncode("a=d%*")));
    assertEquals(")  (", percentDecode(percentEncode(")  (")));
    assertEquals("%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25",
                    percentEncode("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %"));
    assertEquals("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %", percentDecode(
                    "%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25"));

    assertEquals("%23456", percentDecode(percentEncode("%23456")));

}
8
Cuga

URLにエンコードされた "/"(%2F)が含まれている場合は、まだ問題があります。

RFC 3986 - セクション2.2には、「URIコンポーネントのデータが予約文字の区切り文字としての目的と矛盾する場合は、URIが形成される前に矛盾するデータをパーセントエンコードする必要があります。」 (RFC 3986 - セクション2.2)

しかし、Tomcatには問題があります。

http://Tomcat.Apache.org/security-6.html - Apache Tomcat 6.0.10で修正済み

重要:ディレクトリトラバーサルCVE-2007-0450

Tomcatは '\'、 '%2F'、 '%5C'を許可します[...]。

次のJavaシステムプロパティがTomcatに追加され、URL内のパス区切り文字の処理をさらに制御できるようになりました(両方のオプションともデフォルトはfalseです)。

  • org.Apache.Tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH:true | false
  • org.Apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH:true | false

すべてのURLがプロキシサーバー内にあるのでTomcatによって処理されることを保証することは不可能であるため、Tomcatは常にコンテキストアクセスを制限するプロキシが使用されていないかのように保護されるべきです。

影響:6.0.0〜6.0.9

そのため、%2F文字を含むURLを取得した場合、Tomcatは「400無効なURI:noSlash」を返します。

Tomcat起動スクリプトでバグ修正を切り替えることができます。

set Java_OPTS=%Java_OPTS% %LOGGING_CONFIG%   -Dorg.Apache.Tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true 
7
simonox

残念ながら、URLEncodingはHTTP URLを正しくエンコードできます。あなたが渡した文字列 " http://search.barnesandnoble.com/booksearch/first book.pdf"は、正しく完全にエンコードされています。 URLエンコード形式あなたはURLのパラメータとして戻ってきたgobbledigookの長い文字列全体を渡すことができ、それはあなたが渡した文字列と全く同じものにデコードすることができます。

URL全体をパラメータとして渡すのとは少し違ったことをしたいようです。私が集めたものから、「 http://search.barnesandnoble.com/booksearch/whateverTheUserPassesIn 」のような検索URLを作成しようとしています。 。エンコードする必要があるのは "whateverTheUserPassesIn"ビットだけなので、おそらくあなたがする必要があるのは次のようなものです。

String url = "http://search.barnesandnoble.com/booksearch/" + 
       URLEncoder.encode(userInput,"UTF-8");

それはあなたにとってかなり有効な何かを生み出すはずです。

7

前の答えを読んで私自身の方法を書くことはできませんでした。前の答えの解決策を使って何かがうまく機能していなかった場合、それは私には良さそうです。

public static URL convertToURLEscapingIllegalCharacters(String toEscape) throws MalformedURLException, URISyntaxException {
            URL url = new URL(toEscape);
            URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
            //if a % is included in the toEscape string, it will be re-encoded to %25 and we don't want re-encoding, just encoding
            return new URL(uri.toString().replace("%25", "%"));
}
7
Emilien Brigand

私はマットに同意します。確かに、私はそれがチュートリアルでうまく説明されたことを見たことがありませんが、一つの問題はURLパスをエンコードする方法です、そして非常に異なるものはURLに追加されるパラメータをエンコードする方法です。 「シンボル)。それらは同じようなエンコーディングを使いますが、同じではありません。

空白文字のエンコーディング用です。 URLパスでは%20としてエンコードする必要がありますが、クエリ部分では%20と "+"記号も使用できます。最善のアイデアは、Webブラウザを使用して、Webサーバーに対して自分自身でテストすることです。

どちらの場合も、IALWAYSは、COMPONENT BY COMPONENTをエンコードします。文字列全体は決してエンコードされません。確かにURLEncoderはクエリ部分にそれを許可します。パス部分にはクラスURIを使用できますが、この場合は単一のコンポーネントではなく文字列全体を要求します。

とにかく、私はこれらの問題を避けるための最良の方法は個人的で矛盾しないデザインを使うことだと思います。どうやって?たとえば、a-Z、A-Z、0-9、および_以外の文字を使用してディレクトリやパラメータに名前を付けることは決してありません。そのように、それはユーザ入力から来るかもしれず、そして使用される文字は未知であるので、唯一の必要性はあらゆるパラメータの値を符号化することである。

4
negora

GUAVAとpath escaperを使うこともできます:UrlEscapers.urlFragmentEscaper().escape(relativePath)

3
To Kra

たぶんorg.springframework.web.utilで UriUtils を試すことができます

UriUtils.encodeUri(input, "UTF-8")
3
micahli123

Carlos Heubergerの返答に加えて、デフォルト(80)以外のものが必要な場合は、7パラメータのコンストラクタを使用する必要があります。

URI uri = new URI(
        "http",
        null, // this is for userInfo
        "www.google.com",
        8080, // port number as int
        "/ig/api",
        "weather=São Paulo",
        null);
String request = uri.toASCIIString();
2
Martin Dimitrov

私は上記の内容を取り、それを少し前後に変更しました。私は最初にポジティブロジックが好きです、そして私はHashSetがStringを通して検索するような他のいくつかのオプションより良いパフォーマンスを与えるかもしれないと思いました。ただし、自動ボクシングのペナルティがそれだけの価値があるかどうかはわかりませんが、コンパイラがASCII charsに最適化すると、ボクシングのコストは低くなります。

/***
 * Replaces any character not specifically unreserved to an equivalent 
 * percent sequence.
 * @param s
 * @return
 */
public static String encodeURIcomponent(String s)
{
    StringBuilder o = new StringBuilder();
    for (char ch : s.toCharArray()) {
        if (isSafe(ch)) {
            o.append(ch);
        }
        else {
            o.append('%');
            o.append(toHex(ch / 16));
            o.append(toHex(ch % 16));
        }
    }
    return o.toString();
}

private static char toHex(int ch)
{
    return (char)(ch < 10 ? '0' + ch : 'A' + ch - 10);
}

// https://tools.ietf.org/html/rfc3986#section-2.3
public static final HashSet<Character> UnreservedChars = new HashSet<Character>(Arrays.asList(
        'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
        'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
        '0','1','2','3','4','5','6','7','8','9',
        '-','_','.','~'));
public static boolean isSafe(char ch)
{
    return UnreservedChars.contains(ch);
}
2
ChrisG65

次の標準的なJavaソリューションを使用してください( Web Plattform Tests で提供されるテストケースを約100回通過します)。

0。URLがすでにエンコードされているかどうかをテストします

1。URLを構造部分に分割します。それにはJava.net.URLを使ってください。

2。各構造部分を正しくエンコードしてください。

3.ホスト名を Punycode でエンコードするには、IDN.toASCII(putDomainNameHere)を使用します。

4。パーセントエンコード、NFCエンコードされたUnicodeへのJava.net.URI.toASCIIString()の使用 - (より良いのはNFKCでしょう!).

ここでもっと見つける: https://stackoverflow.com/a/49796882/1485527

1
jschnasse

私は同じ問題を抱えていました。を使用してこれを解決しました:

Android.net.Uri.encode(urlString, ":/");

文字列をエンコードしますが、 ":"と "/"はスキップします。

0
Richard R

HTTP URLの構築を手助けする新しいプロジェクトを作成しました。ライブラリは自動的にパスセグメントとクエリパラメータをURLエンコードします。

あなたはソースを見て、 https://github.com/Widen/urlbuilder でバイナリをダウンロードすることができます。

この質問のURLの例:

new UrlBuilder("search.barnesandnoble.com", "booksearch/first book.pdf").toString()

作り出す

http://search.barnesandnoble.com/booksearch/first%20book.pdf

0
Uriah Carpenter