Todas las partes de una URL y qué puede contener cada una

Cada parte de una URL señalada sobre un ejemplo real, qué caracteres hay que codificar en porcentaje y dónde, cómo se comportan de verdad las cadenas de consulta y cómo detectar un enlace que miente sobre su host.

Una URL es una sola cadena que dice qué protocolo hablar, con qué máquina hablarlo y qué pedir. Leerla correctamente es sobre todo cuestión de saber dónde termina cada parte, porque los delimitadores son caracteres sueltos y gana el primero que aparece.

https://alex:s3cret@www.example.co.uk:8443/docs/intro?lang=en&page=2#notes
│       │           │                 │   │           │              └─ fragment
│       │           │                 │   │           └──────────────── query
│       │           │                 │   └──────────────────────────── path
│       │           │                 └──────────────────────────────── port
│       │           └────────────────────────────────────────────────── host
│       └────────────────────────────────────────────────────────────── userinfo
└────────────────────────────────────────────────────────────────────── scheme

Las partes

ParteDelimitada porPara qué sirveLlega al servidor
Esquematermina en el primer :Qué protocolo y qué reglas se aplican al restoDetermina la conexión
Userinfotras //, termina en @Credenciales, obsoletas desde hace mucho para http y httpsSolo si el cliente decide enviarlas
Hosttermina en :, /, ? o #Un nombre de dominio o dirección IP que resolver y a la que conectarseSí, en la cabecera Host
Puertotras :, termina en /, ? o #El puerto TCP, 80 por defecto para http y 443 para httpsSe usa para la conexión
Rutaempieza en /, termina en ? o #Qué recurso de ese hostSí, en la línea de petición
Consultaempieza en ?, termina en #Parámetros para ese recursoSí, en la línea de petición
Fragmentoempieza en #, llega hasta el finalUna posición dentro del recursoNo

El host y el puerto juntos forman la autoridad. El fragmento es la excepción: se elimina antes de construir la petición, así que un servidor nunca lo ve. Los navegadores lo usan para anclas y para el enrutado en el cliente, y algunos flujos de autenticación devuelven tokens en un fragmento a propósito, para que esos tokens no aparezcan nunca en un registro del servidor. Aun así queda en el historial del navegador, así que no es privado, solo no se envía.

Caracteres reservados y no reservados

La codificación porcentual escribe un byte como % más dos dígitos hexadecimales, usando los bytes UTF-8 del carácter. é se convierte en %C3%A9, dos bytes y dos escapes.

Cuatro caracteres son no reservados junto a las letras y los dígitos, y no hace falta codificarlos en ninguna parte: - . _ ~. Todo lo demás o es reservado, es decir, cumple una función estructural en algún punto de una URL, o tiene que codificarse. El conjunto reservado es : / ? # [ ] @ y ! $ & ' ( ) * + , ; =.

La palabra «reservado» no significa «prohibido siempre». Significa que el carácter es un delimitador en alguna parte, y que solo hay que codificarlo en las partes donde se leería como tal.

CarácterEn un segmento de rutaEn un valor de consulta
Espacio%20, siempre%20 o +
/%2F, o parte el segmentoVálido tal cual
?%3F, o la ruta acaba ahíVálido tal cual
#%23, siempre%23, siempre
& y =Válidos tal cual%26 y %3D, o el par se parte
+Válido, y significa un signo más%2B, o puede decodificarse como un espacio

Esa es la regla práctica: codifica el carácter que terminaría la parte en la que estás. Un espacio en una ruta tiene que ser %20, porque un espacio sin codificar termina la URL en la mayoría de analizadores, mientras que un espacio en un valor de consulta puede ser %20 o el antiguo +. Un codificador de URL es la forma fiable de hacerlo, porque codificar una URL entera y codificar un solo componente son operaciones distintas y casi siempre lo que hace falta es la segunda.

Las cadenas de consulta son una convención

La especificación solo dice que la consulta es todo lo que hay entre ? y #, tomado del conjunto de caracteres permitidos. La forma key=value&key=value viene de la codificación de formularios HTML, no del estándar de URI. Nada impide que un servidor interprete ?a:1;b:2 como le convenga.

Como la forma es una convención, las claves repetidas no tienen un significado definido y cada plataforma eligió su propia respuesta:

Comportamiento con ?id=1&id=2Dónde
Ambos valores, como listaparse_qs de Python, querystring de Node, Express
Solo el primer valorQuery().Get de Go, getParameter de Java
Solo el último valorPHP, Rails
Unidos en 1,2Colecciones de petición de ASP.NET

Los arrays heredan el mismo problema. ids=1&ids=2, ids[]=1&ids[]=2, ids[0]=1&ids[1]=2 e ids=1,2 están todos muy extendidos, y solo el servidor decide cuál entiende. La notación con corchetes hay que codificarla para que sea estrictamente válida (ids%5B%5D=1), aunque la mayoría de servidores aceptan los corchetes sin codificar. Elige la forma que documente el lado receptor y mantenla.

El signo más merece un aviso aparte. En datos application/x-www-form-urlencoded, es decir, un cuerpo de petición codificado como formulario, + significa un espacio. En una ruta de URL significa un más literal. En una cadena de consulta depende del analizador, y la mayoría de frameworks web aplican ahí la decodificación de formulario, así que + se convierte en un espacio sin avisar. Cualquier valor que pueda contener legítimamente un más, como la salida estándar de Base64 o un número de teléfono, tiene que enviarse como %2B.

Referencias relativas

Una referencia relativa se resuelve contra una URL base sustituyendo todo lo que hay después de la última / de la base. Esa última barra es toda la regla, y es la razón por la que una barra final importa.

BaseReferenciaResultado
https://ex.com/docs/introguidehttps://ex.com/docs/guide
https://ex.com/docs/intro/guidehttps://ex.com/docs/intro/guide
https://ex.com/docs/intro/guidehttps://ex.com/guide
https://ex.com/docs/intro/../guidehttps://ex.com/docs/guide
https://ex.com/docs/intro?a=1?b=2https://ex.com/docs/intro?b=2
https://ex.com/docs/intro?a=1#tophttps://ex.com/docs/intro?a=1#top
https://ex.com/docs/intro//cdn.ex.com/x.jshttps://cdn.ex.com/x.js

La última fila es una referencia relativa al esquema: dos barras iniciales conservan el esquema de la base y sustituyen la autoridad. Una referencia que empieza por ? conserva la ruta y descarta la consulta anterior, y una que empieza por # conserva las dos.

Cuándo dos URL son el mismo recurso

El esquema y el host no distinguen mayúsculas de minúsculas, así que HTTPS://Example.COM y https://example.com son una misma dirección. Todo lo que va después del host sí las distingue en lo que respecta al estándar, aunque un servidor concreto decida ignorarlas.

Estos pares son equivalentes:

  • https://example.com:443/a y https://example.com/a, porque el puerto es el que se usa por defecto
  • https://example.com y https://example.com/, porque una ruta vacía significa la raíz
  • /a/%7Euser y /a/~user, porque ~ es no reservado y codificarlo no cambia nada

Estos pares no lo son:

  • /docs y /docs/, que son recursos distintos, aunque la mayoría de servidores redirigen uno al otro
  • /p?a=1&b=2 y /p?b=2&a=1, ya que el orden de los parámetros forma parte de la cadena
  • /index.html y /, salvo que el servidor diga lo contrario

Las cachés y las CDN indexan por los bytes exactos, así que un parámetro de seguimiento añadido o una consulta reordenada divide un objeto en caché en dos y reduce a la mitad la tasa de aciertos. Los buscadores tratan las variantes como páginas duplicadas salvo que un enlace canónico apunte a una forma elegida. La solución es escoger una sola forma, redirigir el resto a ella con un 301 y eliminar los parámetros que no cambian la respuesta.

Longitud, registros y privacidad

En la especificación no existe ningún límite de longitud, pero existen límites en todos los demás sitios. Los valores por defecto habituales de los servidores limitan la línea de petición completa a unos 8 KB, algunos proxies y dispositivos a 4 KB, y los clientes antiguos a unos 2 KB. Todo lo que tenga que sobrevivir a clientes de correo, códigos QR y acortadores de enlaces está más seguro por debajo de unos 2000 caracteres.

El argumento más fuerte para mantener las URL cortas es que una cadena de consulta no es privada. Se escribe en los registros de acceso, se pasa a terceros en la cabecera Referer, se guarda en el historial del navegador, se almacena con los marcadores y se copia cada vez que alguien comparte el enlace. Por eso los tokens de sesión, los códigos de restablecimiento de contraseña, las claves de API y los datos personales no pintan nada ahí. Los datos grandes o sensibles van en el cuerpo de la petición, que no tiene un techo de tamaño práctico y no se registra por defecto.

Leer un enlace antes de hacer clic

La autoridad empieza después de :// y termina en la primerísima /, ? o #. Lee ese tramo y luego lee sus dos o tres últimas etiquetas. Ese es el host real, y nada que esté a su izquierda lo cambia.

https://www.paypal.com@198.51.100.7/secure/login
        ^^^^^^^^^^^^^^ ^^^^^^^^^^^^
        userinfo       real host

Disfraces habituales que conviene reconocer:

  • El texto que va antes de una @ es userinfo, nunca el host, y puede ser el nombre de cualquier marca.
  • paypal.com.secure-login.example es un subdominio de secure-login.example. Las etiquetas se leen de derecha a izquierda.
  • https://short.example/https://www.bank.com/login mete una URL entera y convincente en la ruta.
  • Un host que empieza por xn-- es Punycode, la forma ASCII de un nombre de dominio internacionalizado que produce IDNA. münchen.de viaja por la red como xn--mnchen-3ya.de, lo cual es legítimo, pero el mismo mecanismo permite homógrafos: la а cirílica (U+0430) es visualmente idéntica a la a latina, y un dominio que la use se codifica como algo parecido a xn--pple-43d.com. Los navegadores muestran la forma Punycode cuando una etiqueta mezcla alfabetos, aunque las reglas varían y no son ninguna garantía.
  • Los delimitadores codificados, como %2F o %40, dentro de un host son un intento deliberado de confundir a un analizador.

Pegar el enlace en un analizador de URL lo resuelve en un solo paso, porque un analizador real aplica la misma regla de «gana el primer delimitador» que aplicará el navegador y muestra el host por separado. Para comprobar muchos enlaces a la vez, un probador de regex es una forma rápida de confirmar cuáles tienen de verdad la autoridad que esperas.