encodeURI vs encodeURIComponent: qual usar
O encodeURIComponent codifica 11 caracteres que o encodeURI deixa intactos, e são esses 11 caracteres que decidem se um valor continua dentro do seu parâmetro.
O JavaScript tem duas funções nativas para codificação percentual, e elas diferem em um único aspecto: quanto da sintaxe da URL deixam intacto. O encodeURI parte do princípio de que recebeu uma URL completa e preserva os caracteres que dão estrutura a ela, como /, ? e &. O encodeURIComponent parte do princípio de que recebeu um único dado e codifica também esses caracteres, para que o dado não seja confundido com estrutura. Resumindo: para colocar um valor em uma URL, use encodeURIComponent ou, melhor ainda, a API URL.
A diferença exata
As duas funções convertem para UTF-8 todo caractere que não preservam e escrevem cada byte como % seguido de dois dígitos hexadecimais maiúsculos, então é vira %C3%A9 e € vira %E2%82%AC. A única diferença é o conjunto de caracteres ASCII imprimíveis que cada uma deixa intacto:
| Caracteres | encodeURI | encodeURIComponent |
|---|---|---|
A-Z a-z 0-9 - _ . ! ~ * ' ( ) | Preservados | Preservados |
# $ & + , / : ; = ? @ | Preservados | Codificados |
Espaço e " % < > [ \ ] ^ ` { | } | Codificados | Codificados |
| Caracteres de controle e tudo o que está fora do ASCII | Codificados como bytes UTF-8 | Codificados como bytes UTF-8 |
A linha do meio conta toda a história. Esses 11 caracteres separam as partes de uma URL, então deixá-los intactos dentro de um valor muda o significado da URL:
const s = 'Tom & Jerry/2?';
encodeURI(s); // 'Tom%20&%20Jerry/2?'
encodeURIComponent(s); // 'Tom%20%26%20Jerry%2F2%3F'
Quando usar cada um
Use encodeURIComponent para tudo o que você insere em uma URL: o valor ou o nome de um parâmetro de query, um único segmento de caminho, um fragmento ou uma URL inteira passada como parâmetro, como redirect=. Veja o que dá errado com a outra função:
const q = 'rock & roll';
// Wrong: the & starts a new parameter
encodeURI('https://example.com/search?q=' + q);
// 'https://example.com/search?q=rock%20&%20roll'
// The server sees q = 'rock ' and a second parameter ' roll'
// Right
'https://example.com/search?q=' + encodeURIComponent(q);
// 'https://example.com/search?q=rock%20%26%20roll'
O encodeURI serve para o caso restrito em que você já tem uma URL completa e bem estruturada que só contém espaços ou caracteres fora do ASCII, como https://example.com/café?name=Zoë. Mesmo assim, há duas armadilhas:
- Ele codifica
%. Uma URL que já está parcialmente codificada acaba codificada duas vezes:encodeURI('https://example.com/a%20b')devolvehttps://example.com/a%2520b. - Ele codifica
[e]. Esses caracteres envolvem endereços IPv6, entãohttp://[::1]:8080/virahttp://%5B::1%5D:8080/, que deixa de ser um host válido.
Se você está montando uma URL a partir de partes, new URL() costuma ser a melhor ferramenta: ela codifica cada parte de acordo com o lugar onde ela aparece.
const url = new URL('https://example.com/search');
url.searchParams.set('q', 'café & thé');
url.href; // 'https://example.com/search?q=caf%C3%A9+%26+th%C3%A9'
Como elas se comparam com a RFC 3986
A RFC 3986 define dois grupos de caracteres. Os caracteres não reservados (seção 2.3) nunca precisam ser codificados: letras, dígitos e - . _ ~. Os caracteres reservados (seção 2.2) têm um papel sintático: os delimitadores gerais : / ? # [ ] @ e os subdelimitadores ! $ & ' ( ) * + , ; =.
Nenhuma das duas funções do JavaScript segue isso à risca. O encodeURIComponent deixa sem codificar cinco subdelimitadores: ! ' ( ) *. Navegadores e a maioria dos servidores não se importam, mas sistemas que calculam assinaturas sobre a URL codificada se importam. As assinaturas do OAuth 1.0 (RFC 5849) e a AWS Signature Version 4 exigem que todo caractere, exceto os não reservados, seja codificado, então um valor com apóstrofo ou parênteses pode gerar uma assinatura que não confere. A correção habitual é esta:
function encodeRFC3986(str) {
return encodeURIComponent(str).replace(/[!'()*]/g,
c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
}
encodeRFC3986("it's (fine)!*"); // 'it%27s%20%28fine%29%21%2A'
URLSearchParams e o sinal de mais
O URLSearchParams, e portanto url.searchParams, não usa nenhuma das duas funções. Ele segue o formato application/x-www-form-urlencoded que os formulários HTML enviam, que difere em dois pontos: o espaço vira +, e só letras, dígitos e * - . _ ficam intactos, então ! ' ( ) ~ também são codificados.
encodeURIComponent('a b+c'); // 'a%20b%2Bc'
new URLSearchParams({ q: 'a b+c' }).toString(); // 'q=a+b%2Bc'
Os dois resultados estão corretos em uma query string. O problema começa quando o lado que codifica e o lado que decodifica não seguem a mesma convenção. O decodeURIComponent não transforma + em espaço, então decodeURIComponent('a+b%2Bc') devolve a+b+c, enquanto new URLSearchParams('q=a+b%2Bc').get('q') devolve a b+c. Decodifique query strings com URLSearchParams, ou troque + por espaço antes de chamar decodeURIComponent. Em um caminho, + é apenas um sinal de mais.
decodeURI vs decodeURIComponent
As funções de decodificação espelham as de codificação. O decodeURIComponent decodifica todas as sequências de escape. O decodeURI preserva os escapes dos caracteres que o encodeURI não teria codificado, # $ & + , / : ; = ? @, para que decodificar uma URL não mude a sua estrutura: decodeURI('%C3%A9%20%2F%3F') devolve é %2F%3F, enquanto decodeURIComponent devolve é /?.
As duas lançam URIError: URI malformed (a redação do V8, usada pelo Chrome e pelo Node.js) quando um % não é seguido de dois dígitos hexadecimais, como em 100%, ou quando os bytes não são UTF-8 válido, como em %E9 vindo de um sistema que codificou é em Latin-1. As funções de codificação lançam o mesmo erro para uma metade isolada de um par substituto (surrogate pair), como uma string cortada no meio de um emoji.
escape() não é uma alternativa
O escape() é uma função legada mantida por causa de código antigo. Ele deixa intactos @ * _ + - . / e não usa UTF-8: caracteres até U+00FF viram um único byte Latin-1, e tudo o que fica acima disso vira uma sequência %u fora do padrão. escape('café €') devolve caf%E9%20%u20AC, que servidores que esperam UTF-8 não conseguem decodificar, e decodeURIComponent('%E9') lança erro. Troque-o por encodeURIComponent, e o unescape() por decodeURIComponent, lembrando que dados já armazenados no formato antigo precisam ser convertidos.
Codificação dupla
Toda função de codificação codifica o próprio % como %25, então rodar uma delas duas vezes produz sequências como %2520: encodeURIComponent(encodeURIComponent('a b')) devolve a%2520b. Codifique valores brutos exatamente uma vez, no momento em que eles entram na URL, e nunca codifique uma string que você já recebeu codificada. O único caso legítimo é uma URL dentro de outra URL: codificar https://example.com/?q=a%20b como parâmetro dá, corretamente, ...q%3Da%2520b, e o lado que recebe decodifica uma vez para recuperar a URL interna.
Para comparar os dois modos com a sua própria entrada, o codificador de URL tem um seletor para encodeURIComponent e encodeURI. O decodificador de URL decodifica enquanto você digita e trata + como espaço, como fazem os dados de formulário, então um valor que contém um sinal de mais de verdade deve trazê-lo como %2B.