SlideShare a Scribd company logo
1 of 22
SQLite2と3のエスケープ
関数の違いとその対策
第六回闇PHP勉強会
2015-11-22 @noldorinfo
自己紹介
• 竹腰彰成(たけこしあきしげ)
• Twitter: @noldorinfo
• http://blog.noldor.info/
• 作ったもの:KinoWiki(2005年)
• 10年前、SQLite2で作ったものです。
• CentOS 7にジャンプアップしたら動かなくなったという連絡を受けて
SQLite3に移行できるように修正しました。
• 案外と手間がかかりました。SQLite2と3に互換性が一部ありません。
• というわけで、今回はその話をします。
2
今日のテーマ
• SQLite2と3のエスケープ関数の違い
• SQLite2はバイナリセーフ、3はバイナリセーフではない
• どうしてそうなったか
• SQLite2はネイティブがバイナリ非対応でPHPで頑張っちゃった
• SQLite3はネイティブでバイナリ対応なのでPHPで頑張らなかった
• そのためPHPとしては互換関数を用意しにくい
• 使う側としての対応の仕方
• 自前で互換関数を作成する
3
エスケープではなく
プレースホルダ使えという
話は横に置かせてください
ネイティブのSQLite2と3
DBとしてのバイナリの取り扱い
4
SQLite2
• 組み込み型RDBMS
• 1ファイル1データベース
• データ型を指定する必要がない
• データはすべてC言語の文字列(=nullターミネートのchar配列)
• PHPの暗黙型キャストと非常に親和性が高い
• 数字の文字列なら数値とみなす、など
• Int/Real/Text/BLOBなどを指定可能だが無視している
• BLOBに0は入らない(nullターミネータとして判定される)
• バイナリはsqlite_encode_binary()で’0’をエンコードしてから保
存、読み込み時にsqlite_decode_binary()でデコードするルール
5
SQLite3
• 型が増えた
• null/Integer/Real/Text/BLOB
• IntegerをIntegerとして取り扱うなどで高速化
• SQLite2での足し算などは文字列をIntegerに変換していた
• BLOBもある
• リテラル表現で0が表現可能になった
• X’FFFFFF……’
• 文字列の前にXを置いて16進表記
• 0は「X’00’」
6
SQLite2と3でバイナリの扱いが変わった
• SQLite2ではすべて“C言語の文字列”
• C言語の文字列=nullターミネートのchar配列
• バイナリはsqlite_encode_binary()で’0’をエンコードしてから保存、
読み込み時にsqlite_decode_binary()でデコードするルール
• SQLite3ではBLOB型が加わった
• リテラル表現は「X’FFFFFF……’」
• sqlite_encode_string() / sqlite_decode_binary()は廃止
• 作者曰く「ネイティブで対応したからもう要らないよね」
http://sqlite.1065341.n5.nabble.com/Is-it-mandatory-to-
use-sqlite-encode-binary-amp-sqlite-decode-binary-to-store-
data-structures-imagess-td63311.html
7
PHPのSQLite2と3
バイナリの取り扱いに互換性がなくなった
8
PHPのSQLite3のエスケープ関数
• SQLite3と同時期にPDOができ、SQLite3はPDOでの提供と
なった
• 命名ルール的にはsqlite_escape_string()の代わりが
SQLite3::escapeString()のように見える
• でもバイナリセーフじゃない
• バグ報告がある
• https://bugs.php.net/bug.php?id=63419
• https://bugs.php.net/bug.php?id=62361
• ともに未解決
• 「PHPのバグじゃないよ、SQLite3のmprintf(“%q”)呼んでるだけ
だよ」
9
SQLite2/3のmprintf(“%q”)
• The %q option works like %s in that it substitutes a nul-
terminated string from the argument list. But %q also
doubles every ''' character. %q is designed for use inside
a string literal. By doubling each ''' character it escapes
that character and allows it to be inserted into the string.
• nullターミネートの文字列を受け取って、シングルクォートを2
つ重ねる(SQL向けにエスケープする)
• ということは、PHPの文字列に’0’があるとそこで途切れる
• これがバイナリセーフではない正体
10
SQLite2のsqlite_encode_binary()
• http://www.sqlite.org/cgi/src/artifact/fc8c51f0b61bc803
1. 「適切な数値」e を選ぶ(エスケープ結果が小さくなるよう調整)
2. 文字列のすべてのバイトに対して数値eを引く(ただし8ビットなの
でmod 256)
3. 次のルールで各バイトを置換する。
• x00 -> x01 x01
• x01 -> x01 x02
• x27 -> x01 x28
4. 置換済み文字列の前に数値eを付与してreturnする
11
0、x27(シングルクォート)
が文字列から無くなる
PHPのsqlite_escape_string()
• PHPのsqlite_escape_string()はネイティブの
sqlite_encode_binary()を呼び出しているだけじゃない
1. 空文字列なら空文字列を返す(※ガード条件)
2. 「先頭がx01」または「0を含んでいる」なら
sqlite_encode_binary()を呼び出し、先頭にx01を付与して返す
3. ‘0’がなければmprintf(“%q”)を呼び出す
• 役割が2つある
• バイナリのエンコード
• 文字列のエスケープ
12
sqlite_escape_string()の役割
バイナリのエンコード 文字列のエスケープ
SQLite2
sqlite_enscape_string()
実体はsqlite_encode_binary()
とフラグ立て(先頭にx01)
sqlite_escape_string()
実体はmprintf(“%q”)
SQLite3 なし(リテラル表現可能)
SQLite3::escapeString()
実体はmprintf(“%q”)
13
SQLite3::escapeString()はsqlite_escape_string()の代わりではない
PHPで実装
C言語を読んでPHPに書き換える
14
バイナリのエンコード関数を作る
• ないものは作ればいい
• sqlite_escape_string()との互換性を持つ関数があれば置き換え可能
• (本当はプレースホルダにしたいところだけど……レガシーコードゆ
え許されたし)
• リテラル表現では互換性を取れない
• X’FFFFFF……’ だがSQLite2を使うPHPコードはシングルクォートの
外にXを置く想定をしていない
• エンコードフラグx01はPHP側で実装していたのでSQLite側は無関与
• sqlite_escape_string()をPHPで再実装するのがてっとり早い
• それぞれの関数はコード量が非常に小さい
• SQLiteはコメントたっぷり
15
sqlite_escape_string():33行
• https://github.com/php/php-src/blob/PHP-
5.3.29/ext/sqlite/sqlite.c#L3153
16
PHP_FUNCTION(sqlite_escape_string)
{
char *string = NULL;
int stringlen;
char *ret;
if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"s", &string, &stringlen)) {
return;
}
if (stringlen && (string[0] == 'x01' || memchr(string, '0',
stringlen) != NULL)) {
/* binary string */
int enclen;
ret = safe_emalloc(1 + stringlen / 254, 257, 3);
ret[0] = 'x01';
enclen = php_sqlite_encode_binary(string, stringlen, ret+1);
RETVAL_STRINGL(ret, enclen+1, 0);
} else if (stringlen) {
ret = sqlite_mprintf("%q", string);
if (ret) {
RETVAL_STRING(ret, 1);
sqlite_freemem(ret);
}
} else {
RETURN_EMPTY_STRING();
}
}
sqlite_encode_binary():41行
• http://www.sqlite.org/cgi/src/artifact/fc8c51f0b61bc803
17
int sqlite_encode_binary(const unsigned char *in, int n,
unsigned char *out){
int i, j, e, m;
unsigned char x;
int cnt[256];
if( n<=0 ){
if( out ){
out[0] = 'x';
out[1] = 0;
}
return 1;
}
memset(cnt, 0, sizeof(cnt));
for(i=n-1; i>=0; i--){ cnt[in[i]]++; }
m = n;
for(i=1; i<256; i++){
int sum;
if( i==''' ) continue;
sum = cnt[i] + cnt[(i+1)&0xff] + cnt[(i+''')&0xff];
if( sum<m ){
m = sum;
e = i;
if( m==0 ) break;
}
}
if( out==0 ){
return n+m+1;
}
out[0] = e;
j = 1;
for(i=0; i<n; i++){
x = in[i] - e;
if( x==0 || x==1 || x=='''){
out[j++] = 1;
x++;
}
out[j++] = x;
}
out[j] = 0;
assert( j==n+m+1 );
return j;
}
sqlite_decode_binary():13行
• http://www.sqlite.org/cgi/src/artifact/fc8c51f0b61bc803
18
int sqlite_decode_binary(const unsigned char *in, unsigned char *out){
int i, e;
unsigned char c;
e = *(in++);
i = 0;
while( (c = *(in++))!=0 ){
if( c==1 ){
c = *(in++) - 1;
}
out[i++] = c + e;
}
return i;
}
PHPでの実装
• unsigned char配列操作のPHP実装は次の点がポイント
• ord():文字列の先頭1文字からASCIIコードを返す(string→int)
• chr():ASCIIコードから1文字の文字列を返す(int→string)
• 文字列への[]演算子でchar配列としてのアクセスが可能
• 「文字列への文字単位のアクセスと修正」
http://php.net/manual/ja/language.types.string.php#langua
ge.types.string.substr
• ビット演算子&で1バイトにマスクする
• 実装しました
• https://github.com/noldor/kino2/commit/8059e70df9d592305f
7d6a1a5e1364b9e49a043a
19
残った難点
• SQLite2と3でバイナリデータの互換性がない
• SQLiteそのものはSQLダンプで移行可能
• $ sqlite OLD.DB .dump | sqlite3 NEW.DB
• エンコードフラグx01はPHP側で勝手に用意したオレオレルール
• 自前実装版もx01を勝手に使うオレオレルール
• エンコードするとsqlite3コマンドで意味が通らなくなる
• そのためPHP本体では互換関数を用意しにくい
20
今日のまとめ
• SQLite2と3のエスケープ関数の違い
• SQLite2はバイナリセーフ、3はバイナリセーフではない
• どうしてそうなったか
• SQLite2はネイティブがバイナリ非対応でPHPで頑張っちゃった
• SQLite3はネイティブでバイナリ対応なのでPHPで頑張らなかった
• そのためPHPとしては互換関数を用意しにくい
• 使う側としての対応の仕方
• 自前で互換関数を作成する
21
ご清聴ありがとうございました
22

More Related Content

Viewers also liked

Testing PHP extension on Travis CI
Testing PHP extension on Travis CITesting PHP extension on Travis CI
Testing PHP extension on Travis CI
Yoshio Hanawa
 
zval をダイエットしてみた
zval をダイエットしてみたzval をダイエットしてみた
zval をダイエットしてみた
Yoshio Hanawa
 
Zend OPcacheの速さの秘密を探る
Zend OPcacheの速さの秘密を探るZend OPcacheの速さの秘密を探る
Zend OPcacheの速さの秘密を探る
Yoshio Hanawa
 
PHP-FPMとuWSGI——mod_php以外の選択肢を探る
PHP-FPMとuWSGI——mod_php以外の選択肢を探るPHP-FPMとuWSGI——mod_php以外の選択肢を探る
PHP-FPMとuWSGI——mod_php以外の選択肢を探る
Yoshio Hanawa
 

Viewers also liked (17)

Laungage Update PHP編
Laungage Update PHP編Laungage Update PHP編
Laungage Update PHP編
 
PHP7の内部実装から学ぶ性能改善テクニック
PHP7の内部実装から学ぶ性能改善テクニックPHP7の内部実装から学ぶ性能改善テクニック
PHP7の内部実装から学ぶ性能改善テクニック
 
realpathキャッシュと OPcacheの面倒すぎる関係
realpathキャッシュと OPcacheの面倒すぎる関係realpathキャッシュと OPcacheの面倒すぎる関係
realpathキャッシュと OPcacheの面倒すぎる関係
 
PHPの拡張モジュールをGoで作る
PHPの拡張モジュールをGoで作るPHPの拡張モジュールをGoで作る
PHPの拡張モジュールをGoで作る
 
PHP7はなぜ速いのか
PHP7はなぜ速いのかPHP7はなぜ速いのか
PHP7はなぜ速いのか
 
偶然にも500万個のSSH公開鍵を手に入れた俺たちは
偶然にも500万個のSSH公開鍵を手に入れた俺たちは偶然にも500万個のSSH公開鍵を手に入れた俺たちは
偶然にも500万個のSSH公開鍵を手に入れた俺たちは
 
PHP7で変わること ——言語仕様とエンジンの改善ポイント
PHP7で変わること ——言語仕様とエンジンの改善ポイントPHP7で変わること ——言語仕様とエンジンの改善ポイント
PHP7で変わること ——言語仕様とエンジンの改善ポイント
 
スピーカーから始める勉強会
スピーカーから始める勉強会スピーカーから始める勉強会
スピーカーから始める勉強会
 
Sinatra風マイクロフレームワークで始めるPython
Sinatra風マイクロフレームワークで始めるPythonSinatra風マイクロフレームワークで始めるPython
Sinatra風マイクロフレームワークで始めるPython
 
groceryCRUDとtank_authで簡単に管理画面と認証機能を作る
groceryCRUDとtank_authで簡単に管理画面と認証機能を作るgroceryCRUDとtank_authで簡単に管理画面と認証機能を作る
groceryCRUDとtank_authで簡単に管理画面と認証機能を作る
 
Testing PHP extension on Travis CI
Testing PHP extension on Travis CITesting PHP extension on Travis CI
Testing PHP extension on Travis CI
 
zval をダイエットしてみた
zval をダイエットしてみたzval をダイエットしてみた
zval をダイエットしてみた
 
家庭用ブロードバンドルータ上でWordPressを動かそう
家庭用ブロードバンドルータ上でWordPressを動かそう家庭用ブロードバンドルータ上でWordPressを動かそう
家庭用ブロードバンドルータ上でWordPressを動かそう
 
CodeIgniter3マニュアル和訳の方法と感想
CodeIgniter3マニュアル和訳の方法と感想CodeIgniter3マニュアル和訳の方法と感想
CodeIgniter3マニュアル和訳の方法と感想
 
2017 02-14 キュー実装に見る排他処理
2017 02-14 キュー実装に見る排他処理2017 02-14 キュー実装に見る排他処理
2017 02-14 キュー実装に見る排他処理
 
Zend OPcacheの速さの秘密を探る
Zend OPcacheの速さの秘密を探るZend OPcacheの速さの秘密を探る
Zend OPcacheの速さの秘密を探る
 
PHP-FPMとuWSGI——mod_php以外の選択肢を探る
PHP-FPMとuWSGI——mod_php以外の選択肢を探るPHP-FPMとuWSGI——mod_php以外の選択肢を探る
PHP-FPMとuWSGI——mod_php以外の選択肢を探る
 

Similar to SQLite2と3のエスケープ関数の違いとその対策

Inside of excel 方眼紙撲滅委員会 #pyfes
Inside of excel 方眼紙撲滅委員会 #pyfesInside of excel 方眼紙撲滅委員会 #pyfes
Inside of excel 方眼紙撲滅委員会 #pyfes
Takeshi Komiya
 
Seas で語られたこととは?
Seas で語られたこととは?Seas で語られたこととは?
Seas で語られたこととは?
Masayuki Ozawa
 
Maatkit で MySQL チューニング
Maatkit で MySQL チューニングMaatkit で MySQL チューニング
Maatkit で MySQL チューニング
Kensuke Nagae
 

Similar to SQLite2と3のエスケープ関数の違いとその対策 (17)

初心者向け SQLite の始め方
初心者向け SQLite の始め方初心者向け SQLite の始め方
初心者向け SQLite の始め方
 
OpenFlowで覚えるネットワーク
OpenFlowで覚えるネットワークOpenFlowで覚えるネットワーク
OpenFlowで覚えるネットワーク
 
si-1. SQLite 3 のインストールと基本操作
 si-1. SQLite 3 のインストールと基本操作 si-1. SQLite 3 のインストールと基本操作
si-1. SQLite 3 のインストールと基本操作
 
Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜
Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜
Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜
 
Wtm
WtmWtm
Wtm
 
PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門
 
Inside of excel 方眼紙撲滅委員会 #pyfes
Inside of excel 方眼紙撲滅委員会 #pyfesInside of excel 方眼紙撲滅委員会 #pyfes
Inside of excel 方眼紙撲滅委員会 #pyfes
 
ハードウェア脳とソフトウェア脳
ハードウェア脳とソフトウェア脳ハードウェア脳とソフトウェア脳
ハードウェア脳とソフトウェア脳
 
全脳型アーキテクチュアHandout
全脳型アーキテクチュアHandout全脳型アーキテクチュアHandout
全脳型アーキテクチュアHandout
 
Seas で語られたこととは?
Seas で語られたこととは?Seas で語られたこととは?
Seas で語られたこととは?
 
Rubyにおけるトレース機構の刷新
Rubyにおけるトレース機構の刷新Rubyにおけるトレース機構の刷新
Rubyにおけるトレース機構の刷新
 
LINQ の概要とかもろもろ
LINQ の概要とかもろもろLINQ の概要とかもろもろ
LINQ の概要とかもろもろ
 
イルカさんチームからゾウさんチームに教えたいMySQLレプリケーション
イルカさんチームからゾウさんチームに教えたいMySQLレプリケーションイルカさんチームからゾウさんチームに教えたいMySQLレプリケーション
イルカさんチームからゾウさんチームに教えたいMySQLレプリケーション
 
Maatkit で MySQL チューニング
Maatkit で MySQL チューニングMaatkit で MySQL チューニング
Maatkit で MySQL チューニング
 
Cocoa勉強会#6-SQLiteをCocoaで使う
Cocoa勉強会#6-SQLiteをCocoaで使うCocoa勉強会#6-SQLiteをCocoaで使う
Cocoa勉強会#6-SQLiteをCocoaで使う
 
Dotnetconf2017
Dotnetconf2017Dotnetconf2017
Dotnetconf2017
 
オープンソースでExcelレポートプログラミング
オープンソースでExcelレポートプログラミングオープンソースでExcelレポートプログラミング
オープンソースでExcelレポートプログラミング
 

SQLite2と3のエスケープ関数の違いとその対策