More Related Content Similar to SQLite2と3のエスケープ関数の違いとその対策 (17) SQLite2と3のエスケープ関数の違いとその対策2. 自己紹介
• 竹腰彰成(たけこしあきしげ)
• Twitter: @noldorinfo
• http://blog.noldor.info/
• 作ったもの:KinoWiki(2005年)
• 10年前、SQLite2で作ったものです。
• CentOS 7にジャンプアップしたら動かなくなったという連絡を受けて
SQLite3に移行できるように修正しました。
• 案外と手間がかかりました。SQLite2と3に互換性が一部ありません。
• というわけで、今回はその話をします。
2
5. SQLite2
• 組み込み型RDBMS
• 1ファイル1データベース
• データ型を指定する必要がない
• データはすべてC言語の文字列(=nullターミネートのchar配列)
• PHPの暗黙型キャストと非常に親和性が高い
• 数字の文字列なら数値とみなす、など
• Int/Real/Text/BLOBなどを指定可能だが無視している
• BLOBに0は入らない(nullターミネータとして判定される)
• バイナリはsqlite_encode_binary()で’0’をエンコードしてから保
存、読み込み時にsqlite_decode_binary()でデコードするルール
5
7. 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
10. 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
16. 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();
}
}
17. 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;
}
19. 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