Saturday, January 14, 2012

mysql_escape_string と mysql_real_escape_string の違いまとめ

mysql_escape_string と mysql_real_escape_string の違いを調査したのでまとめる。
大まかに以下の違いがある
  • mysql_escape_string : 文字コードを気にせず バックスラッシュ escape する
  • mysql_real_escape_string : DB へ connect した際に取得した文字コードを元に複数バイト文字はエスケープしない。不正な文字はバックスラッシュ escape する
バイナリデータを MySQL に入れる際に使用するため、文字コード判定が入ってるというのは意外。
日本語で最も影響があるのは、SJIS を使ってるときで、「ソフト」を escape してみると、
<?php
mysql_connect('DB host', 'user', 'pass');
$ret = mysql_set_charset('sjis');
$str = "\x83\x5c\x83\x74\x83\x67"; // ソフト
echo mysql_escape_string($str), "\n";
echo mysql_real_escape_string($str), "\n";
出力は以下のとおり
ソ\フト
ソフト

ソ の2バイト目は \ なので mysql_escape_string の方ではエスケープする。 しかし、mysql_real_escape_string の方は文字コード判定するのでエスケープしない。

ソース見ると latin1 かコネクト時に取得した文字コードを使うかの違い以外は、同じ関数を使って処理してる。
// ./libmysql/libmysql.c
   1620 ulong STDCALL
   1621 mysql_escape_string(char *to,const char *from,ulong length)
   1622 {
// default_charset_info は latin1 (1バイトの文字コード(asciiと下位互換))
   1623   return (uint) escape_string_for_mysql(default_charset_info, to, 0, from, length);
   1624 }
   1625
   1626 ulong STDCALL
   1627 mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
   1628              ulong length)
   1629 {
// コネクト時の文字コードを使用
   1630   if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
   1631     return (uint) escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
   1632   return (uint) escape_string_for_mysql(mysql->charset, to, 0, from, length);
   1633 }
//./mysys/charset.c
    675 size_t escape_string_for_mysql(CHARSET_INFO *charset_info,
    676                                char *to, size_t to_length,
    677                                const char *from, size_t length)
    678 {
    679   const char *to_start= to;
    680   const char *end, *to_end=to_start + (to_length ? to_length-1 : 2*length);
    681   my_bool overflow= FALSE;
    682 #ifdef USE_MB
// latin1 の場合は use_mb の値が false
    683   my_bool use_mb_flag= use_mb(charset_info);
    684 #endif
    685   for (end= from + length; from < end; from++)
    686   {
    687     char escape= 0;
    688 #ifdef USE_MB
    689     int tmp_length;
// 文字コードが複数バイトの場合の処理(複数バイト場合はここで、continue)
    690     if (use_mb_flag && (tmp_length= my_ismbchar(charset_info, from, end)))
    691     {
    692       if (to + tmp_length > to_end)
    693       {
    694         overflow= TRUE;
    695         break;
    696       }
    697       while (tmp_length--)
    698     *to++= *from++;
    699       from--;
    700       continue;
    701     }
// 不正文字の場合の処理
    713     if (use_mb_flag && (tmp_length= my_mbcharlen(charset_info, *from)) > 1)
    714       escape= *from;
    715     else
// 1バイト文字の場合の処理。switch case 使って いくつかの文字だけエスケープ
    717     switch (*from) {
    718     case 0:             /* Must be escaped for 'mysql' */
    719       escape= '0';
    720       break;
    721     case '\n':              /* Must be escaped for logs */
    722       escape= 'n';
    723       break;
    724     case '\r':
    725       escape= 'r';
    726       break;
    727     case '\\':
    728       escape= '\\';
    729       break;
    730     case '\'':
    731       escape= '\'';
    732       break;
    733     case '"':               /* Better safe than sorry */
    734       escape= '"';
    735       break;
    736     case '\032':            /* This gives problems on Win32 */
    737       escape= 'Z';
    738       break;
    739     }

mysql_real_escape_string だと、以下の場合に不具合が起きるかもしれない。

  • データベースの文字コードが SJIS で、バイナリデータに対して、mysql_real_escape_string を使った場合。
DB 側での SQLの解析でも同じ文字コード判定をしているなら問題はないが。

SJIS は2バイト目に ASCII と文字がかぶってるものがあるから問題となるが、 もうUTF8しか使わないので、文字コード判定なんてしなくてもいいはず。
PHP の関数は deprecated だが、C API の方は「should instead!」と書いてるだけで、使うなとは書いてない。
http://dev.mysql.com/doc/refman/5.5/en/mysql-escape-string.html
mysql_eacape_string の方は処理がシンプルだし、負荷が大きいような箇所では mysql_escape_string を使うことにしよう

Saturday, January 7, 2012

CentOS 5系に dotcloud のコマンドラインツールを install する

python2.4 しかないので、2.6 系を入れてところから始める。 かなりはまったのでメモ

python 2.6 系を yum で入れる
$ sudo rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-4.noarch.rpm
$ sudo perl -pi -e 's/enabled=1/enabled=0/' /etc/yum.repos.d/epel.repo
$ sudo yum -y install python26 --enablerepo=epel
easy_install-2.6 を入れる
$ wget http://peak.telecommunity.com/dist/ez_setup.py
$ sudo python ez_setup.py
$ sudo python ez_setup.py -U setuptools
以下のとおり、/usr/local/bin/easy_install-2.6 が /usr/bin/python を実行してるので、
$ head -2 /usr/local/bin/easy_install-2.6
#!/usr/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'setuptools==0.6c12dev-r88846','console_scripts','easy_install-2.6'
python26 を実行するように注意して、pip 入れなおす
$ sudo pip uninstall pip
$ sudo python26 /usr/bin/easy_install-2.6 pip
dotcloud を入れる
$ sudo pip install dotcloud
終わり

Sunday, November 6, 2011

ImageMagick を使って画像縮小

mixi のログイン用画像が大きいので、ImageMagick を使って縮小する
$ wget "http://developer.mixi.co.jp/about-platform/policies/image_guidelines/m_balloon_icon.png"
$ sudo yum install ImageMagick --exclude=*.i386
$ convert -geometry 32x32 m_balloon_icon.png mixi.png

yum 使用時、64bit環境で i386パッケージをインストールしない

exclude をつける
sudo yum install ImageMagick --exclude=*.i386

Sunday, August 7, 2011

PHP の parse_ini_file 調査

parse_ini_file が遅い気がするので、調べてみた。 以下、サンプルコード。
[192.168.162.128] cat parse_ini.php
<?php
$ret1 = parse_ini_file("sample.ini");
$ret2 = parse_ini_file("sample.ini");

[192.168.162.128] cat sample.ini
[first_section]
one = 1
five = 5
animal = BIRD

[second_section]
path = "/usr/local/bin"
URL = "http://www.example.com/~username"
parsekit にかけてみると、
[192.168.162.128] php -r 'var_dump( parsekit_compile_file("parse_ini.php", $error, PARSEKIT_SIMPLE));'
array(10) {
  [0]=>
  string(37) "ZEND_SEND_VAL UNUSED 'sample.ini' 0x1"
  [1]=>
  string(42) "ZEND_DO_FCALL T(0) 'parse_ini_file' UNUSED"
  [2]=>
  string(26) "ZEND_ASSIGN T(1) T(0) T(0)"
  [3]=>
  string(37) "ZEND_SEND_VAL UNUSED 'sample.ini' 0x1"
  [4]=>
  string(42) "ZEND_DO_FCALL T(2) 'parse_ini_file' UNUSED"
  [5]=>
  string(26) "ZEND_ASSIGN T(3) T(0) T(2)"
  [6]=>
  string(27) "ZEND_RETURN UNUSED 1 UNUSED"
  [7]=>
  string(42) "ZEND_HANDLE_EXCEPTION UNUSED UNUSED UNUSED"
  ["function_table"]=>
  NULL
  ["class_table"]=>
  NULL
}
当たり前かもしれないが、INI ファイルは、opコードに展開されない。つまり、APCではキャッシュしない。
strace してみると、
[192.168.162.128] strace php parse_ini.php 2>&1
open("/home/sikaku/dev/fuwarin/trunk/php/exp/ini/sample.ini", O_RDONLY) = 8
fstat(8, {st_mode=S_IFREG|0664, st_size=130, ...}) = 0
lseek(8, 0, SEEK_CUR)                   = 0
read(8, "[first_section]\none = 1\nfive = 5"..., 8192) = 130
read(8, "", 8192)                       = 0
read(8, "", 8192)                       = 0
close(8)                                = 0
getcwd("/home/sikaku/dev/fuwarin/trunk/php/exp/ini", 4096) = 43
open("/home/sikaku/dev/fuwarin/trunk/php/exp/ini/sample.ini", O_RDONLY) = 8
fstat(8, {st_mode=S_IFREG|0664, st_size=130, ...}) = 0
lseek(8, 0, SEEK_CUR)                   = 0
read(8, "[first_section]\none = 1\nfive = 5"..., 8192) = 130
read(8, "", 8192)                       = 0
read(8, "", 8192)                       = 0
close(8)                                = 0
どうやら、同じファイルにも関わらず毎回パースしてるようだ。

Wednesday, March 9, 2011

mysql で永続接続するか、毎回 connect するか

mysql で永続接続するか、毎回 connect するかの stack overflow リンク集 mysql_ping だけなら結構速いので、ping に失敗した時に close して connect し直せば良い。 以下のようなコードで良さそう。
bool MysqlUtil::init(const char *host, const char *user, const char *pass, const char *dbname){
  if( _isInit ){
    if(0 == mysql_ping(_mysql) ){
      return true;
    }
    mysql_close(_mysql);
  }

  _mysql = mysql_init(NULL);
  if( !mysql_real_connect(_mysql, host, user, pass, dbname, 0, NULL, 0) ){
    fprintf(stderr, "ERROR:mysql_real_connect()\n");
    return false;
  }
  if( 0 != mysql_set_character_set(_mysql, "utf8") ){
    fprintf(stderr, "ERROR:mysql_set_character_set()\n");
    return false;
  }

  _isInit = true;
  return true;
}
mysql 側の設定

Saturday, February 26, 2011

apache libapreq のソース読み


apacheモジュールでGETやPOSTのパラメータを処理するときは、libapreq2 を使って、 以下のように書けばよいが、
    apreq_handle_t *hl = apreq_handle_cgi(r->pool);
    apr_table_t *params = apreq_params(hl, pool);
どこでquery string をパースしてるのかを調べてみた。
  1. apreq_handle_t の初期化
    library/module_cgi.c
        655 APREQ_DECLARE(apreq_handle_t *)apreq_handle_cgi(apr_pool_t *pool)
        671     req->handle.module        = &cgi_module;
        677     req->args_status =
        678         req->jar_status =
        679             req->body_status = APR_EINIT;
    
    cgi_module がどこにも定義されていないと思ったら以下のMACROが
        653 static APREQ_MODULE(cgi, 20050425);
    
    以下に展開される
    static const apreq_module_t cgi_module = { "cgi", 20050425, cgi_jar, cgi_args, cgi_body, cgi_jar_get, cgi_args_get, cgi_body_get, cgi_parser_get, cgi_parser_set, cgi_hook_add, cgi_brigade_limit_get, cgi_brigade_limit_set, cgi_read_limit_get, cgi_read_limit_set, cgi_temp_dir_get, cgi_temp_dir_set, };
    
  2. apreq_params の中身
    library/module.c
         32 APREQ_DECLARE(apr_table_t *)apreq_params(apreq_handle_t *req, apr_pool_t *p)
         33 {
         34     const apr_table_t *args, *body;
         35     apreq_args(req, &args);
         36     apreq_body(req, &body);
         37
         38     if (args != NULL)
         39         if (body != NULL)
         40             return apr_table_overlay(p, args, body);
         41         else
         42             return apr_table_copy(p, args);
         43     else
         44         if (body != NULL)
         45             return apr_table_copy(p, body);
         46         else
         47             return NULL;
         48
         49 }
    
    apreq_args が cgi_argsを呼んでGETパラメータ, apreq_body が cgi_body, cgi_read を呼んでPOSTパラメータに対応する
  3. cgi_args でGETパラメータ初期化
    ./library/param.c
        140 APREQ_DECLARE(apr_status_t) apreq_parse_query_string(apr_pool_t *pool,
        141                                                      apr_table_t *t,
        142                                                      const char *qs)
    
  4. cgi_body cgi_read でPOSTパラメータ初期化 mod_apreq を使う場合や、apreq_initialize() を呼んだときに、 以下のパース用関数が、content-type にあわせて用意されている。
    library/parser.c
        114     default_parsers = apr_hash_make(default_parser_pool);
        115
        116     apreq_register_parser("application/x-www-form-urlencoded",
        117                           apreq_parse_urlencoded);
        118     apreq_register_parser("multipart/form-data", apreq_parse_multipart);
        119     apreq_register_parser("multipart/related", apreq_parse_multipart);
    
    URL エンコードされた POST に対しては以下の関数で処理される。
    ./library/parser_urlencoded.c
        158 APREQ_DECLARE_PARSER(apreq_parse_urlencoded)