Skip to content

Guide

Codificação de URL Explicada: percent-encoding, query strings e as armadilhas

Metade dos bugs no tratamento de URLs vem de codificar a coisa errada — ou codificá-la duas vezes.

By Published

Codificação de URL é um daqueles tópicos que parecem triviais até que a produção começa a retornar 400 Bad Request em cada busca que contém um acento francês. As regras são simples, mas estão espalhadas em quatro especificações que não concordam totalmente, e os auxiliares JavaScript têm nomes que induzem ativamente ao erro. Este guia reúne as regras em um único lugar e marca os pontos onde as normas discordam silenciosamente.

O que “codificação de URL” realmente significa

URLs são restritas a um pequeno subconjunto de ASCII. Qualquer caractere fora desse conjunto — ou qualquer caractere que tenha um significado estrutural que o parser interpretaria de outra forma — precisa ser representado como um ou mais bytes prefixados por %. Isso é percent-encoding, e as regras para o que codificar vêm do RFC 3986. Você pode experimentar qualquer um dos exemplos abaixo com nosso codificador de URL.

O RFC 3986 divide os caracteres em três grupos. O conjunto não reservado é A-Z a-z 0-9 - . _ ~; estes são sempre seguros e nunca precisam de codificação. O conjunto reservado(: / ? # [ ] @ ! $ & ' ( ) * + , ; =) tem significado estrutural em alguma parte da URL; codifique-os quando quiser o caractere literal, deixe-os intactos quando quiser o delimitador. Todo o resto — incluindo espaço, ", <, >,{ e todo não-ASCII — deve ser percent-encoded.

Onde cada caractere precisa ser codificado

O mesmo byte pode ou não precisar de codificação dependendo de qual componente da URL ele aparece. Um segmento de caminho permite : e @ não codificados; um valor de query não precisa codificar / ou ? (porque o parser para em #); um fragmento é o componente mais permissivo de todos. Na prática, quase todos ignoram essas distinções e codificam qualquer coisa fora do conjunto não reservado ao interpolar um valor em uma URL. Isso é conservador e correto.

O único lugar onde isso importa: não codifique os caracteres / que separam seus segmentos de caminho. Codifique cada segmento, depois junte com /. Se você codificar a barra também, você obterá %2F no caminho, que a maioria dos servidores recusará por razões de segurança (caso contrário permitiria que atacantes contrabandeassem sequências.. além dos guardas de diretório).

encodeURI vs encodeURIComponent em JavaScript

JavaScript fornece dois codificadores, e a diferença entre eles é fonte de uma quantidade enorme de tempo de depuração desperdiçado.

  • encodeURI(value) assume que value já é uma URL completa. Deixa os caracteres reservados (; / ? : @ & = + $ , #) intactos para que a URL permaneça analisável. Codifica espaços, acentos e o pequeno conjunto de caracteres que nunca são permitidos em nenhum lugar de uma URL.
  • encodeURIComponent(value) assume que value é um único componente sendo inserido em uma URL — geralmente um segmento de caminho ou um valor de query. Codifica tudo exceto o conjunto não reservado mais ! ' ( ) *.

A regra prática: se você está construindo uma URL por concatenação, use encodeURIComponent em cada valor interpolado. Se você está normalizando uma URL já construída, encodeURI é a ferramenta certa. Quase não há caso em que você queira aplicar os dois.

Dados de formulário: a exceção do +/espaço

Submissões de formulários HTML codificam usando o tipo de mídia application/x-www-form-urlencoded, definido no padrão WHATWG URL. Esse formato diverge do RFC 3986 de duas formas. Primeiro, espaços tornam-se + em vez de %20. Segundo, um + literal no valor é codificado como %2B para que o decodificador possa diferenciá-los.

Tanto %20 quanto + decodificam para um espaço na prática — todo parser do lado do servidor popular aceita os dois em uma query string. Mas emissores devem ser consistentes. Use + em corpos de formulário e em query strings que imitam corpos de formulário; use %20 em outros lugares, especialmente em segmentos de caminho onde + é um sinal de adição literal.

Arrays e objetos aninhados: não há padrão

Query strings foram projetadas para pares planos key=value. Estruturas de dados aninhadas precisam ser achatadas, e a regra de achatamento é uma decisão por framework. As quatro convenções em uso generalizado:

  1. Chaves repetidas. ?tag=js&tag=ts torna-se um array no servidor. A convenção mais simples; o padrão no Express, net/url do Go e a maioria dos parsers Node.
  2. Colchetes estilo PHP. ?tag[]=js&tag[]=ts para arrays; ?user[name]=alex para objetos aninhados. Os colchetes devem ser percent-encoded (%5B / %5D) para serem estritamente compatíveis com os padrões, embora a maioria dos servidores aceite a forma bruta.
  3. Notação de ponto Rails. ?user.name=alex&user.age=30. Menos comum hoje; aparece em algumas APIs Ruby e .NET mais antigas.
  4. Separado por vírgula. ?tag=js,ts. Comum em APIs REST (o OpenAPI lista como estilo form/explode=false). Conciso mas ambíguo se os valores em si puderem conter vírgulas.

Escolha uma convenção por API e documente-a. Os bugs mais dolorosos nessa área vêm de um cliente que emite colchetes conversando com um servidor que espera chaves repetidas — ambos parecem funcionar, mas um deles silenciosamente mantém apenas o último valor.

CJK, emoji e outros não-ASCII

Todo caractere fora do ASCII deve ser codificado como seus bytes UTF-8, depois percent-encoded byte a byte. O caractere (U+5317) são os três bytes E5 8C 97 em UTF-8, que tornam-se %E5%8C%97 em uma URL. Emoji seguem a mesma regra: 🔥 (U+1F525) são os quatro bytes F0 9F 94 A5, codificados como %F0%9F%94%A5.

Sistemas legados às vezes emitem o mesmo caractere em uma codificação diferente — GBK para chinês, Shift-JIS para japonês, Windows-1251 para cirílico. Se você está integrando com um backend pré-2010 e seu texto decodificado sai como lixo, tente interpretar os bytes como Windows-1252 ou a codificação nativa do locale antes de assumir que a URL está corrompida.

Nomes de domínio internacionalizados

Nomes de host seguem uma regra diferente. Nomes de host não ASCII são codificados como Punycode (RFC 3492), não como percent-encoding. O domínio 例え.jp torna-se xn--r8jz45g.jp na consulta DNS real. Navegadores exibem a forma Unicode para scripts reconhecidos, mas transmitem a forma Punycode pela rede. Se você estiver construindo uma URL com um host não ASCII, converta o host com um codificador Punycode, não o codificador de URL.

Armadilhas comuns

  • Codificação dupla. Codificar um valor e depois passar o resultado por outro codificador produz %2520 em vez de %20. Rastreie cada etapa de codificação explicitamente; nunca “codifique para garantir” em um valor que você não produziu.
  • Fragmentos de hash são apenas do lado do cliente. Tudo após # nunca é enviado ao servidor. Colocar estado no fragmento é uma escolha deliberada (privacidade, roteamento de single-page-app); colocá-lo lá por acidente oculta bugs até que o servidor comece a depender dele.
  • Sinais de adição em links mailto. mailto:[email protected] funciona porque URLs mailto usam as regras do RFC 3986, não codificação de formulário. Construa links mailto manualmente, não com um codificador de formulário.
  • O conjunto não reservado exclui o til historicamente. O RFC 2396 (predecessor ao 3986) tratava ~ como reservado. Codificadores mais antigos ainda emitem %7E; o padrão moderno considera-o não reservado. Ambos decodificam da mesma forma.
  • Limites de comprimento são reais. A maioria dos servidores limita URLs a 8 KB. Navegadores variam de 2 KB (IE mais antigo) a 32 KB (Chrome moderno). Se sua query string se aproximar desse intervalo, mova o payload para um corpo POST.

Um fluxo de trabalho prático

Ao construir URLs em código: construa um objeto representando os componentes (esquema, host, segmentos de caminho, mapa de query), depois serialize uma vez com uma biblioteca confiável — URL e URLSearchParams em navegadores, os equivalentes em todas as outras linguagens principais. URLs montadas manualmente são os bugs.

Lembre que a URL codificada é apenas a linha de requisição; a mesma camada HTTP carrega cabeçalhos que seu código raramente escapa manualmente mas ainda deve tratar com cuidado. O cabeçalho Referer vaza a URL anterior (incluindo sua query string) para o próximo host a menos que você a remova com uma política de referrer, a string User-Agent deixa servidores variarem comportamento por cliente, e o endereço IP de conexão chega na camada TCP ou — por trás de um proxy — em um cabeçalho X-Forwarded-For. Qualquer coisa que você colocar na query string é visível para os três.

Ao depurar uma URL codificada que alguém enviou para você, cole-a em nosso codificador/decodificador de URL e alterne a direção. A ferramenta decodifica uma vez por clique, então se você precisar clicar duas vezes para obter texto legível, você confirmou codificação dupla upstream.

A conclusão honesta

Use encodeURIComponent em cada valor interpolado, nunca em uma URL completa. Combine sua codificação de formulário (espaço-como-mais) com seu contexto de transporte. Escolha uma convenção de array por API e documente-a. Confie no UTF-8. E quando algo parece duplamente codificado, quase certamente está — rastreie o segundo codificador em vez de cobrir com uma passagem de decodificação.

Frequently asked questions

Devo codificar a URL inteira ou apenas partes dela?
Codifique apenas as partes que você controla — segmentos de caminho e valores de query. Nunca codifique uma URL inteira com encodeURIComponent porque isso vai corromper o separador de esquema (`://`) e os delimitadores `?` e `&`. Codifique cada segmento de caminho e cada valor de query independentemente e monte-os depois.
Por que minha URL mostra + em vez de %20 para espaços?
Esse é o comportamento de `application/x-www-form-urlencoded`, usado por submissões de formulários HTML. Nesse tipo de mídia, um espaço é codificado como `+` e um `+` literal é codificado como `%2B`. Em caminhos de URL RFC 3986, espaços são sempre `%20`. Servidores geralmente decodificam os dois, mas se você estiver construindo uma URL manualmente, prefira `%20` em todo lugar exceto em corpos de formulário.
encodeURI() é alguma vez a escolha certa em JavaScript?
Raramente. `encodeURI()` deixa caracteres reservados como `?`, `#`, `&` e `/` intocados porque assume que você está codificando uma URL completa. Isso é quase nunca o que você realmente quer — você geralmente quer codificar um valor que será inserido em uma URL, que é o trabalho de `encodeURIComponent`. Use `encodeURI` apenas ao sanitizar uma URL literal digitada pelo usuário contendo caracteres não ASCII.
Como caracteres CJK são codificados?
O RFC 3986 requer UTF-8 seguido de percent-encoding de cada byte. O caractere `日` (U+65E5) é `E6 97 A5` em UTF-8, então é codificado como `%E6%97%A5`. Sistemas mais antigos às vezes usavam bytes GBK ou Shift-JIS, por isso raspar sites CJK legados ocasionalmente revela `%C8%D5` para o mesmo caractere. Navegadores modernos sempre emitem UTF-8.
Como codifico arrays em query strings?
Não há um padrão único. PHP e Rails usam notação de colchetes (`tags[]=a&tags[]=b`); Express e a maioria dos frameworks Node aceitam colchetes ou chaves repetidas (`tags=a&tags=b`); algumas APIs requerem separado por vírgula (`tags=a,b`). Verifique o parser do consumidor antes de escolher uma convenção e documente-a na referência da API.
Por que estou obtendo valores duplamente codificados como %2520?
Algo codificou o valor, então outra coisa codificou o resultado. `%20` (um espaço codificado) tornou-se `%2520` porque o `%` foi recodificado como `%25`. A correção é encontrar a segunda etapa de codificação e removê-la — nunca decodifique e recodifique como solução alternativa, pois você perderá informações para valores que legitimamente continham `%25` antes de qualquer codificação.

Related

Published May 31, 2026