web-dev-qa-db-ja.com

文字列に無効なエンコード文字が含まれているかどうかを判断する方法

使用シナリオ

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))

  1. character.getBytes()[0]が '?'の63に等しい場合
  2. character.getType(character.charAt(0))がOTHER_SYMBOLを返す場合

コード

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

32
Daniel Hiller

同じ質問をしました

TomcatのURIでの文字エンコーディングの処理

私は最近解決策を見つけましたが、それは私にとって非常にうまく機能しています。試してみてください。ここにあなたがする必要があるものがあります、

  1. URIエンコーディングをLatin-1のままにします。 Tomcatで、server.xmlのコネクタにURIEncoding = "ISO-8859-1"を追加します。
  2. 手動でURLデコードする必要がある場合は、Latin1を文字セットとしても使用してください。
  3. FixEncoding()関数を使用して、エンコードを修正します。

たとえば、クエリ文字列からパラメータを取得するには、

  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に変更すると、間違っていてもすべての文字が保持され、修正または破棄する機会が与えられます。

ここに投稿した解決策は完全ではありませんが、これまでに見つけた最良の解決策です。

32
ZZ Coder

無効な文字が見つかった場合に例外をスローするように構成されたCharsetDecoderを使用できます。

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

CodingErrorAction.REPORT を参照してください

14
ante

これは私がエンコーディングをチェックするために使用したものです:

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の提案で更新

6
luca

すべての制御文字を空の文字列に置き換えます

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

RLDecoder は、指定されたエンコードにデコードします。これにより、エラーに適切にフラグが立てられます。ただし、ドキュメントには次のように記載されています。

このデコーダーが不正な文字列を処理できる方法は2つあります。不正な文字をそのまま残すか、IllegalArgumentExceptionをスローする可能性があります。デコーダがどのアプローチを取るかは実装に任されています。

したがって、おそらく試してみてください。以下にも注意してください(decode()メソッドのドキュメントから):

World Wide Web Consortium Recommendation は、UTF-8を使用する必要があることを示しています。そうしないと、非互換性が生じる可能性があります

他に考えるべきことがあります!

編集:Apache Commons RLDecode エンコーディングが正しくない場合、適切な例外をスローするように要求します。

3
Brian Agnew

私は同様の「エンコーディングを推測する」問題に取り組んできました。最適なソリューションには、知るエンコーディングが含まれます。それがなければ、経験に基づいた推測を行い、UTF-8とISO-8859-1を区別できます。

文字列が適切にエンコードされたUTF-8であるかどうかを検出する方法に関する一般的な質問に答えるために、次のことを確認できます。

  1. 0x00、0xC0、0xC1、または0xF5-0xFFの範囲のバイトはありません。
  2. テールバイト(0x80-0xBF)の前には、常にヘッドバイト0xC2-0xF4または別のテールバイトが付きます。
  3. ヘッドバイトは、テールバイトの数を正しく予測する必要があります(たとえば、0xC2-0xDFの任意のバイトの後に、0x80-0xBFの範囲の1バイトが正確に続く必要があります)。

文字列がこれらすべてのテストに合格した場合、有効なUTF-8として解釈できます。それはis UTF-8を保証するものではありませんが、良い予測子です。

ISO-8859-1の有効な入力には、行区切り以外の制御文字(0x00-0x1Fおよび0x80-0x9F)は含まれない可能性があります。 0x7FもISO-8859-1で定義されていないようです。

(私はこれをWikipediaページのUTF-8およびISO-8859-1に基づいています。)

3
Adrian McCarthy

リクエストに既知のパラメータを含めることができます。 「...&encTest =ä€」、異なるエンコーディングを安全に区別します。

2
mfx

最初から文字エンコーディングを設定する必要があります。適切なものを送信してみてください コンテンツタイプ ヘッダー、たとえば コンテンツタイプ: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");

また、 この記事をご覧ください をご覧ください。お役に立てば幸いです。

1
daniel

次の正規表現があなたにとって興味深いかもしれません:

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
1
dimus

触れることができる場所であればいつも、UTF-8をデフォルトとして使用するようにしてください。 (データベース、メモリ、およびUI)

単一の文字セットエンコーディングを使用すると、多くの問題を軽減でき、実際にはWebサーバーのパフォーマンスを向上させることができます。エンコード/デコードに無駄な処理能力とメモリが非常に多くあります。

0
Dennis C