http://http://http://@http://http://?http://#http:// es una URL válida

Siete http:// seguidos, separados únicamente por una @, un ? y un #:

http://http://http://@http://http://?http://#http://

Parece una cadena rota por un error de concatenación, pero es una URL completamente válida. Los navegadores la aceptan, el constructor new URL() de JavaScript la procesa sin lanzar ningún error y curl también.

Cómo la lee el navegador

Si la pasas por new URL(), la API estándar de JavaScript en el navegador y en Node.js, puedes ver qué papel juega cada bloque:

const input = "http://http://http://@http://http://?http://#http://";

URL.canParse(input); // true

const url = new URL(input);

El objeto resultante descompone la cadena así:

{
  protocol: "http:",
  username: "",
  password: "",
  hostname: "http",
  port: "",
  pathname: "//http://@http://http://",
  search: "?http://",
  hash: "#http://"
}

Los siete http:// se reparten entre los cinco componentes de la URL, y tres de ellos acaban dentro de la ruta:

Fragmento Componente Por qué es válido
http: Protocolo (scheme) Define el esquema de la URL, igual que mailto: o sms:
//http: Autoridad (host + puerto vacío) El host se llama http; los dos puntos sin número detrás indican el puerto por defecto (80)
//http://@http://http:// Ruta (pathname) Dentro del path se permiten varias barras seguidas, dos puntos y el carácter @
?http:// Query string (search) El primer ? abre la consulta; tanto : como / son caracteres válidos ahí
#http:// Fragmento (hash) Todo lo que va tras # pertenece al fragmento, la misma parte que aprovechan los fragmentos de texto

La clave está en la primera / que aparece después del segundo http:. Según las reglas de parsing, la autoridad (donde viven el usuario, la contraseña, el host y el puerto) termina en cuanto aparece una /, un ? o un #. Como la @ aparece después de esa barra, ya no actúa como separador de credenciales: es un carácter más dentro del path.

Hay un detalle adicional al leer url.href. El parser normaliza la URL y elimina un carácter:

http://http://http://@http://http://?http://#http://
http://http//http://@http://http://?http://#http://
           ^ desaparecen los dos puntos del puerto vacío

Como los dos puntos detrás del host no iban seguidos de ningún número de puerto, son redundantes y el serializador los descarta. Chrome, Firefox y Safari hacen exactamente la misma limpieza si pegas la URL en la barra de direcciones.

Con https ocurre lo mismo:

https://https://https://@https://https://?https://#https://

Qué cambia entre un parser y otro

Que sea válida no significa que todos los parsers la lean igual. Hace 4 años, curl la descomponía de una forma muy distinta a la del navegador:

Fragmento Navegador / WHATWG curl (en 2022)
http Host Usuario
//http:// Parte del path Contraseña
http: Parte del path Host (puerto por defecto)
//http:// Parte del path Path (//http://)
http:// Query Query
http:// Fragmento Fragmento

El motivo era que el parser de curl buscaba la @ para separar usuario y contraseña antes de comprobar si había aparecido una / que cerrara la autoridad. Para curl, todo lo que había entre el primer http:// y la @ eran credenciales, aun teniendo barras de por medio, algo que la RFC 3986 no permite sin codificar (%2F).

Las versiones actuales de curl ya no la leen así. Si haces la prueba con una reciente (aquí, curl 8.7.1) usando -w para inspeccionar cómo trocea la URL, verás que coincide con el navegador:

curl -w "user: %{url.user}\nhost: %{url.host}\npath: %{url.path}\nquery: %{url.query}\n" \
  "http://http://http://@http://http://?http://#http://" -o /dev/null -s
user: 
host: http
path: //http://@http://http://
query: http://

El urllib.parse de Python, el módulo URI de Ruby y el crate url de Rust llegan al mismo resultado: sin usuario, sin contraseña y con @ dentro del path.

Dos estándares para el mismo concepto

Parte del problema de fondo es que no existe una única especificación de URL:

Entre ambos estándares y las implementaciones propias de cada lenguaje, una misma cadena con caracteres como @, \ o varios : puede apuntar a un host distinto según quién la lea.

Por qué importa en producción

Esta diferencia de interpretación entre parsers no es solo una curiosidad sintáctica; es una fuente clásica de vulnerabilidades web:

La próxima vez que alguien te diga que una URL es “algo sencillo”, enséñale esta. 😄

Fuentes


Comparte


Artículos relacionados

Cómo obtener la URL actual con JavaScript (y sacarle provecho)

Aprende a usar window.location para leer, descomponer y manipular URLs en el navegador sin dependencias externas.

Leer artículo→

Enlaces sms: el mailto: de los mensajes de texto

El esquema de URL sms: abre la app de mensajes del móvil con el destinatario y el texto ya escritos. Cómo funciona según la RFC 5724 y qué cambia en iPhone.

Leer artículo→

Fragmentos de texto en URLs: navegación exacta sin anclas

Los Fragmentos de Texto llevan la precisión de los enlaces web al siguiente nivel. Ya no necesitas IDs ni anclas (anchor) para dirigir a tus usuarios al contenido exacto de una página: resalta el texto clave y mejora la experiencia de navegación. Descubre cómo implementar esta técnica para crear enlaces más útiles y directos.

Leer artículo→