最近在写Z-BlogPHP的二进制文件上传函数,调用cURL时处理文件,原来的代码是这么写的:
public function addBinary($name, $entity) {
// ....
if (!is_file($entity)) {
$tempFileName = $zbp->path . 'zb_users/cache/curl_uploadtemp_' . md5(rand(1, 10)) . '.tmp';
file_put_contents($tempFileName, $entity);
$entity = $tempFileName;
$this->__deleteTempList[] = $entity;
}
// Something here...
if (class_exists('CURLFile')) {
$this->postdata[$name] = new CURLFile($entity, $mime);
return;
}
$entity = realpath($entity);
$value = "@{$entity}";
.....
$this->postdata[$name] = $value;
}
因为,根据PHP的官方手册,要用cURL上传就要用CURLFile;而这个类,根据手册(http://php.net/manual/en/class.curlfile.php ),没有根据二进制来加载的方法。
但是,偶然间得知了这个方法,不需要一个中转文件,当然,在老版本PHP中也不需要【@】符号,即可直接用cURL上传。
if (!is_file($entity)) {
$key = "$name\"; filename=\"$name\r\nContent-Type: application/octet-stream\r\n";
$this->postdata[$key] = $entity;
return;
}
等等,StackOverFlow有相关吗?查查看。
http://stackoverflow.com/questions/3085990/post-a-file-string-using-curl-in-php
http://stackoverflow.com/questions/11913550/using-curl-to-upload-a-file-without-symbol
看起来像是奇技淫巧类似的东西,居然查不到?
But, why?! 它的原理是什么?为什么引号只写半边?!
首先先抓包,抓包得知,其发的包内容如下:
POST /zbphp/aaaaa.php HTTP/1.1
Host: test.zsxsoft.com
Accept: */*
User-Agent: Mozilla/5.0 (WINNT; IIS8.5; PHP 5.6.4; mysqli; curl) Z-BlogPHP/1.5 Beta Build 150301
Content-Length: 701
Expect: 100-continue
Content-Type: multipart/form-data; boundary=------------------------051df2d456a2c986
HTTP/1.1 100 Continue
--------------------------051df2d456a2c986
Content-Disposition: form-data; name="filde"; filename="filde
Content-Type: application/octet-stream
"
XXXX
注意Content-Type这行后面的换行没有?
于是我滚去翻代码了。
首先在PHP源代码的ext\curl\interface.c,在
_php_curl_setopt
函数里的
CURLOPT_POSTFIELDS
分支里有这么几句:
if (Z_TYPE_P(current) == IS_OBJECT &&
instanceof_function(Z_OBJCE_P(current), curl_CURLFile_class)) {
// Something
error = curl_formadd(&first, &last,
CURLFORM_COPYNAME, string_key->val,
CURLFORM_NAMELENGTH, string_key->len,
CURLFORM_FILENAME, filename ? filename : postval,
CURLFORM_CONTENTTYPE, type ? type : "application/octet-stream",
CURLFORM_FILE, postval,
CURLFORM_END);
}
// .......
else {
error = curl_formadd(&first, &last,
CURLFORM_COPYNAME, string_key->val,
CURLFORM_NAMELENGTH, (zend_long)string_key->len,
CURLFORM_COPYCONTENTS, postval,
CURLFORM_CONTENTSLENGTH, (zend_long)Z_STRLEN_P(current),
CURLFORM_END);
}
为什么这玩意会触发cURL的文件上传啊?又没有@符号。玄机就在这里了。于是我又圆润地滚去翻libcurl的代码了。
通过搜索【Boundary】可知,Curl_getformdata在/lib/formdata.c里,负责拼接boundary。而调用它的,在lib/http.c里,有
if(HTTPREQ_POST_FORM == httpreq) {
/* we must build the whole post sequence first, so that we have a size of
the whole transfer before we start to send it */
result = Curl_getformdata(data, &http->sendit, data->set.httppost,
Curl_checkheaders(conn, "Content-Type:"),
&http->postsize);
if(result)
return result;
}
搜
HTTPREQ_POST_FORM
然后搜了半天我就被绕晕了,这部分代码看的不是很明白,还是恳请高手不吝赐教。
接着研究这个拼接到底是什么原理。
在libcurl源代码的/lib/formdata.c里,
Curl_getformdata
函数有这么几句
result = AddFormDataf(&form, &size,
"Content-Disposition: form-data; name=\"");
if(result)
break;
result = AddFormData(&form, FORM_DATA, post->name, post->namelength,
&size);
if(result)
break;
result = AddFormDataf(&form, &size, "\"");
if(result)
break;
嗯,那大概就是这里就是为什么这个办法可以组装出合法(?)的分隔了吧。
so, 这似乎是一个可以通杀全版本(PHP 5.2 - 7可用)的,呃,算是BUG还是特性呢……?