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

    Desenvolvedor configurando servidor HTTPS e certificado TLS com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crie servidor HTTPS local no Python

    Aprenda HTTPSServer no Python para servir HTTPS localmente, configurar certificados, usar threads e entender limites de segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Arquivos protegidos representando extração segura de TAR com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extraia TAR com segurança

    Aprenda tarfile extraction_filter no Python para extrair arquivos TAR com validação, segurança e controle de caminhos.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python exibindo avisos controlados com catch_warnings
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: capture warnings em testes Python

    Aprenda catch_warnings no Python para capturar, testar e controlar avisos com filtros específicos e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python representando referências persistentes do pickle
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serialize referências externas

    Aprenda pickle persistent_id no Python para serializar referências externas com IDs estáveis, validação, segurança e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binário representando buffers e memória no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: conte valores sem copiar buffers

    Aprenda memoryview.count no Python para contar bytes e valores em buffers sem cópias, com formatos, limites e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python com anotações de tipos
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evite imports circulares em anotações

    Aprenda annotationlib no Python para ler anotações adiadas, evitar imports circulares e criar ferramentas de introspecção seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026