使用シナリオ
Webフロントエンド開発者が(php apiを介して)製品データを表示するために内部的に使用するWebサービスを実装しました。 Webサイトで、ユーザーは何か(クエリ文字列)を入力します。内部的に、WebサイトはAPIを介してサービスを呼び出します。
注:Tomcatではなくrestletを使用します
元の問題
Firefox 3.0.10は、ブラウザで選択されたエンコーディングを尊重し、選択されたエンコーディングに従ってURLをエンコードするようです。これにより、ISO-8859-1とUTF-8のクエリ文字列が異なります。
このWebサイトはユーザーからの入力を転送し、変換しません(変換する必要があるため)。ドイツ語のウムラウトを含むクエリ文字列を使用して、Webサービスを呼び出すapiを介してサービスを呼び出します。
つまりクエリパーツのように
...v=abcädef
「ISO-8859-1」が選択されている場合、送信されたクエリ部分は次のようになります
...v=abc%E4def
しかし、「UTF-8」が選択されている場合、送信されたクエリ部分は次のようになります
...v=abc%C3%A4def
望ましい解決策
サービスを制御しているので、サービスを実装しているため、サーバー側にutf-8以外の文字が含まれているかどうかを確認したい場合は、4xx httpステータスで応答します
現在のソリューションの詳細
各文字を確認します(== string.substring(i、i + 1))
コード
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;
}
質問
これはすべての無効な(utfエンコードされていない)文字をキャッチしますか?あなたの誰がより良い(簡単な)解決策を持っていますか?
注: URLDecoderを次のコードでチェックしました
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") );
}
これは印刷します:
v=abc?def
v=abcädef
v=abcädef
v=abcädef
not IllegalArgumentExceptionをスローsigh
同じ質問をしました
私は最近解決策を見つけましたが、それは私にとって非常にうまく機能しています。試してみてください。ここにあなたがする必要があるものがあります、
たとえば、クエリ文字列からパラメータを取得するには、
String name = fixEncoding(request.getParameter("name"));
これはいつでもできます。正しいエンコーディングの文字列は変更されません。
コードが添付されています。幸運を!
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;
}
編集:あなたのアプローチはさまざまな理由で機能しません。エンコードエラーがある場合、Tomcatから得られるものを当てにすることはできません。時々�または?また、何も取得しない場合、getParameter()はnullを返します。 「?」をチェックできるとします。クエリ文字列に有効な「?」が含まれている場合はどうなりますか?
また、リクエストを拒否しないでください。これはユーザーの責任ではありません。最初の質問で述べたように、ブラウザはURLをUTF-8またはLatin-1でエンコードする場合があります。ユーザーは制御できません。両方を受け入れる必要があります。サーブレットをLatin-1に変更すると、間違っていてもすべての文字が保持され、修正または破棄する機会が与えられます。
ここに投稿した解決策は完全ではありませんが、これまでに見つけた最良の解決策です。
無効な文字が見つかった場合に例外をスローするように構成されたCharsetDecoderを使用できます。
CharsetDecoder UTF8Decoder =
Charset.forName("UTF8").newDecoder().onMalformedInput(CodingErrorAction.REPORT);
CodingErrorAction.REPORT を参照してください
これは私がエンコーディングをチェックするために使用したものです:
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 ");
}
編集:Vouzeの提案で更新
すべての制御文字を空の文字列に置き換えます
value = value.replaceAll("\\p{Cntrl}", "");
RLDecoder は、指定されたエンコードにデコードします。これにより、エラーに適切にフラグが立てられます。ただし、ドキュメントには次のように記載されています。
このデコーダーが不正な文字列を処理できる方法は2つあります。不正な文字をそのまま残すか、IllegalArgumentExceptionをスローする可能性があります。デコーダがどのアプローチを取るかは実装に任されています。
したがって、おそらく試してみてください。以下にも注意してください(decode()メソッドのドキュメントから):
World Wide Web Consortium Recommendation は、UTF-8を使用する必要があることを示しています。そうしないと、非互換性が生じる可能性があります
他に考えるべきことがあります!
編集:Apache Commons RLDecode エンコーディングが正しくない場合、適切な例外をスローするように要求します。
私は同様の「エンコーディングを推測する」問題に取り組んできました。最適なソリューションには、知るエンコーディングが含まれます。それがなければ、経験に基づいた推測を行い、UTF-8とISO-8859-1を区別できます。
文字列が適切にエンコードされたUTF-8であるかどうかを検出する方法に関する一般的な質問に答えるために、次のことを確認できます。
文字列がこれらすべてのテストに合格した場合、有効なUTF-8として解釈できます。それはis UTF-8を保証するものではありませんが、良い予測子です。
ISO-8859-1の有効な入力には、行区切り以外の制御文字(0x00-0x1Fおよび0x80-0x9F)は含まれない可能性があります。 0x7FもISO-8859-1で定義されていないようです。
(私はこれをWikipediaページのUTF-8およびISO-8859-1に基づいています。)
リクエストに既知のパラメータを含めることができます。 「...&encTest =ä€」、異なるエンコーディングを安全に区別します。
最初から文字エンコーディングを設定する必要があります。適切なものを送信してみてください コンテンツタイプ ヘッダー、たとえば コンテンツタイプ:text/html; charset = utf-8 適切なエンコーディングを修正します。標準準拠 適切なエンコーディングとしてutf-8およびutf-16を参照 Webサービス。応答ヘッダーを調べます。
また、サーバー側では(ブラウザーがサーバーから送信されたエンコードを適切に処理しない場合)、新しい文字列を割り当ててエンコードを強制します。また、エンコードされたutf-8文字列の各バイトを確認するには、単一の each_byte&0x80、結果がゼロでないことを確認します。
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");
また、 この記事をご覧ください をご覧ください。お役に立てば幸いです。
次の正規表現があなたにとって興味深いかもしれません:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/Ruby/ruby-talk/185624
次のようにRubyで使用します:
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
触れることができる場所であればいつも、UTF-8をデフォルトとして使用するようにしてください。 (データベース、メモリ、およびUI)
単一の文字セットエンコーディングを使用すると、多くの問題を軽減でき、実際にはWebサーバーのパフォーマンスを向上させることができます。エンコード/デコードに無駄な処理能力とメモリが非常に多くあります。