@kyanny's blog

商品にならぬ技術は役に立たない - トーマス・エジソン

POSIX 文字クラスで文字列から文字になり損ねたバイトを取り除く

英語で書こうと思ったけど難しくて挫折した。

とあるサイトにワンタイムトークンつきの URL をメールで送る機能があり、とあるスマートフォン端末っていうか IS06 からその URL を踏むと末尾に空白文字がくっついてワンタイムトークンの文字列が一致しなくなる問題があった。

ログを探して Emacs のバッファにコピペしたらなんか空白文字の見え方が違う。あれ?と思って irb で " " == " " (うしろのはペーストした空白っぽい文字) してみたら false なのでいわゆるホワイトスペースじゃないものなんだなと把握。でいじってみたらこうなった。これ単体だと文字として意味をなさないバイト列、ということか。

>> " "[0]
=> 194
>> " "[0]
=> 32

>> 194.chr
=> "\302"

送ったメールの文面にはそういう文字が入り込む余地はないので機種依存またはメールアプリケーション依存の問題と特定。で、そっちは手出しできないのでワンタイムトークンのゴミ掃除をしてから先の処理へすすむようにした。それにしてもよく気づけたもんだ。

ワンタイムトークンは英数のみで構成されるので [a-zA-Z0-9]+ とかでもよかったんだけど、「すべての出来損ない文字を消し去りたい」という気分だったので [[:print:]] 文字クラスの否定表現を使った。 POSIX キャラクタクラスというやつ。実装はこんな感じ。

token = params[:token].gsub(/[^[:print:]]/,'')

これの厳密な仕様がよくわからなくて、 [[:print:]] と [^[:cntrl]] は等価であるとか書いてあるページもあるけど実際上記の \302 は [^[:cntrl]] にはマッチしないんだよねーとか、 [[:print:]] と [[:graph:]] の差は具体的にどういう文字に対してあらわれるの?とか、どうなってんだろうと思ったので Ruby のソースコードを探検してみた。

graph で git grep してみて眺めた結果、 regparse.c がそれっぽいかなと思ったのでみてみると、 ONIGENC_CTYPE_GRAPH というのが見つかったのでそれを探すと switch 文のなかで使われていたんだけど [[:print:]] と対応してる雰囲気の ONIGENC_CTYPE_PRINT と同じ処理が走るように見えて、これつまり [[:graph:]] と [[:print:]] を区別してないということなのかな、と思ったけどそんなことはなくて

ruby-1.9.2-p180 :001 > "a b".scan(/[[:graph:]]/)
 => ["a", "b"] 
ruby-1.9.2-p180 :002 > "a b".scan(/[[:print:]]/)
 => ["a", " ", "b"] 

このように全然違う。この差はいったいどこからくるんだろう。謎。ということでソースを読んでも疑問は解決しなかった (というと語弊がある、疑問を解決するに至るだけのソースコード読解力がおれには無いのだ) んだけど、疑問を感じたら情報のソース (まさに!) にあたる、原典を参照しようという姿勢は悪くないんじゃないかなと思った。おしまい。

どうでもいいけどはてなダイアリーは [ と ] を二つ続けてかくとキーワードリンクになってしまうので [ #amp;#93; に置換しないといけないのが面倒だ。