Slide 1

Slide 1 text

nkfのソースコード リーディングをした話 〜そのために必要な最低限の文字コード入門〜 2024.08.26 Kashiwa.rb #2 LT Koji NAKAMURA (@kozy4324)

Slide 2

Slide 2 text

kozy4324 = { name: "Koji NAKAMURA", alias_name: "こーじ", 𝕏: "@kozy4324", belongs_to: [ "Classi株式会社", "Shinjuku.rb", "Kashiwa.rb", ], } 自己紹介

Slide 3

Slide 3 text

あらすじ

Slide 4

Slide 4 text

バグ? or Notバグ?

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

その前に nkf とは ● Ruby 標準添付ライブラリの一つ ○ https://docs.ruby-lang.org/ja/latest/class/NKF.html ● ネットワーク用漢字コード変換フィルタ ○ ものすごく古くからある(初版は 1987年) ○ 実装はC言語 ● それを Ruby から使うためのモジュールが nkf gem ● 機能は文字コード変換と文字コード推測 ○ 冒頭の「シュールな振る舞い」と書いたのは文字コード推測の #guess メソッド

Slide 7

Slide 7 text

nkf のソースコードリーディングをしてみる (1) ● /ext/nkf/nkf-utf8/nkf.c にある kanji_convert 関数から見ていくと良さそう ○ https://github.com/ruby/nkf/blob/24e6ae66395a14d3022e1dd1210d04168e2c8d9e/ext/nkf/nkf- utf8/nkf.c#L5847 ● 文字のバイト列先頭から1バイト読むごとに code_status 関数で e_status, s_status, w_status を呼び出して何かを評価している ○ https://github.com/ruby/nkf/blob/24e6ae66395a14d3022e1dd1210d04168e2c8d9e/ext/nkf/nkf- utf8/nkf.c#L3273 ● それぞれが特定の文字コードらしいかどうかを評価しているみたい ○ e_status => EUC-JP ○ s_status => Shift_JIS ○ w_status => UTF-8

Slide 8

Slide 8 text

そもそも nkf の文字コード判定ロジックどうなっている? ● AI に聞いた ○ バイトパターンの検査 ○ 頻度解析 ○ 例外処理とファイルヘッダの利用 ○ その他の補助的な判断

Slide 9

Slide 9 text

nkfのソースコードリーディングをしてみる (2) ● 特定の文字コードらしいかどうかをスコア化して比較する ○ スコアを足し込みする関数が code_score ○ https://github.com/ruby/nkf/blob/24e6ae66395a14d3022e1dd1210d04168e2c8d9e/ext/nkf/nkf- utf8/nkf.c#L3019-L3049 ○ 足し込みされる値は定数化されている ○ https://github.com/ruby/nkf/blob/24e6ae66395a14d3022e1dd1210d04168e2c8d9e/ext/nkf/nkf- utf8/nkf.c#L2956-L2964 ● スコアが大きくなるほど、その文字コードではない、と判定される ○ 無効なコードポイントが出てくると SCORE_NO_EXIST ○ バイト列全体としておかしい場合は SCORE_ERROR ● 1番スコアの低い {e,s,w}_status のものが推定された文字コードとなる

Slide 10

Slide 10 text

nkfのソースコードリーディングをしてみる (3) ● 「ゔ」「あ」それぞれのUTF-8バイト列 ○ ゔ => e38294(3バイト) ○ あ => e38182(3バイト) ● 「ゔ」「ゔああ」 ○ Shift_JIS: SCORE_ERROR が設定される ○ UTF-8: 「ゔ」で SCORE_NO_EXIST が設定される? ○ 結果として UTF-8 と判定される ● 「ゔあ」「ゔあああ」 ○ Shift_JIS: 文字化けはする(「繧斐 ≠縺ゅ≠」)が Shift_JIS のバイト列として有効 ○ UTF-8: 「ゔ」で SCORE_NO_EXIST が設定される? ○ 結果として Shift_JIS と判定される、なるほど??

Slide 11

Slide 11 text

nkfのソースコードリーディングをしてみる (4) ● SCORE_NO_EXIST が設定される w_status を読み進めてみた ○ https://github.com/ruby/nkf/blob/24e6ae66395a14d3022e1dd1210d04168e2c8d9e/ext/nkf/nkf- utf8/nkf.c#L3215 ○ w2e_conv 関数を呼び出している ○ https://github.com/ruby/nkf/blob/24e6ae66395a14d3022e1dd1210d04168e2c8d9e/ext/nkf/nkf- utf8/nkf.c#L3246-L3250 ○ w(UTF-8) to e(EUC-JP) に変換している??? ○ さらに w2e_conv では unicode_to_jis_common 関数を呼び出している ○ https://github.com/ruby/nkf/blob/24e6ae66395a14d3022e1dd1210d04168e2c8d9e/ext/nkf/nkf- utf8/nkf.c#L2073 ○ JIS???? ○ EUC-JP どこいった?????

Slide 12

Slide 12 text

文字コード何もわからん \(^o^)/

Slide 13

Slide 13 text

作戦変更: まず文字コードについて基本を理解しよう

Slide 14

Slide 14 text

符号化文字集合 ● ASCII ○ 128の符号位置があって 7bit で表せる ● JIS X 0201 ○ ラテン文字集合(ASCIIと2文字違う)と片仮名の 1バイト文字集合 ● JIS X 0208 ○ 日本で使われる漢字・平仮名・片仮名等を収録した 2バイト文字集合 ○ 漢字は第1水準と第2水準の物が含まれる ● JIS X 0212 ○ 補助漢字、JIS X 0208 と組み合わせて用いる ● JIS X 0213 ○ JIS X 0208 に足りない文字を補完するために開発された ● Unicode ○ 世界中の文字を収めることを目標にした符号化文字集合

Slide 15

Slide 15 text

符号化方式 ● EUC-JP ○ ASCII と JIS X 0208 を同時に用いる 8ビットの符号化方式 ○ ASCII は 1バイト、JIS X 0208 は 2バイトで表現される ○ 制御文字 SS2 と SS3 を使って JIS X 0201 片仮名集合と JIS X 0212 も扱える ● Shift_JIS ○ JIS X 0201 に JIS X 0208 を変形のうえ押し込んだもの ○ JIS X 0201 は 1バイト、JIS X 0208 は 2バイトで表現される ● UTF-8 ○ Unicode の符号化方式、1文字で 1バイト〜 4バイトまでの長さをとり得る ○ 符号位置とバイト列の対応 ■ 00000000 〜 0000007F => 0xxxxxxx ■ 00000080 〜 000007FF => 110xxxxx 10xxxxxx ■ 00000800 〜 0000FFFF => 1110xxxx 10xxxxxx 10xxxxxx ■ 00010000 〜 0010FFFF => 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Slide 16

Slide 16 text

● https://ja.wikipedia.org/wiki/JIS_X_0213 ○ > 平仮名 - 半濁点付きのか行(鼻濁音)、「ヴ」「ヵ」「ヶ」に対応する平仮名  つまり「ゔ」は JIS X 0208 (EUC-JP) には含まれていない文字! JIS X 0213 で追加された文字の概略

Slide 17

Slide 17 text

nkfのソースコードリーディングをしてみる (リベンジ) ● w2e_conv => unicode_to_jis_common という流れ ○ EUC-JP は JIS X 0208 を単純に含むもの ○ なので JIS (= JIS X 0208) への変換という解釈ができた ● UTF-8 から EUC-JP への変換は変換テーブルを使う ○ utf8_to_euc_E382 に行き着いた ○ https://github.com/ruby/nkf/blob/24e6ae66395a14d3022e1dd1210d04168e2c8d9e/ext/nkf/nkf- utf8/utf8tbl.c#L4370-L4379 ○ たしかに「ヴ」「ヵ」「ヶ」に対応する平仮名箇所のテーブルが歯抜けになっている ○ SCORE_NO_EXIST となる機序は確認できた

Slide 18

Slide 18 text

まとめ

Slide 19

Slide 19 text

まとめ ● nkf の文字コード推測において、UTF-8 の入力に JIS X 0213 の文字(というか EUC-JP にない文字)が含まれると期待する結果は得られない ○ UTF-8 かどうかの判定は内部で UTF-8 → EUC-JP という変換をもって行われているため ○ 文字コード推測はあくまで推測であって常に期待する結果を得ることはできないもの ○ nkf 利用時は可能な限り明示的に入力の文字コードを指定すべきと思った ● 文字コードについて入門した ○ nkf のソースコードはなんとなく読める程度にはなった ■ ラピュタでいうムスカ大佐状態 ■ 「読める!読めるぞ!!」 ○ 何事も基礎は大事 ○ 文字コードに関連したセキュリティについて整理したいという背景もあった ■ 文字コードに起因した SQL インジェクションが発生する機序の話とか