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

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

Ответ 1

Класс 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 (см. Комментарий fatih).
Метод 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();

Ответ 2

Пожалуйста, обратите внимание, что большинство ответов выше НЕПРАВИЛЬНЫ.

Класс 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.

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

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

Ответ 3

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

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

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

Ответ 4

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

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 " %$&+,/:;[email protected]<>#%".indexOf(ch) >= 0;
    }

}

Ответ 5

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

Ответ 6

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

Итак, у меня есть два варианта для вас:

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

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

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

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

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

Ответ 7

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

Ответ 8

К сожалению, org.apache.commons.httpclient.util.URIUtil устарел, а replacement org.apache.commons.codec.net.URLCodec делает кодирование подходящим для сообщений в форме, а не в фактических URL. Поэтому мне пришлось написать свою собственную функцию, которая делает один компонент (не подходит для целых строк запроса, которые имеют? И &) s)

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

Ответ 9

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

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

/**
 * Percent-encodes a string so it 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")));

}

Ответ 10

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

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

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

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

Ответ 11

По-прежнему существует проблема, если у вас есть закодированный "/" (% 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

important: Обход каталога CVE-2007-0450

Tomcat разрешает '\', '% 2F' и '% 5C' [...].

Следующие свойства системы Java были добавлены в Tomcat для дополнительный контроль над обработкой разделителей маршрутов в URL-адресах (оба варианта по умолчанию - false):

  • org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH: правда | ложь
  • org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH: правда | ложь

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

Влияет: 6.0.0-6.0.9

Итак, если у вас есть URL-адрес с символом% 2F, Tomcat возвращает: "400 Invalid URI: noSlash"

Вы можете включить исправление в Tomcat startup script:

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

Ответ 12

Я прочитал предыдущие ответы, чтобы написать свой собственный метод, потому что я не мог нормально работать, используя решение предыдущих ответов, он выглядит хорошо для меня, но если вы можете найти 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", "%"));
}

Ответ 13

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

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

В обоих случаях я ВСЕГДА будет кодировать COMPONENT BY COMPONENT, но не всю строку. Действительно, URLEncoder позволяет это для части запроса. Для части пути вы можете использовать URI класса, хотя в этом случае он запрашивает всю строку, а не один компонент.

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

Ответ 14

Возможно, вы можете попробовать UriUtils в org.springframework.web.util

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

Ответ 15

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

Ответ 16

В дополнение к ответу Карлоса Хаубергера: если требуется значение, отличное от значения по умолчанию (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();

Ответ 17

Используйте следующее стандартное решение Java (проходит около 100 тестовых случаев, предоставляемых Web Plattform Tests):

0. Проверьте, если URL уже закодирован.

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

2. Правильно закодируйте каждую деталь конструкции!

3. Используйте IDN.toASCII(putDomainNameHere) чтобы Punycode кодировал имя хоста!

4. Используйте java.net.URI.toASCIIString() для кодирования в процентах, кодированного в NFC юникода - (лучше было бы NFKC!).

Узнайте больше здесь: fooobar.com/questions/14909/...

Ответ 18

Я взял содержание выше и немного изменил его. Сначала мне нравится позитивная логика, и я подумал, что HashSet может дать лучшую производительность, чем некоторые другие параметры, такие как поиск по строке. Хотя я не уверен, стоит ли штраф за автобокс, но если компилятор оптимизирует 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);
}

Ответ 19

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

Ответ 20

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

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

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

Ответ 21

Я создаю библиотеку, которая служит этой цели: 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

Ответ 22

Вы можете использовать такую ​​функцию. Заполните и внесите необходимые изменения:

/**
     * 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

Ответ 23

String url = " http://search.barnesandnoble.com/booksearch/;

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

String имя_файла; // получить имя файла

String urlEnc = url + fileName.replace( "," %20");

Ответ 24

Как насчет:

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;

}