stringprep no Python: prepare Unicode

Publicado em: 12/08/2026
Tempo de leitura: 7 minutos
Protocolo seguro na internet representando preparação Unicode com stringprep no Python

O módulo stringprep expõe as tabelas definidas pelo RFC 3454 para preparação de strings Unicode usadas em protocolos de internet. A proposta do padrão é transformar identificadores para uma forma comparável, remover ou mapear certos caracteres, rejeitar categorias proibidas e aplicar regras de texto bidirecional antes que os dados sejam enviados ou armazenados.

É importante entender o limite da biblioteca: stringprep não oferece uma função única que “limpa” qualquer texto. Ele fornece funções de consulta e mapeamento. Cada protocolo precisa definir um perfil indicando quais tabelas usar, se deve aplicar case folding, qual normalização executar e quais regras adicionais validar.

Um módulo baseado em um padrão histórico

O RFC 3454 foi criado para perfis como Nameprep, usado nas primeiras versões de nomes de domínio internacionalizados. As tabelas são baseadas em Unicode 3.2, por isso não refletem automaticamente todos os caracteres adicionados em versões modernas do Unicode.

Não invente um perfil próprio para autenticação, domínios ou identificadores críticos. Use o padrão atual do protocolo e uma biblioteca específica quando disponível. O módulo é valioso para interoperabilidade, manutenção de sistemas legados e compreensão das etapas de preparação.

Tabelas como funções

As tabelas seriam grandes se fossem representadas por listas. Por isso, o Python as expõe como funções características que retornam verdadeiro ou falso, e funções de mapeamento que devolvem a substituição.

import stringprep

caractere = "\u00ad"  # soft hyphen
print(stringprep.in_table_b1(caractere))

A tabela B.1 contém caracteres normalmente mapeados para nada. O resultado da consulta não altera a string; a aplicação precisa realizar a transformação.

Pontos de código não atribuídos

in_table_a1() informa se um caractere estava não atribuído no Unicode 3.2.

def tem_nao_atribuido(texto):
    return any(stringprep.in_table_a1(c) for c in texto)

Um perfil pode proibir esses pontos para evitar que um caractere adquira significado diferente depois de uma atualização. Porém, como a tabela é histórica, caracteres modernos podem aparecer como não atribuídos nesse contexto.

Mapear caracteres para nada

A tabela B.1 inclui caracteres que alguns perfis removem, como certas marcas de formatação.

def aplicar_b1(texto):
    return "".join(
        c for c in texto
        if not stringprep.in_table_b1(c)
    )

Remover caracteres pode criar colisões entre identificadores originalmente distintos. O sistema deve detectar duplicatas depois de toda a preparação e preservar o valor original para auditoria e exibição.

Case folding com B.2 e B.3

map_table_b2() implementa um mapeamento de case folding pensado para uso com NFKC. map_table_b3() corresponde ao mapeamento sem normalização.

def mapear_b2(texto):
    return "".join(stringprep.map_table_b2(c) for c in texto)

O resultado de uma função pode conter mais de um caractere. Não suponha uma relação um-para-um nem use o comprimento original como limite depois do mapeamento.

Normalização NFKC

Muitos perfis históricos aplicam NFKC depois do mapeamento.

import unicodedata

def preparar_base(texto):
    texto = aplicar_b1(texto)
    texto = mapear_b2(texto)
    return unicodedata.normalize("NFKC", texto)

NFKC reduz diferenças de compatibilidade e pode alterar caracteres estilísticos, largura e alguns símbolos. Isso é adequado somente quando o perfil exige. Para texto de apresentação, preserve o original.

Espaços ASCII e não ASCII

As tabelas C.1.1 e C.1.2 identificam espaços ASCII e não ASCII. A função combinada in_table_c11_c12() verifica ambos.

espacos_especiais = [
    c for c in texto
    if stringprep.in_table_c11_c12(c)
]

Um perfil pode mapear espaços para ASCII, proibi-los ou permitir apenas alguns. Não substitua automaticamente todos por espaço comum sem consultar a especificação.

Caracteres de controle

As tabelas C.2.1 e C.2.2 cobrem controles ASCII e não ASCII. in_table_c21_c22() testa a união.

def possui_controle(texto):
    return any(
        stringprep.in_table_c21_c22(c)
        for c in texto
    )

Controles podem alterar logs, terminais, protocolos e apresentação. Mesmo quando um protocolo permite algum deles, interfaces e registros devem escapar caracteres invisíveis.

Uso privado, não caracteres e surrogates

As funções in_table_c3(), in_table_c4() e in_table_c5() identificam pontos de uso privado, noncharacters e códigos surrogate.

Strings Python válidas podem conter alguns valores problemáticos dependendo da origem e do tratamento de erros. Rejeite-os conforme o perfil e antes de serializar para sistemas que exigem Unicode escalar válido.

Caracteres inadequados

As tabelas C.6 e C.7 marcam caracteres inadequados para texto simples ou representação canônica. C.8 cobre caracteres que alteram propriedades de exibição ou estão obsoletos, e C.9 cobre tagging.

Essas categorias mostram por que uma simples chamada a strip() ou lower() não implementa preparação de protocolo.

Regras bidirecionais

As tabelas D.1 e D.2 identificam caracteres com propriedades bidi R/AL e L. Perfis baseados no RFC 3454 normalmente impõem regras quando há texto da direita para a esquerda.

def validar_bidi(texto):
    tem_randal = any(stringprep.in_table_d1(c) for c in texto)
    if not tem_randal:
        return True
    if any(stringprep.in_table_d2(c) for c in texto):
        return False
    return (
        stringprep.in_table_d1(texto[0])
        and stringprep.in_table_d1(texto[-1])
    )

O exemplo demonstra a lógica histórica básica, mas deve seguir exatamente o perfil adotado. Texto bidi pode ter ordem visual diferente da lógica e exige atenção em logs e interfaces.

Construir uma função orientada por perfil

import unicodedata

class StringPrepError(ValueError):
    pass

def preparar_legado(texto):
    mapeado = "".join(
        "" if stringprep.in_table_b1(c)
        else stringprep.map_table_b2(c)
        for c in texto
    )
    normalizado = unicodedata.normalize("NFKC", mapeado)

    proibidas = (
        stringprep.in_table_c12,
        stringprep.in_table_c21_c22,
        stringprep.in_table_c3,
        stringprep.in_table_c4,
        stringprep.in_table_c5,
        stringprep.in_table_c6,
        stringprep.in_table_c7,
        stringprep.in_table_c8,
        stringprep.in_table_c9,
    )
    for c in normalizado:
        if any(tabela(c) for tabela in proibidas):
            raise StringPrepError(f"caractere proibido: U+{ord(c):04X}")

    if not validar_bidi(normalizado):
        raise StringPrepError("regra bidirecional inválida")
    return normalizado

Esse código é apenas um exemplo educacional. A lista de tabelas, a política para A.1 e a ordem exata precisam vir da especificação do perfil.

Validação depois da transformação

Limites devem ser verificados depois de mapeamento e normalização, pois o tamanho pode aumentar ou diminuir. Confira também representação em bytes quando o protocolo define limite codificado.

Se a string preparada será uma chave única, aplique unicidade sobre o valor final dentro de uma transação. Duas entradas diferentes podem convergir.

IDNA e nomes de domínio

Não monte manualmente Nameprep para domínios modernos. Use a API ou biblioteca IDNA apropriada ao padrão exigido. Regras de domínio incluem processamento por labels, Punycode, validação e versões de IDNA.

stringprep pode ajudar a entender sistemas antigos, mas não substitui uma implementação atualizada do protocolo.

Senhas e nomes de usuário

Não aplique stringprep genérico a senhas sem uma especificação. Remover ou mapear caracteres pode reduzir entropia e mudar o segredo digitado. Protocolos modernos de autenticação definem perfis próprios, como PRECIS ou regras específicas.

Para nomes de usuário, preserve o original, defina uma chave preparada separada e monitore colisões e homógrafos.

Homógrafos

Case folding e NFKC não eliminam todos os caracteres visualmente semelhantes. Letras de scripts diferentes podem continuar distintas. Para identificadores públicos sensíveis, use políticas de scripts, detecção especializada e revisão.

Testes necessários

Crie vetores com controles, espaços não ASCII, caracteres removidos, expansão de case folding, texto RTL, misturas L/R, caracteres modernos e entradas vazias. Compare com vetores oficiais do protocolo.

Registre a versão da implementação e evite alterar a preparação de chaves existentes sem uma migração.

Erros frequentes

  • Acreditar que o módulo oferece uma função completa de limpeza.
  • Criar um perfil sem consultar um RFC.
  • Ignorar que as tabelas usam Unicode 3.2.
  • Validar tamanho antes do mapeamento.
  • Não detectar colisões depois da preparação.
  • Aplicar NFKC ou remoção a senhas arbitrariamente.
  • Ignorar regras bidi e homógrafos.

Boas práticas

  • Use uma biblioteca específica do protocolo.
  • Preserve o texto original.
  • Versione a função de preparação.
  • Aplique etapas na ordem da especificação.
  • Valide comprimento e unicidade no final.
  • Escape caracteres invisíveis em logs.
  • Teste com vetores oficiais e Unicode adversarial.

Conteúdos relacionados

Veja unicodedata, locale, textwrap, fnmatch e contextvars.

Consulte a documentação oficial do stringprep e o RFC 3454.

Conclusão

stringprep é uma caixa de ferramentas de baixo nível para perfis históricos de preparação Unicode. Ele deve ser usado sob uma especificação clara, com consciência da base Unicode 3.2, da possibilidade de colisões e das regras bidirecionais. Para protocolos modernos, prefira implementações especializadas e atualizadas.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Rede de conexões representando I/O não bloqueante com selectors no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    selectors no Python: I/O não bloqueante

    Aprenda selectors no Python para monitorar vários sockets, eventos de leitura e escrita, timeouts e conexões não bloqueantes com segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026
    Fluxo de dados em rede representando contexto assíncrono com contextvars no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    contextvars no Python: contexto assíncrono

    Aprenda contextvars no Python para armazenar estado por tarefa, evitar vazamentos em asyncio, copiar contextos e restaurar valores com tokens.

    Ler mais

    Tempo de leitura: 6 minutos
    11/08/2026
    Código de programação representando operações como funções com operator no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    operator no Python: operações como funções

    Aprenda operator no Python para usar operações como funções, ordenar campos, acessar itens, chamar métodos e trabalhar com pipelines funcionais.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026
    Alfabeto tridimensional representando normalização Unicode com unicodedata no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    unicodedata no Python: normalize Unicode

    Aprenda unicodedata no Python para normalizar Unicode, consultar nomes, categorias, números, caracteres combinantes e largura de exibição.

    Ler mais

    Tempo de leitura: 6 minutos
    11/08/2026
    Rede de servidores representando gerenciamento de recursos com ExitStack no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ExitStack no Python: gerencie recursos

    Aprenda ExitStack no Python para gerenciar arquivos, conexões e limpezas dinâmicas com segurança e ordem previsível.

    Ler mais

    Tempo de leitura: 6 minutos
    11/08/2026
    Pasta com cadeado representando tipos e permissões com stat no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    stat no Python: tipos e permissões

    Aprenda stat no Python para interpretar tipos de arquivo, permissões, links, timestamps, atributos do Windows e flags de Unix com

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026