Todas as partes de um URL e o que cada uma pode conter
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
| Parte | Delimitada por | Para que serve | Chega ao servidor |
|---|---|---|---|
| Esquema | termina no primeiro : | Que protocolo e que regras se aplicam ao resto | Determina a ligação |
| Userinfo | depois de //, termina em @ | Credenciais, há muito desaconselhadas em http e https | Só se o cliente decidir enviá-las |
| Host | termina em :, /, ? ou # | Um nome de domínio ou endereço IP a resolver e ao qual ligar | Sim, no cabeçalho Host |
| Porta | depois de :, termina em /, ? ou # | A porta TCP, por omissão 80 para http e 443 para https | Usada para a ligação |
| Caminho | começa em /, termina em ? ou # | Que recurso nesse host | Sim, na linha de pedido |
| Query | começa em ?, termina em # | Parâmetros para esse recurso | Sim, na linha de pedido |
| Fragmento | começa em #, vai até ao fim | Uma localização dentro do recurso | Nã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áter | Num segmento de caminho | Num valor de query |
|---|---|---|
| Espaço | %20, sempre | %20 ou + |
/ | %2F, ou divide o segmento | Vá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=2 | Onde |
|---|---|
| Ambos os valores, como lista | Python parse_qs, Node querystring, Express |
| Só o primeiro valor | Go Query().Get, Java getParameter |
| Só o último valor | PHP, Rails |
Juntos em 1,2 | Coleçõ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.
| Base | Referência | Resultado |
|---|---|---|
https://ex.com/docs/intro | guide | https://ex.com/docs/guide |
https://ex.com/docs/intro/ | guide | https://ex.com/docs/intro/guide |
https://ex.com/docs/intro | /guide | https://ex.com/guide |
https://ex.com/docs/intro/ | ../guide | https://ex.com/docs/guide |
https://ex.com/docs/intro?a=1 | ?b=2 | https://ex.com/docs/intro?b=2 |
https://ex.com/docs/intro?a=1 | #top | https://ex.com/docs/intro?a=1#top |
https://ex.com/docs/intro | //cdn.ex.com/x.js | https://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/aehttps://example.com/a, porque a porta é a predefinidahttps://example.comehttps://example.com/, porque um caminho vazio significa a raiz/a/%7Eusere/a/~user, porque~é não reservado e codificá-lo em percentagem não altera nada
Estes pares não são:
/docse/docs/, que são recursos diferentes, embora a maioria dos servidores redirecione um para o outro/p?a=1&b=2e/p?b=2&a=1, uma vez que a ordem dos parâmetros faz parte da cadeia/index.htmle/, 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 desecure-login.example. As etiquetas leem-se da direita para a esquerda.https://short.example/https://www.bank.com/logincoloca 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.dena rede, o que é legítimo, mas o mesmo mecanismo permite homógrafos: oаcirílico (U+0430) é visualmente idêntico aoalatino, e um domínio que o use codifica para algo comoxn--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
%2Fou%40dentro 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.