Text
Page: 1
本当はこわい
エンコーディングの話
とみたまさひろ
東京Ruby会議10
2013-01-13
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 2
自己紹介
とみた まさひろ
http://tmtms.hatenablog.com
https://twitter.com/tmtms
好きなもの/環境
Ruby, Rabbit, MySQL, Emacs, Git,
Ubuntu, ThinkPad
所属など
長野県北部在住 / 某社プログラマー / 日本
MySQLユーザ会 / 長野ソフトウェア技術者
グループ(NSEG)
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 3
エンコーディング
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 4
エンコーディングとは
文字符号化方式
文字をどのようなバイト列で表現するか
UTF-8 とか EUC-JP とか SHIFT_JIS と
かそーゆー奴
「charset」とか呼ばれたりする
「文字コード」とか呼ばれたりする
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 5
同じバイト列でも別の文字
0xC2 0xA9 の2バイトは
UTF-8 では「©」1文字
EUC-JP では「息」1文字
SHIFT_JIS では「ツゥ」2文字
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 6
Ruby 1.8
"\xC2\xA9" という文字列は Ruby 的に
はただのバイト列
エンコーディング情報を持たない
"©"(UTF-8) として扱うか "息"(EUC-JP)
として扱うかはプログラム次第
正規表現にはエンコーディングあり
/〜/n
本当はこわいエンコーディングの話
/〜/s
/〜/u
/〜/e
Powered by Rabbit 2.0.6
Page: 7
Ruby 1.9
文字列のエンコーディングは文字列自身が
知っている
"©"(UTF-8) と "息"(EUC-JP) は同じバイ
ト列だけど異なる文字列
"あ"(UTF-8) と "あ"(EUC-JP) は同じ文
字を表してるけど等しくない
同じプログラム中で複数のエンコーディ
ングの文字列を同時に扱える(珍しいかも)
正規表現にもエンコーディングあり
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 8
エンコーディング一覧 (1.9.3)
Ruby 自身が持ってるので環境に依存しない
ASCII-8BIT Big5 Big5-HKSCS Big5-UAO CP50220 CP50221 CP51932 CP850
CP852 CP855 CP949 CP950 CP951 EUC-JP EUC-KR EUC-TW Emacs-Mule
GB12345 GB18030 GB1988 GB2312 GBK IBM437 IBM737 IBM775 IBM852
IBM855 IBM857 IBM860 IBM861 IBM862 IBM863 IBM864 IBM865 IBM866
IBM869 ISO-2022-JP ISO-2022-JP-2 ISO-2022-JP-KDDI ISO-8859-1
ISO-8859-10 ISO-8859-11 ISO-8859-13 ISO-8859-14 ISO-8859-15
ISO-8859-16 ISO-8859-2 ISO-8859-3 ISO-8859-4 ISO-8859-5 ISO-8859-6
ISO-8859-7 ISO-8859-8 ISO-8859-9 KOI8-R KOI8-U MacJapanese
SJIS-DoCoMo SJIS-KDDI SJIS-SoftBank Shift_JIS TIS-620 US-ASCII
UTF-16 UTF-16BE UTF-16LE UTF-32 UTF-32BE UTF-32LE UTF-7 UTF-8
UTF8-DoCoMo UTF8-KDDI UTF8-MAC UTF8-SoftBank Windows-1250
Windows-1251 Windows-1252 Windows-1253 Windows-1254 Windows-1255
Windows-1256 Windows-1257 Windows-1258 Windows-31J Windows-874
eucJP-ms macCentEuro macCroatian macCyrillic macGreek macIceland
macRoman macRomania macThai macTurkish macUkraine
stateless-ISO-2022-JP stateless-ISO-2022-JP-KDDI
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 9
うれしいこと
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 10
1.8ではバイト単位
"あいう".size
#=> 9
"あいう".bytesize #=> 9
"あいう".chars{|c| ... }
#=> "\xE3","\x81","\x82", ...
"あいう"[0]
#=> 0xE3
"あいう".reverse
#=> "\x86\x81\xE3\x84\x81\xE3\x82\x81\xE3"
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 11
1.9では文字単位
"あいう".size
#=> 3
"あいう".bytesize #=> 9
"あいう".chars{|c| ... }
#=> "あ", "い", "う"
"あいう"[0]
"あいう".reverse
本当はこわいエンコーディングの話
#=> "あ"
#=> "ういあ"
Powered by Rabbit 2.0.6
Page: 12
エンコーディング変換
# -*- coding: utf-8 -*-
s = "あいう"
#=> "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86"
s.encoding
#=> #<Encoding:UTF-8>
s2 = s.encode("CP932")
#=> "\x82\xA0\x82\xA2\x82\xA4"
s2.encoding
#=> #<Encoding:Windows-31J>
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 13
IOで変換してくれる
File.open("cp932.txt", "r:CP932").read
#=> CP932 文字列
File.open("cp932.txt", "r:CP932:UTF-8").read
#=> UTF-8 文字列
File.open("cp932.txt").read
#=> 環境依存
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 14
うれしいことばかりじゃない
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 15
変換先にない文字
# -*- coding: utf-8 -*-
"あ♥".encode("CP932")
#=> Encoding::UndefinedConversionError
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 16
変換元にない文字
# -*- coding: utf-8 -*-
"あ\xFF".encode("CP932")
#=> Encoding::InvalidByteSequenceError
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 17
エンコーディングがあっても
変換できるとは限らない
# -*- coding: utf-8 -*-
"あいう".encode("UTF-7")
#=> Encoding::ConverterNotFoundError
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 18
エンコーディングの不一致
utf8 = "あいう"
cp932 = "あ".encode("CP932")
utf8.start_with?(cp932)
#=> Encoding::CompatibilityError
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 19
文字列と正規表現の
エンコーディングの不一致
utf8 = "あいう"
re = /./s
utf8 =~ re
#=> Encoding::CompatibilityError
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 20
エンコーディングが同じでも
不正な文字を含んでいる
utf8 = "あ\xFF"
utf8 =~ /./
#=> invalid byte sequence in UTF-8
# (ArgumentError)
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 21
IO
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 22
メソッドによって
エンコーディングが異なる
テキスト読み込み(エンコードあり)
IO#gets
IO#getc
IO#lines
IO#read 等
バイナリ読み込み(ASCII-8BIT固定)
IO#read(n)
IO#sysread 等
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 23
IO#read
IO#read(size) は ASCII-8BIT
IO#read() は外部エンコーディング依存
引数の有無によって結果のエンコーディ
ングが異なる!
なにそれこわい
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 24
外部エンコーディング
ファイル自身は自分の内容のエンコーデ
ィングを知らない
ファイルから読み込んだ文字列の Ruby
内でのエンコーディングは何らかの方法
で指定する必要がある
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 25
引数で指定
File.open(filename, "r:UTF-8")
File.read(filename, :encoding=>"UTF-8")
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 26
環境変数
引数で指定されてない場合は環境変数が参照
される
LC_ALL
LC_CTYPE
LANG
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 27
環境変数による違い
% cat utf-8.txt
あいうえお
% export LC_ALL=C
% ruby -e 'p File.read("utf-8.txt").size'
16
% export LC_ALL=ja_JP.UTF-8
% ruby -e 'p File.read("utf-8.txt").size'
6
環境変数によって動きが変わっちゃう!こわい
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 28
入力時にはエラーにならない
utf8 = File.read("utf8.txt", :encoding=>"UTF-8")
# 実は UTF-8 として不正な文字が含まれていて
〜〜〜〜〜〜
# ずっと後で別のメソッドでエラーになったり
utf8 =~ /./
#=> invalid byte sequence in UTF-8 (ArgumentError)
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 29
CGI
require "cgi"
cgi = CGI.new
不正な文字のパラメータを渡すとエラー
GET http://example.com/hoge.cgi?fuga=%FF
#=> Accept-Charset encoding error (CGI::InvalidEncoding)
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 30
Rails
不正な文字のパラメータを渡すとエラー
POST http://example.com/posts
post[title]=%FF
#=> ArgumentError (invalid byte sequence in UTF-8)
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 31
エラーになりすぎこわい!
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 32
対処
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 33
変換先にない文字を置換
"あ♥".encode("CP932")
#=> Encoding::UndefinedConversionError
"あ♥".encode("CP932", :undef=>:replace)
#=> "あ?"
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 34
変換元にない文字を置換
"あ\xFF".encode("CP932")
#=> Encoding::InvalidByteSequenceError
"あ\xFF".encode("CP932", :invalid=>:replace)
#=> "あ?"
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 35
置換文字の指定
"あ♥".encode("CP932", :undef=>:replace, :replace=>"〓")
#=> CP932 で "あ〓"
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 36
そもそも変換が必要になるような
ことをしないのが吉
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 37
UTF-8に統一すれば
たいていは問題ない
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 38
UTF-8に統一したつもりでも
他のエンコーディングが現れるこ
とも
File.open(filename, "r:UTF-8").read
#=> UTF-8 文字列
File.open(filename).read
#=> 環境依存
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 39
いちいち引数で指定する?
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 40
デフォルト値を指定する
プログラムで使用するファイルのエンコーディ
ングがすべて同一であれば
Encoding.default_external = "UTF-8"
File.read(filename)
本当はこわいエンコーディングの話
#=> UTF-8文字列
Powered by Rabbit 2.0.6
Page: 41
これで問題ない?
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 42
ASCII-8BIT
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6
Page: 43
メソッドによっては ASCII-8BIT
本当はこわいエンコーディングの話
Powered by Rabbit 2.0.6