web-dev-qa-db-fra.com

Codage d'adresse URL HTTP dans Java

Mon application autonome Java obtient une URL (qui pointe vers un fichier) de l'utilisateur et je dois la cliquer puis la télécharger. Le problème auquel je suis confronté est que je ne parviens pas à coder correctement l'adresse URL HTTP ...

Exemple:

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

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

me retourne:

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

Mais ce que je veux c'est

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

(espace remplacé par% 20)

J'imagine que URLEncoder n'est pas conçu pour coder des URL HTTP ... Le JavaDoc indique "Classe d'utilitaire pour le codage de formulaire HTML" ... Existe-t-il un autre moyen de procéder?

354
Sudhakar R

La classe Java.net.URI peut vous aider. dans la documentation de l'URL que vous trouvez

Notez que la classe URI échappe à ses champs de composant dans certaines circonstances. La méthode recommandée pour gérer le codage et le décodage des URL consiste à utiliser un URI.

Utilisez l'un des constructeurs avec plus d'un argument, comme:

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

(le constructeur d'argument unique d'URI n'échappe PAS aux caractères non autorisés)


Seuls les caractères illégaux sont échappés par le code ci-dessus - cela n'échappe PAS aux caractères non-ASCII (voir le commentaire de fatih).
La méthode toASCIIString peut être utilisée pour obtenir une chaîne uniquement avec des caractères US-ASCII:

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

Pour une URL avec une requête comme http://www.google.com/ig/api?weather=São Paulo, utilisez la version à 5 paramètres du constructeur:

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

Veuillez noter que la plupart des réponses ci-dessus sont INCORRECTES.

La classe URLEncoder, malgré son nom, n'est PAS ce qui doit être ici. Il est regrettable que Sun ait nommé cette classe si ennuyeuse. URLEncoder sert à transmettre des données en tant que paramètres et non à coder l'URL elle-même.

En d'autres termes, "http://search.barnesandnoble.com/booksearch/first book.pdf" est l'URL. Les paramètres seraient, par exemple, "http://search.barnesandnoble.com/booksearch/first book.pdf?parameter1=this&param2=that". Les paramètres sont ceux pour lesquels vous utiliseriez URLEncoder.

Les deux exemples suivants mettent en évidence les différences entre les deux.

Ce qui suit produit les mauvais paramètres, conformément à la norme HTTP. Notez que l'esperluette (&) et plus (+) sont codés de manière incorrecte.

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:)

Ce qui suit produira les paramètres corrects, avec la requête correctement codée. Notez les espaces, les esperluettes et les marques plus.

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

Je vais ajouter une suggestion ici destinée à Android utilisateurs. Vous pouvez faire cela, ce qui évite d'avoir à avoir des bibliothèques externes. En outre, toutes les solutions de recherche/remplacement de caractères suggérées dans certaines des réponses ci-dessus sont périlleuses et doivent être évitées.

Essayez ceci:

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

Vous pouvez voir que dans cette URL particulière, je dois coder ces espaces pour pouvoir l'utiliser pour une requête.

Cela tire parti de quelques fonctionnalités disponibles dans les classes Android. Premièrement, la classe d'URL peut fractionner une URL en ses propres composants, vous évitant ainsi de devoir effectuer un travail de recherche/remplacement de chaîne. Deuxièmement, cette approche tire parti de la fonctionnalité de classe d'URI consistant à échapper correctement les composants lorsque vous construisez un URI via des composants plutôt qu'à partir d'une seule chaîne.

La beauté de cette approche réside dans le fait que vous pouvez utiliser n'importe quelle chaîne d'URL valide et la faire fonctionner sans aucune connaissance particulière de celle-ci.

76
Craig B

une solution que j'ai développée et beaucoup plus stable que toute autre:

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

Si vous avez une URL, vous pouvez passer url.toString () dans cette méthode. Commencez par décoder pour éviter le double codage (par exemple, coder un espace donne% 20 et coder un signe de pourcentage donne% 25, ​​de sorte que le double codage transformera un espace en% 2520). Ensuite, utilisez l'URI comme expliqué ci-dessus, en ajoutant toutes les parties de l'URL (pour ne pas supprimer les paramètres de la requête).

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

Oui, le codage d'URL va coder cette chaîne afin qu'elle soit correctement passée dans une URL à une destination finale. Par exemple, vous ne pouvez pas avoir http://stackoverflow.com?url=http://yyy.com . UrlEncoding le paramètre permettrait de corriger cette valeur de paramètre.

Donc j'ai deux choix pour vous:

  1. Avez-vous accès au chemin distinct du domaine? Si c'est le cas, vous pourrez peut-être simplement UrlEncode le chemin. Toutefois, si ce n'est pas le cas, l'option 2 peut être pour vous.

  2. Obtenez commons-httpclient-3.1. Cela a une classe URIUtil:

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

Cela produira exactement ce que vous recherchez, car il ne codera que la partie chemin de l'URI.

Pour votre information, vous aurez besoin de commons-codec et commons-logging pour que cette méthode fonctionne au moment de l'exécution.

26
Nathan Feger

Nitpicking: une chaîne contenant par définition un caractère d'espacement n'est pas un URI. Vous recherchez donc un code implémentant l'échappement d'URI défini dans Section 2.1 de la RFC 3986 .

11
Julian Reschke

Malheureusement, org.Apache.commons.httpclient.util.URIUtil est obsolète et le replacement org.Apache.commons.codec.net.URLCodec convient au codage pour les publications de formulaire et non pour les URL réelles. Je devais donc écrire ma propre fonction, qui ne fait qu'un seul composant (ne convient pas pour les chaînes de requête entières qui ont? Et '& 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);
  }
}
11
Jeff Tsay

Si quelqu'un ne veut pas ajouter de dépendance à son projet, ces fonctions peuvent être utiles.

Nous passons la partie "chemin" de notre URL dans ici. Vous ne voulez probablement pas transmettre l'URL complète en tant que paramètre (les chaînes de requête nécessitent des échappées différentes, etc.).

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

Et des tests:

@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

Il y a toujours un problème si vous avez un "/" codé (% 2F) dans votre URL.

RFC 3986 - Section 2.2 dit: "Si les données d'un composant URI sont en conflit avec le but d'un caractère réservé en tant que délimiteur, les données en conflit doivent être codées en pourcentage avant que l'URI ne soit formé." (RFC 3986 - Section 2.2)

Mais il y a un problème avec Tomcat:

http://Tomcat.Apache.org/security-6.html - Corrigé dans Apache Tomcat 6.0.10

important: Traversée de répertoire CVE-2007-0450

Tomcat autorise les balises '\', '% 2F' et '% 5C' [...].

Les propriétés système Java suivantes ont été ajoutées à Tomcat pour fournir un contrôle supplémentaire sur la gestion des délimiteurs de chemin d'accès dans les URL (les deux options sont définies par défaut sur false):

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

En raison de l'impossibilité de garantir que toutes les URL sont gérées par Tomcat comme dans les serveurs proxy, Tomcat doit toujours être sécurisé comme si aucun accès au contexte de restriction de proxy n'était utilisé.

Affecte: 6.0.0-6.0.9

Donc, si vous avez une URL avec le caractère% 2F, Tomcat renvoie: "400 Invalid URI: noSlash".

Vous pouvez activer le correctif dans le script de démarrage Tomcat:

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

URLEncoding peut très bien encoder les URL HTTP, comme vous l'avez malheureusement découvert. La chaîne que vous avez transmise, " http://search.barnesandnoble.com/booksearch/first book.pdf", était correctement et complètement codée dans un formulaire codé en URL. Vous pouvez passer toute la longue chaîne de gobbledigook que vous avez récupérée en tant que paramètre dans une URL et vous pouvez la décoder à nouveau dans la chaîne que vous avez transmise.

Il semble que vous souhaitiez faire quelque chose d'un peu différent que de transmettre l'URL entière en tant que paramètre. D'après ce que je comprends, vous essayez de créer une URL de recherche qui ressemble à " http://search.barnesandnoble.com/booksearch/wwhatTheUserPassesIn ". La seule chose que vous devez encoder est le bit "whatTheUUserPassesIn", alors peut-être que tout ce que vous avez à faire est quelque chose comme ceci:

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

Cela devrait produire quelque chose d'un peu plus valable pour vous.

7

J'ai lu les réponses précédentes pour écrire ma propre méthode car je ne pouvais pas faire fonctionner correctement quelque chose en utilisant la solution des réponses précédentes, cela me paraissait bien, mais si vous pouvez trouver une URL qui ne fonctionne pas avec ceci, merci de me le faire savoir.

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

Je suis d'accord avec Matt. En effet, je ne l’ai jamais bien expliquée dans les tutoriels, mais l’important est de savoir comment coder le chemin de l’URL, et très différent, comment coder les paramètres qui sont ajoutés à l’URL (la partie requête, derrière le "?" " symbole). Ils utilisent un encodage similaire, mais pas le même.

Spécialement pour l'encodage du caractère d'espace blanc. Le chemin de l'URL nécessite son codage en tant que% 20, alors que la partie requête autorise% 20 ainsi que le signe "+". La meilleure idée est de le tester nous-mêmes sur notre serveur Web, à l'aide d'un navigateur Web.

Dans les deux cas, I TOUJOURS encoderait COMPOSANT PAR COMPOSANT, jamais la chaîne entière. En effet, URLEncoder permet cela pour la partie requête. Pour la partie chemin, vous pouvez utiliser l'URI de classe, bien que dans ce cas, il demande la chaîne complète, pas un seul composant.

Quoi qu'il en soit, je pense que le meilleur moyen d'éviter ces problèmes est d'utiliser un modèle personnel non conflictuel. Comment? Par exemple, je ne nommerais jamais de répertoires ou de paramètres utilisant des caractères autres que a-Z, A-Z, 0-9 et _. De cette façon, le seul besoin est de coder la valeur de chaque paramètre, car elle peut provenir d’une entrée utilisateur et les caractères utilisés sont inconnus.

4
negora

Vous pouvez également utiliser GUAVA et chemin escaper: UrlEscapers.urlFragmentEscaper().escape(relativePath)

3
To Kra

Peut-être que peut essayer riUtils dans org.springframework.web.util

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

Outre la réponse de Carlos Heuberger: si un paramètre différent de celui par défaut (80) est requis, le constructeur à 7 paramètres doit être utilisé:

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

J'ai pris le contenu ci-dessus et l'ai modifié un peu. J'aime d'abord la logique positive, et je pensais qu'un HashSet pourrait offrir de meilleures performances que d'autres options, comme la recherche dans une chaîne. Bien que, je ne suis pas sûr que la peine de la sélection automatique en vaille la peine, mais si le compilateur optimise les caractères ASCII, le coût de la boxe sera faible.

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

Utilisez la solution standard Java suivante (transmet environ 100 des cas de test fournis par Web Plattform Tests ):

0. Test si l'URL est déjà codée .

1. Fractionner l'URL en éléments structurels. Utilisez Java.net.URL pour cela.

2. Encodez chaque pièce structurelle correctement!

3. Utilisez IDN.toASCII(putDomainNameHere) to Punycode pour encoder le nom de l'hôte!

4. Utilisez Java.net.URI.toASCIIString() pour coder en pourcentage, NFC codé en unicode le mieux serait NFKC!).

Trouvez plus ici: --- (https://stackoverflow.com/a/49796882/1485527

1
jschnasse

J'ai eu le même problème. Résolu ceci en utilisant:

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

Il code la chaîne mais ignore ":" et "/".

0
Richard R

J'ai créé un nouveau projet pour aider à construire des URL HTTP. La bibliothèque va automatiquement encoder les segments de chemin et les paramètres de requête par URL.

Vous pouvez afficher le code source et télécharger un fichier binaire à l’adresse suivante: https://github.com/Widen/urlbuilder

L'URL d'exemple dans cette question:

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

produit

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

0
Uriah Carpenter