Português (Brasil)

Ferramentas de desenvolvedor · Codificador e decodificador URL

escape() vs encodeURIComponent: como a codificação URL de JavaScript evoluiu

· Fundo

javascript codificação de URL história

Três gerações de funções de codificação JavaScript URL
Ilustração vetorial original ToolAcre

JavaScript teve três gerações de funções de codificação URL, e a mais antiga ainda está escondida no código de produção. Esta postagem explica o que escape() faz de errado, por que ES3 adicionou as funções URI e por que elas preservam ! * ' ( ).

O %u20AC em um log legado — a impressão digital inconfundível de escape() e a falha de decodificação que ele causa

Um arquivo JavaScript herdado contém uma chamada de codificação URL usando a função escape() obsoleta. A saída em um arquivo de log ou mensagem de erro inclui a sequência %u20AC – uma impressão digital inconfundível da função escape() obsoleta que ninguém mais usa. Esta sequência não corresponde a nenhuma codificação URL padrão e um decodificador construído nas regras RFC 3986 ou WHATWG não a reconhecerá. Os dados não podem passar de ida e volta por meio de ferramentas modernas. É um sinal comum de código anterior a ES3 e não foi atualizado desde 1990.

A função escape() foi projetada na era Netscape, antes de JavaScript ter padrões ou regras formais de codificação URL. Ele codifica a maioria dos caracteres não ASCII usando a notação %uXXXX, um código hexadecimal de quatro dígitos que ninguém mais usa e nenhum padrão define em qualquer lugar. Isso fazia sentido para usos únicos em um navegador, mas quebrava a compatibilidade com os padrões URL e tornava os dados impossíveis de decodificar em outro lugar.

escape() e unescape(): um design da era Netscape - suposições Latin-1, a invenção %uXXXX e por que nunca correspondeu a nenhum padrão

escape() e unescape() assumem que a entrada é Latin-1 (ISO 8859-1), a codificação de caracteres anterior a UTF-8 e Unicode. Eles convertem cada caractere em um código hexadecimal, usando %XX para caracteres Latin-1 de bits altos e %uXXXX para tudo fora de Latin-1. Um caractere não latino-1 como um emoji não pode ser representado de forma alguma. As funções são simples e rápidas, mas também são completamente inadequadas para qualquer caso de uso moderno.

Ambas as funções foram adicionadas a JavaScript antes da existência dos padrões. Eles foram descontinuados imediatamente após ES3 introduzir a codificação URL adequada em 1999. Eles permanecem em JavaScript para compatibilidade com versões anteriores – removê-los quebraria o código antigo. Mas qualquer código novo nunca deve usá-los. Eles são uma relíquia legada.

ES3 (1999) adiciona encodeURI e encodeURIComponent — UTF-8 codificação percentual alinhada com RFC 2396

ES3 introduziu duas funções: encodeURI e encodeURIComponent. Ambos executam codificação percentual UTF-8: convertem caracteres não ASCII em UTF-8 bytes e, em seguida, escrevem cada byte como% HH. Ambos se alinham com RFC 2396, que era atual na época. RFC 3986 veio depois e não alterou o comportamento da codificação. Essas funções ainda são o padrão hoje e devem ser usadas.

encodeURI destina-se a codificar um URI completo; encodeURIComponent destina-se a codificar um componente dentro de um URI, como um valor de consulta ou segmento de caminho. A diferença é absolutamente crítica e fácil de ser mal interpretada. encodeURI preserva caracteres estruturais como: /? # @ = & e ;. encodeURIComponent codifica todos eles, deixando-os seguros para serem incorporados em um URI maior.

Por que ! * ' ( ) ainda permanecem sem codificação - os caracteres RFC 2396 'marca' congelados no idioma depois que RFC 3986 os moveu

Ambas as funções deixam esses caracteres não codificados: letras, dígitos, hífen (-), sublinhado (_), ponto final (.), til (~) e os cinco sinais de pontuação! * ' ( ). As marcas vieram de RFC 2396, que as listou como caracteres de "marca" não reservados. RFC 3986 saiu em 2005 e moveu esses cinco para uma categoria diferente, mas JavaScript já havia congelado encodeURI e encodeURIComponent em 1999. Alterar quais caracteres eles deixam de lado quebraria o código existente, então eles permaneceram.

A decisão de manter essas cinco marcas não codificadas para compatibilidade com versões anteriores significa que a codificação de JavaScript não corresponde perfeitamente ao padrão RFC 3986 ou ao padrão WHATWG. Está próximo o suficiente para uso prático e alterá-lo agora é completamente impossível. Esta é uma lição de estabilidade API: depois de congelar o comportamento, você não poderá alterá-lo, mesmo que o padrão evolua.

Exemplo resolvido: a mesma string por meio de escape, encodeURI e encodeURIComponent — três saídas comparadas

Pegue a string "P&D (pesquisa) = café's". Execute-o através de escape(), encodeURI e encodeURIComponent. escape() produz "R%26D%20(research)%20%3D%20caf%E9's", misturando parênteses não codificados e apóstrofo com e comercial codificado por porcentagem e iguais. encodeURI produz "R&D%20(research)%20=%20caf%C3%A9's", deixando os sinais de "e" comercial e de igual sozinhos porque são estruturais. encodeURIComponent produz "R%26D%20%28research%29%20%3D%20caf%C3%A9%27s", codificando tudo, incluindo parênteses e apóstrofo.

Cole a mesma string no codificador e decodificador URL e alterne entre encodeURI e encodeURIComponent para ver a diferença. Em seguida, inspecione o que escape() produziria (você pode chamá-lo no console do navegador, embora ele avise você). Você vê imediatamente que as três funções produzem três resultados completamente diferentes.

Migrando de escape() — mapeando chamadas antigas para a função moderna correta e manipulando dados %uXXXX armazenados

O código antigo usando escape() deve ser atualizado. Se escape() foi usado para codificar um componente URI, substitua-o por encodeURIComponent. Se foi usado para codificar um URI completo, use encodeURI. Para dados armazenados que contêm sequências %uXXXX, você precisa de um decodificador personalizado: converta cada %uXXXX em um ponto de código Unicode e, em seguida, colete os pontos de código em uma string. O unescape() integrado de JavaScript lerá %uXXXX, mas o resultado pode não estar correto UTF-8.

Após substituir escape(), teste o código com strings contendo caracteres não ASCII, pontuação e caracteres especiais. O resultado deve agora corresponder ao que as ferramentas e padrões modernos esperam. Se o seu código for anterior a ES3 significativamente, ele também poderá usar outros padrões desatualizados; uma auditoria abrangente vale o esforço.

O que isso não cobre: ​​as APIs URL e URLSearchParams, que são abordadas separadamente

As APIs URL e URLSearchParams, adicionadas muito mais tarde, fornecem interfaces de nível superior para construção de URL e codificação de componentes. Eles lidam com todo o escape automaticamente e correspondem exatamente ao padrão WHATWG URL. Eles são a forma preferida de criar URLs programaticamente no JavaScript moderno.

Esta postagem cobre apenas as funções de codificação, não as APIs de nível superior. URL e URLSearchParams analisam a estrutura, selecionam regras de componentes e serializam o resultado, enquanto encodeURIComponent transforma uma string fornecida sem saber onde ela será colocada. Essa distinção é o limite: migre uma chamada escape() antiga de acordo com o fato de ela ter manipulado um valor ou um endereço e, em seguida, considere substituir a concatenação manual circundante pelas APIs estruturadas como um refator separado.

Conclusão: três funções, um par sobrevivente - como o codificador e decodificador URL mostra o comportamento moderno de encodeURI e encodeURIComponent lado a lado

O desenvolvimento moderno de JavaScript deve usar encodeURI ou encodeURIComponent, nunca escape(). As funções foram padronizadas em 1999 e não mudaram desde então. Eles codificam caracteres não ASCII como UTF-8 bytes e manipulam caracteres reservados padrão corretamente. A ferramenta codificadora e decodificadora URL implementa ambas as funções e permite que você veja seu comportamento lado a lado, facilitando a escolha da função certa para o seu componente.

Se você encontrar sequências %u em logs antigos ou dados armazenados, elas serão saídas de escape() e deverão ser migradas. A migração é direta quando você identifica o padrão. O código moderno nunca deveria produzi-los.