基本的に、まともな国際化ライブラリを使っていれば、上記のような不正な文字コードはきちんと処理してくれるはずです。実際、 Opera, Firefox, IE ともに適切にエスケープしてくれました。また、 UCS に変換した後にエスケープ処理を行うことでも対処できるかもしれません。しかし、複数のモジュールで構成されるような規模の大きいアプリケーションでは、そのすべてが適切な処理を行っていると保証するのも、なかなか難しいかと思います。ここはやはり、すべての外部入力に含まれる不正なシーケンスを、水際で正規化するという処理を徹底するのが一番かと思います。

例えば Ruby の場合、不正な UTF-8 コードを検出する最も簡単な方法は、 String#unpack を使って UCS へ変換してみることです（昨日の記事への kazutanaka さんからのはてぶコメントにて、 iconv でも同様なことができるとご教示いただきました。まだきちんと確認していないのですが、 Ruby 以外でも使える汎用的なテクニックとして有用だと思います）。

utf8_str. unpack ( "U*" )

※コメントで PuniPuni さんがご指摘のように、 String#unpack, Iconv ともにサロゲートペアの領域が検出できないようです。サロゲートペアの領域も検出する必要がある場合は、やはり自分で検証を行わなければなりません。

もし utf8_str に不正なシーケンスが含まれていれば、例外が発生します。しかし、この方法だと不正なシーケンスを含んだ文字列は全体を捨てるしか方法がありません。単純に処理を中断することが許されない場合は、やはり自分で正規化を行うしかありません。参考までに、私の JSON パーサー で使っている正規化処理を単体で使えるようにしたものを再掲載しておきます。この関数は、不正なシーケンスを見つけると引数 malformed_chr で指定された文字に置き換え、その結果の文字列を返します。

# str は正規化する UTF-8 文字列、 malformed_chr は不正なシーケンスを置き換える文字です。 # malformed_chr は文字列ではなく Unicode の整数を与えてください。 def validate_utf8 ( str, malformed_chr ) code = 0 rest = 0 range = nil ucs = [ ] str. each_byte do |c| if rest <= 0 case c when 0x01..0x7f then rest = 0 ; ucs << c when 0xc0..0xdf then rest = 1 ; code = c & 0x1f ; range = 0x00080..0x0007ff when 0xe0..0xef then rest = 2 ; code = c & 0x0f ; range = 0x00800..0x00ffff when 0xf0..0xf7 then rest = 3 ; code = c & 0x07 ; range = 0x10000..0x10ffff else ucs << malformed_chr end elsif ( 0x80..0xbf ) === c code = ( code << 6 ) | ( c & 0x3f ) if ( rest -= 1 ) <= 0 if ! ( range === code ) || ( 0xd800..0xdfff ) === code code = malformed_chr end ucs << code end else ucs << malformed_chr rest = 0 end end ucs. pack ( 'U*' ) end

やっていることは、 UCS にいったん変換した上で不正なコードを malformed_chr に置き換え、再度 UTF-8 に戻しているだけです。ですので、最後の行で配列 ucs をそのまま返せば、 UTF-8 → UCS-4 変換になります。他にうまいアルゴリズムなどがありましたら、教えていただけると嬉しいです。