Construir um URL com parâmetros que sobrevive a um copiar e colar
Publicado a 11/08/2026 · 12 min de leitura · Ferramentas para programadores
Daniel Okonkwo — Programador front-end e redator de Tecnologia na Allin
Desempenho web · Formatos de ficheiro
Verificado a partir de 4 fontes
Uma cadeia de consulta parte-se quando um carácter que significa algo para o URL — um espaço, um E comercial, um sinal de igual, um cardinal — fica tal e qual dentro de um valor. A codificação por cento resolve isso, mas há três codificações e elas divergem, e a divergência vê-se no espaço. encodeURIComponent escreve um espaço como %20 e deixa intactos ! ' ( ) * ~. O serializador application/x-www-form-urlencoded, o que URLSearchParams e todos os formulários HTML usam, escreve um espaço como + e codifica ! ' ( ) ~ deixando o * intacto. O RFC 3986 estrito codifica tudo o que fica fora de A-Z a-z 0-9 - . _ ~, portanto também escapa o * e mantém o ~. Este gerador oferece as três, e o seu modo formulário foi comparado com o URLSearchParams do Node sobre dezassete valores, incluindo um emoji, uma quebra de linha e uma string acentuada: idêntico byte a byte de todas as vezes. Codifica de facto duas vezes: escreva %20 como valor e obtém %2520. É correto — está a escrever um valor em bruto, não um já codificado — e o analisador da própria ferramenta devolve-o ao literal %20. O erro a sério está noutro sítio. Dê-lhe um URL base com um fragmento, https://example.com/page#section, e a cadeia é acrescentada depois do fragmento: o analisador de URL do navegador reporta então uma parte de consulta vazia e mete todos os parâmetros dentro do hash, onde nenhum servidor os verá alguma vez. O seu analisador também deita fora o fragmento, e uma sequência antiga como %E9 falha a descodificação, fica como texto literal e volta como %25E9 na construção seguinte.
Três codificações, uma diferença visível: %20 ou +. O modo formulário do gerador reproduz URLSearchParams byte a byte em dezassete valores — mas dê-lhe um URL base com um fragmento e todos os parâmetros vão parar dentro do hash, onde nenhum servidor os vê.
Duas normas, e os navegadores aplicam a mais recente
O RFC 3986 é o documento mais antigo e o que toda a gente cita. Divide os carateres entre não reservados — A-Z, a-z, 0-9 e os quatro sinais - . _ ~ — e tudo o resto, reservado ou a codificar por cento. É um modelo limpo e não é o que o seu navegador executa. Os navegadores aplicam o WHATWG URL Standard, um documento vivo que especifica a análise e a serialização através de vários conjuntos de codificação com nome, um por parte do URL. Os dois concordam nos casos comuns e divergem nas margens, e é nas margens que um URL deixa de funcionar.
A norma URL é explícita quanto a essa relação. Diz que usar o seu conjunto de codificação «component» com UTF-8 dá resultados idênticos ao encodeURIComponent do JavaScript. E define o conjunto application/x-www-form-urlencoded como o conjunto component mais ! ' ( ) ~ — e depois diz o mesmo ao contrário, na frase que vale a pena decorar: esse conjunto contém todos os pontos de código exceto os alfanuméricos ASCII e * - . _ . Quatro carateres. É essa toda a lista segura de um envio de formulário, e explica num relance porque é que os dois modos divergem exatamente em ! ' ( ) ~ enquanto ambos deixam o asterisco intacto.
O terceiro modo do gerador, RFC 3986 estrito, é o que se usa quando um servidor assina o URL. Pega na saída do encodeURIComponent e escapa ainda ! ' ( ) * , deixando o ~ intacto, porque o til é não reservado no RFC 3986 e escapá-lo mudaria a assinatura. As passarelas de pagamento, as assinaturas OAuth 1.0 e algumas API empresariais mais antigas calculam um hash sobre a string codificada, portanto um codificador que deixa um apóstrofo tal e qual produz um hash diferente do deles e o pedido é rejeitado sem explicação aproveitável.
Sim, codifica duas vezes, e é a resposta certa
A dupla codificação é a queixa clássica contra os geradores de URL, por isso foi testada diretamente. Escreva %20 num campo de valor e a saída é %2520. Escreva caf%C3%A9 e obtém caf%25C3%25A9. Escreva um simples sinal de percentagem e obtém %25. Não é um erro: é a definição do campo. A caixa de valor contém o valor que o seu utilizador escreveu, e se esse valor for realmente a sequência c a f % 2 0, então %2520 é a única codificação que a entregará.
A forma de verificar se um gerador acerta nisto não é olhar para uma saída, mas fazer uma ida e volta. Construa um URL, cole-o de volta no campo de análise da ferramenta e construa outra vez. Seis valores passaram por esse ciclo — uma frase acentuada com um E comercial, um sinal de mais, um %20 literal, um cardinal dentro de um valor, um valor vazio e um objeto JSON — e os seis voltaram idênticos byte a byte. O %2520 colado descodificou-se para o literal %20, que se recodificou para %2520. Um gerador que retirasse uma camada para ficar arrumado falharia precisamente aqui, em silêncio, no único valor que interessa.
O erro: um fragmento no URL base engole todos os parâmetros
O gerador junta a sua cadeia à base procurando um ponto de interrogação: se a base já tem um, acrescenta com um E comercial; senão, acrescenta com um ponto de interrogação. Os dois ramos acrescentam no fim. Um URL não funciona assim. A ordem das partes é fixa — esquema, autoridade, caminho, consulta, fragmento — e o fragmento vem em último, portanto tudo o que se escreva depois pertence-lhe.
Ponha a base em https://example.com/page#section, acrescente q e page, e a ferramenta imprime https://example.com/page#section?q=caf%C3%A9%20%26%20croissant&page=2. Parece correto. Entregue essa cadeia ao analisador de URL do navegador e ele reporta um caminho /page, uma parte de consulta vazia e um hash que contém #section?q=caf%C3%A9%20%26%20croissant&page=2 — todos os parâmetros dentro do fragmento. Um fragmento nunca é enviado ao servidor. A página vai carregar, não aparecerá erro nenhum em lado nenhum, e os parâmetros simplesmente não existirão do ponto de vista do servidor. A variante com uma consulta já presente, https://example.com/page?a=1#frag, conta o mesmo: a parte de consulta volta como ?a=1 e os novos parâmetros estão dentro do hash.
A análise tem o problema simétrico: corta a entrada no primeiro cardinal e deita fora tudo o que vem a seguir. Cole https://example.com/p?a=1#frag e a base volta como https://example.com/p, sem fragmento. Portanto não pode usar a ferramenta de todo num URL que tenha fragmento — perde-o à entrada e coloca mal a consulta à saída. Enquanto não for corrigido, o remendo é retirar o fragmento você mesmo, construir a cadeia e voltar a montar à mão: a base, depois a consulta, depois o cardinal, por essa ordem.
Três pormenores menores a saber antes de confiar nele
Primeiro, uma sequência por cento que não consegue descodificar fica como texto literal e é depois recodificada à saída. Cole um URL com a=%E9 — uma sequência Latin-1 de um só byte, como as que muitíssimos sistemas anteriores a 2010 ainda emitem — e o descodificador lança um erro, a ferramenta apanha-o e guarda os três carateres % E 9 como valor. Construa outra vez e passa a a=%25E9, um URL diferente do que colou. Nada o avisa. O prudente é rever qualquer parâmetro que volte a conter um sinal de percentagem.
Segundo, o gerador agrupa os parâmetros por chave em vez de manter a ordem das suas linhas. As linhas a=1, b=2, a=3 saem como a=1&a=3&b=2: as duas linhas a são juntadas e b desce. Para um servidor web normal isto é inofensivo, já que quase nada se importa com a ordem dos parâmetros. Para um pedido assinado não é, porque a assinatura é calculada sobre a cadeia exata. Compare a saída com a ordem que escreveu sempre que o destinatário fizer um hash da consulta.
Terceiro, a opção de ordenar chaves ordena com a comparação sensível à configuração regional do navegador, não por ponto de código. Com as chaves b, a, B e _x produz _x, a, b, B. Uma ordenação por ponto de código — a que qualquer esquema de assinatura existente especifica — produz B, _x, a, b. As duas diferem assim que as suas chaves misturam maiúsculas e minúsculas ou começam por um sublinhado, que é justamente o que os nomes de parâmetros de uma API assinada costumam fazer. Use a opção para tornar legível um URL longo; não a use para o canonizar.
Duas coisas que faz bem e que é fácil errar. As suas quatro sintaxes de valores repetidos codificam todas os parênteses retos: a[]=1 escreve-se a%5B%5D=1 e a[0]=1 escreve-se a%5B0%5D=1, o que é correto face ao RFC 3986, onde os parênteses retos estão reservados a literais de anfitrião IPv6 — e o que PHP, Rails e Express descodificam sem problema. E em modo vírgula, a vírgula entre valores também é codificada: color=red,blue escreve-se color=red%2Cblue, legal e seguro com qualquer servidor que descodifique antes de dividir.
| Valor escrito | encodeURIComponent | x-www-form-urlencoded | RFC 3986 estrito |
|---|---|---|---|
| two words | two%20words | two+words | two%20words |
| café & croissant | caf%C3%A9%20%26%20croissant | caf%C3%A9+%26+croissant | caf%C3%A9%20%26%20croissant |
| C'est (chouette) ! | C'est%20(chouette)%20! | C%27est+%28chouette%29+%21 | C%27est%20%28chouette%29%20%21 |
| ~tilde*star | ~tilde*star | %7Etilde*star | ~tilde%2Astar |
| a=b&c#d | a%3Db%26c%23d | a%3Db%26c%23d | a%3Db%26c%23d |
| %20 escrito literalmente | %2520 | %2520 | %2520 |
Perguntas frequentes
- Que codificação escolho se não sei o que o servidor espera?
- Escolha encodeURIComponent, o modo por omissão. Um espaço passa a %20, que qualquer servidor descodifica bem, ao passo que o + só é interpretado como espaço por código que sabe estar a ler dados de formulário. Essa assimetria é toda a razão para preferir %20 num URL que vai colar num e-mail, numa mensagem de chat ou numa folha de cálculo: sobrevive a ser lido por algo que não sabe que veio de um formulário. Mude para x-www-form-urlencoded só quando estiver a reproduzir o que um formulário do navegador enviaria — por exemplo ao depurar porque um envio difere do seu URL construído à mão. Mude para o RFC 3986 estrito quando um servidor assinar ou fizer hash da cadeia de consulta, porque os escapes adicionais de ! ' ( ) * são os que a maioria das bibliotecas de assinatura produz.
- Porque desapareceram os meus parâmetros quando a ligação acabava em #section?
- Porque foram escritos depois do fragmento, e tudo o que segue um cardinal é o fragmento. Este gerador acrescenta a cadeia no fim daquilo que lhe deu como base, portanto uma base https://example.com/page#section produz https://example.com/page#section?q=x, e o navegador lê toda a cauda como um único fragmento. Um fragmento é resolvido inteiramente no cliente; não faz parte da linha de pedido e nenhum servidor o vê. Corrija à mão: pegue na base sem o fragmento, acrescente a cadeia e volte a pôr o fragmento mesmo no fim, o que dá https://example.com/page?q=x#section. Se colar esse URL corrigido de volta na ferramenta, ela analisará bem a consulta mas perderá outra vez o fragmento, portanto guarde o fragmento noutro sítio enquanto trabalha.
- Como envio uma lista de valores para o mesmo parâmetro?
- Não há norma, e é por isso que a ferramenta oferece quatro sintaxes. Repetir a chave, color=red&color=blue, é o que um formulário HTML produz quando várias caixas partilham um nome, e o que URLSearchParams.getAll devolve; é a opção por omissão mais segura. Os parênteses retos, color[]=red&color[]=blue, são uma convenção do PHP que Rails e vários frameworks de PHP também leem. Os parênteses indexados, color[0]=red, preservam a posição e usam-se onde o servidor reconstrói um array ordenado. Um valor unido por vírgulas, color=red,blue, é um único parâmetro com um único valor que o servidor divide sozinho. Escolha o que o seu servidor documentar; se não documentar nenhum, repita a chave. Note que a ferramenta codifica os parênteses e a vírgula, o que é legal e que todos esses frameworks descodificam antes de analisar.
- É prudente pôr um endereço de e-mail ou um número de encomenda numa cadeia de consulta?
- Tecnicamente vai funcionar, e mesmo assim convém evitar. Uma cadeia de consulta faz parte do URL, e os URL são escritos no histórico do navegador, nos registos de acesso do servidor, nos de proxies e CDN, e no cabeçalho Referer enviado a qualquer script de terceiros que a página carregue. Qualquer dado pessoal que ponha aí é copiado para todos esses sítios por sistemas a quem ninguém disse que era pessoal, e fica lá durante o período de retenção de cada um. Ponha identificadores na cadeia à vontade — um id de produto, um número de página, um nome de campanha — e ponha tudo o que identifique uma pessoa no corpo de um pedido POST, ou por trás de um token opaco curto que o seu próprio servidor resolva. O mesmo vale para tudo o que concede acesso: um token num URL é um token num ficheiro de registo.
- Quanto pode um URL medir antes de algo o truncar?
- Nenhuma especificação fixa um limite; cada implementação fixa o seu, e o que morde primeiro geralmente não é o navegador. O RFC 3986 recusa explicitamente impor um máximo e recomenda antes que tudo o que manipule URL aguente comprimentos maiores do que espera. Na prática, os navegadores aceitam dezenas de milhares de carateres, enquanto servidores web e proxies costumam recusar uma linha de pedido acima de cerca de 8 kilobytes e respondem com um estado 414. As ferramentas de análise e de resultados de pesquisa truncam muito antes. O conselho prático não depende de nenhum número exato: se a sua cadeia de consulta chega aos milhares de carateres, os dados pertencem a um corpo POST ou a um identificador curto, não a uma ligação. Os URL longos também se partem ao serem colados em clientes de e-mail e aplicações de mensagens, que os cortam numa coluna e transformam uma ligação em duas.
Artigos que podem interessar-lhe
Todos os guias →Ferramentas relacionadas
Estes números vêm de executar estas ferramentas sobre ficheiros reais e comprimir o resultado, não de uma promessa de fabricante. O peso depende inteiramente do ficheiro: uma folha de estilos escrita com comentários longos não comprime como uma sem eles, e os seus números não serão os nossos. Os minificadores também não são equivalentes — dois deste site divergem perante a mesma entrada — portanto trate qualquer saída minificada como código novo, a rever antes de publicar. Guarde o original legível no controlo de versões, minifique na compilação e confirme a página num navegador antes de publicar.
Fontes
- WHATWG — URL Standard — the percent-encode sets: the component set, which the specification states gives identical results to encodeURIComponent when used with UTF-8, and the application/x-www-form-urlencoded set, which contains every code point except the ASCII alphanumerics and * - . _
- IETF — RFC 3986, Uniform Resource Identifier (URI): Generic Syntax — section 2.3 on the unreserved set A-Z a-z 0-9 - . _ ~, section 3.5 on the fragment being the last component, and section 3 on the absence of any maximum length
- Ecma International — ECMA-262, the ECMAScript language specification — encodeURIComponent and the unescapedURIComponentSet it preserves, which is where the exceptions for ! ' ( ) * ~ come from
- WHATWG — HTML Standard, form control infrastructure — how a form's entry list is serialised, and why a submitted form writes a space as + rather than %20
Detetaste um erro neste artigo?