Analisador e decodificador de URL
Analise qualquer URL em seus componentes · protocolo, host, porta, caminho, parâmetros de consulta e fragmento.
Anatomia de uma URL: seis componentes, uma longa história
Uma URL é analisada em seis partes conceptuais: scheme://userinfo@host:port/path?query#fragment. O scheme diz ao cliente que protocolo usar (https, http, ftp, mailto, file, data) e é a única parte sempre presente. A componente userinfo (username:password@) é rara em uso moderno; os navegadores costumam retirá-la das URLs exibidas porque tem sido um vector de phishing desde os anos 90. O host é a localização de rede, um nome de domínio registado, um endereço IP (IPv4 em quartetos pontuados ou IPv6 entre parênteses rectos) ou um nome especial como localhost. O port é a porta TCP/UDP (80 por defeito para HTTP, 443 para HTTPS, etc.); se omitido, aplica-se o defeito do scheme. O path é a hierarquia separada por barras que identifica o recurso dentro do host. A query string (tudo o que vem depois do ?) leva pares chave-valor separados por &, usados para filtragem, paginação, tracking, submissão de formulários. O fragment (tudo o que vem depois do #) é a única parte da URL que nunca é enviada ao servidor, é processada totalmente do lado do cliente pelo navegador para fazer scroll até uma secção específica ou, em single-page apps, para indicar o estado da rota.
O formato da query string em si tem uma bifurcação: tradicional ?key=value&key2=value2 com valores percent-encoded segundo RFC 3986, vs a convenção mais antiga form-encoded application/x-www-form-urlencoded onde + significa um espaço (originalmente para submissões de formulários HTML). A maioria dos parsers lida com ambos, mas a conversão é assimétrica: %20 descodifica sempre para um espaço; + só descodifica para um espaço dentro de uma query string, nunca dentro de um path. Este é um dos bugs de parsing de URL mais comuns em produção.
Uma breve história da URL
A URL (originalmente «Universal Document Identifier», depois «Universal Resource Locator») foi inventada por Tim Berners-Lee entre o seu memorando de Março de 1989 «Information Management: A Proposal» no CERN (aquele que o seu chefe Mike Sendall anotou «Vague but exciting») e as primeiras páginas web publicamente navegáveis de Agosto de 1991. A primeira URL canónica foi http://info.cern.ch/hypertext/WWW/TheProject.html, publicada a 6 de Agosto de 1991. As discussões da IETF de 1992 renomearam UDIs para URLs para evitar uma luta de vocabulário. RFC 1738 («Uniform Resource Locators»), de Berners-Lee, Masinter e McCahill, foi publicado em Dezembro de 1994 como a primeira sintaxe formal de URL. RFC 2396 seguiu em Agosto de 1998, generalizando URLs para o conceito mais amplo de URI. A spec canónica actual é RFC 3986 («URI Generic Syntax»), publicada em Janeiro de 2005, editada por Berners-Lee, Roy Fielding e Larry Masinter, um Internet Standard STD 66, o nível de maturidade mais alto da IETF. RFC 3986 é o que cada parser de URL nominalmente almeja. Na prática, navegadores modernos divergem de RFC 3986 em numerosos casos limite, razão pela qual a WHATWG mantém um URL Living Standard separado em url.spec.whatwg.org; a spec WHATWG visa explicitamente tornar obsoletos a RFC 3986 e a RFC 3987 ao longo do tempo, e as duas ainda divergem em coisas como tratamento de espaços iniciais, conjuntos de percent-encoding e normalização Unicode.
Caracteres não reservados, reservados e percent-encoded
RFC 3986 §2.3 define os caracteres não reservados: os únicos caracteres garantidamente seguros em qualquer componente de URI sem percent-encoding: A-Z, a-z, 0-9, hífen (-), ponto (.), underscore (_) e til (~). 66 caracteres no total. Tudo o resto é ou um carácter reservado com significado estrutural em alguma componente (gen-delims (:/?#[]@) e sub-delims (!$&'()*+,;=)) ou «outro» e tem de ser percent-encoded se aparecer numa URI. O percent-encoding (RFC 3986 §2.1) pega na sequência de bytes de um carácter (em UTF-8 a menos que o scheme diga o contrário) e substitui cada byte por %HH onde HH é o valor hex de dois dígitos do byte. Assim um é codificado em UTF-8 (bytes 0xC3 0xA9) torna-se %C3%A9; a palavra russa привет torna-se %D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82: dois bytes por carácter, seis triplets %XX e 36 caracteres percent-encoded de URL para seis letras cirílicas.
Os navegadores exibem paths percent-encoded de duas formas: a maioria dos navegadores modernos (Chrome, Firefox, Safari) descodifica e renderiza os glifos Unicode originais na barra de endereços quando a codificação é UTF-8 válido, mas copia a forma percent-encoded literal quando o utilizador copia a URL. Navegadores mais antigos e muitos logs web mostram apenas a forma percent-encoded, razão pela qual «URLs Unicode bonitas» podem ser enganadoras: parecem belas na barra de endereços e feias em qualquer texto onde sejam partilhadas. RFC 3987 («Internationalized Resource Identifiers», IRIs), publicado em Janeiro de 2005, formalizou URLs Unicode na sua forma não codificada; Punycode (RFC 3492, Março de 2003) define como os nomes de domínio internacionalizados são codificados em ASCII para DNS, label a label, o label de topo chinês 中国 torna-se xn--fiqs8s, pelo que example.中国 resolve ao nível do DNS como example.xn--fiqs8s. A demonstração canónica são as URLs IRI da Wikipédia: https://ja.wikipedia.org/wiki/東京 funciona em qualquer navegador moderno mesmo que o pedido subjacente codifique o path como /wiki/%E6%9D%B1%E4%BA%AC.
O WHATWG URL Standard, o que os navegadores realmente fazem
O RFC 3986 da IETF diz uma coisa; os navegadores fazem algo ligeiramente diferente. A WHATWG (o organismo de padronização dos fabricantes de navegadores) mantém um URL Living Standard separado em url.spec.whatwg.org descrevendo a máquina de estados algorítmica que os navegadores realmente executam, incluindo tratamento de espaços iniciais, caracteres de controlo, conjuntos de percent-encoding que variam por componente, e normalização Unicode. A spec WHATWG é o que o construtor URL do navegador (new URL(input)) implementa, e em que Node.js, Deno e Bun convergiram para o seu parsing de URL incorporado. O parser Ada: escrito em C++ por Yagiz Nizipli, Daniel Lemire e outros, tornou-se o parser conforme à WHATWG que alimenta o parsing de URL do Node.js desde Node.js 18.16.0 (Abril de 2023), substituindo o caminho mais antigo url.parse(); é mensuravelmente mais rápido que qualquer implementação anterior e é o padrão de facto para parsing de URL de alta performance em 2026. RFC 3986 e a spec WHATWG ainda não estão totalmente reconciliados, e a divergência histórica continua a aparecer em caminhos de código legacy e versões mais antigas de runtime.
A query string, e a API URLSearchParams
A query string é tecnicamente apenas «tudo o que vem depois do ? e antes do #», a spec não define realmente como interpretá-la. A convenção ?key=value&key=value com separadores & é convenção, não exigência. Na prática, dois formatos de query string dominam: application/x-www-form-urlencoded (o formato de submissão de formulário HTML por defeito, onde + significa um espaço) e a convenção de query URI padrão (onde o espaço é sempre %20). A API URLSearchParams do navegador (parte do WHATWG URL Living Standard, baseline em todos os navegadores modernos desde aproximadamente 2017) lida com ambos os formatos de forma transparente para parsing e emite a variante form-encoded ao stringificar. Chaves repetidas são legais: ?tag=red&tag=blue&tag=green é válido, e URLSearchParams.getAll('tag') devolve ['red', 'blue', 'green']. Diferentes frameworks web lidam com chaves repetidas de forma diferente, Rails e Express agrupam chaves repetidas em arrays, enquanto PHP sobrescreve valores anteriores pelos posteriores a menos que a chave use a convenção de colchetes name[]: o que é uma fonte constante de bugs cross-framework em integrações de API.
Armadilhas comuns do parsing de URL
- Codificação dupla. Codificar uma URL já codificada produz
%2520(o próprio%é codificado como%25). Aparece quando URLs viajam por múltiplas camadas (frontend → backend → analytics) e cada camada «amavelmente» codifica mais uma vez. A solução é descodificar até ao fim antes de recodificar uma só vez. - + vs %20 em query strings.
+significa um espaço dentro de uma query string form-encoded mas significa um+literal dentro de um path ou fragment. Misturar as convenções produz bugs difíceis de depurar onde «John+Doe» se torna «John Doe» na query mas permanece como «John+Doe» no path. - Sensibilidade a maiúsculas. O host é insensível a maiúsculas (
EXAMPLE.comeexample.comsão o mesmo host); o path é sensível a maiúsculas na maioria dos servidores (Linux/Unix) mas insensível noutros (Windows IIS por defeito). Isto significa que a mesma URL pode resolver para conteúdo diferente dependendo do servidor. - IPv6 em URLs. Os endereços IPv6 contêm dois pontos, que entram em conflito com o separador host:port. A solução é envolver o endereço IPv6 em parênteses rectos:
http://[2001:db8::1]:8080/path. Muitos parsers de URL falharam historicamente nisto; navegadores modernos e o parser WHATWG lidam correctamente com isso. - Tokens OAuth no fragment. O fluxo OAuth 2.0 implicit grant devolvia access tokens no fragment de URL (
#access_token=...) para que não aparecessem em logs do servidor. As orientações modernas de OAuth desencorajam este fluxo a favor de authorisation code com PKCE, mas sistemas legacy ainda emitem tokens no fragment. - Não-identidade do round-trip. Fazer parse de uma URL e re-stringificá-la nem sempre produz a string original, o parser normaliza (descodifica caracteres não reservados percent-encoded, passa o host a minúsculas, ordena parâmetros de query em algumas implementações). Não assuma que
parse(url).toString() === url.
Casos de uso comuns
- Depuração de pedidos API. Um endpoint REST com uma query string longa é difícil de ler; fazer parse mostra cada parâmetro na sua própria linha.
- Inspecção de callback OAuth. As URLs de fluxo de auth levam state, code, scope e access tokens codificados que precisam ser descodificados para depuração sem os expor a um servidor.
- Rastreio de cadeias de redireccionamento. Quando uma URL redirecciona através de várias URLs intermédias, fazer parse de cada uma ajuda a seguir a cadeia e identificar onde um redireccionamento se está a partir.
- Auditoria de etiquetas UTM. As URLs de analytics (
?utm_source=...&utm_medium=...&utm_campaign=...) são mais fáceis de ler parâmetro a parâmetro do que como uma parede de query string. - Auditoria de segurança. Procura por padrões de injecção SQL ou sequências de path traversal em parâmetros de URL; o parsing expõe cada valor separadamente para revisão.
- Parsing de body form-encoded. O mesmo formato usado em query strings de URL é usado em bodies POST
application/x-www-form-urlencoded. - Inspecção de deep link. Os deep links de apps móveis e as rotas de webapps codificam estado complexo no path ou query; o parsing torna a estrutura visível.
- Revisão de links de afiliados / partilha. Os links de tracking de campanhas de email ou programas de afiliados levam URLs de redireccionamento codificadas e IDs de tracking que beneficiam de descodificação.
Privacidade: as URLs carregam segredos reais
As URLs não são geralmente tratadas como secretas, mas frequentemente carregam dados que o são. As URLs de callback OAuth incluem access tokens. As URLs de magic-link login incluem tokens de autenticação de utilização única. Os links de reposição de palavra-passe incluem reset tokens. As URLs internas de API incluem nomes de host internos e caminhos de routing que revelam infra-estrutura. Mesmo URLs ordinárias de aplicação revelam comportamento do utilizador através dos parâmetros de query, termos de pesquisa, selecções de filtros, IDs de perfil, identificadores de sessão. O cabeçalho Referer deixa vazar a URL anterior para cada site ligado, mitigado pelo cabeçalho Referrer-Policy introduzido como W3C Candidate Recommendation em 2017 (os defaults dos navegadores ainda variam). As URLs acabam em logs de acesso do servidor, no histórico do navegador, em marcadores, em logs de CDN, em pipelines de analytics, em previews de links de apps de chat. Um parser de URL do lado do servidor vê cada URL nele colada; um parser apenas no navegador, não. Para URLs internas de API, callbacks OAuth com tokens, links de reposição de palavra-passe, ou qualquer URL que não queira ver copiada para o disco rígido de um estranho, um parser apenas no navegador é a arquitectura certa. Verifique no separador Network das DevTools enquanto faz parse, ou ponha a página offline (modo avião) depois de carregar.
Perguntas frequentes
Que partes contém uma URL?
Seis partes conceptuais: scheme (https, http, ftp, mailto), userinfo (rara em uso moderno, maioritariamente removida pelos navegadores como mitigação de phishing), host (domínio ou IP), port (por defeito 80 para HTTP, 443 para HTTPS), path (hierarquia separada por barras), query (pares chave-valor depois de ?) e fragment (depois de #, nunca enviado ao servidor). A gramática completa está em RFC 3986 §3 (Janeiro de 2005, STD 66) e no WHATWG URL Living Standard.
Como descodifico caracteres URL-encoded?
O percent-encoding substitui caracteres inseguros por um % seguido do código hex do byte: um espaço é %20, dois pontos é %3A, uma barra é %2F, um e comercial é %26, a arroba é %40. Os caracteres multi-byte UTF-8 são codificados byte a byte, por isso é torna-se %C3%A9 (dois bytes). O parser descodifica automaticamente todos os caracteres percent-encoded na saída exibida. As funções JavaScript padrão são encodeURIComponent() para codificar valores individuais e decodeURIComponent() para descodificar.
O que é um fragment de URL?
O fragment (tudo o que vem depois do #) é a única parte da URL que é processada inteiramente do lado do cliente, nunca é enviada ao servidor web em pedidos HTTP. Propósito original: fazer scroll do navegador até um elemento âncora com esse ID. Usos modernos incluem estado de rota de single-page applications (#/dashboard/profile), tokens de fluxo implícito OAuth (agora desencorajados a favor de authorisation code com PKCE), e navegação por página de PDF (file.pdf#page=5). Como os fragments não chegam ao servidor, são um lugar para esconder valores que não deveriam aparecer em logs do servidor.
Porque é que + às vezes significa espaço e às vezes +?
Existem duas convenções de codificação. application/x-www-form-urlencoded (o formato de submissão de formulário HTML por defeito) codifica espaços como +; o percent-encoding padrão (segundo RFC 3986) codifica espaços como %20. Ambos são válidos em query strings; só %20 é válido em paths e fragments. URLSearchParams lida com ambos de forma transparente. O bug cross-context surge quando o código usa encodeURIComponent (que codifica espaço como %20) para parâmetros de query que o servidor espera em form-encoded, ou vice-versa.
Lida com URLs relativas?
O parser espera uma URL completa com um scheme. Para um path relativo como /api/users, prefixe uma URL base (https://example.com/api/users) para a fazer parse. Algum parsing de URL relativa (resolução contra uma URL base como o navegador faz para atributos href) está no roadmap, a forma de dois argumentos do construtor URL da WHATWG (new URL(relative, base)) lida com isto e é o que o código de produção deveria usar.
As minhas URLs são enviadas para algum sítio?
Não. O parsing corre inteiramente no seu navegador via o construtor URL da WHATWG, a URL que cola nunca deixa o seu dispositivo. Verifique no separador Network das DevTools enquanto clica em Parse, ou ponha a página offline (modo avião) depois de carregar. Seguro para URLs de callback OAuth contendo access tokens, links de reposição de palavra-passe contendo tokens de utilização única, URLs internas de API que revelam infra-estrutura, ou qualquer URL que não queira ver copiada para o disco rígido de um estranho.