Кодирование URL-адреса HTTP в Java


мое автономное приложение Java получает URL (который указывает на файл) от пользователя, и мне нужно нажать на него и загрузить его. Проблема, с которой я сталкиваюсь, заключается в том, что я не могу правильно кодировать URL-адрес HTTP...

пример:

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-адресов... JavaDoc говорит " класс утилиты для HTML кодирование формы"... Есть ли другой способ сделать это?

24 331

24 ответа:

класс java.net.URI может помочь; в документации URL вы найдете

обратите внимание, что класс URI выполняет экранирование своих полей компонентов в определенных обстоятельствах. Рекомендуемый способ управления кодированием и декодированием URL-адресов-использовать URI

использовать один из конструкторов с более чем одним аргументом, например:

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

(конструктор с одним аргументом URI не является незаконным символы)


только незаконные символы экранируются вышеуказанным кодом-он не экранирует символы, отличные от ASCII (см. комментарий Фатиха).
Элемент toASCIIString метод может быть использован для получения строки только с US-ASCII символов:

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

для URL-адреса с запросом типа http://www.google.com/ig/api?weather=São Paulo, используйте 5-параметрическую версию конструктора:

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

пожалуйста, имейте в виду, что большинство ответов выше неверны.

The URLEncoder класс, несмотря на это имя, это не то, что должно быть здесь. Жаль, что Солнце назвало этот класс так раздражающе. URLEncoder предназначен для передачи данных в качестве параметров, а не для кодирования самого URL-адреса.

другими словами, "http://search.barnesandnoble.com/booksearch/first book.pdf" - это URL. Параметры будут, например,"http://search.barnesandnoble.com/booksearch/first book.pdf?parameter1=this&param2=that". Параметры-это то, что вы бы использовали URLEncoder для.

следующие два примера подчеркивает различия между ними.

следующее производит неправильные параметры, согласно стандарту 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

Я собираюсь добавить одно предложение здесь, направленное на пользователей Android. Вы можете сделать это, что позволяет избежать необходимости получать какие-либо внешние библиотеки. Кроме того, все решения для поиска/замены символов, предложенные в некоторых ответах выше, являются опасными и их следует избегать.

дайте этому попытку:

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-адресе мне нужно закодировать эти пробелы, чтобы я мог использовать его для запроса.

Это использует в своих интересах пару функции, доступные для вас в классах для Андроид. Во-первых, класс URL может разбить url на соответствующие компоненты, поэтому вам не нужно выполнять поиск/замену строк. Во-вторых, этот подход использует функцию класса URI правильного экранирования компонентов при создании URI через компоненты, а не из одной строки.

красота этого подхода заключается в том, что вы можете взять любую допустимую строку url и заставить ее работать без каких-либо специальных знаний об этом себе.

решение я разработал и гораздо более стабильным, чем любой другой:

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

}

если у вас есть 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;
    }
}

да кодировка URL будет кодировать эту строку, чтобы она была правильно передана в url-адрес конечного пункта назначения. Например, вы не могли иметь http://stackoverflow.com?url=http://yyy.com. UrlEncoding параметр будет исправить это значение параметра.

У меня есть два варианта для вас:

  1. есть ли у вас доступ к пути отдельно от домена? Если это так, вы можете просто UrlEncode путь. Однако, если это не тогда Вариант 2 может быть для вас.

  2. получить commons-httpclient-3.1. Это имеет класс URIUtil:

    система.из.метод println(URIUtil.encodePath("http://example.com/x г", "ИСО-8859-1"));

это выведет именно то, что вы ищете, так как он будет кодировать только часть пути URI.

FYI, вам понадобится commons-codec и commons-logging для работы этого метода во время выполнения.

Nitpicking: строка, содержащая символ пробела по определению не является URI. Так что вы ищете код, который реализует URI escaping, определенный в раздел 2.1 RFC 3986.

к сожалению, 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);
  }
}

URLEncoding может кодировать http-адреса просто отлично, как вы, к сожалению, обнаружили. Строка, которую вы передали,"http://search.barnesandnoble.com/booksearch/first книга.pdf", был правильно и полностью закодирован в URL-кодированную форму. Вы можете передать всю эту длинную строку gobbledigook, которую вы вернули в качестве параметра в URL-адресе, и ее можно декодировать обратно в точно переданную строку.

похоже, вы хотите сделать что-то немного отличается от передачи всего URL-адреса в качестве параметра. Из того, что я понял, вы пытаетесь создать URL-адрес поиска, который выглядит как"http://search.barnesandnoble.com/booksearch/whateverTheUserPassesIn". единственное, что вам нужно кодировать, это бит" whateverTheUserPassesIn", поэтому, возможно, все, что вам нужно сделать, это что-то вроде этого:

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

Это должно привести к чему-то более значимому для вас.

все еще существует проблема, если у вас есть закодированный "/" (%2F) в URL-адресе.

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.апаш.кот.утиль.баф.UDecoder.ALLOW_ENCODED_SLASH: true / false
  • org.апаш.Каталина.соединитель.Койотадаптер.ALLOW_BACKSLASH: true / false

из-за невозможности гарантировать что все URL-адреса обрабатываются Tomcat как они находятся в прокси-серверах, Tomcat всегда должен быть обеспечен, как будто нет прокси ограничение доступа к контексту было используемый.

влияет: 6.0.0-6.0.9

поэтому, если у вас есть URL-адрес с символом %2F, Tomcat возвращает: "400 недопустимый URI: noSlash"

вы можете переключение исправления в скрипте запуска Tomcat:

set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG%   -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true 

Я прочитал предыдущие ответы, чтобы написать свой собственный метод, потому что я не мог иметь что-то правильно работает с помощью решения предыдущих ответов, это выглядит хорошо для меня, но если вы можете найти URL, который не работает с этим, пожалуйста, дайте мне знать.

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

Если кто-то не хочет добавлять зависимость к своему проекту, эти функции могут быть полезны.

мы передаем часть "путь" нашего 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")));

}

Я согласен с Мэттом. Действительно, я никогда не видел, чтобы это хорошо объяснялось в учебниках, но один вопрос заключается в том, как кодировать путь URL, а совсем другой-как кодировать параметры, которые добавляются к URL (часть запроса, за "?" символ.) Они используют подобную кодировку, но не то же самое.

специально для кодирования символа пробела. Путь URL-адреса должен быть закодирован как %20, тогда как часть запроса позволяет %20, а также знак"+". Лучшая идея проверьте это сами против нашего веб-сервера, используя веб-браузер.

в обоих случаях, я всегда будет кодировать КОМПОНЕНТ ЗА КОМПОНЕНТОМ, не всю строку. Действительно, средств кодирования позволяет, что для части запросов. Для части пути вы можете использовать класс URI, хотя в этом случае он запрашивает всю строку, а не один компонент.

во всяком случае, я верю, что лучший способ избежать этих проблем-использовать личный неконфликтный дизайн. как? Например, я никогда не буду называть каталоги или параметры, используя другие символы, кроме a-Z, A-Z, 0-9 и _ . Таким образом, необходимо только кодировать значение каждого параметра, так как оно может поступать из пользовательского ввода, а используемые символы неизвестны.

может, попробовать UriUtils в org.springframework.сеть.утиль

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

вы также можете использовать GUAVA и путь escaper: UrlEscapers.urlFragmentEscaper().escape(relativePath)

в дополнение к ответу Карлоса Хойбергера: если требуется что-то отличное от значения по умолчанию (80), следует использовать конструктор 7 param:

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

Я создал новый проект, чтобы помочь построить 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

У меня была та же проблема. Решил это путем unsing:

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

кодирует строку, но пропускает ":" и "/".

1. разделить строку URL на структурные части. Используйте java.net.URL для него.

2. кодировать каждую структурную часть правильно!

3. использовать IDN.toASCII(putDomainNameHere) до Punycode кодировать имя хоста!

4. использовать java.net.URI.toASCIIString() для процентного кодирования, NFC кодируется unicode- (лучше бы NFKC!). Для получения дополнительной информации см.:Как правильно закодировать этот URL

URL url= new URL("http://search.barnesandnoble.com/booksearch/first book.pdf);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
String correctEncodedURL=uri.toASCIIString(); 
System.out.println(correctEncodedURL);

печать

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

Я взял над содержимым и изменил его вокруг немного. Сначала мне нравится положительная логика, и я подумал, что хэш-набор может дать лучшую производительность, чем некоторые другие параметры, такие как поиск по строке. Хотя, я не уверен, что штраф автобоксинга стоит того, но если компилятор оптимизирует для символов ASCII, то стоимость бокса будет низкой.

/***
 * 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);
}

Я разрабатываю библиотеку, которая служит этой цели: galimatias. Он анализирует URL-адрес так же, как это делают веб-браузеры. То есть, если URL-адрес работает в браузере, он будет правильно обработан galimatias.

В этом случае:

// Parse
io.mola.galimatias.URL.parse(
    "http://search.barnesandnoble.com/booksearch/first book.pdf"
).toString()

даст вам: http://search.barnesandnoble.com/booksearch/first%20book.pdf. Конечно, это самый простой случай, но он будет работать с чем угодно, далеко за пределами java.net.URI.

вы можете проверить это по адресу:https://github.com/smola/galimatias

вы можете использовать такую функцию. Заполните и измените его в соответствии с вашими потребностями:

/**
     * Encode URL (except :, /, ?, &, =, ... characters)
     * @param url to encode
     * @param encodingCharset url encoding charset
     * @return encoded URL
     * @throws UnsupportedEncodingException
     */
    public static String encodeUrl (String url, String encodingCharset) throws UnsupportedEncodingException{
            return new URLCodec().encode(url, encodingCharset).replace("%3A", ":").replace("%2F", "/").replace("%3F", "?").replace("%3D", "=").replace("%26", "&");
    }

пример использования :

String urlToEncode = ""http://www.growup.com/folder/intérieur-à_vendre?o=4";
Utils.encodeUrl (urlToEncode , "UTF-8")

результат:http://www.growup.com/folder/int%C3%A9rieur-%C3%A0_vendre?o=4

строка url=""http://search.barnesandnoble.com/booksearch/;

Это будет постоянным я думаю, и только имя файла изменяется dyamically так получить имя файла

строковое имя файла; // получить имя файла

строка urlEnc=url+имя файла.заменить(" ","%20");

Как насчет:

public String UrlEncode (String in_) {

String retVal = "";

try {
    retVal = URLEncoder.encode(in_, "UTF8");
} catch (UnsupportedEncodingException ex) {
    Log.get().exception(Log.Level.Error, "urlEncode ", ex);
}

return retVal;

}