ホーム > mysql | perl > libmysqlclientを使うプログラムはset namesをutf8であっても使ってはいけない

libmysqlclientを使うプログラムはset namesをutf8であっても使ってはいけない

  • 投稿者: chiba
  • 2009/2/5 木曜日 2:41:22
  • mysql | perl

mysql_enable_utf8 => 1 で DBIC::UTF8Columns 要らなくなるっぽいComments
上記の記事のブクマに

set namesを直接実行しちゃうのはutf8であってもコンパイルオプションによっては問題起こるのでお勧めできない
http://b.hatena.ne.jp/nihen/20090204#bookmark-11950629

ってことを書かせてもらったんだけど、この最後のset namesはutf8でも使っちゃダメという話を軽く説明します。

まずは、基本的なことはMySQL5開拓団 – 日本語処理の鉄則 / KLab株式会社を読んでください。mysqlの日本語処理についてのドキュメントとしては、私は今一番信頼できるドキュメントだと思っています。

さて、上記のページの< 図3:クライアント側文字コードの指定チャート>を、勝手ながらすべて引用させていただくことにする。(手抜きもいいところだな)

< 図3:クライアント側文字コードの指定チャート>

■初期値の設定
│
├mysqlコマンドの場合
│└【my.cnfの[mysql]にdefault-character-setで指定する】
│
└my.cnfを読めてdefault-character-setを解釈することができるクライアントか?
  ├(yes)→【my.cnfの[client]にdefault-character-setで指定する】
  └(no )→「SET NAMESコース」へ

■途中で変更したい
│
├【途中で変更しなければならないような構成はやめて、初期値だけに頼るようにする】
│
├mysqlコマンドの場合
│└5.0.25以上か?
│  ├(yes)→【charset命令で指定する】
│  └(no) →「SET NAMESコース」へ
│
└C言語APIのmysql_set_character_set()かmysql_options()が使えるクライアントか?
  ├(yes)→【mysql_set_character_set()かmysql_options()で指定する】
  └(no) →「SET NAMESコース」へ

■SET NAMESコース
│
└指定したいのはシフトJIS(cp932かsjis)か?
  │
  ├(no )→【SET NAMES文で指定する】
  └(yes)→【MyNAパッチ(注5)を当てた上で、SET NAMES文で指定する】
http://www.klab.jp/media/mysql/index6.html

で、今回のDBD::mysqlはどこにあてはまるかというと、、

my.cnfを読めてdefault-character-setを解釈することができるクライアントか?

=> yes
mysql_read_default_fileを指定することにより読み込めます。

use DBI;
my $dbh = DBI->connect(
    'DBI:mysql:database=sandbox;host=localhost;mysql_read_default_file=/etc/mysql/my.cnf',
    qw/id password/,
);

下記のように一時ファイルを使うこともできるようにファイルの場所はどこでもかまわなかったりする。

use DBI;
use File::Temp qw/tempfile/;

my ($fh, $filename) = tempfile();
print {$fh} "[client]\ndefault-character-set=utf8\n";
close $fh;

my $dbh = DBI->connect(
    'DBI:mysql:database=sandbox;host=localhost;mysql_read_default_file=' . $filename,
    qw/id password/,
);

さて、実は上記のようにmysql_read_default_fileを使う方法がDBD::mysqlで日本語を扱う際のほぼ唯一の”正しい”接続方法になる。

なぜか?

とりあえず上記のチャートを進めてみよう。

└C言語APIのmysql_set_character_set()かmysql_options()が使えるクライアントか?

=> 半分 yes /半分no
mysql_set_character_set()は使えない。
mysql_options()は直接は使えないが、上記で話題にあがったmysql_enable_utf8を使うとmysql_optionsでMYSQL_SET_CHARSET_NAMEが設定できる。のでutf8の場合のみこのオプションを使うことで”正しい”接続ができる。experimentalだけどね。ちなみにphpではmysql_set_charsetというのがちゃんと用意されていたりする。

さて、最後に問題のSET NAMESコースである。

■SET NAMESコース
│
└指定したいのはシフトJIS(cp932かsjis)か?
  │
  ├(no )→【SET NAMES文で指定する】
  └(yes)→【MyNAパッチ(注5)を当てた上で、SET NAMES文で指定する】

さて、ここではShift_JIS以外はSET NAMESでも問題ないと書いてあるように読める。実際ほとんどの場合はそうなのだ。
しかし、これはlatin-1がlibmysqlclientのデフォルトキャラクタセットの場合だけであって(実際コンパイルのデフォルトはそうなっている)libmysqlclientを–witth-charset=cp932オプション付きでコンパイルしていた場合なんかだとその限りではない。
–with-charset=cp932でコンパイルしているlibmysqlclientの場合、下記のコードにおいて\x5cがエスケープされず、SQLインジェクションの危険性が発生する。(手軽に確認するならmysql_read_default_fileを使ってdefault-character-setにcp932をセットすればいい)

my $dbh = DBI->connect(
    'DBI:mysql:database=sandbox;host=localhost',
    qw/id password/,
);

$dbh->do('set names utf8');
my $sth = $dbh->prepare('insert into sandbox (name) values(?)');
$sth->execute("\xe3\x81\x95\x5c");

“\xe3\x81\x95\x5c”というのは分解すると
\xe3\x81\x95 == “さ”(UTF-8)
\xe3\x81 == “縺”(Shift_JIS)
\x95\x5c == “表”(Shift_JIS)
になる。
libmysqlclientは”\xe3\x81\x95\x5c”をShift_JISとしてparseしてエスケープするため最後の\x5cはエスケープされない。しかしset names utf8しているのでmysqlサーバ側はutf8としてパースし、最後の\x5cをバックスラッシュとして処理してしまう。

長々と書いてしまったが、libmysqlclientを(間接的にでも)使っているプログラマは、libmysqlclientが認識している文字コードとmysqldが認識している文字コードはかならず一致させるという原則を守るといいと思うよ。もちろん今回出したケースはかなり特殊なケースでコンパイルオプションをきちんと管理していれば起こらない問題だけどプログラム側で回避できる問題でもあるのできちんと対策しとくべき。

まぁ、これいっちゃうとlatin-1使う場合でもちゃんとdefault-character-set指定しないと駄目ってことでなんか極論な気がしないでもないけどね・・・。

追記1; ああ、重要なことを1点言い忘れてるね。mysql_server_prepareを使えばこういう問題はもちろん起きません。なのでこっちの対策を個人的には推奨したい。

はてなブックマーク - libmysqlclientを使うプログラムはset namesをutf8であっても使ってはいけない

コメント:0

コメントフォーム
入力した情報を記憶する

トラックバック:2

この記事のトラックバック URL
http://blog.everqueue.com/chiba/2009/02/05/129/trackback/
トラックバックの送信元リスト
libmysqlclientを使うプログラムはset namesをutf8であっても使ってはいけない - へぼい日記 より
pingback - MySQLを使ったUTF8のサイトとEUCのサイトを混在して稼働させる方法 « TECH Matari より 2009/8/13 木曜日

[…] […]

trackback - blog より 2010/7/23 金曜日

PHPを用いたTwitterタイムラインの大量取得とデータベースへの格納(1)

友人の研究の手伝いで、あらかじめリストアップしたTwitterアカウントからタイムラインをごっそり取得してデータベースに格納するというPHPスクリプトを書いた。職業別に集めた大量の…

ホーム > mysql | perl > libmysqlclientを使うプログラムはset namesをutf8であっても使ってはいけない

検索
フィード
メタ情報

ページの上部に戻る