Inspetor de codificação por percentagem

A codificação por percentagem não é uma regra, mas várias, e discordam em cerca de uma dúzia de caracteres. Esta página passa o mesmo texto por encodeURIComponent, encodeURI e pela serialização application/x-www-form-urlencoded lado a lado, assinala os caracteres em que discordam, imprime uma linha por carácter com os seus bytes UTF-8 e a sua classe RFC 3986, e diz quantas vezes uma string foi codificada. O lado da descodificação lê uma string com uma regra estrita ou tolerante e diz qual o escape que está partido e onde, em vez de devolver em silêncio um resultado errado.
Todo o processamento é feito localmente no seu navegador — os seus dados nunca são enviados para nenhum servidor.

Não sabe o que é a codificação por percentagem? A codificação por percentagem explicada em linguagem simples — o que é, porque é que um espaço se torna %20 ou +, e o que significa quando um URL mostra %25.
O que isto faz:
Como ler os escapes:
encodeURIComponent
encodeURI
formulário (application/x-www-form-urlencoded)
Ctrl+Enter na entrada executa o modo ativo.
Escolha um modo e clique no seu botão de ação. Codificar e inspecionar mostra as três codificações do seu texto mais uma linha por carácter; Descodificar lê os escapes de volta e conta as camadas. O botão Autoteste volta a executar cada exemplo documentado, cada carácter ASCII imprimível e as cadeias de camadas registadas no ficheiro de dados desta página, e reporta aqui o número.
Onde os codificadores discordam:só os caracteres abaixo são tratados de forma diferente pelos três métodos. Tudo o resto passa pelos três sem alterações, ou é escapado de forma idêntica pelos três.
Cada carácter, uma linha de cada:os bytes UTF-8 em que um carácter se transforma, a classe que a RFC 3986 lhe atribui, e o escape que cada contexto escreve. As quatro colunas de contexto são os conjuntos de codificação por percentagem de URL do WHATWG, que são o que um navegador usa ao construir um caminho, uma consulta ou um fragmento; uma linha sombreada é um carácter que os três codificadores tratam de forma diferente.
O conjunto exato que esta página escapa para cada parte de um URL:impresso a partir do próprio codificador, por isso esta tabela e o código que preenche as colunas acima não se podem afastar. Cada conjunto também escapa os caracteres de controlo C0 (U+0000 a U+001F) e cada ponto de código acima de U+007E, e é por isso que o texto não ASCII é sempre escapado em todos os contextos.
Estes conjuntos derivam dos conjuntos de codificação por percentagem do WHATWG e diferem deles em dois pontos deliberados, ambos os quais tornam a montagem manual segura: um cardinal é escapado dentro de uma consulta, porque um cardinal cru iniciaria o fragmento; e o conjunto de componente é fixado exatamente aos caracteres que encodeURIComponent escapa, apóstrofo incluído, enquanto o conjunto de formulário é fixado ao que URLSearchParams escapa. As duas colunas da tabela acima são, portanto, o comportamento da própria plataforma e não uma reimplementação dele. Essa lista também explica um resultado que as pessoas acham surpreendente: um sinal de percentagem é escapado pelo encodeURIComponent e pelo encodeURI, mas não está em nenhum dos conjuntos de contexto do WHATWG, por isso as colunas de caminho, consulta e fragmento da tabela por carácter o deixam em paz. Um sinal de percentagem cru num URL que montou à mão continua a ser um risco, porque a norma espera que a entrada tenha sido validada primeiro: %41 dentro de uma consulta é um único A para muitas frameworks de servidor, e a leitura estrita desta página trata um % isolado como um escape partido. Escapá-lo você mesmo como %25 é o hábito mais seguro, e a tabela por carácter mostra as duas grafias lado a lado.
Quantas vezes esta string foi codificada:cada ronda de descodificação é uma camada. Um valor que foi codificado duas vezes começa por%25onde uma única codificação deixaria%20, e um valor que foi codificado três vezes começa por%2525.
Os caracteres que um URL pode transportar tal como estão:A RFC 3986 divide o intervalo ASCII em três grupos, e as regras de escape só fazem sentido face a essa divisão.
Os caracteres reservados não são proibidos, estão ocupados: têm significado para o analisador de URL. A regra que decorre da lista é curta — se um carácter é reservado e o quer como dados, escape-o; se é não reservado, nunca precisa de o fazer.
Os três métodos, e o que cada um deixa em paz:
As listas são os conjuntos exatos que cada método deixa sem escape no intervalo ASCII; o Autoteste verifica cada carácter ASCII imprimível contra elas, por isso a tabela não se pode afastar dos codificadores que descreve.
Quão grande é a entrada que esta página aceita, e porquê:a codificação por percentagem é uma passagem única sobre o texto, por isso, ao contrário dos conversores de base numérica deste site, o custo cresce em linha reta e não ao quadrado: não há comprimento a partir do qual a própria aritmética se torne o problema. Os limites abaixo existem porque a tabela por carácter é desenhada no documento, e uma tabela com um milhão de linhas é o que de facto para um navegador.
O que é a codificação por percentagem?

A codificação por percentagem — também chamada codificação de URL, e em documentos mais antigos escape — é a regra que permite a um URL transportar caracteres que não tem permissão para conter. Um escape por percentagem são três caracteres ASCII: um sinal de percentagem e dois dígitos hexadecimais, representando um byte. %20 é o byte 0x20, que é um espaço. %2F é o byte 0x2F, que é uma barra. Não há mais nada envolvido: o escape nomeia um byte, não um carácter.

Porque nomeia bytes, o texto tem de ser primeiro convertido em bytes, e a codificação de bytes é UTF-8. É daí que vêm os escapes longos: um carácter chinês são três bytes UTF-8 e, portanto, três escapes, e um emoji são quatro bytes e doze caracteres ASCII.

A versão de 30 segundos: um espaço é %20 como valor de URL mas + em dados de formulário; # é %23; um sinal de percentagem literal é %25. Os três codificadores desta página concordam nas letras, nos dígitos e em -_., e discordam em cerca de uma dúzia de sinais de pontuação, o til incluído — e é exatamente por isso que encodeURIComponent e encodeURI produzem strings diferentes para o mesmo texto, e por que o errado dos dois pode partir um valor em dois parâmetros de consulta.
Porque é que um URL precisa disto

Um URI é definido como uma sequência de caracteres ASCII (RFC 3986, secção 2.1), e dentro desse intervalo ASCII alguns caracteres são reservados: são a pontuação que dá estrutura ao URL. Dois pontos e duas barras separam o esquema, um arroba separa a informação do utilizador da máquina, um ponto de interrogação inicia a consulta, um cardinal inicia o fragmento, um e comercial separa os parâmetros de consulta, e um sinal de percentagem introduz um escape.

Isto cria um problema e uma brecha. O problema: um valor que contém um desses caracteres — uma caixa de pesquisa com "a&b", um nome de ficheiro com uma barra, uma palavra-passe com dois pontos — de outra forma mudaria a estrutura do URL onde viaja. A brecha: o próprio sinal de percentagem, para que o codificador possa simplesmente estender o seu próprio alfabeto. % é escrito como %25, e os dois dígitos hexadecimais a seguir são caracteres não reservados normais.

Exemplos resolvidos

Estas são as saídas exatas dos três codificadores que esta página executa, com entradas escolhidas porque se comportam de forma diferente umas das outras. A página recalcula-as à sua frente, e o seu Autoteste verifica de novo cada linha contra as implementações da própria plataforma.

Os três métodos, e quando cada um é o correto

A versão curta que evita a maioria dos erros: tudo o que vai para um único valor de parâmetro passa por encodeURIComponent (ou por um serializador de formulário, que faz o mesmo trabalho com um conjunto seguro diferente), e um URL completo já montado passa por encodeURI se precisar de algum escape. Escolher o errado dos dois é o erro mais comum de codificação por percentagem em código web.

Os caracteres que um URL pode transportar, na íntegra
Dois sinais de mais, dois significados

Um espaço é o único carácter cuja codificação depende de para onde o texto vai, e o sinal de mais é a razão.

A armadilha está no meio dessa tabela. Nos dados de formulário, um sinal de mais significa um espaço, por isso um valor que contém genuinamente um sinal de mais — um número de telefone escrito como +44 20 7946 0958, uma expressão aritmética, um endereço de e-mail com uma etiqueta com mais — tem de viajar como %2B. Se viajar como um sinal de mais literal, o recetor lê-o de volta como um espaço, e +44 chega como " 44".

Codificado duas vezes, e como o reconhecer

Uma codificação dupla acontece quando uma string já está escapada e volta a ser escapada. O sinal de percentagem do primeiro escape é um carácter como qualquer outro, por isso torna-se %25, e a camada por baixo sobrevive como texto visível.

Reconhecê-la é uma questão de contar: descodifique a string uma vez e observe o resultado. Se ainda contém escapes que parecem deliberados — um %XX válido ou um sinal de mais de formulário onde se espera um espaço — há outra camada. Se contém %25 seguido de dois dígitos hexadecimais, a camada seguinte é certamente um escape.

Duas notas práticas. Primeiro, descodificar duas vezes não é inofensivo: é uma forma bem conhecida de fazer um filtro de segurança e o código por trás dele verem strings diferentes, por isso um filtro que corre antes de uma segunda descodificação está a filtrar o texto errado. Segundo, a contagem de camadas aqui é uma propriedade da string, não uma promessa sobre a intenção: um valor legítimo pode ter uma só camada de profundidade e um valor partido pode ter duas.

Os erros que esta página foi feita para apanhar
  • Um valor codificado com encodeURI. Os separadores sobrevivem, por isso um valor que contenha & ou = divide-se em vários parâmetros do outro lado. Compare as duas colunas no topo desta página com a entrada a&b=c: a coluna de componente escapa os dois caracteres, a coluna encodeURI mantém-nos.
  • Um sinal de mais literal enviado como dados de formulário. Chega como um espaço. Use %2B.
  • Um valor escapado duas vezes « para estar seguro ». Não está mais seguro. O recetor descodifica uma vez e obtém uma string que ainda contém escapes por percentagem, que é a mesma falha de um conjunto de caracteres errado, uma camada mais abaixo.
  • Descodificar o que nunca foi codificado. Um sinal de percentagem literal num texto — 100% sure — não é um escape. Numa leitura estrita é um escape partido, na posição que esta página reporta; numa leitura tolerante sobrevive, e é por isso que tolerante é o predefinido aqui e não o contrário.
  • Presumir que o escape tem de estar em maiúsculas. A descodificação trata %2f e %2F como o mesmo byte, e ambos são válidos. As maiúsculas são apenas a grafia canónica que a RFC 3986 recomenda, e a que as outras ferramentas produzirão.
  • Presumir que a codificação por percentagem torna o texto seguro. Torna o texto inequívoco dentro de um URL. Não trava injeção, não é cifragem, e não é um sanitizador: o escape tem de ser o último passo antes da montagem, para o contexto em que o texto vai entrar.
Respostas rápidas

“Um sinal de mais é um espaço num URL?”

Só em dados de formulário, e só na parte de consulta de uma submissão de formulário. Num caminho, num fragmento e na consulta de uma ligação simples, um sinal de mais é um sinal de mais literal. É por isso que esta página tem um interruptor para isso em vez de adivinhar.

“Porque é que a minha ligação mostra %25?”

Porque algo codificou um sinal de percentagem. Ou um sinal de percentagem literal passou por um codificador — 100% sure torna-se 100%25%20sure — ou uma string que já continha um escape foi codificada uma segunda vez. Descodifique a string uma vez e observe o que volta: se o resultado ainda contém %XX, há outra camada.

“Importa se uma barra dentro de um valor de consulta é %2F ou /?”

Para o analisador de URL, não: ambos chegam ao servidor como uma barra, e o conjunto de consulta do WHATWG deixa uma barra em paz. Para a interoperabilidade, por vezes sim: muitas frameworks de servidor fazem a sua própria descodificação sobre uma cadeia de consulta crua, e algumas delas discordam sobre caracteres que nunca foram escapados. Quando um valor é montado à mão em vez de por um serializador, escapar explicitamente os caracteres reservados é o hábito mais seguro.

“Porque é que a coluna de formulário escapa um til que as outras duas deixam em paz?”

Porque o conjunto seguro do codificador de formulário é mais estreito: apenas letras, dígitos e -_.*. O codificador de formulário WHATWG escapa ~ ! ' ( ) embora a RFC 3986 liste o ~ como não reservado. Ambas as grafias descodificam para o mesmo valor; a diferença só importa quando se comparam strings byte a byte.

“Os mesmos caracteres podem aparecer sem escape na barra de endereços de um navegador?”

Sim, e isso é a barra de endereços, não o URL. Os navegadores mostram uma forma legível de um URL e codificam-na ao enviá-la, por isso 中文 numa ligação torna-se %E4%B8%AD%E6%96%87 na rede. Se copiar o URL da barra de endereços pode obter qualquer das formas, consoante o navegador.

“Todos os escapes aqui parecem válidos, mas o carácter que sai não é o que eu esperava. Porquê?”

Duas falhas diferentes escondem-se por trás de "descodificou mal", e a página reporta-as de forma diferente. A primeira é um escape partido: um sinal de percentagem que não é seguido de dois dígitos hexadecimais, como um 100% no fim ou um %zz. É um problema de sintaxe, e a leitura estrita recusa-o e diz onde está, enquanto a leitura tolerante o mantém como texto literal e o lista. A segunda é um escape cujo byte é perfeitamente legal mas cujos bytes não soletram um carácter UTF-8 válido, como um %FF sozinho ou um carácter de três bytes cortado como %E4%B8. Não é um problema de sintaxe, por isso ambas as leituras devolvem o texto, substituem os bytes que não podem ser descodificados pelo carácter de substituição U+FFFD ("�"), e listam um aviso que o diz. Esse aviso é a parte útil: texto que chega com caracteres de substituição costuma significar que o remetente usou outro conjunto de caracteres, ou que a string foi escapada uma vez a mais. A codificação por percentagem nomeia bytes, e esta página, tal como a norma de URL, lê sempre esses bytes como UTF-8.

“O que é que esta página não faz?”

Não divide um URL em esquema, máquina, caminho e consulta — para isso serve o Analisador de URL. Não verifica se uma ligação é segura para abrir, não contacta nada, e não adivinha o conjunto de caracteres dos bytes por trás de um escape: lê-os sempre como UTF-8, que é o que a norma de URL exige.

“De que alternativa precisa a descodificação, e como sei que está certa?”

Prima Autoteste. Compara os codificadores da própria página com encodeURIComponent, encodeURI e URLSearchParams da plataforma para cada carácter ASCII imprimível, volta a executar cada exemplo das tabelas acima, e volta a verificar as cadeias de camadas registadas, e depois reporta aqui o número de verificações.

Normas e âmbito

A codificação por percentagem está especificada na RFC 3986, secção 2.1, com a definição de caracteres reservados e não reservados nas secções 2.2 e 2.3 e a regra de maiúsculas/minúsculas na secção 6.2.2.1. Os conjuntos que um navegador realmente usa ao construir um URL estão na Norma URL do WHATWG, que os divide por contexto: um conjunto para o fragmento, um para a consulta, um para a consulta especial, um para o caminho, um para a informação do utilizador, e o mais estreito usado para um único componente. A submissão de formulários, incluindo o sinal de mais, está na Norma HTML em application/x-www-form-urlencoded. As duas funções encodeURI e encodeURIComponent fazem parte do próprio ECMAScript.

Esta página preenche as colunas de caminho, consulta e fragmento a partir dos seus próprios conjuntos por contexto, e imprime a lista exata de cada contexto ao lado do resultado. Esses conjuntos derivam dos do WHATWG e diferem deles em dois pontos deliberados: um cardinal é escapado dentro de uma consulta, porque um cardinal cru iniciaria o fragmento, e o conjunto de componente é fixado exatamente aos caracteres que o encodeURIComponent escapa. Onde uma função da plataforma é a definição da resposta — encodeURIComponent, encodeURI e a serialização de formulário — essa função é chamada diretamente, por isso essas colunas são o comportamento do próprio navegador e não uma reimplementação dele.

O texto desta página foi escrito para este site. Cada valor nas tabelas acima é produzido pelo próprio código da página no carregamento e foi verificado contra as implementações da plataforma nas ferramentas de teste deste site; o Autoteste repete essa comparação no seu navegador.