はじめに
この記事は42Tokyoというエンジニアスクールの課題で「Unicode対応のプログラム」を作成することがあったので、Unicodeコンソーシアムの公式ドキュメントやレポート、Unicode標準入門という書籍を参考に、学習した内容をまとめた記事です。
この記事でわかる・できること
- Unidoce標準に準拠したプログラムが満たすべきエラー処理
- UTF-8エンコーディング形式の要件
この記事の対象者
Unicodeの仕様については理解しているものの、どうすればUnicode(UTF-8)を安全に扱えるのかを知りたい方。
今回は、Unicodeを扱う上で重要な【エラー処理】について書きます。
Unicodeのバージョン
Unicode Version 17.0.0 (2025 September 9)
Unicode標準に準拠するための要件
この章では、「Unicode標準に準拠するための要件とは何か」「なぜ準拠する必要があるのか」を学びます。
文字コードの仕組みを正しく理解すると、文字化けやデータの不具合を防げるようになります。
Unicodeに準拠したプログラムとは、単に文字を表示できるだけではなく、次の条件を満たす必要があります。
- データを正しく解釈できること
- データを正しく処理できること
- 不正なデータ(仕様に反するバイト列など)を安全に扱えること
正規のバイト列だけを受け取り、それをそのまま出力するだけのプログラムは、Unicodeを「扱えている」とは言えません。
Unicode標準に準拠するための要件とは何か
Unicodeは単なる「文字コード表」ではありません。
プログラムがどのように文字を正しく扱うかも定義する仕様です。
Unicode公式FAQでは、次のように説明されています。
Q: What does Unicode conformance require?
Chapter 3, Conformance discusses this in detail. Here's a very informal version:
- Unicode characters don't fit in 8 bits; deal with it.
- 2 Byte order is only an issue in I/O. If you don't know, assume big-endian.
- Loose surrogates have no meaning.
- Neither do U+FFFE and U+FFFF.
- Leave the unassigned code points alone.
- It's OK to be ignorant about a character, but not plain wrong.
- Subsets are strictly up to you.
- Canonical equivalence matters.
- Don't garble what you don't understand.
- Process UTF-* by the book.
- Ignore illegal encodings.
- Right-to-left scripts have to go by bidi rules.
和訳
Q:「Unicode準拠」とは何を満たすことを要求されるのか?
Unicode 標準の 第3章: Conformance では詳細に説明されています。以下は、少しくだけた形で要約した内容です。
- 文字は8ビットに収まらない
- エンディアンはI/O(入出力)時にのみ考慮すべき。わからないなら、ビッグエンディアンだと仮定。(UTF-16, UTF-32)
- 孤立したサロゲート値(ペアを成していない上位・下位サロゲート)は意味を持たない
- 非文字(U+FFFEとU+FFFFなど)も意味を持たない
- 未割り当てのコードポイントはそのままにしておく。勝手に文字を定義してはいけない
- 知らない文字があってもいいが、間違った扱いをしてはならない
- Unicodeのどの部分集合をサポートするかは、実装者の自由
- 正規等価性(Canonical Equivalence)を尊重しなければならない(同じ文字を異なる符号化で表した場合でも、同一として扱う必要がある)
- 理解していない文字データを壊してはいけない
- UTF-8/UTF-16/UTF-32 などの符号化形式は、仕様書に忠実に処理する
- 不正なエンコーディング(仕様に反するバイト列)は無視する
- 右から左に書く文字(RTL scripts)(アラビア語やヘブライ語など)は、双方向テキスト処理(BiDiルール)に従う必要がある
なぜ準拠する必要があるのか
詳しくは次の章をお読みください。
Unicode標準に準拠していないプログラムで起こること
Unicode準拠は、単なる文字コードの統一ではなく、プログラムが文字データを正しく扱い、安全に処理するための指針です。準拠しない場合、次のような問題が起こり得ます。(確実に不具合が起こるというわけではありません。)
文字化け・データ破損
マルチバイト文字を誤って扱うと、文字化けやデータ破損が発生する。
例えば、UTF-8で3バイトで表される「字」をコードユニット列(3バイト)で扱わず、1バイトずつ細切れにするなど。
不正なデータの誤処理
非文字コード(U+FFFE, U+FFFF)や未割り当てコードポイント、サロゲート領域を誤って処理すると、セキュリティ上の問題につながる場合があります。
他システムとの互換性
Unicode準拠は異なるOSやアプリケーション間で文字を正しくやり取りするための共通ルールです。
準拠していないと、テキストファイルや通信データを渡した際に文字化けや情報欠落が起きやすくなります。
表示順序の崩れや、検索、比較(ソート)などが正しく動作しない
アラビア語やヘブライ語などのRTL文字をサポートする場合は、Unicode規格に従わなければ表示順序が崩れることなどがあります。
同じ文字でも複数の符号化方法が存在する場合、正規等価性を考慮しないと検索や比較が正しく動作しないことがあります。
例えば、「é」は、単一のコードポイント
U+00E9
でも、U+0065(e) + U+0301(結合アクセント)
という合成でも表せます。
Unicodeのエンコーディング規格
まず、Unicodeにおけるエンコーディング形式について、3.2.4 Character Encoding Formsでは以下のようにあります。
C8 プロセスが、Unicode文字エンコーディング形式であるとされるコード単位列を解釈する場合、そのコード単位列は対応するコードポイント列に従って解釈しなければならない。
- UTF-8 のコード単位列の仕様は D92 に示されている
- UTF-16 のコード単位列の仕様は D91 に示されている
- UTF-32 のコード単位列の仕様は D90 に示されている
C9 プロセスが、Unicode文字エンコーディング形式であるとされるコード単位列を生成する場合、不正に形成された(ill-formed)コード単位列を出力してはならない。
- 各Unicode文字エンコーディング形式の定義では、その形式における不正なコード単位列が規定されている。例えば、UTF-8(D92)の定義では、
<C0 AF>
のようなコード単位列は不正であると規定されている
C10 プロセスが、Unicode文字エンコーディング形式であるとされるコード単位列を解釈する場合、不正に形成されたコード単位列はエラーとして扱わなければならず、そのような部分列を文字として解釈してはならない。
- 例えば、UTF-8 では、形式 110xxxxx₂ のコード単位は必ず形式 10xxxxxx₂ のコード単位が続く必要がある。したがって、110xxxxx₂ 0xxxxxxx₂ のような列は不正であり、決して生成されてはならない。テキストの変換や解釈時にこの不正なコード単位列に直面した場合、適合するプロセスは最初のコード単位 110xxxxx₂ を不正に終了したコード単位列として扱わなければならない。例えば、エラーを報告するか、U+FFFD(REPLACEMENT CHARACTER)などのマーカーで置き換えることによって処理する
- 不正な部分列を無視して処理することは強く推奨されない。なぜなら、不正な部分列の前後のテキストを結合すると、結果として得られるテキストが新しい意味を持つ可能性があるからである。これは、JavaScript のような埋め込みプログラムコードを含むテキスト形式では特に危険である
- 適合するプロセスは、不正に形成されたコード単位列を解釈してはならない。しかし、適合規則は、Unicode文字エンコーディング形式であるとされないコード単位列に対して処理を行うことを妨げるものではない。例えば、性能上の理由から、低レベルの文字列操作が単にコード単位上で直接処理を行うことは許される(文字として解釈せずに処理する場合)。D89 の説明も参照
- ユーティリティプログラムが「破損した(mangled)」テキストを扱うことは禁止されていない。例えば、ある UTF-8 ファイルに対して、壊れたメールプログラムが80バイトごとに CRLF を挿入してしまった場合を考える。これにより、一部の UTF-8 バイト列が CRLF によって中断され、不正なバイト列が発生する。このような破損したテキストはもはや UTF-8 ではない。適合するプログラムは、このようなテキストを修復し、元々は正しい UTF-8 バイト列であったことを認識することは許される。しかし、このような破損データの修復は特別なケースであり、セキュリティ上の問題を引き起こす状況では使用してはならない。特に、エンコーディング変換や不正なテキストの変換には重要なセキュリティ上の課題がある。詳細は Unicode Technical Report #36「Unicode Security Considerations」を参照
また、Unicodeにおけるエンコーディング形式の変換について、3.9.4 Encoding Form Conversionでは以下のようにあります。
D93 エンコーディング形式変換 (Encoding form conversion): あるUnicodeエンコーディング形式のコード単位列(code unit sequence)から、別のUnicodeエンコーディング形式のコード単位列への変換を直接定義したもの。
Unicode標準の実装において、典型的なAPIは、入力のコード単位列を論理的にUnicodeスカラ値(コードポイント)に変換し、そのスカラ値を出力のコード単位列に変換する。
しかし、エンコーディング形式を適切に分析すれば、コード単位を直接変換することも可能であり、結果は同じでありながら、より効率的な処理を行える。適合する(conformant)エンコーディング形式変換は、どんな不正な(ill-formed)コード単位列もエラーとして扱う(適合規則 C10 を参照)。
これにより、不正なコード単位列を解釈したり出力したりすることが決してないことが保証される。
エンコーディング形式変換の実装は、この要件を考慮する必要がある。なぜなら、エンコーディング形式変換は暗黙のうちに、変換対象のUnicode文字列が実際に正しく形成されたコード単位列を含んでいるかを検証することを伴うからである。
以上から、Unicodeのエンコーディング形式を扱う際(生成・解釈・形式変換・出力など)には、不正なコード単位列をエラーとして扱わなければならないと明記されていることがわかります。
これを踏まえた上で、UTF-8エンコーディング形式について確認していきます。
UTF-8エンコーディング形式
UTF-8エンコーディング形式について、3.9.3 UTF-8ではこのように定義されています。
UTF-8 は、各 Unicode スカラ値(Unicode scalar value)を、1~4バイトの符号なしバイト列として割り当てる Unicode エンコーディング形式であり、その詳細は表 3-6 および表 3-7 に示されている。
- UTF-8 では、コードポイント列
<004D, 0430, 4E8C, 10302>
は<4D D0 B0 E4 BA 8C F0 90 8C 82>
と表現される
<4D>
は U+004D に対応
<D0 B0>
は U+0430 に対応
<E4 BA 8C>
は U+4E8C に対応
<F0 90 8C 82>
は U+10302 に対応
- 表 3-7 に示されるパターンに一致しない UTF-8 バイト列は、すべて不正(ill-formed)である。
- Unicode Standard Version 3.1 以前では、BMP(Basic Multilingual Plane)の文字を複数の方法で表現できる「非最短形式(non-shortest form)」のバイト列が問題とされていた。これらのバイト列は表 3-7 で許可されていないため、不正とみなされる。
- また、サロゲートコードポイントは Unicode スカラ値ではないため、コードポイント U+D800..U+DFFF に対応する UTF-8 バイト列はすべて不正である。
表 3-6 UTF-8 ビット分布 (UTF-8 Bit Distribution)
スカラ値(Scalar Value) | 1バイト目 | 2バイト目 | 3バイト目 | 4バイト目 |
---|---|---|---|---|
00000000 0xxxxxxx |
0xxxxxxx |
|||
00000yyy yyxxxxxx |
110yyyyy |
10xxxxxx |
||
zzzzyyyy yyxxxxxx |
1110zzzz |
10yyyyyy |
10xxxxxx |
|
000uuuuu zzzzyyyy yyxxxxxx |
11110uuu |
10uuzzzz |
10yyyyyy |
10xxxxxx |
表 3-7 正しく形成された UTF-8 バイト列 (Well-Formed UTF-8 Byte Sequences)
コードポイント範囲 | 1バイト目 | 2バイト目 | 3バイト目 | 4バイト目 |
---|---|---|---|---|
U+0000..U+007F | 00..7F | |||
U+0080..U+07FF | C2..DF | 80..BF | ||
U+0800..U+0FFF | E0 | A0..BF | 80..BF | |
U+1000..U+CFFF | E1..EC | 80..BF | 80..BF | |
U+D000..U+D7FF | ED | 80..9F | 80..BF | |
U+E000..U+FFFF | EE..EF | 80..BF | 80..BF | |
U+10000..U+3FFFF | F0 | 90..BF | 80..BF | 80..BF |
U+40000..U+FFFFF | F1..F3 | 80..BF | 80..BF | 80..BF |
U+100000..U+10FFFF | F4 | 80..8F | 80..BF | 80..BF |
【エラー】 1. 不正なバイト列のパターン (Ill-Formed Sequences)
UTF‑8 の構造規則にそぐわないバイト列。以下は有効な Unicode 文字として解釈してはならない。
- UTF-8のleading byte(先頭バイト)やtrailing bytes(継続バイト)以外のビットパターンのバイト
- 先頭バイトが期待するバイト数の継続バイトが、先頭バイトの後に連続しないバイト列
- 継続バイトが先頭バイトの後ろ以外に出現するバイト列
【エラー】 2. 非最短形式 (non-shortest form)
あるコードポイントを表すのに、本来の最短バイト表現より長い形式(非最短)が使われているもの。非最短形式は禁止されており、受け入れると同じ文字が複数のバイト列で表現可能になり、正規化やセキュリティで問題を生む。
例: ASCII
'/' (U+002F)
の非最短表現:C0 AF
← 不正(非最短)
本来1バイト2F
で表せる'/' (U+002F)
をわざわざ2バイトC0 AF
で表しているため不正。
(UTF-8のデコード規則に沿うと、C0 AF
もU+002F
のコードポイントに解釈できる)
【エラー】 3. 不正となるコードポイント
- UTF‑16 でサロゲートペアに使われる値(
U+D800
–U+DFFF
)は、UTF‑8 における有効なコードポイントではなく、これらを表すような UTF‑8 シーケンスは不正とみなす必要があります - Unicode のスカラ値の定義域は
U+0000
-U+10FFFF
まで。これを超えるコードポイントを表すシーケンスも不正とみなされる
UTF-8エンコーディング形式では、これらをエラーとして処理しなくてはならなりません。
- UTF-8変換では、不正なコードユニット列を有効文字として解釈してはいけない
- 後続バイトが有効なら、不正シーケンスに含めてはいけない(セキュリティリスク回避)
- 複数バイトの不正シーケンスは、1エラーとしても、バイトごとに複数エラーとしてもよい
【要注意】 4. 非文字(noncharacter code point)の取り扱い
U+FFFE
,U+FFFF
や各プレーン(面)末尾の非文字(noncharacter code point)は内部的な用途以外には適さない。非文字自体は不正な形式ではないが、状況に応じて適切な取り扱いをしなければならない。
3.2.1 Code Points Unassigned to Abstract Characters
C2 プロセスは、非文字コードポイント (noncharacter code point) を抽象文字として解釈してはならない。
- 非文字コードポイントは、センチネル値や区切りなど、内部的な用途に使用することはできるが、公開データの交換には用いるべきではない。
ただし、23.7 Noncharactersには以下のような記述がある。
非文字は、Unicode 標準で内部使用のために恒久的に予約されたコードポイントである。
Unicode テキストデータの公開交換での使用は推奨されない。
(一部省略...)
アプリケーションは、これらの非文字コードポイントを内部で自由に使用できる。
外部交換の文脈外では標準的な解釈は存在しない。
ただし、外部交換で出現しても違法ではなく、Unicode テキストが不正形式になることもない。
非文字の意図は、Unicode 標準によって恒久的に交換可能な意味が割り当てられないようにすることである。
外部交換される場合でも、非文字は有効な Unicode 文字列中に現れても問題ない。この区別により、API 内の文字列やプロセス間プロトコル、ストレージ内で内部的に交換される場合に非文字が正しく保持される。
非文字を公開交換で受け取った場合、アプリケーションはそれを解釈する必要はない。しかし、非文字であることを認識し、U+FFFD(� REPLACEMENT CHARACTER)などに置換して問題を示すのが望ましい。
非文字コードポイントを単純に削除することは推奨されない。解釈されていない文字を削除すると、セキュリティ上の問題が生じる可能性がある。(Section 3.2 の準拠条項 C7 および Unicode Technical Report #36 を参照)
また、3.2.3 修正(Modification)では、以下のようにある。
C7 有効な符号化文字列の解釈を変更しないと主張するプロセスは、正規等価シーケンスによる文字列の置換を除き、その符号化文字列に変更を加えてはならない。
- 文字列を互換等価シーケンスに置換することは、テキストの解釈を変更することになる。
- プロセスが解釈できない、または解釈しない文字列を置換または削除することも、テキストの解釈を変更することになる。
- 異なるマシンアーキテクチャ間で文字列のビットまたはバイト順序を変更することは、テキストの解釈を変更することにはならない。
- 有効な符号化文字列をある Unicode 文字エンコーディング形式から別の形式に変更することは、テキストの解釈を変更することにはならない。
- コードユニット列のバイトシリアライズをある Unicode 文字エンコーディング方式から別の方式に変更することも、テキストの解釈を変更することにはならない。
特定の内部用途を持たない非文字が処理中に予期せず出現した場合、実装はエラーを通知するか、非文字を U+FFFD REPLACEMENT CHARACTER に置換してよい。実装が非文字を置換、削除、または無視する場合、それはテキストの解釈の変更と見なされる。
一般的に、非文字は未割り当てコードポイントとして扱うべきである。例えば、API が非文字の文字プロパティ値を返す場合、その値は未割り当てコードポイントのデフォルト値と同じになる。
外部ソースから受け取ったテキストから非文字コードポイントを削除すると、セキュリティ上の問題が生じる可能性がある。詳細は Section 23.7「Noncharacters」および Unicode Technical Report #36「Unicode Security Considerations」を参照。
非文字は、抽象文字として解釈してはならない。非文字を公開交換で受け取った場合や、バイト列を抽象文字として解釈する際に意図せず非文字が出現した場合は、実装はエラーとして通知するか、U+FFFD REPLACEMENT CHARACTERに置換するのが望ましい。削除は、セキュリティ上のリスクがあるので避けるべきである。また、非文字は外部との交換には適さないが、仮に非文字が外部交換時に含まれていても不正形式とはならない。内部的にデータを取り扱う際に、実装が非文字を置換、削除、または無視する場合はテキストの解釈を変更することになる。
UTF-8における不正なバイト列のエラー処理
Unicode Technical Report #36「Unicode Security Considerations」の3.6.2 Some Output For All Inputには以下のように記述されている。
文字エンコーディング変換は、不正な入力バイト列を単にスキップしてはならない。代わりに、変換はエラーで停止するか、出力に置換文字(U+FFFD(� REPLACEMENT CHARACTER)など)やエスケープシーケンスを挿入しなければならない。
また、3.6.1 Illegal Input Byte Sequencesに、安全なエラー処理の戦略が提示されている。
以下は、不正なマルチバイトシーケンスを扱う変換コードにとって安全なエラー処理戦略である。(単一/先頭バイトが不正である場合はこの問題を引き起こさない。)
- (戦略1): エラーで停止する。残りのテキストの変換を続けない
- (戦略2): 報告された不正シーケンスの中に、有効な文字をエンコードする非初期バイトあるいは有効なシーケンスの先頭バイトを含めないようにする
- (戦略3): 不正シーケンスの最初のバイトをエラーとして報告し、2番目のバイトから変換を続ける
- 戦略1が最も単純だが、多くの場合できるだけ多くのテキストを変換したいという要求がある。例えば、ウェブブラウザは通常少数の不正なバイト列をそれぞれ U+FFFD に置換してページを可能な限り表示する
- 戦略3は次に単純だが、単一バイトのエラーであるにもかかわらず複数の U+FFFD や他のエラー処理の痕跡を生むことがある
- 戦略2が最も自然であり、ほとんどのエラーが物理的な伝送破損によるのではなく不適切な文字列処理による切断されたマルチバイトシーケンスであるという想定に適合する。また多くの場合、以前のバイトストリーム位置に戻ることを避けられる
今回はW3Cで採用され、Unicodeでも解説されている、戦略2(最大部分置換(Substitution of Maximal Subparts))のエラー処理を扱います。
最大部分置換(Substitution of Maximal Subparts)
3.9.6 U+FFFD Substitution of Maximal Subpartsでは以下のように解説されている。
多くの実装が、W3Cのエンコーディング標準で規定されている、不正形式(ill-formed)な部分列の取り扱いを採用し始めており、一貫したU+FFFD置換を実現している。
Unicode標準は、この方法を準拠のために必須とはしていないが、以下の文章ではこの方法について説明し、詳細な例を示している。
変換不能オフセット(Unconvertible offset)
:その位置(オフセット)から始まる任意のコード単位部分列が、いずれも正しい形式(well-formed)ではないような、コード単位列内のオフセット。
不正形式部分列の最大部分(Maximal subpart of an ill-formed subsequence)
:変換不能オフセットから始まる最長のコード単位部分列で、次のいずれかの条件を満たすもの。
a. 正しい形式のコード単位列の先頭部分列である。
b. 長さが1である部分列。この「最大部分(maximal subpart)」の定義は、置換を行う際にどの程度処理を進めるかを説明するために用いられる。すなわち、常に少なくとも1つのコード単位を処理するか、または、正しい形式の文字の先頭に一致する限りのコード単位を処理し、次のコード単位を追加する>と不正形式になる地点、すなわち部分的な文字を継続できなくなるオフセットに達するまで処理する
より形式的に述べると次のようになる:
コード単位列の変換中に変換不能オフセットに到達したとき:
- そのオフセットにおける最大部分(maximal subpart)は、単一のU+FFFDに置き換えられる。
- 変換は、その最大部分の直後のオフセットから再開される。
この「最大部分の置換」手法は、UTF-32またはUTF-16の符号化形式にも容易に適用できるが、主にUTF-8文字列を変換する際に関心が持たれる。
不正形式部分列の先頭が、正しい形式の部分列の先頭と一致しない場合、この手法では、不正形式のUTF-8シーケンスのほとんどすべてのバイトが、それぞれ1つのU+FFFDで置き換えられる。
3.9.6 U+FFFD Substitution of Maximal Subpartsでは、この後に具体例で解説されていますが、少々わかりづらいと感じたので、以下に簡単にまとめました。
最大部分置換(Substitution of Maximal Subparts)では、以下の3つのパターンに分けられる
1. コード単位列の先頭が不正なバイトであるとき、そのバイトを1つの置換文字U+FFFD
に置換する。
- leading byte以外がコード単位列の先頭にある
- 2バイト文字のleading byteが
0xC2未満
である。(1バイト目で非最短形式と判断できる)
2. コード単位列の2バイト目が不正なバイトであるとき、すでに読み込んだ有効な1バイトを1つの置換文字U+FFFD
に置換する。(追加された2バイト目は新たな1バイト目として、①で有効なleading byteか検査する。)
- マルチバイト文字の有効な1バイト目の次のバイト(2バイト目)でtrailing byte以外が出てきた場合。
- 3バイト文字のleading byteが
0xE0
で、次のtrailingバイトが0xA0未満
(かつ0x80以上
)である。(2バイト目で非最短形式と判断できる) - 4バイト文字のleading byteが
0xF0
で、次のtrailingバイトが0x90未満
(かつ0x80以上
)である。(2バイト目で非最短形式と判断できる) - 3バイト文字のleading byteが
0xED
で、次のtrailingバイトが0xA0以上
(かつ0xBF以下
)である。(2バイト目でサロゲート領域のコードポイントと判断できる) - 4バイト文字のleading byteが
0xF4
で、次のtrailingバイトが0x90以上
(かつ0xBF以下
)である。(2バイト目で、範囲外のコードポイント(U+10FFFF超
)と判断できる)
3. コード単位列の3バイト目または4バイト目が不正なバイトであるとき、すでに読み込んだ有効な2バイトまたは3バイトを1つの置換文字U+FFFD
に置換する。(追加されたバイトは新たな1バイト目として、①で有効なleading byteか検査する。)
- 3バイト文字の3バイト目でtrailing byte以外が出てきた場合、2バイトが1つの置換文字
U+FFFD
に置換 - 4バイト文字の3バイト目でtrailing byte以外が出てきた場合、2バイトが1つの置換文字
U+FFFD
に置換 - 4バイト文字の4バイト目でtrailing byte以外が出てきた場合、3バイトが1つの置換文字
U+FFFD
に置換
1が、b. 長さが1である部分列
で、
2と3が、a. 正しい形式のコード単位列の先頭部分列
の条件に該当する。
つまり、最大部分置換で複数バイトを1つの置換文字U+FFFDに置換するのは、有効な複数バイトが2バイト以上続いていて、3バイト目以降で不正になる場合のみである。
非文字を置換する
非文字の取り扱いは、プログラム自体の役割や、非文字を内部的に使用しているか、データを扱う目的などによって異なります。
今回は、外部から受け取ったUTF-8のデータを表示することを目的に、非文字は置換するという方針を取ります。
3.2.1 Code Points Unassigned to Abstract Characters
C2 プロセスは、非文字コードポイント (noncharacter code point) を抽象文字として解釈してはならない。
- 非文字コードポイントは、センチネル値や区切りなど、内部的な用途に使用することはできるが、公開データの交換には用いるべきではない。
非文字を公開交換で受け取った場合、アプリケーションはそれを解釈する必要はない。しかし、非文字であることを認識し、U+FFFD(� REPLACEMENT CHARACTER)などに置換して問題を示すのが望ましい。
非文字コードポイントを単純に削除することは推奨されない。解釈されていない文字を削除すると、セキュリティ上の問題が生じる可能性がある。
C7 特定の内部用途を持たない非文字が処理中に予期せず出現した場合、実装はエラーを通知するか、非文字を U+FFFD REPLACEMENT CHARACTER に置換してよい
Unicode では
- BMP(基本多言語面)内の非文字
U+FDD0
~U+FDEF
(32個) -
U+1FFFE
,U+1FFFF
など、各面の最後の2つのコードポイントも非文字
合計で66個の非文字コードポイントが予約されています。
今回は、以下のように完成したシーケンスをコードポイントにデコードして、非文字かどうかを判定します。
bool is_noncharacter(wchar_t codepoint)
{
// BMP 内の非文字
if (codepoint >= 0xFDD0 && codepoint <= 0xFDEF)
return true;
// 各面の最後の 2 文字
if ((codepoint & 0xFFFE) == 0xFFFE && codepoint <= 0x10FFFF)
return true;
return false;
}
非文字だった場合は、非文字を1つの置換文字U+FFFD
に変換します。
識別できない文字の処理(おまけ)
アプリケーションがサポートする文字集合や、その環境で使用されるグリフには違いがあります。これらはUnicodeの原則と、実用面を考慮し、状況に合わせて処理するのが望ましいでしょう。
書籍Unicode標準入門の第5章の4「識別できない文字の処理」では以下のようにあります。
ときには、アプリケーションは次のような識別不能なコード値を受け取ることがあります。
- 割り当てられていない(未定義)コード値
- 私用領域のコード値
- 単にアプリケーションでサポートしていない文字のコード値
このような場合の原則は”いらぬ世話をやかない”ことです。アプリケーションは、識別できないコード値を他のデータに置き換えるべきではありません。データを書き出す際には、その識別できないコード値も、ほかのデータといっしょに書き出した方がいいでしょう。
そして、データを表示する場合には、アプリケーションは、できる限り何かーー黒や白の四角、コード値が未割り当てであることを示す特別なグリフ、そのコード値を四角く囲んだ記号のグリフなどーーを表示するように努力するべきです。
未割り当てのコードポイントについて
3.2.1 Code Points Unassigned to Abstract Characters
C1 プロセスは、高位サロゲートコードポイントまたは低位サロゲートコードポイントを抽象文字として解釈してはならない。
- 高位サロゲートおよび低位サロゲートのコードポイントは、UTF-16 文字エンコーディング形式におけるサロゲートコード単位として予約されている。これらは、いかなる抽象文字にも割り当てられていない。
C2 プロセスは、非文字コードポイント (noncharacter code point) を抽象文字として解釈してはならない。非文字コードポイントは、センチネル値や区切りなど、内部的な用途に使用することはできるが、公開データの交換には用いるべきではない。
C3 プロセスは、未割り当てのコードポイント (unassigned code point) を抽象文字として解釈してはならない。
- ただし、この条項は、未割り当てコードポイントに対して一般的な意味を付与すること(例:文字ブロック内の位置を示すためにグリフを表示するなど)を妨げるものではない。これは、サポート範囲外のコードポイントを処理する際に「優雅な挙動 (graceful behavior)」を可能にする。
- 未割り当てコードポイントには既定のプロパティ値を持つものもある(D26 を参照)。
未指定のコードポイントは、将来の Unicode バージョンで新たな抽象文字に割り当てられる可能性がある。したがって、こうしたコードポイントを扱う際には、将来のバージョンに基づくデータにも対応できるよう、一般的な意味付けにおいて慎重な設計を行うことが望ましい。
参考資料
おわりに
今回の記事では、Unicode準拠プログラムが守るべきルールや、UTF-8の不正バイト列の扱い方、非文字の考え方をまとめました。
今回は単純な表示だけを目的にしましたが、ソートや検索などでUnicodeを扱う場合は、さらに注意すべき点が増えそうです。
ポイントは「正しく解釈・正しく処理・安全にエラー処理」の3つ。この3つを押さえておけば、文字化けやデータ破損、セキュリティリスクを避けやすくなります。
UTF-8についてもコードユニット列の仕組みを解説する資料はあるものの、安全に扱うための具体的な情報は日本語ではあまり見かけませんでした。
正直、情報を集めるのには苦労しましたが、公式のドキュメントやレポートを読みこむという良い経験になりました。