RFC 9839 e o Unicode problemático
(tbray.org)- RFC 9839 define claramente os caracteres Unicode problemáticos que podem aparecer em campos de texto no desenvolvimento de software
- Este RFC trata dos problemas causados pela falta de consistência no tratamento desses caracteres em diferentes linguagens e bibliotecas
- A 9839 propõe três subconjuntos menos problemáticos, que podem ser usados opcionalmente
- Em comparação com o framework PRECIS existente, sua aplicação é mais fácil e simples
- Uma biblioteca em Go para RFC 9839 também foi publicada, ajudando no uso prático
Contexto e visão geral da RFC 9839
- O Unicode é usado como padrão em quase todo o processamento de dados textuais
- Porém, ao projetar estruturas de dados ou protocolos reais, permitir todos os caracteres Unicode pode causar problemas
- Paul Hoffman e o autor enviaram um rascunho individual ao IETF para apresentar critérios claros sobre problemas recorrentes com Unicode
- Após dois anos de discussão, ele foi adotado como padrão oficial e publicado como RFC 9839
- Este documento explica em detalhes os tipos de caracteres problemáticos, por que eles causam problemas (por razões técnicas e normativas) e três subconjuntos que os usuários podem escolher conforme a necessidade
Principais pontos da RFC 9839
- É um documento de referência essencial no projeto de campos de texto em ambientes de software e rede
- A RFC 9839 tem 10 páginas, sendo relativamente concisa para um padrão do IETF
- Foi escrita de forma acessível, voltada principalmente a desenvolvedores de software e engenheiros de redes
Exemplos de caracteres Unicode problemáticos
- Por exemplo, uma string como a seguinte pode aparecer no campo
usernamede um JSON{ "username": "\u0000\u0089\uDEAD\uD9BF\uDFFF" } - Problemas de cada ponto de código
U+0000: caractere NULL sem significado, que atrapalha o funcionamento de algumas linguagens de programaçãoU+0089: código de controle C1 (CHARACTER TABULATION WITH JUSTIFICATION), cujo comportamento é complexo e inconsistenteU+DEAD: caractere surrogate não pareado, um problema que decorre das limitações do UTF-16. Isso gera dados indesejáveis\uD9BF\uDFFF(na práticaU+7FFFF) : Noncharacter, cuja troca é proibida pelo padrão
- Pontos de código como esses impossibilitam um tratamento consistente em estruturas de dados e protocolos, além de causar erros inesperados
- A RFC 9839 define formalmente esses caracteres problemáticos e indica claramente quais tipos devem ser excluídos
O projeto do JSON e suas limitações
- Não é responsabilidade do criador do JSON, Doug Crockford
- O formato foi projetado numa época em que o Unicode ainda não estava suficientemente maduro, então não foi possível restringir rigorosamente o conjunto de caracteres
- Como o padrão já não pode ser alterado, é necessário excluir empiricamente os caracteres problemáticos
Diferenças em relação ao framework PRECIS do IETF
- Mesmo antes da RFC 9839 de 2025, o IETF já oferecia vários padrões, como o RFC 8264 (PRECIS Framework)
- Esse framework trata em detalhes de como preparar, aplicar e comparar strings internacionalizadas
- Com 43 páginas, ele é abrangente tanto na explicação do contexto quanto nas soluções
- O PRECIS depende fortemente da versão do Unicode, além de ser complexo e difícil de aplicar
- A RFC 9839 é concisa e focada na praticidade, facilitando adoção rápida na definição de novos protocolos
Subconjuntos da RFC 9839 e exemplos de uso
- A 9839 apresenta três subconjuntos práticos:
scalars,XMLeassignables - Cada subconjunto varia ligeiramente na faixa de caracteres problemáticos que exclui
- A seguir, um resumo de como os principais formatos de dados e os subconjuntos da RFC 9839 tratam caracteres problemáticos
- Alguns formatos como CBOR, TOML, XML e YAML excluem parcialmente surrogates ou caracteres de controle
- I-JSON exclui surrogates e noncharacters
- JSON comum e Protobufs não os excluem
- XML e YAML, por suas características de charset, excluem apenas parcialmente noncharacters/códigos de controle
- Observação: XML e YAML não excluem noncharacters fora do Basic Multilingual Pane
Biblioteca RFC 9839 para Go
- Foi publicada uma pequena biblioteca em Go que oferece validação de caracteres para os três subconjuntos da RFC 9839
- Ela já foi suficientemente testada, embora a otimização ainda esteja em andamento
- Testes e feedback em uso real são bem-vindos
A importância da RFC 9839 e o processo de trabalho
- A RFC 9839 foi publicada oficialmente após mais de 15 revisões de rascunho, com feedback repetido dos coautores
- Graças às discussões e contribuições de muitos especialistas da comunidade, ela evoluiu para um documento muito mais completo do que a versão inicial
- Os colaboradores são mencionados na seção “Acknowledgements”
A experiência de uma submissão individual de RFC
- A RFC 9839 foi conduzida como submissão individual (individual submission)
- Em comparação com o método tradicional via Working Group, isso envolve maior esforço e carga processual
- Comparando com a experiência de participar de um Working Group, o método tradicional é mais eficiente e recomendável
1 comentários
Comentários no Hacker News
Acho claro que certos caracteres causam problemas, mas me parece que o pior cenário é quando quem projeta estruturas de dados ou protocolos passa a ter a tendência de não permitir arbitrariamente todo tipo de caractere, até mesmo os devidamente escapados. Por exemplo, acredito que a validação de nome de usuário deveria ser tratada em outra camada. É algo como verificar se o nome tem menos de 60 caracteres, proibir emoji ou caracteres zalgo, proibir byte nulo etc., e retornar um erro apropriado na API. Não quero que isso falhe na etapa de parsing de JSON por esse tipo de problema em vez de uma validação prévia adequada. Claro que há classes de caracteres claramente inadequadas para nomes de usuário. Mas se eu estiver transmitindo um arquivo de texto em que caracteres de tabulação etc. são realmente usados, espero que o que meu tipo
stringutf8 da linguagem consegue representar também possa ser codificado. Em especial, o byte nulo tem muitos usos e, na prática, aparece com certa frequência em JSON. Ainda assim, se for preciso usar apenas um conjunto limitado de Unicode "normal", acho melhor existir um padrão do que cada um inventar seu próprio mini padrão. No fim, a ideia em si parece boa, mas não achei muito convincente a lógica apresentada no post do blogFalando de 2025, acho que os únicos formatos de representação de string em protocolos wire de baixo nível que ainda dá para defender na prática são estes
stringdo Python)stringdo JavaScript)stringdo Go)Falando sério, eu gostaria que arquivos de texto puro não usassem caracteres C0 (exceto quebra de linha e, a contragosto, HT) nem caracteres C1. Entendo querer salvar algo como marcação de cor ANSI, mas nesse caso aquilo não é realmente texto puro, e sim algum tipo de formato de marcação textual. Parecido com Markdown, mas com a diferença de usar uma codificação da faixa C0. Só porque os dados ficam bonitos ao usar o comando
cate afins não dá para dizer que são texto puro. Reconheço que existem muitos formatos de marcação codificados como texto puro por razões de interoperabilidadeAcho que a própria opinião de que o pior cenário é começar a proibir conjuntos arbitrários de caracteres em estruturas de dados e protocolos está desconectada da realidade. O verdadeiro pior cenário é haver uma falha de software, como em um parser, que leve a uma violação de segurança
Fico me perguntando se existe algum sistema que permita UTF-8 em nomes de usuário. Parece óbvio que todo identificador que será manipulado ou avaliado programaticamente, como nome de login e senha, deveria ser obrigatoriamente ASCII. Nem ISO-8859-1, apenas ASCII. Unicode não serve para esse tipo de uso. Se for só para exibir o nome de usuário, tudo bem, mas como identificador de login de sistema, qualquer codificação não ASCII deveria ser proibida sem exceção. Nem mesmo o software de teclado consegue garantir consistência da representação visual do UTF-8 quando se sai do ASCII, e isso fica ainda mais confuso dependendo do sistema operacional e das configurações. Também não há garantia de que binários preservados no futuro e IAs que interpretem Unicode vão concordar entre si. Além disso, quanto à consistência, nem o RFC 9839 nem a matéria deixam claro se casos como IVS ou normalização (NFC/NFD/NFKC/NFKD) estão explicitamente dentro ou fora do escopo. Parece faltar completamente uma seção de objetivo. Há apenas algo vago como a existência de "pontos de código não caracteres"
Fico curioso para saber por que emojis deveriam ser proibidos em nomes de usuário
Quero dizer que a IETF não esperou até 2025 para lidar com Bad Unicode. Já faz tempo que o RFC 8264: PRECIS Framework trata amplamente de vários problemas de Bad Unicode. Também ajuda consultar RFCs relacionados, como o RFC 8265(link) e o 8266(link). Em geral, coisas como senhas, que podem mudar a direção do texto ou ser codificadas de forma diferente dependendo do dispositivo de entrada, não deveriam ser usadas em usuário/senha. Dá para lidar com isso com segurança por meio desses perfis RFC. Para esse objetivo, "failing closed" (bloquear de forma mais restritiva) é mais seguro. Mesmo que surjam novos emojis, eu prefiro proibir de forma conservadora em vez de permitir em nomes de usuário e afetar todas as páginas
Unicode claramente tem partes "boas", mas é decepcionante ter de saber que existem caracteres que precisam ser excepcionalmente excluídos. É o resultado de tentar abarcar de forma abrangente os sistemas de escrita das línguas e acabar criando complexidade demais. É cansativo ter sempre de pensar em quais caracteres exigem tratamento especial. Por isso, trato strings Unicode como uma unidade de dados autônoma. Eu recebo, armazeno, renderizo e comparo igualdade de dados, mas não tento interpretar o conteúdo. Até concatenar ou manipular strings me deixa desconfortável
Unicode parece um abismo sem fim de trivia e decisões ruins. Por exemplo, os RFCs relacionados alertam sobre caracteres de controle ASCII obsoletos, por risco de confusão visual, mas não mencionam em nenhum momento caracteres de mudança de direção com problemas graves de segurança, como Explicit Directional Overrides
Como exemplo simples, se a primeira string termina com um modificador de emoji órfão e a segunda começa com um emoji modificável, o problema já aparece. E quanto mais aumentam os casos complexos, pior fica
A complexidade é grande, mas coisas como surrogates e códigos de controle não existem para registrar línguas; são resultado de decisões estranhas mantidas por legado
Unicode é incômodo, mas acho menos incômodo do que os outros padrões de codificação antigos
Acho que a maioria dos problemas pode ser tratada rejeitando sequências de bytes UTF-8 inválidas ou retornando erro de forma geral. Por exemplo, surrogates etc. já são ilegais em UTF-8 por definição, então linguagens que usam utf-8 deveriam retornar erro para essas sequências. O que realmente me parece problemático são certos "pontos de código" problemáticos (não imprimíveis etc.). Isso é claramente um conceito separado de sequência de bytes inválida, e é mais útil tratá-los assim
Unicode já define categorias para cada ponto de código (General Category) para classificar tipos estranhos de caracteres. Dá para consultar este artigo da Wikipédia. Por exemplo, em Python,
unicodedata.category(chr(0))retorna "Cc" (control), eunicodedata.category(chr(0xdead))retorna "Cs" (surrogate)Acho exagerado excluir todos os "legacy control" não só em forma literal, mas também em strings escapadas (por exemplo, "\u0027"). C1 quase não é usado, então tudo bem, mas alguns caracteres C0 têm exemplos reais de uso.
escape, EOF, NUL etc. ainda têm utilidade claraAcho que alguns caracteres C0 mais estranhos, como U+001E Record Separator, são muito úteis em fluxos de dados. Talvez devam ser bloqueados em documentos, mas em dados de streaming são úteis
Já vi o caractere form feed (U+000C) em código-fonte de programas. O Emacs tem suporte antigo a navegação por páginas, então esse tipo de coisa aparece
Não acho que Unicode seja bom. Seja qual for o conjunto de caracteres, os tipos de caracteres realmente permitidos (caracteres de controle, caracteres gráficos, tamanho máximo etc.) acabam tendo de ser definidos de acordo com cada aplicação. Tentar incluir ou excluir isso em JSON e afins não resolve muita coisa. Às vezes pode ser útil dar nome a um certo subconjunto (ou superset) de Unicode, ASCII ou outro charset, mas não dá para achar que será a melhor escolha para todo mundo. O RFC 9839 dá nome a alguns subconjuntos de Unicode, mas isso não garante que sejam automaticamente corretos para o serviço que eu quiser criar. Minha conclusão é que também deveríamos considerar não usar Unicode, ou pelo menos não impô-lo
Fico pensando se devo controlar a entrada, ou então encapsulá-la em um tipo de dado que faça saída segura para entrada não confiável (para web + log + debug)
Eu gostaria que houvesse um limite padrão para a quantidade de valores escalares Unicode que podem entrar em uma unidade gráfica. Da última vez que vi isso (há alguns anos), o padrão não tinha esse limite; em vez disso, apenas recomendava que aplicações de streaming limitassem a unidade gráfica a 128 bytes. Se esse tipo de limite estivesse claramente definido no padrão, a implementação seria muito mais fácil e não haveria restrições desnecessárias
Já passei por casos em que um programa quebrava por assumir que "não há caracteres de controle" (form feed para separação de páginas, caractere escape para uso em terminal etc. são comuns). A suposição de que "é tudo UTF-8" também costuma falhar (arquivos de dados antigos, logs etc.). Se você não vai fazer processamento textual significativo, o melhor é simplesmente passar o conteúdo como sequência de bytes, sem alterar nada. Mas, por causa do Microsoft Windows, às vezes é preciso passar uma sequência
char16_t. UTF-16 é fundamentalmente diferente de UTF-8 na entrada e saída. Ao converter, deve-se usar WTF-8 (UTF-16) e surrogate escape (UTF-8), respectivamente, na transformação de dados externos para formato interno. Não dá para misturar as duas abordagens