Caractères Unicode dans les pseudos des utilisateurs

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Dans le tutoriel de Spacefox sur l’internationalisation, on trouve ce passage (Tout ce qui vient avec une langue mais qui n’était pas prévu):

On ne supprime jamais les diacritiques. Il n’y a plus de raison de faire ça

Si votre crainte (légitime) est qu’un utilisateur abuse de votre largesse en employant des caractères différents, mais visuellement identiques (comme les Α grec, А cyrillique et A latins), ce n’est pas un problème de diacritique, mais d’homoglyphes et Unicode fournit les outils pour les résoudre.

Ce que le tutoriel ne dit pas, c’est comment on fait concrètement ?

Je réfléchis éventuellement à autoriser des pseudos avec des caractères non ASCII, non latins, mais je ne trouve pas comment faire concrètement pour éviter que plusieurs utilisateurs choisissent des noms trop ressemblants.

Le principe de base est, lors de l’inscription d’un nouveau pseudo, de comparer avec des versions normalisées des pseudos existants. Comment construit-on cette forme normalisée ? C’est là toute la question, c’est là que j’ai du mal à trouver comment faire, même en demandant gentiment à mon moteur de recherche favori. J’ai quelque pistes partielles, mais rien de complet…

En premier lieu, j’aimerais fixer quelques règles de base:

  • J’accepte donc des pseudos en lettres latines, cyrillique, grec, arabe, etc. Mais pas de mélange d’alphabet dans un même pseudo. Ainsi "ABC", "ΑΒΓ" et "АБВ" sont acceptables, mais pas "ΑΒC". Je pense que ça limitera pas mal d’abus.
  • Je suis d’accord d’accepter des lettres et des chiffres, mais pas non plus n’importe quel caractère Unicode. Par exemple je ne veux pas d’émojis parce que les gens font vraiment n’importe quoi avec, ni de symboles de maths, de chiffres entourés et autres qui n’ont, de mon point de vue, rien à faire dans un pseudo. Un pseudo est un nom, pas une oeuvre artistique; et c’est aussi probablement une potentielle possibilité d’attaque.

Première bonne nouvelle, la méthode toLowerCase a l’air de fonctionner correctement:

jshell> "AΑА" .toLowerCase()
"aαа"

Du côté de la suppression des accents, parce qu’il va quand même falloir le faire, j’ai trouvé une méthode qui est censée être générique:

public static String unaccent (String s) {
  s = Normalizer.normalize(s, Normalizer.Form.NFD);
  StringBuilder sb = new StringBuilder();
  s.codePoints() //
  .filter(c -> {
    int t = Character.getType(c);
    return t!=Character.NON_SPACING_MARK && t!=Character.ENCLOSING_MARK && t!=Character.COMBINING_SPACING_MARK;
  })
  .forEach(sb::appendCodePoint);
  return sb.toString();
}

jshell> unaccent("Ça a marché !")
"Ca a marche !"

Par contre cette méthode n’est pas complète:

jshell> unaccent("œ")
"œ"

Dans ce cas, je m’attends à ce que "œ" devienne "oe". Ca ne change rien si je choisis le mode NFKD au lieu de NFD (d’ailleurs c’est quoi la différence entre les deux ?)

JE voudrais aussi autoriser le tiret et le point, mais là par contre le Normalizer ne m’est visiblement d’aucun secours. IL faudrait que tous les tirets de plus ou moins grande longueur se normalisent en point de code 45. et pareil avec les différents points (p.ex. point médian).

Pour distinguer les différents alphabets, je suis aussi perdu. Comment les distinguer ?

jshell> Character.getType('A')
1
jshell> Character.getType('\u0410') // Le A en cyrillique
1

Bref, c’est bien beau tout ça, mais ça parait hyper compliqué.

J’aimerais tant que possible éviter une méthode qui fait des replace à l’ancienne, parce qu’on ne peut pas faire quelque chose de fiable, et parce que je suis sûr qu’il existe mieux. La bibliothèque standard de Java me donne quelques outils mais ils ne sont pas complets, ou alors je ne sais pas comment les utiliser correctement.

Merci pour votre aide !

Et un petit troll pour finir: Est-ce qu’il existe quelque chose du côté de PHP 7 ?

+0 -0

Salut,

Alors, comme le dit mon voisin du dessus, la première chose à faire est de lister exactement les règles fonctionnelles que tu veux appliquer. Parce qu’il n’y a pas qu’une seule façon de faire.

En particulier, ceci est pour moi une erreur :

Du côté de la suppression des accents, parce qu’il va quand même falloir le faire

C’est très exactement quelque chose qu’il ne faut pas faire. Il y a plein d’interdictions légitimes dans les caractères utilisables pour un pseudonyme, je n’en vois pas à celle-ci.

Ensuite, d’un point de vue technique : les normalisations, quelle qu’elles soient, ne servent pas à supprimer des accents ou d’autres choses. Ça n’est absolument pas leur but. Elle servent à comparer deux chaines de caractères qui peuvent être identiques visuellement tout en présentant une série d’octets différents. C’est hélas une erreur fréquente sur Internet… Ça explique pourquoi ça « ne fonctionne pas » avec les exemple que tu donnes : œ est différent de oe visuellement, tout comme les différents tirets.

L’idée pour l’implémentation technique est la suivante :

  1. Tu limites les catégories de caractères que tu autorises, en fonction des catégories de points de code Unicode (par exemple, tu peux autoriser le latin mais pas le grec ou les emojis). Ça se fait en Java avec Character.getType(c) et les constantes de Character – comme dans le code que tu as donné en exemple, mais la personne qui a pondu ce code fait n’importe quoi avec.
  2. Éventuellement, tu normalises le pseudo.
  3. Tu peux, en plus du pseudo, enregistrer un « squelette » du pseudo. Ce squelette n’est pas utilisable directement, mais il va permettre de détecter les éventuels pseudos avec lequel il peut être confondu.

Si tu es en Java ou en Python, tu as une bibliothèque pour faire ce genre de chose (et, dans le cas de Java, une autre écrite par IBM, mais avec une API à la IBM…)


PS : pourquoi on trouve souvent cette horreur qui consiste à « éliminer les accents » en jouant avec les formes normalisée d’Unicode, et pourquoi ça ne fonctionne pas ?

Parce que les caractères accentués (le glyphe tracé réellement à l’écran) sont pour la plupart composables sous deux formes :

  • Le caractère pré-composé (un seul point de code : ê)
  • Le caractère d’accent seul + le caractère de la lettre à accentuer (deux points de code : ^e).

Les formes normalisées permettent de forcer l’une ou l’autre de ces formes, ce qui permet de comparer deux chaines de caractères sans risquer de penser qu’elles sont différentes parce que différentes en terme de point de code (donc d’octets) mais pas de rendu. Pour la petite histoire, ça a longtemps été un problème avec MacOS X, qui normalisait systématiquement les accents dans les chemins de fichiers en points de code séparés, chose incompréhensible pour les autres OS.

Certains petits malins se sont dits qu’il suffisait de normaliser en points de code séparés puis de faire un filtre sur les caractères de type accent pour désaccentuer un texte. Ce qui n’est pas fiable techniquement, et surtout une aberration fonctionnelle.

Bonsoir,

C’est très exactement quelque chose qu’il ne faut pas faire. Il y a plein d’interdictions légitimes dans les caractères utilisables pour un pseudonyme, je n’en vois pas à celle-ci.

Mème dans le cadre de la fonction de normalisation uniquement ? Parce que le but n’est pas d’enlever les accents à l’affichage, mais seulement d’empêcher que "Gege" ne puisse s’inscrire si "Gégé" existe déjà. C’est facile de confondre.

Mais bon en même temps tu as raison sur un point, il faut décider jusqu’où aller. Est-ce que je devrais considérer que "Löcher" et "Loecher" est la même chose ? J’ai envie de répondre non, mais un germanophone natif me dira peut-être le contraire.

Ensuite, d’un point de vue technique : les normalisations, quelle qu’elles soient, ne servent pas à supprimer des accents ou d’autres choses. Ça n’est absolument pas leur but. Elle servent à comparer deux chaines de caractères qui peuvent être identiques visuellement tout en présentant une série d’octets différents. C’est hélas une erreur fréquente sur Internet… Ça explique pourquoi ça « ne fonctionne pas » avec les exemple que tu donnes : œ est différent de oe visuellement, tout comme les différents tirets.

Pourtant j’ai tiré ça d’une réponse Stackoverflow ou Cora (je ne sais plus lequel des deux), des sites qui donnent habituellement des réponses assez exactes et assez bonnes. Dommage ! Mais au moins tes explications sont claires.

Pour la culture, est-ce que la forme décomposée a encore une utilité aujourd’hui ? Ou bien c’est encore une relique des machines à écrire car à l’époque le seul moyen d’écrire "ê" sur le papier était de taper "^", backspace puis "e" (ou éventuellement l’inverse) ?

  1. Tu limites les catégories de caractères que tu autorises, en fonction des catégories de points de code Unicode (par exemple, tu peux autoriser le latin mais pas le grec ou les emojis). Ça se fait en Java avec Character.getType© et les constantes de Character – comme dans le code que tu as donné en exemple, mais la personne qui a pondu ce code fait n’importe quoi avec.

C’est bien Character.getType que je pensais utiliser, mais visiblement la catégorisation n’est pas assez précise. Le 'A latin et le A cyrillique me retournent la même valeur indiquant une lettre majuscule. C’est correct, mais pas suffisant pour ce que je veux faire: autoriser du latin et du cyrillique, mais interdire un mix des deux.

  1. Éventuellement, tu normalises le pseudo.
  2. Tu peux, en plus du pseudo, enregistrer un « squelette » du pseudo. Ce squelette n’est pas utilisable directement, mais il va permettre de détecter les éventuels pseudos avec lequel il peut être confondu.

C’est bien de ce "squelette" que je veux parler quand j’utilise le mot "normalisation". Ce n’était pas le bon terme et c’est pour ça que je n’ai sûrement pas trouvé ce qu’il fallait. J’ai aussi vu quelquefois le mot "canonicalisation" pour dire encore la même chose.

Du coup je vais aller voir les liens que tu proposes.

Merci.

+0 -0

Est-ce que je devrais considérer que "Löcher" et "Loecher" est la même chose ? J’ai envie de répondre non, mais un germanophone natif me dira peut-être le contraire.

Très clairement, j’ai eu le cas pas plus tard que y 2h …

C’est bien de ce "squelette" que je veux parler quand j’utilise le mot "normalisation". Ce n’était pas le bon terme et c’est pour ça que je n’ai sûrement pas trouvé ce qu’il fallait. J’ai aussi vu quelquefois le mot "canonicalisation" pour dire encore la même chose.

Si tu veux un squelette, je pense que tu peux tout à fait te baser sur la normalisation en point de code avec des règles perso (oe => o, Ô => o, ö => o, œ => o) et si deux pseudos ont le même squelette alors ils sont trop proche. Mais ça sera long à faire ! Tu peux t’inspirer pour ça sur les bibliothèques d’attaque Homoglyphe.

+0 -0

Mème dans le cadre de la fonction de normalisation uniquement ? Parce que le but n’est pas d’enlever les accents à l’affichage, mais seulement d’empêcher que "Gege" ne puisse s’inscrire si "Gégé" existe déjà. C’est facile de confondre.

Mais bon en même temps tu as raison sur un point, il faut décider jusqu’où aller. Est-ce que je devrais considérer que "Löcher" et "Loecher" est la même chose ? J’ai envie de répondre non, mais un germanophone natif me dira peut-être le contraire.

QuentinC

Là tu rentres dans les règles spécifiques aux langues. Pour moi tu devrais te limiter aux transformations pertinentes pour la ou les langues que tu gères, sinon c’est un problème infini.

Comme le dit @ache, c’est à ajouter aux squelette, en règles spécifiques.

Pour la culture, est-ce que la forme décomposée a encore une utilité aujourd’hui ? Ou bien c’est encore une relique des machines à écrire car à l’époque le seul moyen d’écrire "ê" sur le papier était de taper "^", backspace puis "e" (ou éventuellement l’inverse) ?

QuentinC

Ça peut avoir son utilité, par exemple pour éviter les transformations si les caractères sont saisis avec un système de touches mortes (typiquement : l’accent circonflexe ou le tréma en français). Mais surtout, les formes décomposées permettent bien plus de caractères que ceux précomposés. Par exemple, un clavier BÉPO permet de composer âĉêĝĥîĵôŝûŵŷẑếề, pas sûr que tous ces caractères existent en version précomposée. Et les langues latines ont peu de caractères composables, par rapport à d’autres (je pense aux langues indiennes en particulier).

Le 'A latin et le A cyrillique me retournent la même valeur indiquant une lettre majuscule. C’est correct, mais pas suffisant pour ce que je veux faire: autoriser du latin et du cyrillique, mais interdire un mix des deux.

QuentinC

Ça ressemble à ce que fait Confusables#isDangerous() de ConfusablesHomoglyphs.

Ça me fait penser qu’il faudrait que je trouve moyen de pousser la dernière version sur Maven, et donc que je réussisse à refaire fonctionner cette purge de GPG…

C’est bien de ce "squelette" que je veux parler quand j’utilise le mot "normalisation". Ce n’était pas le bon terme et c’est pour ça que je n’ai sûrement pas trouvé ce qu’il fallait. J’ai aussi vu quelquefois le mot "canonicalisation" pour dire encore la même chose.

QuentinC

Attention, la « canonicalisation » d’Unicode, c’est la normalisation dont je parlais plus haut, et pas les squelettes de glyphes que l’on peut confondre.

Si tu veux un squelette, je pense que tu peux tout à fait te baser sur la normalisation en point de code avec des règles perso (oe => o, Ô => o, ö => o, œ => o) et si deux pseudos ont le même squelette alors ils sont trop proche.

C’est en gros ce que fait déjà StringUtils.stripAccents d’apache common, il me semble.

Le truc, c’est que c’est facile avec les lettres accentuées dans les langues que je connais, ou relativement facile avec les lettres latines en général. Avec un clavier suisse, j’ai accès aux précomposés avec ^¨`´~. Je dois tricher pour pouvoir taper Ç œ Œ « » (pour ces quatre là je connais les codes, resp. Alt+0199, 0156, 0171, 0187, et encore, c’est des codes spécifiques windows qui ne marcheraient pas sous linux)

Par contre je n’y connais rien en cyrillique, en grec, en arabe, en japonais, ou que sais-je. Dans les langues qui utilisent ces alphabets-là, ça sera donc facile pour un usurpateur.

La bibliothèque de Spacefox a l’air à priori très bien. C’est semble-t-il le terme confusable qu’il me manquait. Il faut que je regarde un peu ce que ça peut faire.

Je suis quand même étonné que la base de donnée Unicode ne soit pas complètement incluse d’office dans la bibliothèque standard. Il y a pourtant bien celle des fuseaux horaires par exemple, ou encore celles pour gérer une flopée d’encodages…

+0 -0

Le principal problème c’est que les développeurs ne sont pas au fait de toutes ces problématiques et continuent à vouloir "interdire ou supprimer les caractères spéciaux", par simple méconnaissance du sujet. L’historique (pseudo en ASCII ou en codage local uniquement pendant très longtemps), la complexité de la chose, et le fait que le code s’écrit beaucoup en anglais n’aident pas.

Le simple fait que StringUtils.stripAccents() existe en soit est un problème en fait, parce que ça part du principe très anglo-saxon qu’on peut se permettre de dégager des diacritiques sans trop de conséquences. Alors que les règles de passage à l’ASCII (c’est de ça dont il s’agit en réalité) dépendent de la langue. Par exemple ö donne o en français mais oe en allemand. Et je ne parle même pas de la romanisation de langues à écriture non latine qui peut utiliser des caractères hors ASCII (le o long japonais est retranscrit ō selon la norme, ô par les francophones parce que c’est cc qu’on a de plus proche, et souvent ou par les anglophones).

Et en effet c’est très mal géré dans la plupart des langages, comme plein de trucs en fait. Comme par exemple les dates. Et sur ces deux sujets, Java est très loin d’être le moins bien loti (on a une API dates très bien fournie, et le langage est en Unicode – hélas une variante cassée… – par défaut, même si les outils ne sont pas complets).

Alors que les règles de passage à l’ASCII dépendent de la langue

Quand je lis ça, d’un côté, je me demande si ce n’est pas plutôt un algo genre soundex dont j’aurais besoin.

Mais bon, globalement, je crois que je vais pouvoir faire ce que je voulais avec la bibliothèque de Spacefox. C’est en PHP que ça va être plus … sport.

Merci pour vos réponses.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte