]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/HTTP/Request2/Adapter/Curl.php
PEAR::HTTP_Request2 updated to 2.2.1
[quix0rs-gnu-social.git] / extlib / HTTP / Request2 / Adapter / Curl.php
1 <?php\r
2 /**\r
3  * Adapter for HTTP_Request2 wrapping around cURL extension\r
4  *\r
5  * PHP version 5\r
6  *\r
7  * LICENSE\r
8  *\r
9  * This source file is subject to BSD 3-Clause License that is bundled\r
10  * with this package in the file LICENSE and available at the URL\r
11  * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE\r
12  *\r
13  * @category  HTTP\r
14  * @package   HTTP_Request2\r
15  * @author    Alexey Borzov <avb@php.net>\r
16  * @copyright 2008-2014 Alexey Borzov <avb@php.net>\r
17  * @license   http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License\r
18  * @link      http://pear.php.net/package/HTTP_Request2\r
19  */\r
20 \r
21 /**\r
22  * Base class for HTTP_Request2 adapters\r
23  */\r
24 require_once 'HTTP/Request2/Adapter.php';\r
25 \r
26 /**\r
27  * Adapter for HTTP_Request2 wrapping around cURL extension\r
28  *\r
29  * @category HTTP\r
30  * @package  HTTP_Request2\r
31  * @author   Alexey Borzov <avb@php.net>\r
32  * @license  http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License\r
33  * @version  Release: 2.2.1\r
34  * @link     http://pear.php.net/package/HTTP_Request2\r
35  */\r
36 class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter\r
37 {\r
38     /**\r
39      * Mapping of header names to cURL options\r
40      * @var  array\r
41      */\r
42     protected static $headerMap = array(\r
43         'accept-encoding' => CURLOPT_ENCODING,\r
44         'cookie'          => CURLOPT_COOKIE,\r
45         'referer'         => CURLOPT_REFERER,\r
46         'user-agent'      => CURLOPT_USERAGENT\r
47     );\r
48 \r
49     /**\r
50      * Mapping of SSL context options to cURL options\r
51      * @var  array\r
52      */\r
53     protected static $sslContextMap = array(\r
54         'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER,\r
55         'ssl_cafile'      => CURLOPT_CAINFO,\r
56         'ssl_capath'      => CURLOPT_CAPATH,\r
57         'ssl_local_cert'  => CURLOPT_SSLCERT,\r
58         'ssl_passphrase'  => CURLOPT_SSLCERTPASSWD\r
59     );\r
60 \r
61     /**\r
62      * Mapping of CURLE_* constants to Exception subclasses and error codes\r
63      * @var  array\r
64      */\r
65     protected static $errorMap = array(\r
66         CURLE_UNSUPPORTED_PROTOCOL  => array('HTTP_Request2_MessageException',\r
67                                              HTTP_Request2_Exception::NON_HTTP_REDIRECT),\r
68         CURLE_COULDNT_RESOLVE_PROXY => array('HTTP_Request2_ConnectionException'),\r
69         CURLE_COULDNT_RESOLVE_HOST  => array('HTTP_Request2_ConnectionException'),\r
70         CURLE_COULDNT_CONNECT       => array('HTTP_Request2_ConnectionException'),\r
71         // error returned from write callback\r
72         CURLE_WRITE_ERROR           => array('HTTP_Request2_MessageException',\r
73                                              HTTP_Request2_Exception::NON_HTTP_REDIRECT),\r
74         CURLE_OPERATION_TIMEOUTED   => array('HTTP_Request2_MessageException',\r
75                                              HTTP_Request2_Exception::TIMEOUT),\r
76         CURLE_HTTP_RANGE_ERROR      => array('HTTP_Request2_MessageException'),\r
77         CURLE_SSL_CONNECT_ERROR     => array('HTTP_Request2_ConnectionException'),\r
78         CURLE_LIBRARY_NOT_FOUND     => array('HTTP_Request2_LogicException',\r
79                                              HTTP_Request2_Exception::MISCONFIGURATION),\r
80         CURLE_FUNCTION_NOT_FOUND    => array('HTTP_Request2_LogicException',\r
81                                              HTTP_Request2_Exception::MISCONFIGURATION),\r
82         CURLE_ABORTED_BY_CALLBACK   => array('HTTP_Request2_MessageException',\r
83                                              HTTP_Request2_Exception::NON_HTTP_REDIRECT),\r
84         CURLE_TOO_MANY_REDIRECTS    => array('HTTP_Request2_MessageException',\r
85                                              HTTP_Request2_Exception::TOO_MANY_REDIRECTS),\r
86         CURLE_SSL_PEER_CERTIFICATE  => array('HTTP_Request2_ConnectionException'),\r
87         CURLE_GOT_NOTHING           => array('HTTP_Request2_MessageException'),\r
88         CURLE_SSL_ENGINE_NOTFOUND   => array('HTTP_Request2_LogicException',\r
89                                              HTTP_Request2_Exception::MISCONFIGURATION),\r
90         CURLE_SSL_ENGINE_SETFAILED  => array('HTTP_Request2_LogicException',\r
91                                              HTTP_Request2_Exception::MISCONFIGURATION),\r
92         CURLE_SEND_ERROR            => array('HTTP_Request2_MessageException'),\r
93         CURLE_RECV_ERROR            => array('HTTP_Request2_MessageException'),\r
94         CURLE_SSL_CERTPROBLEM       => array('HTTP_Request2_LogicException',\r
95                                              HTTP_Request2_Exception::INVALID_ARGUMENT),\r
96         CURLE_SSL_CIPHER            => array('HTTP_Request2_ConnectionException'),\r
97         CURLE_SSL_CACERT            => array('HTTP_Request2_ConnectionException'),\r
98         CURLE_BAD_CONTENT_ENCODING  => array('HTTP_Request2_MessageException'),\r
99     );\r
100 \r
101     /**\r
102      * Response being received\r
103      * @var  HTTP_Request2_Response\r
104      */\r
105     protected $response;\r
106 \r
107     /**\r
108      * Whether 'sentHeaders' event was sent to observers\r
109      * @var  boolean\r
110      */\r
111     protected $eventSentHeaders = false;\r
112 \r
113     /**\r
114      * Whether 'receivedHeaders' event was sent to observers\r
115      * @var boolean\r
116      */\r
117     protected $eventReceivedHeaders = false;\r
118 \r
119     /**\r
120      * Position within request body\r
121      * @var  integer\r
122      * @see  callbackReadBody()\r
123      */\r
124     protected $position = 0;\r
125 \r
126     /**\r
127      * Information about last transfer, as returned by curl_getinfo()\r
128      * @var  array\r
129      */\r
130     protected $lastInfo;\r
131 \r
132     /**\r
133      * Creates a subclass of HTTP_Request2_Exception from curl error data\r
134      *\r
135      * @param resource $ch curl handle\r
136      *\r
137      * @return HTTP_Request2_Exception\r
138      */\r
139     protected static function wrapCurlError($ch)\r
140     {\r
141         $nativeCode = curl_errno($ch);\r
142         $message    = 'Curl error: ' . curl_error($ch);\r
143         if (!isset(self::$errorMap[$nativeCode])) {\r
144             return new HTTP_Request2_Exception($message, 0, $nativeCode);\r
145         } else {\r
146             $class = self::$errorMap[$nativeCode][0];\r
147             $code  = empty(self::$errorMap[$nativeCode][1])\r
148                      ? 0 : self::$errorMap[$nativeCode][1];\r
149             return new $class($message, $code, $nativeCode);\r
150         }\r
151     }\r
152 \r
153     /**\r
154      * Sends request to the remote server and returns its response\r
155      *\r
156      * @param HTTP_Request2 $request HTTP request message\r
157      *\r
158      * @return   HTTP_Request2_Response\r
159      * @throws   HTTP_Request2_Exception\r
160      */\r
161     public function sendRequest(HTTP_Request2 $request)\r
162     {\r
163         if (!extension_loaded('curl')) {\r
164             throw new HTTP_Request2_LogicException(\r
165                 'cURL extension not available', HTTP_Request2_Exception::MISCONFIGURATION\r
166             );\r
167         }\r
168 \r
169         $this->request              = $request;\r
170         $this->response             = null;\r
171         $this->position             = 0;\r
172         $this->eventSentHeaders     = false;\r
173         $this->eventReceivedHeaders = false;\r
174 \r
175         try {\r
176             if (false === curl_exec($ch = $this->createCurlHandle())) {\r
177                 $e = self::wrapCurlError($ch);\r
178             }\r
179         } catch (Exception $e) {\r
180         }\r
181         if (isset($ch)) {\r
182             $this->lastInfo = curl_getinfo($ch);\r
183             curl_close($ch);\r
184         }\r
185 \r
186         $response = $this->response;\r
187         unset($this->request, $this->requestBody, $this->response);\r
188 \r
189         if (!empty($e)) {\r
190             throw $e;\r
191         }\r
192 \r
193         if ($jar = $request->getCookieJar()) {\r
194             $jar->addCookiesFromResponse($response, $request->getUrl());\r
195         }\r
196 \r
197         if (0 < $this->lastInfo['size_download']) {\r
198             $request->setLastEvent('receivedBody', $response);\r
199         }\r
200         return $response;\r
201     }\r
202 \r
203     /**\r
204      * Returns information about last transfer\r
205      *\r
206      * @return   array   associative array as returned by curl_getinfo()\r
207      */\r
208     public function getInfo()\r
209     {\r
210         return $this->lastInfo;\r
211     }\r
212 \r
213     /**\r
214      * Creates a new cURL handle and populates it with data from the request\r
215      *\r
216      * @return   resource    a cURL handle, as created by curl_init()\r
217      * @throws   HTTP_Request2_LogicException\r
218      * @throws   HTTP_Request2_NotImplementedException\r
219      */\r
220     protected function createCurlHandle()\r
221     {\r
222         $ch = curl_init();\r
223 \r
224         curl_setopt_array($ch, array(\r
225             // setup write callbacks\r
226             CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'),\r
227             CURLOPT_WRITEFUNCTION  => array($this, 'callbackWriteBody'),\r
228             // buffer size\r
229             CURLOPT_BUFFERSIZE     => $this->request->getConfig('buffer_size'),\r
230             // connection timeout\r
231             CURLOPT_CONNECTTIMEOUT => $this->request->getConfig('connect_timeout'),\r
232             // save full outgoing headers, in case someone is interested\r
233             CURLINFO_HEADER_OUT    => true,\r
234             // request url\r
235             CURLOPT_URL            => $this->request->getUrl()->getUrl()\r
236         ));\r
237 \r
238         // set up redirects\r
239         if (!$this->request->getConfig('follow_redirects')) {\r
240             curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);\r
241         } else {\r
242             if (!@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true)) {\r
243                 throw new HTTP_Request2_LogicException(\r
244                     'Redirect support in curl is unavailable due to open_basedir or safe_mode setting',\r
245                     HTTP_Request2_Exception::MISCONFIGURATION\r
246                 );\r
247             }\r
248             curl_setopt($ch, CURLOPT_MAXREDIRS, $this->request->getConfig('max_redirects'));\r
249             // limit redirects to http(s), works in 5.2.10+\r
250             if (defined('CURLOPT_REDIR_PROTOCOLS')) {\r
251                 curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);\r
252             }\r
253             // works in 5.3.2+, http://bugs.php.net/bug.php?id=49571\r
254             if ($this->request->getConfig('strict_redirects') && defined('CURLOPT_POSTREDIR')) {\r
255                 curl_setopt($ch, CURLOPT_POSTREDIR, 3);\r
256             }\r
257         }\r
258 \r
259         // set local IP via CURLOPT_INTERFACE (request #19515)\r
260         if ($ip = $this->request->getConfig('local_ip')) {\r
261             curl_setopt($ch, CURLOPT_INTERFACE, $ip);\r
262         }\r
263 \r
264         // request timeout\r
265         if ($timeout = $this->request->getConfig('timeout')) {\r
266             curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);\r
267         }\r
268 \r
269         // set HTTP version\r
270         switch ($this->request->getConfig('protocol_version')) {\r
271         case '1.0':\r
272             curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);\r
273             break;\r
274         case '1.1':\r
275             curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);\r
276         }\r
277 \r
278         // set request method\r
279         switch ($this->request->getMethod()) {\r
280         case HTTP_Request2::METHOD_GET:\r
281             curl_setopt($ch, CURLOPT_HTTPGET, true);\r
282             break;\r
283         case HTTP_Request2::METHOD_POST:\r
284             curl_setopt($ch, CURLOPT_POST, true);\r
285             break;\r
286         case HTTP_Request2::METHOD_HEAD:\r
287             curl_setopt($ch, CURLOPT_NOBODY, true);\r
288             break;\r
289         case HTTP_Request2::METHOD_PUT:\r
290             curl_setopt($ch, CURLOPT_UPLOAD, true);\r
291             break;\r
292         default:\r
293             curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());\r
294         }\r
295 \r
296         // set proxy, if needed\r
297         if ($host = $this->request->getConfig('proxy_host')) {\r
298             if (!($port = $this->request->getConfig('proxy_port'))) {\r
299                 throw new HTTP_Request2_LogicException(\r
300                     'Proxy port not provided', HTTP_Request2_Exception::MISSING_VALUE\r
301                 );\r
302             }\r
303             curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port);\r
304             if ($user = $this->request->getConfig('proxy_user')) {\r
305                 curl_setopt(\r
306                     $ch, CURLOPT_PROXYUSERPWD,\r
307                     $user . ':' . $this->request->getConfig('proxy_password')\r
308                 );\r
309                 switch ($this->request->getConfig('proxy_auth_scheme')) {\r
310                 case HTTP_Request2::AUTH_BASIC:\r
311                     curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);\r
312                     break;\r
313                 case HTTP_Request2::AUTH_DIGEST:\r
314                     curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);\r
315                 }\r
316             }\r
317             if ($type = $this->request->getConfig('proxy_type')) {\r
318                 switch ($type) {\r
319                 case 'http':\r
320                     curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);\r
321                     break;\r
322                 case 'socks5':\r
323                     curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);\r
324                     break;\r
325                 default:\r
326                     throw new HTTP_Request2_NotImplementedException(\r
327                         "Proxy type '{$type}' is not supported"\r
328                     );\r
329                 }\r
330             }\r
331         }\r
332 \r
333         // set authentication data\r
334         if ($auth = $this->request->getAuth()) {\r
335             curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);\r
336             switch ($auth['scheme']) {\r
337             case HTTP_Request2::AUTH_BASIC:\r
338                 curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);\r
339                 break;\r
340             case HTTP_Request2::AUTH_DIGEST:\r
341                 curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);\r
342             }\r
343         }\r
344 \r
345         // set SSL options\r
346         foreach ($this->request->getConfig() as $name => $value) {\r
347             if ('ssl_verify_host' == $name && null !== $value) {\r
348                 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);\r
349             } elseif (isset(self::$sslContextMap[$name]) && null !== $value) {\r
350                 curl_setopt($ch, self::$sslContextMap[$name], $value);\r
351             }\r
352         }\r
353 \r
354         $headers = $this->request->getHeaders();\r
355         // make cURL automagically send proper header\r
356         if (!isset($headers['accept-encoding'])) {\r
357             $headers['accept-encoding'] = '';\r
358         }\r
359 \r
360         if (($jar = $this->request->getCookieJar())\r
361             && ($cookies = $jar->getMatching($this->request->getUrl(), true))\r
362         ) {\r
363             $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;\r
364         }\r
365 \r
366         // set headers having special cURL keys\r
367         foreach (self::$headerMap as $name => $option) {\r
368             if (isset($headers[$name])) {\r
369                 curl_setopt($ch, $option, $headers[$name]);\r
370                 unset($headers[$name]);\r
371             }\r
372         }\r
373 \r
374         $this->calculateRequestLength($headers);\r
375         if (isset($headers['content-length']) || isset($headers['transfer-encoding'])) {\r
376             $this->workaroundPhpBug47204($ch, $headers);\r
377         }\r
378 \r
379         // set headers not having special keys\r
380         $headersFmt = array();\r
381         foreach ($headers as $name => $value) {\r
382             $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));\r
383             $headersFmt[]  = $canonicalName . ': ' . $value;\r
384         }\r
385         curl_setopt($ch, CURLOPT_HTTPHEADER, $headersFmt);\r
386 \r
387         return $ch;\r
388     }\r
389 \r
390     /**\r
391      * Workaround for PHP bug #47204 that prevents rewinding request body\r
392      *\r
393      * The workaround consists of reading the entire request body into memory\r
394      * and setting it as CURLOPT_POSTFIELDS, so it isn't recommended for large\r
395      * file uploads, use Socket adapter instead.\r
396      *\r
397      * @param resource $ch       cURL handle\r
398      * @param array    &$headers Request headers\r
399      */\r
400     protected function workaroundPhpBug47204($ch, &$headers)\r
401     {\r
402         // no redirects, no digest auth -> probably no rewind needed\r
403         if (!$this->request->getConfig('follow_redirects')\r
404             && (!($auth = $this->request->getAuth())\r
405                 || HTTP_Request2::AUTH_DIGEST != $auth['scheme'])\r
406         ) {\r
407             curl_setopt($ch, CURLOPT_READFUNCTION, array($this, 'callbackReadBody'));\r
408 \r
409         } else {\r
410             // rewind may be needed, read the whole body into memory\r
411             if ($this->requestBody instanceof HTTP_Request2_MultipartBody) {\r
412                 $this->requestBody = $this->requestBody->__toString();\r
413 \r
414             } elseif (is_resource($this->requestBody)) {\r
415                 $fp = $this->requestBody;\r
416                 $this->requestBody = '';\r
417                 while (!feof($fp)) {\r
418                     $this->requestBody .= fread($fp, 16384);\r
419                 }\r
420             }\r
421             // curl hangs up if content-length is present\r
422             unset($headers['content-length']);\r
423             curl_setopt($ch, CURLOPT_POSTFIELDS, $this->requestBody);\r
424         }\r
425     }\r
426 \r
427     /**\r
428      * Callback function called by cURL for reading the request body\r
429      *\r
430      * @param resource $ch     cURL handle\r
431      * @param resource $fd     file descriptor (not used)\r
432      * @param integer  $length maximum length of data to return\r
433      *\r
434      * @return   string      part of the request body, up to $length bytes\r
435      */\r
436     protected function callbackReadBody($ch, $fd, $length)\r
437     {\r
438         if (!$this->eventSentHeaders) {\r
439             $this->request->setLastEvent(\r
440                 'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)\r
441             );\r
442             $this->eventSentHeaders = true;\r
443         }\r
444         if (in_array($this->request->getMethod(), self::$bodyDisallowed)\r
445             || 0 == $this->contentLength || $this->position >= $this->contentLength\r
446         ) {\r
447             return '';\r
448         }\r
449         if (is_string($this->requestBody)) {\r
450             $string = substr($this->requestBody, $this->position, $length);\r
451         } elseif (is_resource($this->requestBody)) {\r
452             $string = fread($this->requestBody, $length);\r
453         } else {\r
454             $string = $this->requestBody->read($length);\r
455         }\r
456         $this->request->setLastEvent('sentBodyPart', strlen($string));\r
457         $this->position += strlen($string);\r
458         return $string;\r
459     }\r
460 \r
461     /**\r
462      * Callback function called by cURL for saving the response headers\r
463      *\r
464      * @param resource $ch     cURL handle\r
465      * @param string   $string response header (with trailing CRLF)\r
466      *\r
467      * @return   integer     number of bytes saved\r
468      * @see      HTTP_Request2_Response::parseHeaderLine()\r
469      */\r
470     protected function callbackWriteHeader($ch, $string)\r
471     {\r
472         // we may receive a second set of headers if doing e.g. digest auth\r
473         if ($this->eventReceivedHeaders || !$this->eventSentHeaders) {\r
474             // don't bother with 100-Continue responses (bug #15785)\r
475             if (!$this->eventSentHeaders\r
476                 || $this->response->getStatus() >= 200\r
477             ) {\r
478                 $this->request->setLastEvent(\r
479                     'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)\r
480                 );\r
481             }\r
482             $upload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD);\r
483             // if body wasn't read by a callback, send event with total body size\r
484             if ($upload > $this->position) {\r
485                 $this->request->setLastEvent(\r
486                     'sentBodyPart', $upload - $this->position\r
487                 );\r
488                 $this->position = $upload;\r
489             }\r
490             if ($upload && (!$this->eventSentHeaders\r
491                             || $this->response->getStatus() >= 200)\r
492             ) {\r
493                 $this->request->setLastEvent('sentBody', $upload);\r
494             }\r
495             $this->eventSentHeaders = true;\r
496             // we'll need a new response object\r
497             if ($this->eventReceivedHeaders) {\r
498                 $this->eventReceivedHeaders = false;\r
499                 $this->response             = null;\r
500             }\r
501         }\r
502         if (empty($this->response)) {\r
503             $this->response = new HTTP_Request2_Response(\r
504                 $string, false, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)\r
505             );\r
506         } else {\r
507             $this->response->parseHeaderLine($string);\r
508             if ('' == trim($string)) {\r
509                 // don't bother with 100-Continue responses (bug #15785)\r
510                 if (200 <= $this->response->getStatus()) {\r
511                     $this->request->setLastEvent('receivedHeaders', $this->response);\r
512                 }\r
513 \r
514                 if ($this->request->getConfig('follow_redirects') && $this->response->isRedirect()) {\r
515                     $redirectUrl = new Net_URL2($this->response->getHeader('location'));\r
516 \r
517                     // for versions lower than 5.2.10, check the redirection URL protocol\r
518                     if (!defined('CURLOPT_REDIR_PROTOCOLS') && $redirectUrl->isAbsolute()\r
519                         && !in_array($redirectUrl->getScheme(), array('http', 'https'))\r
520                     ) {\r
521                         return -1;\r
522                     }\r
523 \r
524                     if ($jar = $this->request->getCookieJar()) {\r
525                         $jar->addCookiesFromResponse($this->response, $this->request->getUrl());\r
526                         if (!$redirectUrl->isAbsolute()) {\r
527                             $redirectUrl = $this->request->getUrl()->resolve($redirectUrl);\r
528                         }\r
529                         if ($cookies = $jar->getMatching($redirectUrl, true)) {\r
530                             curl_setopt($ch, CURLOPT_COOKIE, $cookies);\r
531                         }\r
532                     }\r
533                 }\r
534                 $this->eventReceivedHeaders = true;\r
535             }\r
536         }\r
537         return strlen($string);\r
538     }\r
539 \r
540     /**\r
541      * Callback function called by cURL for saving the response body\r
542      *\r
543      * @param resource $ch     cURL handle (not used)\r
544      * @param string   $string part of the response body\r
545      *\r
546      * @return   integer     number of bytes saved\r
547      * @throws   HTTP_Request2_MessageException\r
548      * @see      HTTP_Request2_Response::appendBody()\r
549      */\r
550     protected function callbackWriteBody($ch, $string)\r
551     {\r
552         // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if\r
553         // response doesn't start with proper HTTP status line (see bug #15716)\r
554         if (empty($this->response)) {\r
555             throw new HTTP_Request2_MessageException(\r
556                 "Malformed response: {$string}",\r
557                 HTTP_Request2_Exception::MALFORMED_RESPONSE\r
558             );\r
559         }\r
560         if ($this->request->getConfig('store_body')) {\r
561             $this->response->appendBody($string);\r
562         }\r
563         $this->request->setLastEvent('receivedBodyPart', $string);\r
564         return strlen($string);\r
565     }\r
566 }\r
567 ?>\r