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))
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
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,
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.
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);
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
Remplacer tous les caractères de contrôle dans une chaîne vide
value = value.replaceAll("\\p{Cntrl}", "");
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.
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:
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.)
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.
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.
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
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.