关于PHP中cURL库提交文件用法之笔记

zsx in 记录整理 / 4 / 7086

最近在写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/16240253/php-curl-file-upload-without-real-file-want-to-write-to-body-a-string

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还是特性呢……?

如果本文对你有帮助,你可以用支付宝支持一下:

Alipay QrCode
Annnle at 2018/7/16[回复]
有没有详细点的代码啊 哥
c at 2015/2/17[回复]
完全看不懂。。。不过比较在意一点,不需要中转文件?比如在不上传到我自己主机的情况下,直接中转提交文件到第三方接口?
zsx at 2015/2/17[回复]
不是。PHP在收到$_FILES的时候已经把文件保存到了临时文件夹,所以可以直接用new CURLFile($_FILES['xx']['tmp_name'])。这个方法的意义是可以直接给cURL二进制,而不需要保存一次文件再用CURLFile这个类。这个方法的缺陷是发给服务器的Content-Type会多一个引号(读libcurl代码也可知),不过无所谓了。
c at 2015/2/17[回复]
恩明白了,谢谢!