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