Pular para o conteúdo

Por que o link quebra quando você cola

Você manda o endereço certo, a outra pessoa clica e cai numa página que não existe. O endereço não mudou no caminho — o que aconteceu é que alguém, do outro lado, decidiu onde ele terminava. Essa decisão é a metade do problema que ninguém explica.

O conserto, antes da explicação

Se o endereço tem parêntese ou termina em pontuação

Escreva ele entre os sinais de menor e maior — assim: <https://exemplo.com.br/verbete_(planta)>. Os sinais não fazem parte do endereço e existem exatamente para dizer onde ele acaba. Quem recomenda isso é a norma dos endereços, desde 2005.

Se você está montando um endereço com dados dentro

Codifique só o valor, no modo componente do codificador de URL — nunca o endereço inteiro. Codificar tudo destrói os separadores que fazem o endereço funcionar.

Se você está desfazendo um endereço

Só use o modo formulário no pedaço que vem depois do ponto de interrogação. Nele, e só nele, o sinal de mais significa espaço — usar esse modo no caminho troca o mais por espaço e o arquivo some.

São dois momentos, e o segundo é o que ninguém vê

Um link atravessa duas decisões antes de virar clique. A primeira é a escrita: alguém monta o endereço e escolhe o que vira %XX. Essa é a parte que toda página sobre o assunto explica. A segunda é a leitura: o endereço chega dentro de um texto — uma mensagem, um email, uma legenda — e o programa que mostra esse texto precisa descobrir onde o endereço começa e onde termina. Ninguém marcou isso para ele.

É por isso que existe aquele caso irritante em que o link funciona perfeitamente quando você abre no navegador e quebra quando você manda para alguém. O endereço está certo nos dois casos. O que muda é quem está lendo o texto em volta dele.

Os 7 casos abaixo são os que aparecem de verdade. A coluna do momento diz onde a decisão foi tomada — 4 deles não têm nada a ver com codificação, e é por isso que codificar não resolve.

O que você colaQuebra emO que acontece
(veja https://pt.wikipedia.org/wiki/Cana_(planta))leituraO link abre numa página que não existe, faltando o final.
Está tudo em https://exemplo.com.br/relatorio.leituraO link leva a um endereço com um ponto sobrando no fim.
https://exemplo.com.br/relatório final.pdfleituraO link corta na metade e o resto vira texto solto.
https://exemplo.com.br/manual/C++/indice.pdfescritaO arquivo não é encontrado, e o endereço que chega tem espaços no lugar dos sinais de mais.
https://exemplo.com.br/notas/relatorio#2.pdfescritaO servidor responde que o arquivo relatorio não existe.
https://exemplo.com.br/busca?nome=Tom & Jerry&pagina=2escritaO sistema recebe o nome como Tom e perde o resto.
https://exemplo.com.br/busca?q=teste&nbsp;leituraO final do endereço some, junto com o último parâmetro.

Nos casos de leitura, dá para mostrar o estrago exato: rodamos a única regra de recorte que alguém publicou — a da GitHub Flavored Markdown, explicada mais abaixo — em cima do que a pessoa colou. 3 dos 7 perdem pedaço:

Vira linkFica de foraPor quê
https://pt.wikipedia.org/wiki/Cana_(planta))O endereço já termina em parêntese e ainda foi colado dentro de outro. Quem detecta o link conta os parênteses e devolve o que sobrou ao texto.
https://exemplo.com.br/relatorio.O ponto que fecha a frase encosta no endereço. A regra publicada manda devolver a pontuação final ao texto, mas nem todo aplicativo aplica.
https://exemplo.com.br/relatórionadaEspaço é o delimitador mais antigo que existe: para quem lê, o endereço acabou ali.
https://exemplo.com.br/busca?q=teste&nbsp;Terminar em ponto e vírgula depois de um & faz o detector achar que aquilo é uma entidade HTML, e não parte do endereço.

O parêntese é o único que quebra dos dois lados

Esse é o achado desta página, e ele só aparece quando se lê os dois documentos ao mesmo tempo — porque nenhum dos dois sabe da existência do outro.

Do lado da leitura, a regra está escrita, com exemplos numerados, na especificação do Markdown do GitHub. Ela é a razão de o seu link de verbete com parênteses chegar sem o final:

“When an autolink ends in ), we scan the entire autolink for the total number of parentheses. If there is a greater number of closing parentheses than opening ones, we don’t consider the unmatched trailing parentheses part of the autolink.”
GitHub Flavored Markdown Spec, extensão de autolink

Traduzindo: se o endereço acaba em parêntese fechando, o programa conta quantos abrem e quantos fecham dentro dele. Sobrou fechando? Devolve para o texto. A intenção é boa — é para você poder escrever um endereço entre parênteses numa frase. O efeito colateral é que todo endereço que legitimamente termina em parêntese perde o final.

A mesma seção resolve o ponto final da frase pelo mesmo raciocínio, e de novo com efeito colateral em quem tem ponto no fim do endereço:

“Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will not be considered part of the autolink, though they may be included in the interior of the link.”
GitHub Flavored Markdown Spec, extensão de autolink

Do lado da escrita, a função que praticamente todo site usa para escapar um valor — encodeURIComponent — não escapa parêntese. Também não escapa exclamação, apóstrofo nem asterisco. São exatamente estes 5 caracteres, medidos aqui rodando a função em todo o ASCII imprimível:

  • !exclamação
  • 'apóstrofo
  • (abre parêntese
  • )fecha parêntese
  • *asterisco

E isso não é descuido de navegador: está na definição da linguagem. A operação que o JavaScript usa para codificar carrega o conjunto escrito à mão:

“Let alwaysUnescaped be the string-concatenation of the ASCII word characters and "-.!~*'()".”
ECMAScript, operação Encode (§19.2.6.5)

Esses nove caracteres têm nome e data. São o conjunto mark da RFC 2396, de agosto de 1998:

“mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
RFC 2396 §2.3, agosto de 1998

Essa RFC foi aposentada em janeiro de 2005 pela RFC 3986, que é a norma dos endereços até hoje. E a lista encolheu de nove para quatro:

“unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
RFC 3986 §2.3, janeiro de 2005

Os cinco que sobraram — inclusive os dois parênteses — deixaram de ser caracteres comuns e viraram sub-delims, pontuação com função reservada. A função do JavaScript continua tratando eles como texto qualquer, vinte e um anos depois. Se ela seguisse a norma atual, o parêntese sairia como %28 e %29, e o link do verbete não quebraria em mensagem nenhuma.

Dois codificadores no mesmo navegador, 6 diferenças

A prova mais curta de que ninguém precisa acreditar em nós: o seu navegador tem duas maneiras oficiais de codificar um valor, e elas dão respostas diferentes. encodeURIComponent responde à especificação do JavaScript; URLSearchParams, que é o que monta uma query string moderna, responde à especificação do URL da WHATWG — essa, sim, alinhada com a RFC 3986. Rodando as duas em cima de todo o ASCII imprimível, elas discordam em 6 caracteres:

  • espaçoencodeURIComponent devolve %20 e o serializador do padrão devolve +.
  • exclamaçãoencodeURIComponent devolve ! e o serializador do padrão devolve %21.
  • apóstrofoencodeURIComponent devolve ' e o serializador do padrão devolve %27.
  • abre parênteseencodeURIComponent devolve ( e o serializador do padrão devolve %28.
  • fecha parênteseencodeURIComponent devolve ) e o serializador do padrão devolve %29.
  • tilencodeURIComponent devolve ~ e o serializador do padrão devolve %7E.

As duas estão certas, cada uma pela sua especificação. É por isso que “codificar do jeito certo” é uma frase incompleta: falta dizer certo para quê.

E a nossa ferramenta? O modo formulário dela faz encodeURIComponent e troca espaço por mais, então herda a mesma sobra de 1998 — ela difere do serializador do padrão em 5 caracteres, que são exclamação, apóstrofo, abre parêntese, fecha parêntese, til. As duas saídas decodificam para o mesmo texto, então para montar link é indiferente. A diferença aparece num lugar só, e vale saber qual: quando alguém compara duas codificações letra a letra, que é o que uma assinatura de requisição faz. O protocolo OAuth escreveu isso na cara, e o aviso final é sobre exatamente este assunto:

“Characters in the unreserved character set as defined by [RFC3986], Section 2.3 (ALPHA, DIGIT, "-", ".", "_", "~") MUST NOT be encoded. All other characters MUST be encoded. […] It MAY be different from the percent-encoding functions provided by web-development frameworks.”
RFC 5849 §3.6, o padrão do OAuth 1.0

A tabela: o que cada modo faz com cada caractere

Esta é a tabela que a gente queria ter achado pronta. Cada linha saiu de rodar as quatro funções de verdade — as três do navegador e a nossa — no caractere da primeira coluna, em 12 de agosto de 2026.

CaractereComponenteURL inteiraFormulário (padrão)
espaço%20%20+
+ mais%2B+%2B
# cerquilha%23#%23
& e comercial%26&%26
= igual%3D=%3D
? interrogação%3F?%3F
/ barra%2F/%2F
% porcentagem%25%25%25
( abre parêntese((%28
) fecha parêntese))%29
! exclamação!!%21
' apóstrofo''%27
* asterisco***
~ til~~%7E
[ abre colchete%5B%5B%5B
] fecha colchete%5D%5D%5D
á a com acento%C3%A1%C3%A1%C3%A1
ç cedilha%C3%A7%C3%A7%C3%A7

Duas leituras que essa tabela entrega de graça. A primeira: a coluna do meio preserva os separadores — é o modo para um endereço que já está pronto, e usar o modo componente nele destruiria a estrutura. A segunda: repare no que a coluna do meio faz com os colchetes, e guarde para o fim desta página.

O sinal de mais só significa espaço num lugar

Esse é o erro de escrita mais caro da lista, porque ele acontece depois, no sistema de outra pessoa, e a página some sem explicação. A regra verdadeira é estreita: o mais vira espaço dentro do analisador de formulário, que roda no nome e no valor de cada parâmetro — nunca no caminho.

“Replace any 0x2B (+) in name and value with 0x20 (SP).”
URL Standard (WHATWG), analisador de application/x-www-form-urlencoded

Repare no “name and value”. A norma dos endereços, a RFC 3986, não dá nenhum significado especial ao mais: ali ele é só um sub-delim. Quem trata o endereço inteiro como formulário aplica a regra num lugar onde ela não vale, e o resultado é este — medido, com o nosso próprio decodificador:

  • O caminho de verdade: /manual/C++/indice.pdf
  • Lido como componente: /manual/C++/indice.pdf — volta inteiro.
  • Lido como formulário: /manual/C /indice.pdf — dois espaços onde havia dois sinais, e o arquivo não existe mais.

O contrário também morde. Um espaço codificado como %20 é entendido em qualquer lugar do endereço; codificado como mais, só depois do ponto de interrogação. É por isso que o modo componente é o padrão da nossa ferramenta: ele é o único que dá o mesmo resultado nos dois pedaços.

A norma dos endereços previu isso tudo em 2005

A parte mais desconcertante desta pesquisa: o Apêndice C da RFC 3986 se chama “Delimitando um URI num contexto” e descreve, com essas palavras, o problema que ainda quebra link no seu celular vinte e um anos depois.

“In such cases, it is important to be able to delimit the URI from the rest of the text, and in particular from punctuation marks that might be mistaken for part of the URI.”
RFC 3986, Apêndice C

O mesmo apêndice tem um parágrafo que parece escrito para quem copia endereço de PDF e de documento do Word, onde o texto foi quebrado em linhas por um programa de diagramação:

“No whitespace should be introduced after a hyphen (-) character. Because some typesetters and printers may (erroneously) introduce a hyphen at the end of line when breaking it […] the hyphen may or may not actually be part of the URI.”
RFC 3986, Apêndice C

É o mesmo tipo de sujeira invisível que a gente já mediu em texto copiado do Word: o que você vê na tela não é o que está no meio da linha.

E a solução que o apêndice recomenda é de uma simplicidade constrangedora — os sinais de menor e maior em volta:

“Using <> angle brackets around each URI is especially recommended as a delimiting style for a reference that contains embedded whitespace.”
RFC 3986, Apêndice C

Essa recomendação de 2005 está viva num lugar que você usa: é exatamente a sintaxe que a API do Slack exige para mandar link, e a documentação dele admite o defeito do espaço na mesma página — “URLs with spaces will break, so we recommend that you remove any spaces from your URL links”, com essas palavras. Quem escreveu a norma sabia; quem escreve os aplicativos de mensagem, na maioria, não leu.

O achado lateral: a função de endereço pronto quebra IPv6

Aquele detalhe dos colchetes, da tabela acima. O modo “URL inteira” existe para não estragar um endereço já montado, e ele preserva os separadores todos — mas escapa colchete, porque o conjunto que o JavaScript manda preservar é o da RFC 2396, e naquela época colchete não era caractere de endereço. Foi a RFC 3986 que o tornou legal, justamente para escrever endereço de IPv6. Resultado, medido:

  • Entra: http://[::1]:3000/painel
  • Sai: http://%5B::1%5D:3000/painel — e esse endereço não abre.

É pequeno, é raro e é a mesma causa de tudo que está acima: uma função congelada numa norma que foi substituída.

O que esta página não mediu

Como o WhatsApp, o Instagram, o iMessage ou o seu cliente de email recortam um link colado. Nenhum dos quatro publica essa regra em lugar nenhum, e não temos como dirigir esses aplicativos daqui — afirmar medição que não houve seria o defeito que a gente cobra dos outros. A regra que roda nesta página é a da GFM, que é a única publicada, e ela explica o comportamento que qualquer um observa; não é prova de que os outros façam idêntico.

Se quiser conferir no seu, o teste leva dez segundos: mande para si mesmo uma mensagem com https://exemplo.com.br/verbete_(planta) e olhe se o link azul cobre o fecha-parêntese. Depois mande a mesma coisa entre menor e maior. O segundo funciona em quase tudo, e é isso que a norma recomendava desde o começo.

Para montar o endereço sem chutar, o codificador de URL daqui faz os três modos com a diferença explicada em cada um — e nada do que você colar sai do seu navegador. Se o assunto for o pedaço legível do endereço, tem também o que é slug de URL, e se o link que quebra é o que virou imagem, por que o QR Code não lê.

Os consertos, em uma linha cada

O arquivo não é encontrado, e o endereço que chega tem espaços no lugar dos sinais de mais.
Decodifique caminho no modo componente e reserve o modo formulário para o que vem depois do ponto de interrogação.
O servidor responde que o arquivo relatorio não existe.
Escreva a cerquilha do nome do arquivo como %23.
O sistema recebe o nome como Tom e perde o resto.
Codifique só o VALOR, no modo componente: o & vira %26.
O link abre numa página que não existe, faltando o final.
Deixe o endereço fora dos parênteses, ou escreva o final dele como %28 e %29.
O link leva a um endereço com um ponto sobrando no fim.
Ponha o endereço no fim da frase sem ponto, ou entre < e >.
O link corta na metade e o resto vira texto solto.
Codifique o espaço como %20 antes de colar. É o que a ferramenta daqui faz no modo componente.
O final do endereço some, junto com o último parâmetro.
Não termine endereço em ponto e vírgula; use < e > em volta.