Todas as partes de um URL e o que cada uma pode conter

Todas as partes de um URL identificadas num exemplo prático, que carateres têm de ser codificados em percentagem e onde, como as query strings se comportam na realidade e como reconhecer uma ligação que mente sobre o seu host.

Um URL é uma única cadeia de carateres que indica que protocolo falar, com que máquina o falar e o que pedir. Lê-lo corretamente é sobretudo uma questão de saber onde termina cada parte, porque os delimitadores são carateres isolados e o primeiro é o que conta.

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

As partes

ParteDelimitada porPara que serveChega ao servidor
Esquematermina no primeiro :Que protocolo e que regras se aplicam ao restoDetermina a ligação
Userinfodepois de //, termina em @Credenciais, há muito desaconselhadas em http e httpsSó se o cliente decidir enviá-las
Hosttermina em :, /, ? ou #Um nome de domínio ou endereço IP a resolver e ao qual ligarSim, no cabeçalho Host
Portadepois de :, termina em /, ? ou #A porta TCP, por omissão 80 para http e 443 para httpsUsada para a ligação
Caminhocomeça em /, termina em ? ou #Que recurso nesse hostSim, na linha de pedido
Querycomeça em ?, termina em #Parâmetros para esse recursoSim, na linha de pedido
Fragmentocomeça em #, vai até ao fimUma localização dentro do recursoNão

O host e a porta em conjunto formam a autoridade. O fragmento é a exceção: é removido antes de o pedido ser construído, por isso um servidor nunca o vê. Os navegadores usam-no para âncoras e para encaminhamento do lado do cliente, e alguns fluxos de autenticação devolvem deliberadamente tokens num fragmento para que esses tokens nunca apareçam num registo do servidor. Ainda assim, fica no histórico do navegador, por isso não é privado, apenas não é enviado.

Carateres reservados e não reservados

A codificação percentual escreve um byte como % seguido de dois dígitos hexadecimais, usando os bytes UTF-8 do caráter. é passa a %C3%A9, dois bytes e dois escapes.

Quatro carateres são não reservados, a par das letras e dos dígitos, e nunca precisam de codificação em parte alguma: - . _ ~. Todos os outros ou são reservados, o que significa que têm uma função estrutural nalguma parte de um URL, ou têm de ser codificados. O conjunto reservado é : / ? # [ ] @ e ! $ & ' ( ) * + , ; =.

A palavra "reservado" não quer dizer "sempre proibido". Quer dizer que o caráter é um delimitador nalguma parte e que só tem de ser codificado nas partes onde seria lido como tal.

CaráterNum segmento de caminhoNum valor de query
Espaço%20, sempre%20 ou +
/%2F, ou divide o segmentoVálido tal como está
?%3F, ou o caminho acaba aíVálido tal como está
#%23, sempre%23, sempre
& e =Válidos tal como estão%26 e %3D, ou o par parte-se
+Válido, e significa um sinal de mais%2B, ou pode ser descodificado como espaço

A regra prática é essa: codifique o caráter que terminaria a parte em que se encontra. Um espaço num caminho tem de ser %20, porque um espaço literal termina o URL na maioria dos analisadores, ao passo que um espaço num valor de query pode ser %20 ou o mais antigo +. Um URL Encoder é a forma fiável de o fazer, já que codificar um URL inteiro e codificar um componente são operações diferentes e a segunda é quase sempre a pretendida.

As query strings são uma convenção

A especificação limita-se a dizer que a query é tudo o que está entre ? e #, formado a partir dos carateres permitidos. O formato key=value&key=value vem da codificação de formulários HTML, não da norma dos URI. Nada impede um servidor de interpretar ?a:1;b:2 como bem entender.

Como o formato é uma convenção, chaves repetidas não têm significado definido e cada plataforma escolheu a sua própria resposta:

Comportamento para ?id=1&id=2Onde
Ambos os valores, como listaPython parse_qs, Node querystring, Express
Só o primeiro valorGo Query().Get, Java getParameter
Só o último valorPHP, Rails
Juntos em 1,2Coleções de pedido do ASP.NET

Os arrays herdam o mesmo problema. ids=1&ids=2, ids[]=1&ids[]=2, ids[0]=1&ids[1]=2 e ids=1,2 estão todos em uso generalizado, e só o servidor decide qual deles compreende. A notação com parênteses retos tem de ser codificada para ser estritamente válida (ids%5B%5D=1), embora a maioria dos servidores aceite os parênteses literais. Escolha o formato que o lado recetor documenta e mantenha-o de forma consistente.

O sinal de mais merece um aviso à parte. Em dados application/x-www-form-urlencoded, ou seja, num corpo de pedido codificado como formulário, + significa um espaço. Num caminho de URL significa um sinal de mais literal. Numa query string depende do analisador, e a maioria das frameworks web aplica aí a descodificação de formulário, pelo que + se transforma silenciosamente num espaço. Qualquer valor que possa legitimamente conter um sinal de mais, como a saída padrão de Base64 ou um número de telefone, tem de ser enviado como %2B.

Referências relativas

Uma referência relativa é resolvida contra um URL base substituindo tudo o que vem depois da última / da base. Essa última barra é toda a regra, e é por isso que uma barra final faz diferença.

BaseReferênciaResultado
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

A última linha é uma referência relativa ao esquema: duas barras iniciais mantêm o esquema da base e substituem a autoridade. Uma referência que começa por ? mantém o caminho e descarta a query antiga, e uma que começa por # mantém ambos.

Quando dois URL são o mesmo recurso

O esquema e o host não distinguem maiúsculas de minúsculas, por isso HTTPS://Example.COM e https://example.com são um só endereço. Tudo o que vem depois do host distingue maiúsculas de minúsculas no que à norma diz respeito, mesmo que um servidor concreto opte por ignorar a caixa.

Estes pares são equivalentes:

  • https://example.com:443/a e https://example.com/a, porque a porta é a predefinida
  • https://example.com e https://example.com/, porque um caminho vazio significa a raiz
  • /a/%7Euser e /a/~user, porque ~ é não reservado e codificá-lo em percentagem não altera nada

Estes pares não são:

  • /docs e /docs/, que são recursos diferentes, embora a maioria dos servidores redirecione um para o outro
  • /p?a=1&b=2 e /p?b=2&a=1, uma vez que a ordem dos parâmetros faz parte da cadeia
  • /index.html e /, a não ser que o servidor diga o contrário

As caches e as CDN indexam pelos bytes exatos, por isso um parâmetro de rastreio acrescentado ou uma query reordenada dividem um objeto em cache em dois e reduzem a taxa de acertos para metade. Os motores de busca tratam as variantes como páginas duplicadas, a não ser que uma ligação canónica aponte para uma forma escolhida. A solução é fixar um único formato, redirecionar os restantes para ele com um 301 e retirar os parâmetros que não alteram a resposta.

Comprimento, registos e privacidade

A especificação não impõe qualquer limite de comprimento, mas há limites em todo o lado. As predefinições habituais dos servidores limitam a linha de pedido inteira a cerca de 8 KB, alguns proxies e appliances a 4 KB e os clientes mais antigos a cerca de 2 KB. Tudo o que tenha de sobreviver a clientes de correio eletrónico, códigos QR e encurtadores de ligações fica mais seguro abaixo dos 2000 carateres.

O argumento mais forte para manter os URL curtos é que uma query string não é privada. É escrita nos registos de acesso, passada a terceiros no cabeçalho Referer, guardada no histórico do navegador, gravada com os marcadores e copiada sempre que alguém partilha a ligação. Tokens de sessão, códigos de reposição de palavra-passe, chaves de API e dados pessoais não têm, portanto, lugar aí. Os dados grandes ou sensíveis vão no corpo do pedido, que não tem teto prático de tamanho e não é registado por omissão.

Ler uma ligação antes de clicar nela

A autoridade começa depois de :// e termina logo no primeiro /, ? ou #. Leia esse troço e depois leia as suas duas ou três últimas etiquetas. Esse é o host verdadeiro, e nada à sua esquerda o altera.

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

Disfarces comuns a reconhecer:

  • O texto antes de um @ é userinfo, nunca o host, e pode ser o nome de qualquer marca.
  • paypal.com.secure-login.example é um subdomínio de secure-login.example. As etiquetas leem-se da direita para a esquerda.
  • https://short.example/https://www.bank.com/login coloca um URL inteiro e convincente dentro do caminho.
  • Um host que começa por xn-- está em Punycode, a forma ASCII de um nome de domínio internacionalizado produzida pelo IDNA. münchen.de é xn--mnchen-3ya.de na rede, o que é legítimo, mas o mesmo mecanismo permite homógrafos: o а cirílico (U+0430) é visualmente idêntico ao a latino, e um domínio que o use codifica para algo como xn--pple-43d.com. Os navegadores mostram a forma Punycode quando uma etiqueta mistura alfabetos, embora as regras variem e não sejam uma garantia.
  • Delimitadores codificados como %2F ou %40 dentro de um host são uma tentativa deliberada de confundir um analisador.

Colar a ligação num URL Parser resolve a questão num só passo, já que um analisador a sério aplica a mesma regra de que o primeiro delimitador ganha que o navegador vai aplicar e mostra o host isolado. Para verificar muitas ligações de uma vez, um Regex Tester é uma forma rápida de confirmar quais delas têm de facto a autoridade que se espera.