# Mojibake: por que café vira cafÃ©, e como recuperar o texto

> O texto fica embaralhado quando bytes gravados num charset são lidos em outro. Como surgem os padrões clássicos (UTF-8 lido como windows-1252, arquivos do DOS e do Windows lidos um como o outro, EBCDIC lido como Latin-1), por que o navegador lê Latin-1 como windows-1252, como um único caractere invisível no rótulo do charset embaralha uma página inteira, como desfazer o estrago e quando isso não é possível, e o que a codificação de uma política de WAF do BIG-IP tem a ver com tudo isso.

Source: https://ronutz.com/pt-BR/learn/mojibake-and-code-pages  
Updated: 2026-10-03  
Related tools: https://ronutz.com/pt-BR/tools/charset-equivalency, https://ronutz.com/pt-BR/tools/unicode-inspector, https://ronutz.com/pt-BR/tools/url-inspector, https://ronutz.com/pt-BR/tools/ascii-table

---

Um arquivo, uma página web ou um campo de banco de dados guarda bytes, não caracteres. Para transformar esses bytes de volta em texto, é preciso saber em que charset eles foram gravados, e nada nos próprios bytes diz qual foi. Se a leitura usar o charset errado, todo caractere fora do ASCII vira outra coisa: "café" vira "cafÃ©", "don’t" vira "donâ€™t". Esse texto embaralhado tem nome, mojibake, e quase sempre vem dos mesmos poucos erros. O [conversor de charsets](https://ronutz.com/pt-BR/tools/charset-equivalency) mostra cada um deles e desfaz a maioria.

## UTF-8 lido como windows-1252

No UTF-8, o é ocupa dois bytes, C3 A9 (veja [UTF-8, UTF-16 e UTF-32, byte por byte](https://ronutz.com/pt-BR/learn/utf-8-and-utf-16-byte-by-byte)). O windows-1252 usa um byte por caractere, então lê esses dois bytes como dois caracteres: C3 é Ã e A9 é ©. Toda letra latina acentuada começa com C3 em UTF-8, e é por isso que o Ã aparece em todo lugar:

| Caractere | Bytes em UTF-8 | Lido como windows-1252 |
|---|---|---|
| é | C3 A9 | Ã© |
| ç | C3 A7 | Ã§ |
| ã | C3 A3 | Ã£ |
| ü | C3 BC | Ã¼ |
| É | C3 89 | Ã‰ |
| ’ | E2 80 99 | â€™ |
| “ | E2 80 9C | â€œ |
| € | E2 82 AC | â‚¬ |
| ™ | E2 84 A2 | â„¢ |

Dois casos escondem parte do estrago. O í é C3 AD, e o AD, no windows-1252, é o soft hyphen (hífen condicional), que não aparece na tela; assim, o í vira um Ã sozinho. Já o ” é E2 80 9D, e o 9D é um dos cinco bytes do windows-1252 que o WHATWG Encoding Standard associa a um caractere de controle C1 invisível: o ” vira â€ seguido de U+009D. Se algum software no caminho remove os caracteres de controle, ele joga fora justamente um byte de que o conserto vai precisar.

## Codificação dupla

Se esse texto embaralhado for salvo de novo como UTF-8 e lido de novo como windows-1252, ele se embaralha outra vez, e fica mais comprido a cada rodada. O é vira Ã© depois de uma rodada, ÃƒÂ© depois de duas e ÃƒÆ’Ã‚Â© depois de três; o ’ vira Ã¢â‚¬â„¢ depois de duas. É o que acontece quando o cliente do banco de dados e o servidor acham, cada um, que é o outro que está fazendo a conversão.

## Latin-1, windows-1252 e o que o navegador faz

O ISO-8859-1 (Latin-1) e o windows-1252 são iguais em todos os bytes, menos na faixa de 0x80 a 0x9F. Nessa faixa, o ISO-8859-1 não tem nenhum caractere imprimível, e o windows-1252 coloca aspas curvas, travessões, o símbolo do euro e algumas letras. O navegador não faz distinção entre os dois. O WHATWG Encoding Standard trata "latin1", "iso-8859-1" e "ascii" como rótulos (labels) do windows-1252; assim, um software que segue o padrão vai, nas palavras dele, "decode 0x80 as U+20AC (€) when asked for the 'Latin1' or 'ASCII' decoding of that byte" (ou seja, decodificar 0x80 como U+20AC, €, quando lhe pedirem a decodificação "Latin1" ou "ASCII" desse byte). Fora do navegador, a resposta pode ser outra: o codec latin-1 do CPython decodifica 0x80 como o caractere de controle U+0080.

A mesma tabela vale para as referências numéricas do HTML: uma referência entre `&#128;` e `&#159;` também é lida pelo windows-1252 (veja [ASCII: os 128 códigos](https://ronutz.com/pt-BR/learn/ascii-the-128-codes)).

## Um caractere invisível no rótulo do charset

Uma página informa o próprio charset em dois lugares: no parâmetro charset do header (cabeçalho) Content-Type e num elemento `<meta charset>`. Se nenhum dos dois estiver presente, ou se o navegador não entender nenhum deles, ele recorre a um charset padrão, e texto UTF-8 lido com o padrão errado vira mojibake.

O próprio guia de operações do BIG-IP ASM, da F5, mostra como isso acontece com facilidade. O K13050156 (atualizado em 2024-07-29) traz o HTML de uma página de bloqueio personalizada cuja linha de charset aparece na tela como `<meta charset="utf-8">`, mas entre "utf" e "8" há um U+00AD SOFT HYPHEN no lugar do hífen; ao todo, o exemplo tem 24 soft hyphens. "utf" + soft hyphen + "8" não é um rótulo que o padrão do WHATWG conheça, então o elemento é ignorado. Servida sem charset no header Content-Type, uma cópia dessa página com texto em português ("Requisição bloqueada") apareceu no Chromium 141 como "RequisiÃ§Ã£o bloqueada", decodificada como windows-1252 (medido em 2026-10-03). Código copiado de um documento merece uma passada pelo [inspetor Unicode](https://ronutz.com/pt-BR/tools/unicode-inspector) antes de entrar no ar.

## Um formulário do navegador e a referência HTML

No sentido contrário também se perde informação. Quando um formulário é enviado a partir de uma página em windows-1252 e alguém digita um caractere que esse charset não tem, o navegador manda no lugar dele uma referência HTML decimal, como `&#8364;` para o símbolo do euro. O padrão do WHATWG deixa claro o preço disso: "a server will not be able to distinguish between an end user entering '💩' and '&amp;#128169;' into a form" (o servidor não tem como distinguir alguém que digitou "💩" de alguém que digitou "&amp;#128169;").

## As páginas de código do DOS: IBM 437 e IBM 850

As páginas de código (code pages) do DOS são outra família de charsets de um byte: o IBM 437, que a tabela de mapeamento do Unicode chama de DOSLatinUS, e o IBM 850, o DOSLatin1, que acrescenta letras que faltam no IBM 437, como Ã e õ. Texto que passa entre elas e o windows-1252 se embaralha de um jeito fácil de reconhecer:

| O que aconteceu | Texto | Vira |
|---|---|---|
| Bytes IBM 850 lidos como windows-1252 | ação | a‡Æo |
| Bytes windows-1252 lidos como IBM 850 | ação | aþÒo |
| Bytes UTF-8 lidos como IBM 437 | café | caf├⌐ |
| Bytes UTF-8 lidos como IBM 850 | ação | a├º├úo |

Os caracteres de moldura (box-drawing) das duas últimas linhas (├ é U+251C) denunciam uma página de código do DOS: o IBM 437 e o IBM 850 guardam linhas e cantos em faixas de bytes em que o Latin-1 e o windows-1252 têm letras acentuadas.

## EBCDIC lido como Latin-1

Os mainframes usam EBCDIC, em que as letras nem são contíguas: no IBM 037, as letras de A a I ocupam de C1 a C9, as de J a R, de D1 a D9, as de S a Z, de E2 a E9, os dígitos ficam de F0 a F9 e o espaço é 40. "HELLO, World" em IBM 037 é `C8 C5 D3 D3 D6 6B 40 E6 96 99 93 84` e, lido como Latin-1, vira ÈÅÓÓÖk@æ seguido de quatro caracteres de controle invisíveis. Um @ em cada lugar onde deveria haver um espaço é a marca do EBCDIC.

## Como recuperar o texto

Dá para desfazer um mojibake quando a leitura errada não perdeu nada: você codifica o texto embaralhado de volta com o charset que fez a leitura errada, o que devolve os bytes originais, e decodifica esses bytes com o charset certo. O windows-1252, do jeito que o padrão do WHATWG o define, associa todo byte a um caractere; por isso, UTF-8 lido errado como windows-1252 por um navegador sempre pode ser recuperado. Duas situações fazem isso falhar:

- **Um byte foi substituído ou descartado.** Um conversor que escreveu ? ou U+FFFD no lugar de um byte que não entendeu, ou uma etapa que removeu os caracteres de controle, destruiu a informação. Só dá para recuperar o resto.
- **Texto certo e texto embaralhado estão misturados.** Consertar a string inteira estragaria a parte que estava certa. Cada parte precisa ser consertada separadamente.

Desfazer uma leitura errada de UTF-8 vem com uma verificação embutida: os bytes precisam formar UTF-8 bem formado, coisa que bytes fora do ASCII raramente fazem por acaso; então, um conserto que passa nesse teste muito provavelmente está certo. Um conserto entre dois charsets de um byte não tem essa verificação, porque todo byte é um caractere válido nos dois; o resultado só parece mais plausível ou menos.

O modo **Consertar mojibake** do conversor tenta as duas coisas, diz que tipo de evidência encontrou, desfaz até três rodadas de codificação dupla e marca com U+FFFD o que não deu para recuperar.

## Como evitar

- **Use UTF-8 e declare isso.** O padrão do WHATWG é direto: "Authors must use the UTF-8 encoding and must use its (ASCII case-insensitive) 'utf-8' label to identify it." (quem escreve a página deve usar a codificação UTF-8 e identificá-la com o rótulo "utf-8"). Mande o charset no header Content-Type, onde nenhum caractere copiado consegue estragá-lo, e também num `<meta charset>`.
- **Converta uma vez só, na borda do sistema.** A codificação dupla acontece onde duas camadas fazem a conversão. Decida qual delas converte.
- **Guarde os bytes.** Quando um texto chega embaralhado, os bytes originais (uma captura de pacotes, um dump hexadecimal do arquivo) valem mais do que o texto. Abra esses bytes no modo **Bytes para texto** do conversor, que mostra todos os charsets lado a lado.

## Num BIG-IP: a codificação da política de segurança

Uma política de segurança do Advanced WAF (ASM) lê headers e parâmetros numa codificação própria. O K6335 da F5 (atualizado em 2023-02-21) define a configuração Application Language como "the default character encoding set for the web application" (a codificação de caracteres padrão da aplicação web), que "determines the default character sets for headers, objects, parameter names, and parameter values" (define os charsets padrão de headers, objetos, nomes e valores de parâmetros). Ela fica fixa durante toda a vida da política: "The character encoding method is a key characteristic of the security policy, and cannot be changed" (o método de codificação é uma característica essencial da política e não pode ser alterado), e "policies cannot be imported to replace a policy that uses a different encoding language" (não é possível importar uma política para substituir outra que use uma codificação diferente). A referência do tmsh para `asm policy` descreve a mesma propriedade, `encoding`, e avisa que alterá-la "causes ASM to reconfigure the security policy and clear all its former data" (faz o ASM reconfigurar a política e apagar todos os dados anteriores).

O guia de operações do ASM (K13050156) explica o que essa configuração verifica: "The BIG-IP ASM system checks to see if the sequence of bytes is valid within the defined encoding. Sequences not found trigger the Failed to convert character violation." (o ASM verifica se a sequência de bytes é válida na codificação definida; as sequências que não existem nela disparam a violação Failed to convert character). A recomendação do guia é manter o padrão, Auto detect, onde ele estiver disponível e, ao definir o idioma manualmente, "make sure you know the character set the application uses for HTML form submission" (ter certeza de qual charset a aplicação usa para enviar formulários HTML). Uma violação Failed to convert character em tráfego que parece legítimo indica que a codificação da política e a da aplicação não batem; os bytes no log da requisição, abertos no conversor, mostram qual charset o cliente usou de fato.

## Fontes

Os rótulos, decodificadores, índices e o comportamento dos formulários vêm do WHATWG Encoding Standard; as tabelas do DOS e do EBCDIC, das tabelas de mapeamento arquivadas do Consórcio Unicode; o comportamento do BIG-IP, do K6335, do K13050156 e da referência do tmsh da F5. As formas embaralhadas das tabelas foram produzidas no conversor de charsets, no TextDecoder do Node 22 e no CPython 3.11 em 2026-10-03; a página de bloqueio foi renderizada no Chromium 141 em 2026-10-03. Tudo lido em 2026-10-03.

- [WHATWG Encoding Standard](https://encoding.spec.whatwg.org/)
- [WHATWG Encoding Standard: index-windows-1252.txt](https://encoding.spec.whatwg.org/index-windows-1252.txt)
- [Tabela de mapeamento do Unicode: CP437 (cp437_DOSLatinUS)](https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP437.TXT)
- [Tabela de mapeamento do Unicode: CP850 (cp850_DOSLatin1)](https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP850.TXT)
- [Tabela de mapeamento do Unicode: CP037 (EBCDIC EUA/Canadá)](https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/EBCDIC/CP037.TXT)
- [Tabelas de mapeamento do Unicode: ReadMe](https://www.unicode.org/Public/MAPPINGS/ReadMe.txt)
- [Documentação do Python: codecs, codificações padrão](https://docs.python.org/3/library/codecs.html)
- [F5 K6335: Overview of encoding language settings for the BIG-IP ASM system](https://my.f5.com/manage/s/article/K6335)
- [F5 K13050156: Policy tuning and enhancement, guia de operações do BIG-IP ASM](https://my.f5.com/manage/s/article/K13050156)
- [Referência do tmsh da F5: asm policy](https://clouddocs.f5.com/cli/tmsh-reference/latest/modules/asm/asm_policy.html)
