Análisis de cadenas de consulta en Android

Java EE tiene ServletRequest.getParameterValues ​​() .

En plataformas que no son EE, URL.getQuery () simplemente devuelve una cadena.

¿Cuál es la forma normal de analizar correctamente la cadena de consulta en una URL cuando no está en Java EE?


< rant >

Es popular en las respuestas para tratar de crear su propio analizador. Este es un proyecto de microencoding muy interesante y emocionante, pero no puedo decir que sea una buena idea 🙁

Los fragmentos de código a continuación son generalmente defectuosos o rotos, por cierto. Romperlos es un ejercicio interesante para el lector. Y a los piratas informáticos que atacan los sitios web que los utilizan .

El análisis de cadenas de consulta es un problema bien definido, pero leer las especificaciones y comprender los matices no es trivial. Es mucho mejor dejar que un codificador de biblioteca de plataforma haga el trabajo duro, ¡y arregle, por usted!

< / rant >

Desde Android M, las cosas se han vuelto más complicadas. La respuesta de android.net.URI .getQueryParameter () tiene un error que rompe espacios antes de JellyBean. Apache URLEncodedUtils.parse () funcionó, pero se desaprobó en L y se eliminó en M.

Entonces, la mejor respuesta ahora es UrlQuerySanitizer . Esto ha existido desde API nivel 1 y todavía existe. También te hace pensar en los problemas difíciles, como manejar personajes especiales o valores repetidos.

El código más simple es

 UrlQuerySanitizer.ValueSanitizer sanitizer = UrlQuerySanitizer.getAllButNullLegal(); // remember to decide if you want the first or last parameter with the same name // If you want the first call setPreferFirstRepeatedParameter(true); sanitizer.parseUrl(url); String value = sanitizer.getValue("paramname"); // get your value 

En Android:

 import android.net.Uri; [...] Uri uri=Uri.parse(url_string); uri.getQueryParameter("para1"); 

En Android, las bibliotecas de Apache proporcionan un analizador de consultas:

http://developer.android.com/reference/org/apache/http/client/utils/URLEncodedUtils.html y http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/ http / client / utils / URLEncodedUtils.html

Esta es la respuesta de BalusC , pero comstack y arroja resultados:

 public static Map> getUrlParameters(String url) throws UnsupportedEncodingException { Map> params = new HashMap>(); String[] urlParts = url.split("\\?"); if (urlParts.length > 1) { String query = urlParts[1]; for (String param : query.split("&")) { String pair[] = param.split("="); String key = URLDecoder.decode(pair[0], "UTF-8"); String value = ""; if (pair.length > 1) { value = URLDecoder.decode(pair[1], "UTF-8"); } List values = params.get(key); if (values == null) { values = new ArrayList(); params.put(key, values); } values.add(value); } } return params; } 

Si tiene librerías de embarcadero (servidor o cliente) en su ruta de clases, puede usar las clases de servicios de embarcadero (consulte javadoc ), por ejemplo:

 import org.eclipse.jetty.util.*; URL url = new URL("www.example.com/index.php?foo=bar&bla=blub"); MultiMap params = new MultiMap(); UrlEncoded.decodeTo(url.getQuery(), params, "UTF-8"); assert params.getString("foo").equals("bar"); assert params.getString("bla").equals("blub"); 

Si está utilizando Spring 3.1 o superior (yikes, esperaba que la asistencia fuera más allá), puede usar UriComponents y UriComponentsBuilder :

 UriComponents components = UriComponentsBuilder.fromUri(uri).build(); List myParam = components.getQueryParams().get("myParam"); 

components.getQueryParams() devuelve un MultiValueMap

Aquí hay algo más de documentación .

Para un servlet o una página JSP puede obtener pares clave / valor de consulta mediante el uso de request.getParameter (“paramname”)

 String name = request.getParameter("name"); 

Hay otras maneras de hacerlo, pero así es como lo hago en todos los servlets y páginas jsp que creo.

El análisis de la cadena de consulta es un poco más complicado de lo que parece, dependiendo de cuán indulgente quieras ser.

Primero, la cadena de consulta es ascii bytes. Lees en estos bytes de uno en uno y los conviertes en caracteres. Si el personaje es? o y luego señala el comienzo de un nombre de parámetro. Si el carácter es =, indica el inicio de un valor de parámetro. Si el carácter es%, indica el inicio de un byte codificado. Aquí es donde se pone difícil.

Cuando lee en% char, tiene que leer los siguientes dos bytes e interpretarlos como dígitos hexadecimales. Eso significa que los siguientes dos bytes serán 0-9, af o AF. Pega estos dos dígitos hexadecimales para obtener tu valor de byte. Pero recuerda, los bytes no son personajes . Debe saber qué encoding se utilizó para codificar los caracteres. El carácter é no codifica lo mismo en UTF-8 que en ISO-8859-1. En general, es imposible saber qué encoding se utilizó para un conjunto de caracteres dado. Siempre utilizo UTF-8 porque mi sitio web está configurado para servir siempre todo usando UTF-8, pero en la práctica no puede estar seguro. Algunos usuarios-agentes le dirán la encoding de caracteres en la solicitud; puede intentar leer eso si tiene una solicitud HTTP completa. Si solo tiene una url en aislamiento, buena suerte.

De todos modos, suponiendo que está utilizando UTF-8 u otra encoding de caracteres de múltiples bytes, ahora que ha descodificado un byte codificado, debe apartarlo hasta que capture el siguiente byte. Necesita todos los bytes codificados que están juntos porque no puede decodificar url correctamente un byte a la vez. Ponga a un lado todos los bytes que están juntos y decodifíquelos todos a la vez para reconstruir su carácter.

Además, se vuelve más divertido si quieres ser indulgente y dar cuenta de los agentes de usuario que destruyen las URL. Por ejemplo, algunos clientes de webmail codifican doblemente cosas. O duplica los caracteres de && (por ejemplo: http://yoursite.com/blah??p1==v1&&p2==v2 ). Si quieres tratar con elegancia esto, necesitarás agregar más lógica a tu analizador.

Solo como referencia, esto es con lo que terminé (basado en URLEncodedUtils, y devolviendo un Mapa).

caracteristicas:

  • acepta la parte de cadena de consulta de la url (puede usar request.getQueryString() )
  • una cadena de consulta vacía producirá un Map vacío
  • un parámetro sin un valor (? prueba) se asignará a una List vacía List

Código:

 public static Map> getParameterMapOfLists(String queryString) { Map> mapOfLists = new HashMap>(); if (queryString == null || queryString.length() == 0) { return mapOfLists; } List list = URLEncodedUtils.parse(URI.create("http://localhost/?" + queryString), "UTF-8"); for (NameValuePair pair : list) { List values = mapOfLists.get(pair.getName()); if (values == null) { values = new ArrayList(); mapOfLists.put(pair.getName(), values); } if (pair.getValue() != null) { values.add(pair.getValue()); } } return mapOfLists; } 

Un helper de compatibilidad (los valores se almacenan en una matriz String al igual que en ServletRequest.getParameterMap () ):

 public static Map getParameterMap(String queryString) { Map> mapOfLists = getParameterMapOfLists(queryString); Map mapOfArrays = new HashMap(); for (String key : mapOfLists.keySet()) { mapOfArrays.put(key, mapOfLists.get(key).toArray(new String[] {})); } return mapOfArrays; } 

En Android, traté de usar la respuesta de @diyism pero encontré el tema del personaje espacial planteado por @rpetrich, por ejemplo: llené un formulario donde username = "us+us" y password = "pw pw" haciendo que una cadena de URL se vea me gusta:

 http://somewhere?username=us%2Bus&password=pw+pw 

Sin embargo, el código @diyism devuelve "us+us" y "pw+pw" , es decir, no detecta el carácter de espacio. Si la URL se reescribió con %20 , se identifica el carácter de espacio:

 http://somewhere?username=us%2Bus&password=pw%20pw 

Esto lleva a la siguiente solución:

 Uri uri = Uri.parse(url_string.replace("+", "%20")); uri.getQueryParameter("para1"); 

Tengo métodos para lograr esto:

1) :

 public static String getQueryString(String url, String tag) { String[] params = url.split("&"); Map map = new HashMap(); for (String param : params) { String name = param.split("=")[0]; String value = param.split("=")[1]; map.put(name, value); } Set keys = map.keySet(); for (String key : keys) { if(key.equals(tag)){ return map.get(key); } System.out.println("Name=" + key); System.out.println("Value=" + map.get(key)); } return ""; } 

2) y la forma más fácil de hacerlo utilizando la clase Uri :

 public static String getQueryString(String url, String tag) { try { Uri uri=Uri.parse(url); return uri.getQueryParameter(tag); }catch(Exception e){ Log.e(TAG,"getQueryString() " + e.getMessage()); } return ""; } 

y este es un ejemplo de cómo usar cualquiera de los dos métodos:

 String url = "http://www.jorgesys.com/advertisements/publicidadmobile.htm?position=x46&site=reform&awidth=800&aheight=120"; String tagValue = getQueryString(url,"awidth"); 

el valor de tagValue es 800

Esto funciona para mí … No estoy seguro de por qué todos buscaban un Mapa, Lista> Todo lo que necesitaba era un simple mapa de valor de nombre.

Para mantener las cosas simples, utilicé la construcción en URI.getQuery ();

 public static Map getUrlParameters(URI uri) throws UnsupportedEncodingException { Map params = new HashMap(); for (String param : uri.getQuery().split("&")) { String pair[] = param.split("="); String key = URLDecoder.decode(pair[0], "UTF-8"); String value = ""; if (pair.length > 1) { value = URLDecoder.decode(pair[1], "UTF-8"); } params.put(new String(key), new String(value)); } return params; } 

En Android es simple como el siguiente código:

 UrlQuerySanitizer sanitzer = new UrlQuerySanitizer(url); String value = sanitzer.getValue("your_get_parameter"); 

Además, si no desea registrar cada clave de consulta esperada, utilice:

 sanitzer.setAllowUnregisteredPatwigters(true) 

Antes de llamar:

 sanitzer.parseUrl(yourUrl) 

En Android, puede usar el método estático Uri.parse de la clase android.net.Uri para hacer el trabajo pesado. Si está haciendo algo con URI e Intenciones, querrá usarlo de todos modos.

El Multimap de Guava es más adecuado para esto. Aquí hay una versión corta y limpia:

 Multimap getUrlParameters(String url) { try { Multimap ret = ArrayListMultimap.create(); for (NameValuePair param : URLEncodedUtils.parse(new URI(url), "UTF-8")) { ret.put(param.getName(), param.getValue()); } return ret; } catch (URISyntaxException e) { throw new RuntimeException(e); } } 

No creo que haya uno en JRE. Puede encontrar funciones similares en otros paquetes como Apache HttpClient. Si no usa ningún otro paquete, solo tiene que escribir el suyo. No es tan dificil. Esto es lo que uso,

 public class QueryString { private Map> parameters; public QueryString(String qs) { parameters = new TreeMap>(); // Parse query string String pairs[] = qs.split("&"); for (String pair : pairs) { String name; String value; int pos = pair.indexOf('='); // for "n=", the value is "", for "n", the value is null if (pos == -1) { name = pair; value = null; } else { try { name = URLDecoder.decode(pair.substring(0, pos), "UTF-8"); value = URLDecoder.decode(pair.substring(pos+1, pair.length()), "UTF-8"); } catch (UnsupportedEncodingException e) { // Not really possible, throw unchecked throw new IllegalStateException("No UTF-8"); } } List list = parameters.get(name); if (list == null) { list = new ArrayList(); parameters.put(name, list); } list.add(value); } } public String getParameter(String name) { List values = parameters.get(name); if (values == null) return null; if (values.size() == 0) return ""; return values.get(0); } public String[] getParameterValues(String name) { List values = parameters.get(name); if (values == null) return null; return (String[])values.toArray(new String[values.size()]); } public Enumeration getParameterNames() { return Collections.enumeration(parameters.keySet()); } public Map getParameterMap() { Map map = new TreeMap(); for (Map.Entry> entry : parameters.entrySet()) { List list = entry.getValue(); String[] values; if (list == null) values = null; else values = (String[]) list.toArray(new String[list.size()]); map.put(entry.getKey(), values); } return map; } } 

En base a la respuesta de BalusC, escribí un ejemplo-Java-Code:

  if (queryString != null) { final String[] arrParameters = queryString.split("&"); for (final String tempParameterString : arrParameters) { final String[] arrTempParameter = tempParameterString.split("="); if (arrTempParameter.length >= 2) { final String parameterKey = arrTempParameter[0]; final String parameterValue = arrTempParameter[1]; //do something with the parameters } } } 
 public static Map  parseQueryString (final URL url) throws UnsupportedEncodingException { final Map  qps = new TreeMap  (); final StringTokenizer pairs = new StringTokenizer (url.getQuery (), "&"); while (pairs.hasMoreTokens ()) { final String pair = pairs.nextToken (); final StringTokenizer parts = new StringTokenizer (pair, "="); final String name = URLDecoder.decode (parts.nextToken (), "ISO-8859-1"); final String value = URLDecoder.decode (parts.nextToken (), "ISO-8859-1"); qps.put (name, value); } return qps; } 

Utilice Apache HttpComponents y conéctelo con algún código de colección para acceder a los parámetros por valor: http://www.joelgerard.com/2012/09/14/parsing-query-strings-in-java-and-accessing-values-by -llave/

usando Guava:

 Multimap parseQueryString(String queryString, String encoding) { LinkedListMultimap result = LinkedListMultimap.create(); for(String entry : Splitter.on("&").omitEmptyStrings().split(queryString)) { String pair [] = entry.split("=", 2); try { result.put(URLDecoder.decode(pair[0], encoding), pair.length == 2 ? URLDecoder.decode(pair[1], encoding) : null); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } return result; } 

este método toma el uri y devuelve el mapa de par y valor par

  public static Map getQueryMap(String uri) { String queryParms[] = uri.split("\\?"); Map map = new HashMap<>();// if (queryParms == null || queryParms.length == 0) return map; String[] params = queryParms[1].split("&"); for (String param : params) { String name = param.split("=")[0]; String value = param.split("=")[1]; map.put(name, value); } return map; } 

Dice “Java” pero “no Java EE”. ¿Quiere decir que está utilizando JSP y / o servlets pero no una stack completa de Java EE? Si ese es el caso, entonces aún debe tener request.getParameter () disponible para usted.

Si quiere decir que está escribiendo Java pero no está escribiendo JSP ni servlets, o simplemente está utilizando Java como punto de referencia, pero está en otra plataforma que no tiene un análisis de parámetros integrado … Wow , eso suena como una pregunta improbable, pero si es así, el principio sería:

 xparm=0 word="" loop get next char if no char exit loop if char=='=' param_name[xparm]=word word="" else if char=='&' param_value[xparm]=word word="" xparm=xparm+1 else if char=='%' read next two chars word=word+interpret the chars as hex digits to make a byte else word=word+char 

(Podría escribir código Java, pero eso sería inútil, porque si tiene Java disponible, puede usar request.getParameters).