CURLOPT_POSTFIELDS problem :(
PHP の curl 関数で POST メソッドを使いたい場合に CURLOPT_POSTFIELDS のお世話になるのだが,巷で見掛けるコードで,CURLOPT_POSTFIELDS に対して連想配列を渡しているものがある.
$params = array('a' => $value1, 'b' => $value2);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
PHP マニュアルには「CURLOPT_POSTFIELDS には文字列を与える必要がある」と書かれているのだが,もし連想配列渡しが期待通りに処理されるのであれば便利だ.Query String を自前で生成するのは面倒臭い (PHP5 であれば http_build_query 関数を利用できるが).
というわけで,何となく動くようだし手抜きもできるし,CURLOPT_POSTFIELDS への連想配列渡しを愛用していたのだが,Twitter API で @ 記法の文字列を投稿する際にハマった.
$params = array('status' => $_REQUEST['status']); // '@foo 腹減ったー'
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
これがエラーコード 26: 'failed creating formpost data' で失敗する.どうやら,値の先頭が '@' の場合に失敗するらしい.
はて,コマンドライン版の curl ではどうなんだろう.以下を実行することで 'failed creating formpost data' エラーを再現できた.
/usr/bin/curl --form 'a=@' 'http://www.yahoo.co.jp'
コマンドライン版のマニュアルを読んでみると,どうやら --form オプションはファイルアップロードのために '@' で始まる値を特別扱いするらしい.PHP の curl 関数でも --form オプション相当の処理を行っているのか!? だとすればひどい設計だ.--form オプションでの '@' はエスケープすることができない.curl 関数では正規のファイルアップロード手段が別に用意されているのだから '@' 記法は必要ない.'@' を特別扱いしない --form-string オプション相当の処理を行えば良いハズなのだ.
何とか抜け道はないものかと,藁にもすがる思いで ext/curl/interface.c を読んでみたら,奇妙な事実が….
case CURLOPT_POSTFIELDS:
if (Z_TYPE_PP(zvalue) == IS_ARRAY || Z_TYPE_PP(zvalue) == IS_OBJECT) {
/* 略 */
for (/* 略 */) {
if (*postval == '@') {
++postval;
/* 略 */
error = curl_formadd(&first, &last,
CURLFORM_COPYNAME, string_key,
CURLFORM_NAMELENGTH, (long)string_key_len - 1,
CURLFORM_FILE, postval,
CURLFORM_END);
はて,'@' 付きの値を curl に渡すのではなく,自前で「ファイル」として処理している….しかも '@' を特別扱いするのは,CURLOPT_POSTFIELDS に連想配列が渡された時だけのようだ.つまり,連想配列ではなく文字列を渡せばエラーは起きないのか!?
$query = sprintf('%s=%s', rawurlencode('status'), rawurlencode($_REQUEST['status'])); // '@foo 腹減ったー'
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
うまくいった….そもそもマニュアルには「文字列を渡せ」と書いてあるのだし,連想配列用の処理は内部的な何かであって,今まで動いていたように見えたのはたまたまだった,ということだろう.何の事はない.
むきー.