web-dev-qa-db-fra.com

Comment déterminer si une chaîne contient des caractères codés non valides

Scénario d'utilisation

Nous avons implémenté un webservice que nos développeurs web frontend utilisent (via une API php) en interne pour afficher les données produit. Sur le site Web, l'utilisateur entre quelque chose (c'est-à-dire une chaîne de requête). En interne, le site Web appelle le service via l'API.

Remarque: nous utilisons restlet, pas Tomcat

Problème d'origine

Firefox 3.0.10 semble respecter l'encodage sélectionné dans le navigateur et encoder une URL en fonction de l'encodage sélectionné. Cela entraîne des chaînes de requête différentes pour ISO-8859-1 et UTF-8.

Notre site Web transmet les entrées de l'utilisateur et ne les convertit pas (ce qu'il devrait), il peut donc passer un appel au service via l'API appelant un service Web à l'aide d'une chaîne de requête qui contient des trémas allemands.

C'est à dire. pour une partie de requête ressemblant

    ...v=abcädef

si "ISO-8859-1" est sélectionné, la partie de requête envoyée ressemble à

...v=abc%E4def

mais si "UTF-8" est sélectionné, la partie de requête envoyée ressemble à

...v=abc%C3%A4def

Solution souhaitée

Comme nous contrôlons le service, parce que nous l'avons implémenté, nous voulons vérifier côté serveur si l'appel contient des caractères non utf-8, si oui, répondez avec un état http 4xx

Solution actuelle en détail

Vérifiez chaque caractère (== string.substring (i, i + 1))

  1. si character.getBytes () [0] est égal à 63 pour '?'
  2. si Character.getType (character.charAt (0)) renvoie OTHER_SYMBOL

Code

protected List< String > getNonUnicodeCharacters( String s ) {
  final List< String > result = new ArrayList< String >();
  for ( int i = 0 , n = s.length() ; i < n ; i++ ) {
    final String character = s.substring( i , i + 1 );
    final boolean isOtherSymbol = 
      ( int ) Character.OTHER_SYMBOL
       == Character.getType( character.charAt( 0 ) );
    final boolean isNonUnicode = isOtherSymbol 
      && character.getBytes()[ 0 ] == ( byte ) 63;
    if ( isNonUnicode )
      result.add( character );
  }
  return result;
}

Question

Est-ce que cela interceptera tous les caractères invalides (non encodés en utf)? L'un de vous a-t-il une meilleure solution (plus facile)?

Note: J'ai vérifié URLDecoder avec le code suivant

final String[] test = new String[]{
  "v=abc%E4def",
  "v=abc%C3%A4def"
};
for ( int i = 0 , n = test.length ; i < n ; i++ ) {
    System.out.println( Java.net.URLDecoder.decode(test[i],"UTF-8") );
    System.out.println( Java.net.URLDecoder.decode(test[i],"ISO-8859-1") );
}

Cela imprime:

v=abc?def
v=abcädef
v=abcädef
v=abcädef

et il ne pas lance une IllegalArgumentException soupir

32
Daniel Hiller

J'ai posé la même question,

Gestion du codage des caractères dans l'URI sur Tomcat

J'ai récemment trouvé une solution et cela fonctionne assez bien pour moi. Vous voudrez peut-être l'essayer. Voici ce que tu dois faire,

  1. Laissez votre encodage URI en Latin-1. Sur Tomcat, ajoutez URIEncoding = "ISO-8859-1" au connecteur dans server.xml.
  2. Si vous devez décoder manuellement les URL, utilisez également Latin1 comme jeu de caractères.
  3. Utilisez la fonction fixEncoding () pour corriger les encodages.

Par exemple, pour obtenir un paramètre de la chaîne de requête,

  String name = fixEncoding(request.getParameter("name"));

Vous pouvez toujours le faire. La chaîne avec un encodage correct n'est pas modifiée.

Le code est joint. Bonne chance!

 public static String fixEncoding(String latin1) {
  try {
   byte[] bytes = latin1.getBytes("ISO-8859-1");
   if (!validUTF8(bytes))
    return latin1;   
   return new String(bytes, "UTF-8");  
  } catch (UnsupportedEncodingException e) {
   // Impossible, throw unchecked
   throw new IllegalStateException("No Latin1 or UTF-8: " + e.getMessage());
  }

 }

 public static boolean validUTF8(byte[] input) {
  int i = 0;
  // Check for BOM
  if (input.length >= 3 && (input[0] & 0xFF) == 0xEF
    && (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) {
   i = 3;
  }

  int end;
  for (int j = input.length; i < j; ++i) {
   int octet = input[i];
   if ((octet & 0x80) == 0) {
    continue; // ASCII
   }

   // Check for UTF-8 leading byte
   if ((octet & 0xE0) == 0xC0) {
    end = i + 1;
   } else if ((octet & 0xF0) == 0xE0) {
    end = i + 2;
   } else if ((octet & 0xF8) == 0xF0) {
    end = i + 3;
   } else {
    // Java only supports BMP so 3 is max
    return false;
   }

   while (i < end) {
    i++;
    octet = input[i];
    if ((octet & 0xC0) != 0x80) {
     // Not a valid trailing byte
     return false;
    }
   }
  }
  return true;
 }

EDIT: Votre approche ne fonctionne pas pour diverses raisons. Lorsqu'il y a des erreurs d'encodage, vous ne pouvez pas compter sur ce que vous obtenez de Tomcat. Parfois, vous obtenez � ou?. D'autres fois, vous n'obtiendrez rien, getParameter () renvoie null. Supposons que vous puissiez vérifier "?", Que se passe-t-il si votre chaîne de requête contient un "?" Valide ?

De plus, vous ne devez rejeter aucune demande. Ce n'est pas la faute de votre utilisateur. Comme je l'ai mentionné dans ma question d'origine, le navigateur peut coder l'URL en UTF-8 ou en Latin-1. L'utilisateur n'a aucun contrôle. Vous devez accepter les deux. Changer votre servlet en Latin-1 préservera tous les caractères, même s'ils sont faux, pour nous donner une chance de le réparer ou de le jeter.

La solution que j'ai publiée ici n'est pas parfaite, mais c'est la meilleure que nous ayons trouvée jusqu'à présent.

32
ZZ Coder

Vous pouvez utiliser un CharsetDecoder configuré pour lever une exception si des caractères non valides sont trouvés:

 CharsetDecoder UTF8Decoder =
      Charset.forName("UTF8").newDecoder().onMalformedInput(CodingErrorAction.REPORT);

Voir CodingErrorAction.REPORT

14
ante

Voici ce que j'ai utilisé pour vérifier l'encodage:

CharsetDecoder ebcdicDecoder = Charset.forName("IBM1047").newDecoder();
ebcdicDecoder.onMalformedInput(CodingErrorAction.REPORT);
ebcdicDecoder.onUnmappableCharacter(CodingErrorAction.REPORT);

CharBuffer out = CharBuffer.wrap(new char[3200]);
CoderResult result = ebcdicDecoder.decode(ByteBuffer.wrap(bytes), out, true);
if (result.isError() || result.isOverflow() ||
    result.isUnderflow() || result.isMalformed() ||
    result.isUnmappable())
{
    System.out.println("Cannot decode EBCDIC");
}
else
{
    CoderResult result = ebcdicDecoder.flush(out);
    if (result.isOverflow())
       System.out.println("Cannot decode EBCDIC");
    if (result.isUnderflow())
        System.out.println("Ebcdic decoded succefully ");
}

Edit: mis à jour avec la suggestion de Vouze

6
luca

Remplacer tous les caractères de contrôle dans une chaîne vide

value = value.replaceAll("\\p{Cntrl}", "");
4
Zhile Zou

RLDecoder décodera en un encodage donné. Cela devrait signaler les erreurs de manière appropriée. Cependant, la documentation indique:

Il existe deux manières possibles pour ce décodeur de traiter les chaînes illégales. Il peut soit laisser les caractères illégaux seuls, soit lever une exception IllegalArgumentException. L'approche adoptée par le décodeur est laissée à la mise en œuvre.

Vous devriez donc probablement l'essayer. Notez également (à partir de la documentation de la méthode decode ()):

Recommandation du World Wide Web Consortium indique que l’UTF-8 doit être utilisé. Ne pas le faire peut introduire des incompatibilités

il y a donc autre chose à penser!

EDIT: Apache Commons RLDecode prétend lever des exceptions appropriées pour les mauvais encodages.

3
Brian Agnew

J'ai travaillé sur un problème similaire "devinez l'encodage". La meilleure solution implique sachant l'encodage. Sauf cela, vous pouvez faire des suppositions éclairées pour faire la distinction entre UTF-8 et ISO-8859-1.

Pour répondre à la question générale de savoir comment détecter si une chaîne est correctement encodée en UTF-8, vous pouvez vérifier les choses suivantes:

  1. Aucun octet n'est 0x00, 0xC0, 0xC1 ou dans la plage 0xF5-0xFF.
  2. Les octets de queue (0x80-0xBF) sont toujours précédés d'un octet de tête 0xC2-0xF4 ou d'un autre octet de queue.
  3. Les octets de tête doivent prédire correctement le nombre d'octets de queue (par exemple, tout octet dans 0xC2-0xDF doit être suivi exactement d'un octet dans la plage 0x80-0xBF).

Si une chaîne passe tous ces tests, elle peut être interprétée comme UTF-8 valide. Cela ne garantit pas qu'il est UTF-8, mais c'est un bon prédicteur.

L'entrée légale dans ISO-8859-1 n'aura probablement aucun caractère de contrôle (0x00-0x1F et 0x80-0x9F) autre que les séparateurs de ligne. Il semble que 0x7F ne soit pas défini non plus dans ISO-8859-1.

(Je fonde cela sur les pages Wikipedia pour UTF-8 et ISO-8859-1.)

3
Adrian McCarthy

Vous souhaiterez peut-être inclure un paramètre connu dans vos demandes, par exemple "... & encTest = ä €", pour différencier en toute sécurité les différents encodages.

2
mfx

Vous devez configurer l'encodage des caractères depuis le début. Essayez d'envoyer le bon Type de contenu en-tête, par exemple Type de contenu: texte/html; charset = utf-8 pour fixer le bon encodage. La conformité standard fait référence à utf-8 et utf-16 comme codage approprié pour les services Web. Examinez vos en-têtes de réponse.

De plus, côté serveur - dans le cas où le navigateur ne gère pas correctement l'encodage envoyé par le serveur - forcez l'encodage en allouant une nouvelle chaîne. Vous pouvez également vérifier chaque octet dans la chaîne utf-8 encodée en faisant un seul each_byte & 0x80, vérifiant le résultat comme non nul.


boolean utfEncoded = true;
byte[] strBytes = queryString.getBytes();
for (int i = 0; i < strBytes.length(); i++) {
    if ((strBytes[i] & 0x80) != 0) {
        continue;
    } else {
        /* treat the string as non utf encoded */
        utfEncoded = false;
        break;
    }
}

String realQueryString = utfEncoded ?
    queryString : new String(queryString.getBytes(), "iso-8859-1");

Aussi, prenez un regardez cet article , j'espère que cela vous aidera.

1
daniel

l'expression régulière suivante pourrait vous intéresser:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/Ruby/ruby-talk/185624

Je l'utilise dans Ruby comme suit:

module Encoding
    UTF8RGX = /\A(
        [\x09\x0A\x0D\x20-\x7E]            # ASCII
      | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
      |  \xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
      | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
      |  \xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
      |  \xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
      | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
      |  \xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
    )*\z/x unless defined? UTF8RGX

    def self.utf8_file?(fileName)
      count = 0
      File.open("#{fileName}").each do |l|
        count += 1
        unless utf8_string?(l)
          puts count.to_s + ": " + l
        end
      end
      return true
    end

    def self.utf8_string?(a_string)
      UTF8RGX === a_string
    end

end
1
dimus

Essayez d'utiliser UTF-8 par défaut comme toujours partout où vous pouvez toucher. (Base de données, mémoire et interface utilisateur)

Un et un seul codage de jeu de caractères pourrait réduire beaucoup de problèmes, et en fait il peut accélérer les performances de votre serveur Web. Il y a tellement de puissance de traitement et de mémoire gaspillée dans le codage/décodage.

0
Dennis C