source: branches/version-2_13-dev/data/module/HTTP/Request.php @ 23141

Revision 23141, 95.8 KB checked in by m_uehara, 11 years ago (diff)

#2275
影響が大きいため、2.13.0では対応を行わない r23125 r23128 r23133 を差し戻します。

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Id
  • Property svn:mime-type set to text/x-httpd-php; charset=UTF-8
Line 
1<<<<<<< .working
2<?php
3/**
4 * Class for performing HTTP requests
5 *
6 * PHP versions 4 and 5
7 *
8 * LICENSE:
9 *
10 * Copyright (c) 2002-2007, Richard Heyes
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * o Redistributions of source code must retain the above copyright
18 *   notice, this list of conditions and the following disclaimer.
19 * o Redistributions in binary form must reproduce the above copyright
20 *   notice, this list of conditions and the following disclaimer in the
21 *   documentation and/or other materials provided with the distribution.
22 * o The names of the authors may not be used to endorse or promote
23 *   products derived from this software without specific prior written
24 *   permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 *
38 * @category    HTTP
39 * @package     HTTP_Request
40 * @author      Richard Heyes <richard@phpguru.org>
41 * @author      Alexey Borzov <avb@php.net>
42 * @copyright   2002-2007 Richard Heyes
43 * @license     http://opensource.org/licenses/bsd-license.php New BSD License
44 * @version     CVS: $Id$
45 * @link        http://pear.php.net/package/HTTP_Request/
46 */
47
48/**
49 * PEAR and PEAR_Error classes (for error handling)
50 */
51require_once 'PEAR.php';
52/**
53 * Socket class
54 */
55require_once 'Net/Socket.php';
56/**
57 * URL handling class
58 */
59require_once 'Net/URL.php';
60
61/**#@+
62 * Constants for HTTP request methods
63 */
64define('HTTP_REQUEST_METHOD_GET',     'GET',     true);
65define('HTTP_REQUEST_METHOD_HEAD',    'HEAD',    true);
66define('HTTP_REQUEST_METHOD_POST',    'POST',    true);
67define('HTTP_REQUEST_METHOD_PUT',     'PUT',     true);
68define('HTTP_REQUEST_METHOD_DELETE',  'DELETE',  true);
69define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
70define('HTTP_REQUEST_METHOD_TRACE',   'TRACE',   true);
71/**#@-*/
72
73/**#@+
74 * Constants for HTTP request error codes
75 */
76define('HTTP_REQUEST_ERROR_FILE',             1);
77define('HTTP_REQUEST_ERROR_URL',              2);
78define('HTTP_REQUEST_ERROR_PROXY',            4);
79define('HTTP_REQUEST_ERROR_REDIRECTS',        8);
80define('HTTP_REQUEST_ERROR_RESPONSE',        16);
81define('HTTP_REQUEST_ERROR_GZIP_METHOD',     32);
82define('HTTP_REQUEST_ERROR_GZIP_READ',       64);
83define('HTTP_REQUEST_ERROR_GZIP_DATA',      128);
84define('HTTP_REQUEST_ERROR_GZIP_CRC',       256);
85/**#@-*/
86
87/**#@+
88 * Constants for HTTP protocol versions
89 */
90define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
91define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
92/**#@-*/
93
94if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
95   /**
96    * Whether string functions are overloaded by their mbstring equivalents
97    */
98    define('HTTP_REQUEST_MBSTRING', true);
99} else {
100   /**
101    * @ignore
102    */
103    define('HTTP_REQUEST_MBSTRING', false);
104}
105
106/**
107 * Class for performing HTTP requests
108 *
109 * Simple example (fetches yahoo.com and displays it):
110 * <code>
111 * $a = &new HTTP_Request('http://www.yahoo.com/');
112 * $a->sendRequest();
113 * echo $a->getResponseBody();
114 * </code>
115 *
116 * @category    HTTP
117 * @package     HTTP_Request
118 * @author      Richard Heyes <richard@phpguru.org>
119 * @author      Alexey Borzov <avb@php.net>
120 * @version     Release: 1.4.4
121 */
122class HTTP_Request
123{
124   /**#@+
125    * @access private
126    */
127    /**
128    * Instance of Net_URL
129    * @var Net_URL
130    */
131    var $_url;
132
133    /**
134    * Type of request
135    * @var string
136    */
137    var $_method;
138
139    /**
140    * HTTP Version
141    * @var string
142    */
143    var $_http;
144
145    /**
146    * Request headers
147    * @var array
148    */
149    var $_requestHeaders;
150
151    /**
152    * Basic Auth Username
153    * @var string
154    */
155    var $_user;
156
157    /**
158    * Basic Auth Password
159    * @var string
160    */
161    var $_pass;
162
163    /**
164    * Socket object
165    * @var Net_Socket
166    */
167    var $_sock;
168
169    /**
170    * Proxy server
171    * @var string
172    */
173    var $_proxy_host;
174
175    /**
176    * Proxy port
177    * @var integer
178    */
179    var $_proxy_port;
180
181    /**
182    * Proxy username
183    * @var string
184    */
185    var $_proxy_user;
186
187    /**
188    * Proxy password
189    * @var string
190    */
191    var $_proxy_pass;
192
193    /**
194    * Post data
195    * @var array
196    */
197    var $_postData;
198
199   /**
200    * Request body
201    * @var string
202    */
203    var $_body;
204
205   /**
206    * A list of methods that MUST NOT have a request body, per RFC 2616
207    * @var array
208    */
209    var $_bodyDisallowed = array('TRACE');
210
211   /**
212    * Methods having defined semantics for request body
213    *
214    * Content-Length header (indicating that the body follows, section 4.3 of
215    * RFC 2616) will be sent for these methods even if no body was added
216    *
217    * @var array
218    */
219    var $_bodyRequired = array('POST', 'PUT');
220
221   /**
222    * Files to post
223    * @var array
224    */
225    var $_postFiles = array();
226
227    /**
228    * Connection timeout.
229    * @var float
230    */
231    var $_timeout;
232
233    /**
234    * HTTP_Response object
235    * @var HTTP_Response
236    */
237    var $_response;
238
239    /**
240    * Whether to allow redirects
241    * @var boolean
242    */
243    var $_allowRedirects;
244
245    /**
246    * Maximum redirects allowed
247    * @var integer
248    */
249    var $_maxRedirects;
250
251    /**
252    * Current number of redirects
253    * @var integer
254    */
255    var $_redirects;
256
257   /**
258    * Whether to append brackets [] to array variables
259    * @var bool
260    */
261    var $_useBrackets = true;
262
263   /**
264    * Attached listeners
265    * @var array
266    */
267    var $_listeners = array();
268
269   /**
270    * Whether to save response body in response object property
271    * @var bool
272    */
273    var $_saveBody = true;
274
275   /**
276    * Timeout for reading from socket (array(seconds, microseconds))
277    * @var array
278    */
279    var $_readTimeout = null;
280
281   /**
282    * Options to pass to Net_Socket::connect. See stream_context_create
283    * @var array
284    */
285    var $_socketOptions = null;
286   /**#@-*/
287
288    /**
289    * Constructor
290    *
291    * Sets up the object
292    * @param    string  The url to fetch/access
293    * @param    array   Associative array of parameters which can have the following keys:
294    * <ul>
295    *   <li>method         - Method to use, GET, POST etc (string)</li>
296    *   <li>http           - HTTP Version to use, 1.0 or 1.1 (string)</li>
297    *   <li>user           - Basic Auth username (string)</li>
298    *   <li>pass           - Basic Auth password (string)</li>
299    *   <li>proxy_host     - Proxy server host (string)</li>
300    *   <li>proxy_port     - Proxy server port (integer)</li>
301    *   <li>proxy_user     - Proxy auth username (string)</li>
302    *   <li>proxy_pass     - Proxy auth password (string)</li>
303    *   <li>timeout        - Connection timeout in seconds (float)</li>
304    *   <li>allowRedirects - Whether to follow redirects or not (bool)</li>
305    *   <li>maxRedirects   - Max number of redirects to follow (integer)</li>
306    *   <li>useBrackets    - Whether to append [] to array variable names (bool)</li>
307    *   <li>saveBody       - Whether to save response body in response object property (bool)</li>
308    *   <li>readTimeout    - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>
309    *   <li>socketOptions  - Options to pass to Net_Socket object (array)</li>
310    * </ul>
311    * @access public
312    */
313    function HTTP_Request($url = '', $params = array())
314    {
315        $this->_method         =  HTTP_REQUEST_METHOD_GET;
316        $this->_http           =  HTTP_REQUEST_HTTP_VER_1_1;
317        $this->_requestHeaders = array();
318        $this->_postData       = array();
319        $this->_body           = null;
320
321        $this->_user = null;
322        $this->_pass = null;
323
324        $this->_proxy_host = null;
325        $this->_proxy_port = null;
326        $this->_proxy_user = null;
327        $this->_proxy_pass = null;
328
329        $this->_allowRedirects = false;
330        $this->_maxRedirects   = 3;
331        $this->_redirects      = 0;
332
333        $this->_timeout  = null;
334        $this->_response = null;
335
336        foreach ($params as $key => $value) {
337            $this->{'_' . $key} = $value;
338        }
339
340        if (!empty($url)) {
341            $this->setURL($url);
342        }
343
344        // Default useragent
345        $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
346
347        // We don't do keep-alives by default
348        $this->addHeader('Connection', 'close');
349
350        // Basic authentication
351        if (!empty($this->_user)) {
352            $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));
353        }
354
355        // Proxy authentication (see bug #5913)
356        if (!empty($this->_proxy_user)) {
357            $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));
358        }
359
360        // Use gzip encoding if possible
361        if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) {
362            $this->addHeader('Accept-Encoding', 'gzip');
363        }
364    }
365
366    /**
367    * Generates a Host header for HTTP/1.1 requests
368    *
369    * @access private
370    * @return string
371    */
372    function _generateHostHeader()
373    {
374        if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
375            $host = $this->_url->host . ':' . $this->_url->port;
376
377        } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
378            $host = $this->_url->host . ':' . $this->_url->port;
379
380        } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
381            $host = $this->_url->host . ':' . $this->_url->port;
382
383        } else {
384            $host = $this->_url->host;
385        }
386
387        return $host;
388    }
389
390    /**
391    * Resets the object to its initial state (DEPRECATED).
392    * Takes the same parameters as the constructor.
393    *
394    * @param  string $url    The url to be requested
395    * @param  array  $params Associative array of parameters
396    *                        (see constructor for details)
397    * @access public
398    * @deprecated deprecated since 1.2, call the constructor if this is necessary
399    */
400    function reset($url, $params = array())
401    {
402        $this->HTTP_Request($url, $params);
403    }
404
405    /**
406    * Sets the URL to be requested
407    *
408    * @param  string The url to be requested
409    * @access public
410    */
411    function setURL($url)
412    {
413        $this->_url = &new Net_URL($url, $this->_useBrackets);
414
415        if (!empty($this->_url->user) || !empty($this->_url->pass)) {
416            $this->setBasicAuth($this->_url->user, $this->_url->pass);
417        }
418
419        if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
420            $this->addHeader('Host', $this->_generateHostHeader());
421        }
422
423        // set '/' instead of empty path rather than check later (see bug #8662)
424        if (empty($this->_url->path)) {
425            $this->_url->path = '/';
426        }
427    }
428
429   /**
430    * Returns the current request URL
431    *
432    * @return   string  Current request URL
433    * @access   public
434    */
435    function getUrl()
436    {
437        return empty($this->_url)? '': $this->_url->getUrl();
438    }
439
440    /**
441    * Sets a proxy to be used
442    *
443    * @param string     Proxy host
444    * @param int        Proxy port
445    * @param string     Proxy username
446    * @param string     Proxy password
447    * @access public
448    */
449    function setProxy($host, $port = 8080, $user = null, $pass = null)
450    {
451        $this->_proxy_host = $host;
452        $this->_proxy_port = $port;
453        $this->_proxy_user = $user;
454        $this->_proxy_pass = $pass;
455
456        if (!empty($user)) {
457            $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
458        }
459    }
460
461    /**
462    * Sets basic authentication parameters
463    *
464    * @param string     Username
465    * @param string     Password
466    */
467    function setBasicAuth($user, $pass)
468    {
469        $this->_user = $user;
470        $this->_pass = $pass;
471
472        $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
473    }
474
475    /**
476    * Sets the method to be used, GET, POST etc.
477    *
478    * @param string     Method to use. Use the defined constants for this
479    * @access public
480    */
481    function setMethod($method)
482    {
483        $this->_method = $method;
484    }
485
486    /**
487    * Sets the HTTP version to use, 1.0 or 1.1
488    *
489    * @param string     Version to use. Use the defined constants for this
490    * @access public
491    */
492    function setHttpVer($http)
493    {
494        $this->_http = $http;
495    }
496
497    /**
498    * Adds a request header
499    *
500    * @param string     Header name
501    * @param string     Header value
502    * @access public
503    */
504    function addHeader($name, $value)
505    {
506        $this->_requestHeaders[strtolower($name)] = $value;
507    }
508
509    /**
510    * Removes a request header
511    *
512    * @param string     Header name to remove
513    * @access public
514    */
515    function removeHeader($name)
516    {
517        if (isset($this->_requestHeaders[strtolower($name)])) {
518            unset($this->_requestHeaders[strtolower($name)]);
519        }
520    }
521
522    /**
523    * Adds a querystring parameter
524    *
525    * @param string     Querystring parameter name
526    * @param string     Querystring parameter value
527    * @param bool       Whether the value is already urlencoded or not, default = not
528    * @access public
529    */
530    function addQueryString($name, $value, $preencoded = false)
531    {
532        $this->_url->addQueryString($name, $value, $preencoded);
533    }
534
535    /**
536    * Sets the querystring to literally what you supply
537    *
538    * @param string     The querystring data. Should be of the format foo=bar&x=y etc
539    * @param bool       Whether data is already urlencoded or not, default = already encoded
540    * @access public
541    */
542    function addRawQueryString($querystring, $preencoded = true)
543    {
544        $this->_url->addRawQueryString($querystring, $preencoded);
545    }
546
547    /**
548    * Adds postdata items
549    *
550    * @param string     Post data name
551    * @param string     Post data value
552    * @param bool       Whether data is already urlencoded or not, default = not
553    * @access public
554    */
555    function addPostData($name, $value, $preencoded = false)
556    {
557        if ($preencoded) {
558            $this->_postData[$name] = $value;
559        } else {
560            $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
561        }
562    }
563
564   /**
565    * Recursively applies the callback function to the value
566    *
567    * @param    mixed   Callback function
568    * @param    mixed   Value to process
569    * @access   private
570    * @return   mixed   Processed value
571    */
572    function _arrayMapRecursive($callback, $value)
573    {
574        if (!is_array($value)) {
575            return call_user_func($callback, $value);
576        } else {
577            $map = array();
578            foreach ($value as $k => $v) {
579                $map[$k] = $this->_arrayMapRecursive($callback, $v);
580            }
581            return $map;
582        }
583    }
584
585   /**
586    * Adds a file to form-based file upload
587    *
588    * Used to emulate file upload via a HTML form. The method also sets
589    * Content-Type of HTTP request to 'multipart/form-data'.
590    *
591    * If you just want to send the contents of a file as the body of HTTP
592    * request you should use setBody() method.
593    *
594    * @access public
595    * @param  string    name of file-upload field
596    * @param  mixed     file name(s)
597    * @param  mixed     content-type(s) of file(s) being uploaded
598    * @return bool      true on success
599    * @throws PEAR_Error
600    */
601    function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
602    {
603        if (!is_array($fileName) && !is_readable($fileName)) {
604            return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE);
605        } elseif (is_array($fileName)) {
606            foreach ($fileName as $name) {
607                if (!is_readable($name)) {
608                    return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE);
609                }
610            }
611        }
612        $this->addHeader('Content-Type', 'multipart/form-data');
613        $this->_postFiles[$inputName] = array(
614            'name' => $fileName,
615            'type' => $contentType
616        );
617        return true;
618    }
619
620    /**
621    * Adds raw postdata (DEPRECATED)
622    *
623    * @param string     The data
624    * @param bool       Whether data is preencoded or not, default = already encoded
625    * @access public
626    * @deprecated       deprecated since 1.3.0, method setBody() should be used instead
627    */
628    function addRawPostData($postdata, $preencoded = true)
629    {
630        $this->_body = $preencoded ? $postdata : urlencode($postdata);
631    }
632
633   /**
634    * Sets the request body (for POST, PUT and similar requests)
635    *
636    * @param    string  Request body
637    * @access   public
638    */
639    function setBody($body)
640    {
641        $this->_body = $body;
642    }
643
644    /**
645    * Clears any postdata that has been added (DEPRECATED).
646    *
647    * Useful for multiple request scenarios.
648    *
649    * @access public
650    * @deprecated deprecated since 1.2
651    */
652    function clearPostData()
653    {
654        $this->_postData = null;
655    }
656
657    /**
658    * Appends a cookie to "Cookie:" header
659    *
660    * @param string $name cookie name
661    * @param string $value cookie value
662    * @access public
663    */
664    function addCookie($name, $value)
665    {
666        $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';
667        $this->addHeader('Cookie', $cookies . $name . '=' . $value);
668    }
669
670    /**
671    * Clears any cookies that have been added (DEPRECATED).
672    *
673    * Useful for multiple request scenarios
674    *
675    * @access public
676    * @deprecated deprecated since 1.2
677    */
678    function clearCookies()
679    {
680        $this->removeHeader('Cookie');
681    }
682
683    /**
684    * Sends the request
685    *
686    * @access public
687    * @param  bool   Whether to store response body in Response object property,
688    *                set this to false if downloading a LARGE file and using a Listener
689    * @return mixed  PEAR error on error, true otherwise
690    */
691    function sendRequest($saveBody = true)
692    {
693        if (!is_a($this->_url, 'Net_URL')) {
694            return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL);
695        }
696
697        $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
698        $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
699
700        if (strcasecmp($this->_url->protocol, 'https') == 0) {
701            // Bug #14127, don't try connecting to HTTPS sites without OpenSSL
702            if (version_compare(PHP_VERSION, '4.3.0', '<') || !extension_loaded('openssl')) {
703                return PEAR::raiseError('Need PHP 4.3.0 or later with OpenSSL support for https:// requests',
704                                        HTTP_REQUEST_ERROR_URL);
705            } elseif (isset($this->_proxy_host)) {
706                return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY);
707            }
708            $host = 'ssl://' . $host;
709        }
710
711        // magic quotes may fuck up file uploads and chunked response processing
712        $magicQuotes = ini_get('magic_quotes_runtime');
713        ini_set('magic_quotes_runtime', false);
714
715        // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
716        // connection token to a proxy server...
717        if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&
718            'Keep-Alive' == $this->_requestHeaders['connection'])
719        {
720            $this->removeHeader('connection');
721        }
722
723        $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||
724                     (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);
725        $sockets   = &PEAR::getStaticProperty('HTTP_Request', 'sockets');
726        $sockKey   = $host . ':' . $port;
727        unset($this->_sock);
728
729        // There is a connected socket in the "static" property?
730        if ($keepAlive && !empty($sockets[$sockKey]) &&
731            !empty($sockets[$sockKey]->fp))
732        {
733            $this->_sock =& $sockets[$sockKey];
734            $err = null;
735        } else {
736            $this->_notify('connect');
737            $this->_sock =& new Net_Socket();
738            $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
739        }
740        PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
741
742        if (!PEAR::isError($err)) {
743            if (!empty($this->_readTimeout)) {
744                $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
745            }
746
747            $this->_notify('sentRequest');
748
749            // Read the response
750            $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);
751            $err = $this->_response->process(
752                $this->_saveBody && $saveBody,
753                HTTP_REQUEST_METHOD_HEAD != $this->_method
754            );
755
756            if ($keepAlive) {
757                $keepAlive = (isset($this->_response->_headers['content-length'])
758                              || (isset($this->_response->_headers['transfer-encoding'])
759                                  && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));
760                if ($keepAlive) {
761                    if (isset($this->_response->_headers['connection'])) {
762                        $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';
763                    } else {
764                        $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
765                    }
766                }
767            }
768        }
769
770        ini_set('magic_quotes_runtime', $magicQuotes);
771
772        if (PEAR::isError($err)) {
773            return $err;
774        }
775
776        if (!$keepAlive) {
777            $this->disconnect();
778        // Store the connected socket in "static" property
779        } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
780            $sockets[$sockKey] =& $this->_sock;
781        }
782
783        // Check for redirection
784        if (    $this->_allowRedirects
785            AND $this->_redirects <= $this->_maxRedirects
786            AND $this->getResponseCode() > 300
787            AND $this->getResponseCode() < 399
788            AND !empty($this->_response->_headers['location'])) {
789
790
791            $redirect = $this->_response->_headers['location'];
792
793            // Absolute URL
794            if (preg_match('/^https?:\/\//i', $redirect)) {
795                $this->_url = &new Net_URL($redirect);
796                $this->addHeader('Host', $this->_generateHostHeader());
797            // Absolute path
798            } elseif ($redirect{0} == '/') {
799                $this->_url->path = $redirect;
800
801            // Relative path
802            } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
803                if (substr($this->_url->path, -1) == '/') {
804                    $redirect = $this->_url->path . $redirect;
805                } else {
806                    $redirect = dirname($this->_url->path) . '/' . $redirect;
807                }
808                $redirect = Net_URL::resolvePath($redirect);
809                $this->_url->path = $redirect;
810
811            // Filename, no path
812            } else {
813                if (substr($this->_url->path, -1) == '/') {
814                    $redirect = $this->_url->path . $redirect;
815                } else {
816                    $redirect = dirname($this->_url->path) . '/' . $redirect;
817                }
818                $this->_url->path = $redirect;
819            }
820
821            $this->_redirects++;
822            return $this->sendRequest($saveBody);
823
824        // Too many redirects
825        } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
826            return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS);
827        }
828
829        return true;
830    }
831
832    /**
833     * Disconnect the socket, if connected. Only useful if using Keep-Alive.
834     *
835     * @access public
836     */
837    function disconnect()
838    {
839        if (!empty($this->_sock) && !empty($this->_sock->fp)) {
840            $this->_notify('disconnect');
841            $this->_sock->disconnect();
842        }
843    }
844
845    /**
846    * Returns the response code
847    *
848    * @access public
849    * @return mixed     Response code, false if not set
850    */
851    function getResponseCode()
852    {
853        return isset($this->_response->_code) ? $this->_response->_code : false;
854    }
855
856    /**
857    * Returns the response reason phrase
858    *
859    * @access public
860    * @return mixed     Response reason phrase, false if not set
861    */
862    function getResponseReason()
863    {
864        return isset($this->_response->_reason) ? $this->_response->_reason : false;
865    }
866
867    /**
868    * Returns either the named header or all if no name given
869    *
870    * @access public
871    * @param string     The header name to return, do not set to get all headers
872    * @return mixed     either the value of $headername (false if header is not present)
873    *                   or an array of all headers
874    */
875    function getResponseHeader($headername = null)
876    {
877        if (!isset($headername)) {
878            return isset($this->_response->_headers)? $this->_response->_headers: array();
879        } else {
880            $headername = strtolower($headername);
881            return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
882        }
883    }
884
885    /**
886    * Returns the body of the response
887    *
888    * @access public
889    * @return mixed     response body, false if not set
890    */
891    function getResponseBody()
892    {
893        return isset($this->_response->_body) ? $this->_response->_body : false;
894    }
895
896    /**
897    * Returns cookies set in response
898    *
899    * @access public
900    * @return mixed     array of response cookies, false if none are present
901    */
902    function getResponseCookies()
903    {
904        return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
905    }
906
907    /**
908    * Builds the request string
909    *
910    * @access private
911    * @return string The request string
912    */
913    function _buildRequest()
914    {
915        $separator = ini_get('arg_separator.output');
916        ini_set('arg_separator.output', '&');
917        $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
918        ini_set('arg_separator.output', $separator);
919
920        $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
921        $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
922        $path = $this->_url->path . $querystring;
923        $url  = $host . $port . $path;
924
925        if (!strlen($url)) {
926            $url = '/';
927        }
928
929        $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
930
931        if (in_array($this->_method, $this->_bodyDisallowed) ||
932            (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||
933             (empty($this->_postData) && empty($this->_postFiles)))))
934        {
935            $this->removeHeader('Content-Type');
936        } else {
937            if (empty($this->_requestHeaders['content-type'])) {
938                // Add default content-type
939                $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
940            } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {
941                $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
942                $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
943            }
944        }
945
946        // Request Headers
947        if (!empty($this->_requestHeaders)) {
948            foreach ($this->_requestHeaders as $name => $value) {
949                $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
950                $request      .= $canonicalName . ': ' . $value . "\r\n";
951            }
952        }
953
954        // Method does not allow a body, simply add a final CRLF
955        if (in_array($this->_method, $this->_bodyDisallowed)) {
956
957            $request .= "\r\n";
958
959        // Post data if it's an array
960        } elseif (HTTP_REQUEST_METHOD_POST == $this->_method &&
961                  (!empty($this->_postData) || !empty($this->_postFiles))) {
962
963            // "normal" POST request
964            if (!isset($boundary)) {
965                $postdata = implode('&', array_map(
966                    create_function('$a', 'return $a[0] . \'=\' . $a[1];'),
967                    $this->_flattenArray('', $this->_postData)
968                ));
969
970            // multipart request, probably with file uploads
971            } else {
972                $postdata = '';
973                if (!empty($this->_postData)) {
974                    $flatData = $this->_flattenArray('', $this->_postData);
975                    foreach ($flatData as $item) {
976                        $postdata .= '--' . $boundary . "\r\n";
977                        $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
978                        $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
979                    }
980                }
981                foreach ($this->_postFiles as $name => $value) {
982                    if (is_array($value['name'])) {
983                        $varname       = $name . ($this->_useBrackets? '[]': '');
984                    } else {
985                        $varname       = $name;
986                        $value['name'] = array($value['name']);
987                    }
988                    foreach ($value['name'] as $key => $filename) {
989                        $fp       = fopen($filename, 'r');
990                        $basename = basename($filename);
991                        $type     = is_array($value['type'])? @$value['type'][$key]: $value['type'];
992
993                        $postdata .= '--' . $boundary . "\r\n";
994                        $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
995                        $postdata .= "\r\nContent-Type: " . $type;
996                        $postdata .= "\r\n\r\n" . fread($fp, filesize($filename)) . "\r\n";
997                        fclose($fp);
998                    }
999                }
1000                $postdata .= '--' . $boundary . "--\r\n";
1001            }
1002            $request .= 'Content-Length: ' .
1003                        (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) .
1004                        "\r\n\r\n";
1005            $request .= $postdata;
1006
1007        // Explicitly set request body
1008        } elseif (0 < strlen($this->_body)) {
1009
1010            $request .= 'Content-Length: ' .
1011                        (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) .
1012                        "\r\n\r\n";
1013            $request .= $this->_body;
1014
1015        // No body: send a Content-Length header nonetheless (request #12900),
1016        // but do that only for methods that require a body (bug #14740)
1017        } else {
1018
1019            if (in_array($this->_method, $this->_bodyRequired)) {
1020                $request .= "Content-Length: 0\r\n";
1021            }
1022            $request .= "\r\n";
1023        }
1024
1025        return $request;
1026    }
1027
1028   /**
1029    * Helper function to change the (probably multidimensional) associative array
1030    * into the simple one.
1031    *
1032    * @param    string  name for item
1033    * @param    mixed   item's values
1034    * @return   array   array with the following items: array('item name', 'item value');
1035    * @access   private
1036    */
1037    function _flattenArray($name, $values)
1038    {
1039        if (!is_array($values)) {
1040            return array(array($name, $values));
1041        } else {
1042            $ret = array();
1043            foreach ($values as $k => $v) {
1044                if (empty($name)) {
1045                    $newName = $k;
1046                } elseif ($this->_useBrackets) {
1047                    $newName = $name . '[' . $k . ']';
1048                } else {
1049                    $newName = $name;
1050                }
1051                $ret = array_merge($ret, $this->_flattenArray($newName, $v));
1052            }
1053            return $ret;
1054        }
1055    }
1056
1057
1058   /**
1059    * Adds a Listener to the list of listeners that are notified of
1060    * the object's events
1061    *
1062    * Events sent by HTTP_Request object
1063    * - 'connect': on connection to server
1064    * - 'sentRequest': after the request was sent
1065    * - 'disconnect': on disconnection from server
1066    *
1067    * Events sent by HTTP_Response object
1068    * - 'gotHeaders': after receiving response headers (headers are passed in $data)
1069    * - 'tick': on receiving a part of response body (the part is passed in $data)
1070    * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)
1071    * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)
1072    *
1073    * @param    HTTP_Request_Listener   listener to attach
1074    * @return   boolean                 whether the listener was successfully attached
1075    * @access   public
1076    */
1077    function attach(&$listener)
1078    {
1079        if (!is_a($listener, 'HTTP_Request_Listener')) {
1080            return false;
1081        }
1082        $this->_listeners[$listener->getId()] =& $listener;
1083        return true;
1084    }
1085
1086
1087   /**
1088    * Removes a Listener from the list of listeners
1089    *
1090    * @param    HTTP_Request_Listener   listener to detach
1091    * @return   boolean                 whether the listener was successfully detached
1092    * @access   public
1093    */
1094    function detach(&$listener)
1095    {
1096        if (!is_a($listener, 'HTTP_Request_Listener') ||
1097            !isset($this->_listeners[$listener->getId()])) {
1098            return false;
1099        }
1100        unset($this->_listeners[$listener->getId()]);
1101        return true;
1102    }
1103
1104
1105   /**
1106    * Notifies all registered listeners of an event.
1107    *
1108    * @param    string  Event name
1109    * @param    mixed   Additional data
1110    * @access   private
1111    * @see      HTTP_Request::attach()
1112    */
1113    function _notify($event, $data = null)
1114    {
1115        foreach (array_keys($this->_listeners) as $id) {
1116            $this->_listeners[$id]->update($this, $event, $data);
1117        }
1118    }
1119}
1120
1121
1122/**
1123 * Response class to complement the Request class
1124 *
1125 * @category    HTTP
1126 * @package     HTTP_Request
1127 * @author      Richard Heyes <richard@phpguru.org>
1128 * @author      Alexey Borzov <avb@php.net>
1129 * @version     Release: 1.4.4
1130 */
1131class HTTP_Response
1132{
1133    /**
1134    * Socket object
1135    * @var Net_Socket
1136    */
1137    var $_sock;
1138
1139    /**
1140    * Protocol
1141    * @var string
1142    */
1143    var $_protocol;
1144
1145    /**
1146    * Return code
1147    * @var string
1148    */
1149    var $_code;
1150
1151    /**
1152    * Response reason phrase
1153    * @var string
1154    */
1155    var $_reason;
1156
1157    /**
1158    * Response headers
1159    * @var array
1160    */
1161    var $_headers;
1162
1163    /**
1164    * Cookies set in response
1165    * @var array
1166    */
1167    var $_cookies;
1168
1169    /**
1170    * Response body
1171    * @var string
1172    */
1173    var $_body = '';
1174
1175   /**
1176    * Used by _readChunked(): remaining length of the current chunk
1177    * @var string
1178    */
1179    var $_chunkLength = 0;
1180
1181   /**
1182    * Attached listeners
1183    * @var array
1184    */
1185    var $_listeners = array();
1186
1187   /**
1188    * Bytes left to read from message-body
1189    * @var null|int
1190    */
1191    var $_toRead;
1192
1193    /**
1194    * Constructor
1195    *
1196    * @param  Net_Socket    socket to read the response from
1197    * @param  array         listeners attached to request
1198    */
1199    function HTTP_Response(&$sock, &$listeners)
1200    {
1201        $this->_sock      =& $sock;
1202        $this->_listeners =& $listeners;
1203    }
1204
1205
1206   /**
1207    * Processes a HTTP response
1208    *
1209    * This extracts response code, headers, cookies and decodes body if it
1210    * was encoded in some way
1211    *
1212    * @access public
1213    * @param  bool      Whether to store response body in object property, set
1214    *                   this to false if downloading a LARGE file and using a Listener.
1215    *                   This is assumed to be true if body is gzip-encoded.
1216    * @param  bool      Whether the response can actually have a message-body.
1217    *                   Will be set to false for HEAD requests.
1218    * @throws PEAR_Error
1219    * @return mixed     true on success, PEAR_Error in case of malformed response
1220    */
1221    function process($saveBody = true, $canHaveBody = true)
1222    {
1223        do {
1224            $line = $this->_sock->readLine();
1225            if (!preg_match('!^(HTTP/\d\.\d) (\d{3})(?: (.+))?!', $line, $s)) {
1226                return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE);
1227            } else {
1228                $this->_protocol = $s[1];
1229                $this->_code     = intval($s[2]);
1230                $this->_reason   = empty($s[3])? null: $s[3];
1231            }
1232            while ('' !== ($header = $this->_sock->readLine())) {
1233                $this->_processHeader($header);
1234            }
1235        } while (100 == $this->_code);
1236
1237        $this->_notify('gotHeaders', $this->_headers);
1238
1239        // RFC 2616, section 4.4:
1240        // 1. Any response message which "MUST NOT" include a message-body ...
1241        // is always terminated by the first empty line after the header fields
1242        // 3. ... If a message is received with both a
1243        // Transfer-Encoding header field and a Content-Length header field,
1244        // the latter MUST be ignored.
1245        $canHaveBody = $canHaveBody && $this->_code >= 200 &&
1246                       $this->_code != 204 && $this->_code != 304;
1247
1248        // If response body is present, read it and decode
1249        $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
1250        $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
1251        $hasBody = false;
1252        if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) ||
1253                0 != $this->_headers['content-length']))
1254        {
1255            if ($chunked || !isset($this->_headers['content-length'])) {
1256                $this->_toRead = null;
1257            } else {
1258                $this->_toRead = $this->_headers['content-length'];
1259            }
1260            while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
1261                if ($chunked) {
1262                    $data = $this->_readChunked();
1263                } elseif (is_null($this->_toRead)) {
1264                    $data = $this->_sock->read(4096);
1265                } else {
1266                    $data = $this->_sock->read(min(4096, $this->_toRead));
1267                    $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
1268                }
1269                if ('' == $data && (!$this->_chunkLength || $this->_sock->eof())) {
1270                    break;
1271                } else {
1272                    $hasBody = true;
1273                    if ($saveBody || $gzipped) {
1274                        $this->_body .= $data;
1275                    }
1276                    $this->_notify($gzipped? 'gzTick': 'tick', $data);
1277                }
1278            }
1279        }
1280
1281        if ($hasBody) {
1282            // Uncompress the body if needed
1283            if ($gzipped) {
1284                $body = $this->_decodeGzip($this->_body);
1285                if (PEAR::isError($body)) {
1286                    return $body;
1287                }
1288                $this->_body = $body;
1289                $this->_notify('gotBody', $this->_body);
1290            } else {
1291                $this->_notify('gotBody');
1292            }
1293        }
1294        return true;
1295    }
1296
1297
1298   /**
1299    * Processes the response header
1300    *
1301    * @access private
1302    * @param  string    HTTP header
1303    */
1304    function _processHeader($header)
1305    {
1306        if (false === strpos($header, ':')) {
1307            return;
1308        }
1309        list($headername, $headervalue) = explode(':', $header, 2);
1310        $headername  = strtolower($headername);
1311        $headervalue = ltrim($headervalue);
1312
1313        if ('set-cookie' != $headername) {
1314            if (isset($this->_headers[$headername])) {
1315                $this->_headers[$headername] .= ',' . $headervalue;
1316            } else {
1317                $this->_headers[$headername]  = $headervalue;
1318            }
1319        } else {
1320            $this->_parseCookie($headervalue);
1321        }
1322    }
1323
1324
1325   /**
1326    * Parse a Set-Cookie header to fill $_cookies array
1327    *
1328    * @access private
1329    * @param  string    value of Set-Cookie header
1330    */
1331    function _parseCookie($headervalue)
1332    {
1333        $cookie = array(
1334            'expires' => null,
1335            'domain'  => null,
1336            'path'    => null,
1337            'secure'  => false
1338        );
1339
1340        // Only a name=value pair
1341        if (!strpos($headervalue, ';')) {
1342            $pos = strpos($headervalue, '=');
1343            $cookie['name']  = trim(substr($headervalue, 0, $pos));
1344            $cookie['value'] = trim(substr($headervalue, $pos + 1));
1345
1346        // Some optional parameters are supplied
1347        } else {
1348            $elements = explode(';', $headervalue);
1349            $pos = strpos($elements[0], '=');
1350            $cookie['name']  = trim(substr($elements[0], 0, $pos));
1351            $cookie['value'] = trim(substr($elements[0], $pos + 1));
1352
1353            for ($i = 1; $i < count($elements); $i++) {
1354                if (false === strpos($elements[$i], '=')) {
1355                    $elName  = trim($elements[$i]);
1356                    $elValue = null;
1357                } else {
1358                    list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
1359                }
1360                $elName = strtolower($elName);
1361                if ('secure' == $elName) {
1362                    $cookie['secure'] = true;
1363                } elseif ('expires' == $elName) {
1364                    $cookie['expires'] = str_replace('"', '', $elValue);
1365                } elseif ('path' == $elName || 'domain' == $elName) {
1366                    $cookie[$elName] = urldecode($elValue);
1367                } else {
1368                    $cookie[$elName] = $elValue;
1369                }
1370            }
1371        }
1372        $this->_cookies[] = $cookie;
1373    }
1374
1375
1376   /**
1377    * Read a part of response body encoded with chunked Transfer-Encoding
1378    *
1379    * @access private
1380    * @return string
1381    */
1382    function _readChunked()
1383    {
1384        // at start of the next chunk?
1385        if (0 == $this->_chunkLength) {
1386            $line = $this->_sock->readLine();
1387            if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
1388                $this->_chunkLength = hexdec($matches[1]);
1389                // Chunk with zero length indicates the end
1390                if (0 == $this->_chunkLength) {
1391                    $this->_sock->readLine(); // make this an eof()
1392                    return '';
1393                }
1394            } else {
1395                return '';
1396            }
1397        }
1398        $data = $this->_sock->read($this->_chunkLength);
1399        $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
1400        if (0 == $this->_chunkLength) {
1401            $this->_sock->readLine(); // Trailing CRLF
1402        }
1403        return $data;
1404    }
1405
1406
1407   /**
1408    * Notifies all registered listeners of an event.
1409    *
1410    * @param    string  Event name
1411    * @param    mixed   Additional data
1412    * @access   private
1413    * @see HTTP_Request::_notify()
1414    */
1415    function _notify($event, $data = null)
1416    {
1417        foreach (array_keys($this->_listeners) as $id) {
1418            $this->_listeners[$id]->update($this, $event, $data);
1419        }
1420    }
1421
1422
1423   /**
1424    * Decodes the message-body encoded by gzip
1425    *
1426    * The real decoding work is done by gzinflate() built-in function, this
1427    * method only parses the header and checks data for compliance with
1428    * RFC 1952
1429    *
1430    * @access   private
1431    * @param    string  gzip-encoded data
1432    * @return   string  decoded data
1433    */
1434    function _decodeGzip($data)
1435    {
1436        if (HTTP_REQUEST_MBSTRING) {
1437            $oldEncoding = mb_internal_encoding();
1438            mb_internal_encoding('iso-8859-1');
1439        }
1440        $length = strlen($data);
1441        // If it doesn't look like gzip-encoded data, don't bother
1442        if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
1443            return $data;
1444        }
1445        $method = ord(substr($data, 2, 1));
1446        if (8 != $method) {
1447            return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD);
1448        }
1449        $flags = ord(substr($data, 3, 1));
1450        if ($flags & 224) {
1451            return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA);
1452        }
1453
1454        // header is 10 bytes minimum. may be longer, though.
1455        $headerLength = 10;
1456        // extra fields, need to skip 'em
1457        if ($flags & 4) {
1458            if ($length - $headerLength - 2 < 8) {
1459                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1460            }
1461            $extraLength = unpack('v', substr($data, 10, 2));
1462            if ($length - $headerLength - 2 - $extraLength[1] < 8) {
1463                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1464            }
1465            $headerLength += $extraLength[1] + 2;
1466        }
1467        // file name, need to skip that
1468        if ($flags & 8) {
1469            if ($length - $headerLength - 1 < 8) {
1470                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1471            }
1472            $filenameLength = strpos(substr($data, $headerLength), chr(0));
1473            if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
1474                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1475            }
1476            $headerLength += $filenameLength + 1;
1477        }
1478        // comment, need to skip that also
1479        if ($flags & 16) {
1480            if ($length - $headerLength - 1 < 8) {
1481                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1482            }
1483            $commentLength = strpos(substr($data, $headerLength), chr(0));
1484            if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
1485                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1486            }
1487            $headerLength += $commentLength + 1;
1488        }
1489        // have a CRC for header. let's check
1490        if ($flags & 1) {
1491            if ($length - $headerLength - 2 < 8) {
1492                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1493            }
1494            $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));
1495            $crcStored = unpack('v', substr($data, $headerLength, 2));
1496            if ($crcReal != $crcStored[1]) {
1497                return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
1498            }
1499            $headerLength += 2;
1500        }
1501        // unpacked data CRC and size at the end of encoded data
1502        $tmp = unpack('V2', substr($data, -8));
1503        $dataCrc  = $tmp[1];
1504        $dataSize = $tmp[2];
1505
1506        // finally, call the gzinflate() function
1507        // don't pass $dataSize to gzinflate, see bugs #13135, #14370
1508        $unpacked = gzinflate(substr($data, $headerLength, -8));
1509        if (false === $unpacked) {
1510            return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ);
1511        } elseif ($dataSize != strlen($unpacked)) {
1512            return PEAR::raiseError('_decodeGzip(): data size check failed', HTTP_REQUEST_ERROR_GZIP_READ);
1513        } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
1514            return PEAR::raiseError('_decodeGzip(): data CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
1515        }
1516        if (HTTP_REQUEST_MBSTRING) {
1517            mb_internal_encoding($oldEncoding);
1518        }
1519        return $unpacked;
1520    }
1521} // End class HTTP_Response
1522?>
1523=======
1524<?php
1525/**
1526 * Class for performing HTTP requests
1527 *
1528 * PHP versions 4 and 5
1529 *
1530 * LICENSE:
1531 *
1532 * Copyright (c) 2002-2007, Richard Heyes
1533 * All rights reserved.
1534 *
1535 * Redistribution and use in source and binary forms, with or without
1536 * modification, are permitted provided that the following conditions
1537 * are met:
1538 *
1539 * o Redistributions of source code must retain the above copyright
1540 *   notice, this list of conditions and the following disclaimer.
1541 * o Redistributions in binary form must reproduce the above copyright
1542 *   notice, this list of conditions and the following disclaimer in the
1543 *   documentation and/or other materials provided with the distribution.
1544 * o The names of the authors may not be used to endorse or promote
1545 *   products derived from this software without specific prior written
1546 *   permission.
1547 *
1548 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1549 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1550 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1551 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1552 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
1553 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
1554 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
1555 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
1556 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1557 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
1558 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1559 *
1560 * @category    HTTP
1561 * @package     HTTP_Request
1562 * @author      Richard Heyes <richard@phpguru.org>
1563 * @author      Alexey Borzov <avb@php.net>
1564 * @copyright   2002-2007 Richard Heyes
1565 * @license     http://opensource.org/licenses/bsd-license.php New BSD License
1566 * @version     CVS: $Id$
1567 * @link        http://pear.php.net/package/HTTP_Request/
1568 */
1569
1570/**
1571 * PEAR and PEAR_Error classes (for error handling)
1572 */
1573require_once 'PEAR.php';
1574/**
1575 * Socket class
1576 */
1577require_once 'Net/Socket.php';
1578/**
1579 * URL handling class
1580 */
1581require_once 'Net/URL.php';
1582
1583/**#@+
1584 * Constants for HTTP request methods
1585 */
1586define('HTTP_REQUEST_METHOD_GET',     'GET',     true);
1587define('HTTP_REQUEST_METHOD_HEAD',    'HEAD',    true);
1588define('HTTP_REQUEST_METHOD_POST',    'POST',    true);
1589define('HTTP_REQUEST_METHOD_PUT',     'PUT',     true);
1590define('HTTP_REQUEST_METHOD_DELETE',  'DELETE',  true);
1591define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
1592define('HTTP_REQUEST_METHOD_TRACE',   'TRACE',   true);
1593/**#@-*/
1594
1595/**#@+
1596 * Constants for HTTP request error codes
1597 */
1598define('HTTP_REQUEST_ERROR_FILE',             1);
1599define('HTTP_REQUEST_ERROR_URL',              2);
1600define('HTTP_REQUEST_ERROR_PROXY',            4);
1601define('HTTP_REQUEST_ERROR_REDIRECTS',        8);
1602define('HTTP_REQUEST_ERROR_RESPONSE',        16);
1603define('HTTP_REQUEST_ERROR_GZIP_METHOD',     32);
1604define('HTTP_REQUEST_ERROR_GZIP_READ',       64);
1605define('HTTP_REQUEST_ERROR_GZIP_DATA',      128);
1606define('HTTP_REQUEST_ERROR_GZIP_CRC',       256);
1607/**#@-*/
1608
1609/**#@+
1610 * Constants for HTTP protocol versions
1611 */
1612define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
1613define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
1614/**#@-*/
1615
1616if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
1617   /**
1618    * Whether string functions are overloaded by their mbstring equivalents
1619    */
1620    define('HTTP_REQUEST_MBSTRING', true);
1621} else {
1622   /**
1623    * @ignore
1624    */
1625    define('HTTP_REQUEST_MBSTRING', false);
1626}
1627
1628/**
1629 * Class for performing HTTP requests
1630 *
1631 * Simple example (fetches yahoo.com and displays it):
1632 * <code>
1633 * $a = &new HTTP_Request('http://www.yahoo.com/');
1634 * $a->sendRequest();
1635 * echo $a->getResponseBody();
1636 * </code>
1637 *
1638 * @category    HTTP
1639 * @package     HTTP_Request
1640 * @author      Richard Heyes <richard@phpguru.org>
1641 * @author      Alexey Borzov <avb@php.net>
1642 * @version     Release: 1.4.4
1643 */
1644class HTTP_Request
1645{
1646   /**#@+
1647    * @access private
1648    */
1649    /**
1650    * Instance of Net_URL
1651    * @var Net_URL
1652    */
1653    var $_url;
1654
1655    /**
1656    * Type of request
1657    * @var string
1658    */
1659    var $_method;
1660
1661    /**
1662    * HTTP Version
1663    * @var string
1664    */
1665    var $_http;
1666
1667    /**
1668    * Request headers
1669    * @var array
1670    */
1671    var $_requestHeaders;
1672
1673    /**
1674    * Basic Auth Username
1675    * @var string
1676    */
1677    var $_user;
1678
1679    /**
1680    * Basic Auth Password
1681    * @var string
1682    */
1683    var $_pass;
1684
1685    /**
1686    * Socket object
1687    * @var Net_Socket
1688    */
1689    var $_sock;
1690
1691    /**
1692    * Proxy server
1693    * @var string
1694    */
1695    var $_proxy_host;
1696
1697    /**
1698    * Proxy port
1699    * @var integer
1700    */
1701    var $_proxy_port;
1702
1703    /**
1704    * Proxy username
1705    * @var string
1706    */
1707    var $_proxy_user;
1708
1709    /**
1710    * Proxy password
1711    * @var string
1712    */
1713    var $_proxy_pass;
1714
1715    /**
1716    * Post data
1717    * @var array
1718    */
1719    var $_postData;
1720
1721   /**
1722    * Request body
1723    * @var string
1724    */
1725    var $_body;
1726
1727   /**
1728    * A list of methods that MUST NOT have a request body, per RFC 2616
1729    * @var array
1730    */
1731    var $_bodyDisallowed = array('TRACE');
1732
1733   /**
1734    * Methods having defined semantics for request body
1735    *
1736    * Content-Length header (indicating that the body follows, section 4.3 of
1737    * RFC 2616) will be sent for these methods even if no body was added
1738    *
1739    * @var array
1740    */
1741    var $_bodyRequired = array('POST', 'PUT');
1742
1743   /**
1744    * Files to post
1745    * @var array
1746    */
1747    var $_postFiles = array();
1748
1749    /**
1750    * Connection timeout.
1751    * @var float
1752    */
1753    var $_timeout;
1754
1755    /**
1756    * HTTP_Response object
1757    * @var HTTP_Response
1758    */
1759    var $_response;
1760
1761    /**
1762    * Whether to allow redirects
1763    * @var boolean
1764    */
1765    var $_allowRedirects;
1766
1767    /**
1768    * Maximum redirects allowed
1769    * @var integer
1770    */
1771    var $_maxRedirects;
1772
1773    /**
1774    * Current number of redirects
1775    * @var integer
1776    */
1777    var $_redirects;
1778
1779   /**
1780    * Whether to append brackets [] to array variables
1781    * @var bool
1782    */
1783    var $_useBrackets = true;
1784
1785   /**
1786    * Attached listeners
1787    * @var array
1788    */
1789    var $_listeners = array();
1790
1791   /**
1792    * Whether to save response body in response object property
1793    * @var bool
1794    */
1795    var $_saveBody = true;
1796
1797   /**
1798    * Timeout for reading from socket (array(seconds, microseconds))
1799    * @var array
1800    */
1801    var $_readTimeout = null;
1802
1803   /**
1804    * Options to pass to Net_Socket::connect. See stream_context_create
1805    * @var array
1806    */
1807    var $_socketOptions = null;
1808   /**#@-*/
1809
1810    /**
1811    * Constructor
1812    *
1813    * Sets up the object
1814    * @param    string  The url to fetch/access
1815    * @param    array   Associative array of parameters which can have the following keys:
1816    * <ul>
1817    *   <li>method         - Method to use, GET, POST etc (string)</li>
1818    *   <li>http           - HTTP Version to use, 1.0 or 1.1 (string)</li>
1819    *   <li>user           - Basic Auth username (string)</li>
1820    *   <li>pass           - Basic Auth password (string)</li>
1821    *   <li>proxy_host     - Proxy server host (string)</li>
1822    *   <li>proxy_port     - Proxy server port (integer)</li>
1823    *   <li>proxy_user     - Proxy auth username (string)</li>
1824    *   <li>proxy_pass     - Proxy auth password (string)</li>
1825    *   <li>timeout        - Connection timeout in seconds (float)</li>
1826    *   <li>allowRedirects - Whether to follow redirects or not (bool)</li>
1827    *   <li>maxRedirects   - Max number of redirects to follow (integer)</li>
1828    *   <li>useBrackets    - Whether to append [] to array variable names (bool)</li>
1829    *   <li>saveBody       - Whether to save response body in response object property (bool)</li>
1830    *   <li>readTimeout    - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>
1831    *   <li>socketOptions  - Options to pass to Net_Socket object (array)</li>
1832    * </ul>
1833    * @access public
1834    */
1835    function HTTP_Request($url = '', $params = array())
1836    {
1837        $this->_method         =  HTTP_REQUEST_METHOD_GET;
1838        $this->_http           =  HTTP_REQUEST_HTTP_VER_1_1;
1839        $this->_requestHeaders = array();
1840        $this->_postData       = array();
1841        $this->_body           = null;
1842
1843        $this->_user = null;
1844        $this->_pass = null;
1845
1846        $this->_proxy_host = null;
1847        $this->_proxy_port = null;
1848        $this->_proxy_user = null;
1849        $this->_proxy_pass = null;
1850
1851        $this->_allowRedirects = false;
1852        $this->_maxRedirects   = 3;
1853        $this->_redirects      = 0;
1854
1855        $this->_timeout  = null;
1856        $this->_response = null;
1857
1858        foreach ($params as $key => $value) {
1859            $this->{'_' . $key} = $value;
1860        }
1861
1862        if (!empty($url)) {
1863            $this->setURL($url);
1864        }
1865
1866        // Default useragent
1867        $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
1868
1869        // We don't do keep-alives by default
1870        $this->addHeader('Connection', 'close');
1871
1872        // Basic authentication
1873        if (!empty($this->_user)) {
1874            $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));
1875        }
1876
1877        // Proxy authentication (see bug #5913)
1878        if (!empty($this->_proxy_user)) {
1879            $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));
1880        }
1881
1882        // Use gzip encoding if possible
1883        if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) {
1884            $this->addHeader('Accept-Encoding', 'gzip');
1885        }
1886    }
1887
1888    /**
1889    * Generates a Host header for HTTP/1.1 requests
1890    *
1891    * @access private
1892    * @return string
1893    */
1894    function _generateHostHeader()
1895    {
1896        if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
1897            $host = $this->_url->host . ':' . $this->_url->port;
1898
1899        } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
1900            $host = $this->_url->host . ':' . $this->_url->port;
1901
1902        } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
1903            $host = $this->_url->host . ':' . $this->_url->port;
1904
1905        } else {
1906            $host = $this->_url->host;
1907        }
1908
1909        return $host;
1910    }
1911
1912    /**
1913    * Resets the object to its initial state (DEPRECATED).
1914    * Takes the same parameters as the constructor.
1915    *
1916    * @param  string $url    The url to be requested
1917    * @param  array  $params Associative array of parameters
1918    *                        (see constructor for details)
1919    * @access public
1920    * @deprecated deprecated since 1.2, call the constructor if this is necessary
1921    */
1922    function reset($url, $params = array())
1923    {
1924        $this->HTTP_Request($url, $params);
1925    }
1926
1927    /**
1928    * Sets the URL to be requested
1929    *
1930    * @param  string The url to be requested
1931    * @access public
1932    */
1933    function setURL($url)
1934    {
1935        $this->_url = &new Net_URL($url, $this->_useBrackets);
1936
1937        if (!empty($this->_url->user) || !empty($this->_url->pass)) {
1938            $this->setBasicAuth($this->_url->user, $this->_url->pass);
1939        }
1940
1941        if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
1942            $this->addHeader('Host', $this->_generateHostHeader());
1943        }
1944
1945        // set '/' instead of empty path rather than check later (see bug #8662)
1946        if (empty($this->_url->path)) {
1947            $this->_url->path = '/';
1948        }
1949    }
1950
1951   /**
1952    * Returns the current request URL
1953    *
1954    * @return   string  Current request URL
1955    * @access   public
1956    */
1957    function getUrl()
1958    {
1959        return empty($this->_url)? '': $this->_url->getUrl();
1960    }
1961
1962    /**
1963    * Sets a proxy to be used
1964    *
1965    * @param string     Proxy host
1966    * @param int        Proxy port
1967    * @param string     Proxy username
1968    * @param string     Proxy password
1969    * @access public
1970    */
1971    function setProxy($host, $port = 8080, $user = null, $pass = null)
1972    {
1973        $this->_proxy_host = $host;
1974        $this->_proxy_port = $port;
1975        $this->_proxy_user = $user;
1976        $this->_proxy_pass = $pass;
1977
1978        if (!empty($user)) {
1979            $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
1980        }
1981    }
1982
1983    /**
1984    * Sets basic authentication parameters
1985    *
1986    * @param string     Username
1987    * @param string     Password
1988    */
1989    function setBasicAuth($user, $pass)
1990    {
1991        $this->_user = $user;
1992        $this->_pass = $pass;
1993
1994        $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
1995    }
1996
1997    /**
1998    * Sets the method to be used, GET, POST etc.
1999    *
2000    * @param string     Method to use. Use the defined constants for this
2001    * @access public
2002    */
2003    function setMethod($method)
2004    {
2005        $this->_method = $method;
2006    }
2007
2008    /**
2009    * Sets the HTTP version to use, 1.0 or 1.1
2010    *
2011    * @param string     Version to use. Use the defined constants for this
2012    * @access public
2013    */
2014    function setHttpVer($http)
2015    {
2016        $this->_http = $http;
2017    }
2018
2019    /**
2020    * Adds a request header
2021    *
2022    * @param string     Header name
2023    * @param string     Header value
2024    * @access public
2025    */
2026    function addHeader($name, $value)
2027    {
2028        $this->_requestHeaders[strtolower($name)] = $value;
2029    }
2030
2031    /**
2032    * Removes a request header
2033    *
2034    * @param string     Header name to remove
2035    * @access public
2036    */
2037    function removeHeader($name)
2038    {
2039        if (isset($this->_requestHeaders[strtolower($name)])) {
2040            unset($this->_requestHeaders[strtolower($name)]);
2041        }
2042    }
2043
2044    /**
2045    * Adds a querystring parameter
2046    *
2047    * @param string     Querystring parameter name
2048    * @param string     Querystring parameter value
2049    * @param bool       Whether the value is already urlencoded or not, default = not
2050    * @access public
2051    */
2052    function addQueryString($name, $value, $preencoded = false)
2053    {
2054        $this->_url->addQueryString($name, $value, $preencoded);
2055    }
2056
2057    /**
2058    * Sets the querystring to literally what you supply
2059    *
2060    * @param string     The querystring data. Should be of the format foo=bar&x=y etc
2061    * @param bool       Whether data is already urlencoded or not, default = already encoded
2062    * @access public
2063    */
2064    function addRawQueryString($querystring, $preencoded = true)
2065    {
2066        $this->_url->addRawQueryString($querystring, $preencoded);
2067    }
2068
2069    /**
2070    * Adds postdata items
2071    *
2072    * @param string     Post data name
2073    * @param string     Post data value
2074    * @param bool       Whether data is already urlencoded or not, default = not
2075    * @access public
2076    */
2077    function addPostData($name, $value, $preencoded = false)
2078    {
2079        if ($preencoded) {
2080            $this->_postData[$name] = $value;
2081        } else {
2082            $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
2083        }
2084    }
2085   
2086    function addPostDataArray($array, $preencoded = false)
2087    {
2088        foreach($array as $key => $val){
2089            $this->addPostData($key, $val, $preencoded);
2090        }
2091    }   
2092
2093   /**
2094    * Recursively applies the callback function to the value
2095    *
2096    * @param    mixed   Callback function
2097    * @param    mixed   Value to process
2098    * @access   private
2099    * @return   mixed   Processed value
2100    */
2101    function _arrayMapRecursive($callback, $value)
2102    {
2103        if (!is_array($value)) {
2104            return call_user_func($callback, $value);
2105        } else {
2106            $map = array();
2107            foreach ($value as $k => $v) {
2108                $map[$k] = $this->_arrayMapRecursive($callback, $v);
2109            }
2110            return $map;
2111        }
2112    }
2113
2114   /**
2115    * Adds a file to form-based file upload
2116    *
2117    * Used to emulate file upload via a HTML form. The method also sets
2118    * Content-Type of HTTP request to 'multipart/form-data'.
2119    *
2120    * If you just want to send the contents of a file as the body of HTTP
2121    * request you should use setBody() method.
2122    *
2123    * @access public
2124    * @param  string    name of file-upload field
2125    * @param  mixed     file name(s)
2126    * @param  mixed     content-type(s) of file(s) being uploaded
2127    * @return bool      true on success
2128    * @throws PEAR_Error
2129    */
2130    function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
2131    {
2132        if (!is_array($fileName) && !is_readable($fileName)) {
2133            return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE);
2134        } elseif (is_array($fileName)) {
2135            foreach ($fileName as $name) {
2136                if (!is_readable($name)) {
2137                    return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE);
2138                }
2139            }
2140        }
2141        $this->addHeader('Content-Type', 'multipart/form-data');
2142        $this->_postFiles[$inputName] = array(
2143            'name' => $fileName,
2144            'type' => $contentType
2145        );
2146        return true;
2147    }
2148
2149    /**
2150    * Adds raw postdata (DEPRECATED)
2151    *
2152    * @param string     The data
2153    * @param bool       Whether data is preencoded or not, default = already encoded
2154    * @access public
2155    * @deprecated       deprecated since 1.3.0, method setBody() should be used instead
2156    */
2157    function addRawPostData($postdata, $preencoded = true)
2158    {
2159        $this->_body = $preencoded ? $postdata : urlencode($postdata);
2160    }
2161
2162   /**
2163    * Sets the request body (for POST, PUT and similar requests)
2164    *
2165    * @param    string  Request body
2166    * @access   public
2167    */
2168    function setBody($body)
2169    {
2170        $this->_body = $body;
2171    }
2172
2173    /**
2174    * Clears any postdata that has been added (DEPRECATED).
2175    *
2176    * Useful for multiple request scenarios.
2177    *
2178    * @access public
2179    * @deprecated deprecated since 1.2
2180    */
2181    function clearPostData()
2182    {
2183        $this->_postData = null;
2184    }
2185
2186    /**
2187    * Appends a cookie to "Cookie:" header
2188    *
2189    * @param string $name cookie name
2190    * @param string $value cookie value
2191    * @access public
2192    */
2193    function addCookie($name, $value)
2194    {
2195        $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';
2196        $this->addHeader('Cookie', $cookies . $name . '=' . $value);
2197    }
2198
2199    /**
2200    * Clears any cookies that have been added (DEPRECATED).
2201    *
2202    * Useful for multiple request scenarios
2203    *
2204    * @access public
2205    * @deprecated deprecated since 1.2
2206    */
2207    function clearCookies()
2208    {
2209        $this->removeHeader('Cookie');
2210    }
2211
2212    /**
2213    * Sends the request
2214    *
2215    * @access public
2216    * @param  bool   Whether to store response body in Response object property,
2217    *                set this to false if downloading a LARGE file and using a Listener
2218    * @return mixed  PEAR error on error, true otherwise
2219    */
2220    function sendRequest($saveBody = true)
2221    {
2222        if (!is_a($this->_url, 'Net_URL')) {
2223            return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL);
2224        }
2225
2226        $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
2227        $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
2228
2229        if (strcasecmp($this->_url->protocol, 'https') == 0) {
2230            // Bug #14127, don't try connecting to HTTPS sites without OpenSSL
2231            if (version_compare(PHP_VERSION, '4.3.0', '<') || !extension_loaded('openssl')) {
2232                return PEAR::raiseError('Need PHP 4.3.0 or later with OpenSSL support for https:// requests',
2233                                        HTTP_REQUEST_ERROR_URL);
2234            } elseif (isset($this->_proxy_host)) {
2235                return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY);
2236            }
2237            $host = 'ssl://' . $host;
2238        }
2239
2240        // magic quotes may fuck up file uploads and chunked response processing
2241        $magicQuotes = ini_get('magic_quotes_runtime');
2242        ini_set('magic_quotes_runtime', false);
2243
2244        // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
2245        // connection token to a proxy server...
2246        if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&
2247            'Keep-Alive' == $this->_requestHeaders['connection'])
2248        {
2249            $this->removeHeader('connection');
2250        }
2251
2252        $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||
2253                     (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);
2254        $sockets   = &PEAR::getStaticProperty('HTTP_Request', 'sockets');
2255        $sockKey   = $host . ':' . $port;
2256        unset($this->_sock);
2257
2258        // There is a connected socket in the "static" property?
2259        if ($keepAlive && !empty($sockets[$sockKey]) &&
2260            !empty($sockets[$sockKey]->fp))
2261        {
2262            $this->_sock =& $sockets[$sockKey];
2263            $err = null;
2264        } else {
2265            $this->_notify('connect');
2266            $this->_sock =& new Net_Socket();
2267            $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
2268        }
2269        PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
2270
2271        if (!PEAR::isError($err)) {
2272            if (!empty($this->_readTimeout)) {
2273                $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
2274            }
2275
2276            $this->_notify('sentRequest');
2277
2278            // Read the response
2279            $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);
2280            $err = $this->_response->process(
2281                $this->_saveBody && $saveBody,
2282                HTTP_REQUEST_METHOD_HEAD != $this->_method
2283            );
2284
2285            if ($keepAlive) {
2286                $keepAlive = (isset($this->_response->_headers['content-length'])
2287                              || (isset($this->_response->_headers['transfer-encoding'])
2288                                  && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));
2289                if ($keepAlive) {
2290                    if (isset($this->_response->_headers['connection'])) {
2291                        $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';
2292                    } else {
2293                        $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
2294                    }
2295                }
2296            }
2297        }
2298
2299        ini_set('magic_quotes_runtime', $magicQuotes);
2300
2301        if (PEAR::isError($err)) {
2302            return $err;
2303        }
2304
2305        if (!$keepAlive) {
2306            $this->disconnect();
2307        // Store the connected socket in "static" property
2308        } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
2309            $sockets[$sockKey] =& $this->_sock;
2310        }
2311
2312        // Check for redirection
2313        if (    $this->_allowRedirects
2314            AND $this->_redirects <= $this->_maxRedirects
2315            AND $this->getResponseCode() > 300
2316            AND $this->getResponseCode() < 399
2317            AND !empty($this->_response->_headers['location'])) {
2318
2319
2320            $redirect = $this->_response->_headers['location'];
2321
2322            // Absolute URL
2323            if (preg_match('/^https?:\/\//i', $redirect)) {
2324                $this->_url = &new Net_URL($redirect);
2325                $this->addHeader('Host', $this->_generateHostHeader());
2326            // Absolute path
2327            } elseif ($redirect{0} == '/') {
2328                $this->_url->path = $redirect;
2329
2330            // Relative path
2331            } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
2332                if (substr($this->_url->path, -1) == '/') {
2333                    $redirect = $this->_url->path . $redirect;
2334                } else {
2335                    $redirect = dirname($this->_url->path) . '/' . $redirect;
2336                }
2337                $redirect = Net_URL::resolvePath($redirect);
2338                $this->_url->path = $redirect;
2339
2340            // Filename, no path
2341            } else {
2342                if (substr($this->_url->path, -1) == '/') {
2343                    $redirect = $this->_url->path . $redirect;
2344                } else {
2345                    $redirect = dirname($this->_url->path) . '/' . $redirect;
2346                }
2347                $this->_url->path = $redirect;
2348            }
2349
2350            $this->_redirects++;
2351            return $this->sendRequest($saveBody);
2352
2353        // Too many redirects
2354        } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
2355            return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS);
2356        }
2357
2358        return true;
2359    }
2360
2361    /**
2362     * Disconnect the socket, if connected. Only useful if using Keep-Alive.
2363     *
2364     * @access public
2365     */
2366    function disconnect()
2367    {
2368        if (!empty($this->_sock) && !empty($this->_sock->fp)) {
2369            $this->_notify('disconnect');
2370            $this->_sock->disconnect();
2371        }
2372    }
2373
2374    /**
2375    * Returns the response code
2376    *
2377    * @access public
2378    * @return mixed     Response code, false if not set
2379    */
2380    function getResponseCode()
2381    {
2382        return isset($this->_response->_code) ? $this->_response->_code : false;
2383    }
2384
2385    /**
2386    * Returns the response reason phrase
2387    *
2388    * @access public
2389    * @return mixed     Response reason phrase, false if not set
2390    */
2391    function getResponseReason()
2392    {
2393        return isset($this->_response->_reason) ? $this->_response->_reason : false;
2394    }
2395
2396    /**
2397    * Returns either the named header or all if no name given
2398    *
2399    * @access public
2400    * @param string     The header name to return, do not set to get all headers
2401    * @return mixed     either the value of $headername (false if header is not present)
2402    *                   or an array of all headers
2403    */
2404    function getResponseHeader($headername = null)
2405    {
2406        if (!isset($headername)) {
2407            return isset($this->_response->_headers)? $this->_response->_headers: array();
2408        } else {
2409            $headername = strtolower($headername);
2410            return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
2411        }
2412    }
2413
2414    /**
2415    * Returns the body of the response
2416    *
2417    * @access public
2418    * @return mixed     response body, false if not set
2419    */
2420    function getResponseBody()
2421    {
2422        return isset($this->_response->_body) ? $this->_response->_body : false;
2423    }
2424
2425    /**
2426    * Returns cookies set in response
2427    *
2428    * @access public
2429    * @return mixed     array of response cookies, false if none are present
2430    */
2431    function getResponseCookies()
2432    {
2433        return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
2434    }
2435
2436    /**
2437    * Builds the request string
2438    *
2439    * @access private
2440    * @return string The request string
2441    */
2442    function _buildRequest()
2443    {
2444        $separator = ini_get('arg_separator.output');
2445        ini_set('arg_separator.output', '&');
2446        $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
2447        ini_set('arg_separator.output', $separator);
2448
2449        $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
2450        $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
2451        $path = $this->_url->path . $querystring;
2452        $url  = $host . $port . $path;
2453
2454        if (!strlen($url)) {
2455            $url = '/';
2456        }
2457
2458        $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
2459
2460        if (in_array($this->_method, $this->_bodyDisallowed) ||
2461            (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||
2462             (empty($this->_postData) && empty($this->_postFiles)))))
2463        {
2464            $this->removeHeader('Content-Type');
2465        } else {
2466            if (empty($this->_requestHeaders['content-type'])) {
2467                // Add default content-type
2468                $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
2469            } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {
2470                $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
2471                $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
2472            }
2473        }
2474
2475        // Request Headers
2476        if (!empty($this->_requestHeaders)) {
2477            foreach ($this->_requestHeaders as $name => $value) {
2478                $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
2479                $request      .= $canonicalName . ': ' . $value . "\r\n";
2480            }
2481        }
2482
2483        // Method does not allow a body, simply add a final CRLF
2484        if (in_array($this->_method, $this->_bodyDisallowed)) {
2485
2486            $request .= "\r\n";
2487
2488        // Post data if it's an array
2489        } elseif (HTTP_REQUEST_METHOD_POST == $this->_method &&
2490                  (!empty($this->_postData) || !empty($this->_postFiles))) {
2491
2492            // "normal" POST request
2493            if (!isset($boundary)) {
2494                $postdata = implode('&', array_map(
2495                    create_function('$a', 'return $a[0] . \'=\' . $a[1];'),
2496                    $this->_flattenArray('', $this->_postData)
2497                ));
2498
2499            // multipart request, probably with file uploads
2500            } else {
2501                $postdata = '';
2502                if (!empty($this->_postData)) {
2503                    $flatData = $this->_flattenArray('', $this->_postData);
2504                    foreach ($flatData as $item) {
2505                        $postdata .= '--' . $boundary . "\r\n";
2506                        $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
2507                        $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
2508                    }
2509                }
2510                foreach ($this->_postFiles as $name => $value) {
2511                    if (is_array($value['name'])) {
2512                        $varname       = $name . ($this->_useBrackets? '[]': '');
2513                    } else {
2514                        $varname       = $name;
2515                        $value['name'] = array($value['name']);
2516                    }
2517                    foreach ($value['name'] as $key => $filename) {
2518                        $fp       = fopen($filename, 'r');
2519                        $basename = basename($filename);
2520                        $type     = is_array($value['type'])? @$value['type'][$key]: $value['type'];
2521
2522                        $postdata .= '--' . $boundary . "\r\n";
2523                        $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
2524                        $postdata .= "\r\nContent-Type: " . $type;
2525                        $postdata .= "\r\n\r\n" . fread($fp, filesize($filename)) . "\r\n";
2526                        fclose($fp);
2527                    }
2528                }
2529                $postdata .= '--' . $boundary . "--\r\n";
2530            }
2531            $request .= 'Content-Length: ' .
2532                        (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) .
2533                        "\r\n\r\n";
2534            $request .= $postdata;
2535
2536        // Explicitly set request body
2537        } elseif (0 < strlen($this->_body)) {
2538
2539            $request .= 'Content-Length: ' .
2540                        (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) .
2541                        "\r\n\r\n";
2542            $request .= $this->_body;
2543
2544        // No body: send a Content-Length header nonetheless (request #12900),
2545        // but do that only for methods that require a body (bug #14740)
2546        } else {
2547
2548            if (in_array($this->_method, $this->_bodyRequired)) {
2549                $request .= "Content-Length: 0\r\n";
2550            }
2551            $request .= "\r\n";
2552        }
2553
2554        return $request;
2555    }
2556
2557   /**
2558    * Helper function to change the (probably multidimensional) associative array
2559    * into the simple one.
2560    *
2561    * @param    string  name for item
2562    * @param    mixed   item's values
2563    * @return   array   array with the following items: array('item name', 'item value');
2564    * @access   private
2565    */
2566    function _flattenArray($name, $values)
2567    {
2568        if (!is_array($values)) {
2569            return array(array($name, $values));
2570        } else {
2571            $ret = array();
2572            foreach ($values as $k => $v) {
2573                if (empty($name)) {
2574                    $newName = $k;
2575                } elseif ($this->_useBrackets) {
2576                    $newName = $name . '[' . $k . ']';
2577                } else {
2578                    $newName = $name;
2579                }
2580                $ret = array_merge($ret, $this->_flattenArray($newName, $v));
2581            }
2582            return $ret;
2583        }
2584    }
2585
2586
2587   /**
2588    * Adds a Listener to the list of listeners that are notified of
2589    * the object's events
2590    *
2591    * Events sent by HTTP_Request object
2592    * - 'connect': on connection to server
2593    * - 'sentRequest': after the request was sent
2594    * - 'disconnect': on disconnection from server
2595    *
2596    * Events sent by HTTP_Response object
2597    * - 'gotHeaders': after receiving response headers (headers are passed in $data)
2598    * - 'tick': on receiving a part of response body (the part is passed in $data)
2599    * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)
2600    * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)
2601    *
2602    * @param    HTTP_Request_Listener   listener to attach
2603    * @return   boolean                 whether the listener was successfully attached
2604    * @access   public
2605    */
2606    function attach(&$listener)
2607    {
2608        if (!is_a($listener, 'HTTP_Request_Listener')) {
2609            return false;
2610        }
2611        $this->_listeners[$listener->getId()] =& $listener;
2612        return true;
2613    }
2614
2615
2616   /**
2617    * Removes a Listener from the list of listeners
2618    *
2619    * @param    HTTP_Request_Listener   listener to detach
2620    * @return   boolean                 whether the listener was successfully detached
2621    * @access   public
2622    */
2623    function detach(&$listener)
2624    {
2625        if (!is_a($listener, 'HTTP_Request_Listener') ||
2626            !isset($this->_listeners[$listener->getId()])) {
2627            return false;
2628        }
2629        unset($this->_listeners[$listener->getId()]);
2630        return true;
2631    }
2632
2633
2634   /**
2635    * Notifies all registered listeners of an event.
2636    *
2637    * @param    string  Event name
2638    * @param    mixed   Additional data
2639    * @access   private
2640    * @see      HTTP_Request::attach()
2641    */
2642    function _notify($event, $data = null)
2643    {
2644        foreach (array_keys($this->_listeners) as $id) {
2645            $this->_listeners[$id]->update($this, $event, $data);
2646        }
2647    }
2648}
2649
2650
2651/**
2652 * Response class to complement the Request class
2653 *
2654 * @category    HTTP
2655 * @package     HTTP_Request
2656 * @author      Richard Heyes <richard@phpguru.org>
2657 * @author      Alexey Borzov <avb@php.net>
2658 * @version     Release: 1.4.4
2659 */
2660class HTTP_Response
2661{
2662    /**
2663    * Socket object
2664    * @var Net_Socket
2665    */
2666    var $_sock;
2667
2668    /**
2669    * Protocol
2670    * @var string
2671    */
2672    var $_protocol;
2673
2674    /**
2675    * Return code
2676    * @var string
2677    */
2678    var $_code;
2679
2680    /**
2681    * Response reason phrase
2682    * @var string
2683    */
2684    var $_reason;
2685
2686    /**
2687    * Response headers
2688    * @var array
2689    */
2690    var $_headers;
2691
2692    /**
2693    * Cookies set in response
2694    * @var array
2695    */
2696    var $_cookies;
2697
2698    /**
2699    * Response body
2700    * @var string
2701    */
2702    var $_body = '';
2703
2704   /**
2705    * Used by _readChunked(): remaining length of the current chunk
2706    * @var string
2707    */
2708    var $_chunkLength = 0;
2709
2710   /**
2711    * Attached listeners
2712    * @var array
2713    */
2714    var $_listeners = array();
2715
2716   /**
2717    * Bytes left to read from message-body
2718    * @var null|int
2719    */
2720    var $_toRead;
2721
2722    /**
2723    * Constructor
2724    *
2725    * @param  Net_Socket    socket to read the response from
2726    * @param  array         listeners attached to request
2727    */
2728    function HTTP_Response(&$sock, &$listeners)
2729    {
2730        $this->_sock      =& $sock;
2731        $this->_listeners =& $listeners;
2732    }
2733
2734
2735   /**
2736    * Processes a HTTP response
2737    *
2738    * This extracts response code, headers, cookies and decodes body if it
2739    * was encoded in some way
2740    *
2741    * @access public
2742    * @param  bool      Whether to store response body in object property, set
2743    *                   this to false if downloading a LARGE file and using a Listener.
2744    *                   This is assumed to be true if body is gzip-encoded.
2745    * @param  bool      Whether the response can actually have a message-body.
2746    *                   Will be set to false for HEAD requests.
2747    * @throws PEAR_Error
2748    * @return mixed     true on success, PEAR_Error in case of malformed response
2749    */
2750    function process($saveBody = true, $canHaveBody = true)
2751    {
2752        do {
2753            $line = $this->_sock->readLine();
2754            if (!preg_match('!^(HTTP/\d\.\d) (\d{3})(?: (.+))?!', $line, $s)) {
2755                return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE);
2756            } else {
2757                $this->_protocol = $s[1];
2758                $this->_code     = intval($s[2]);
2759                $this->_reason   = empty($s[3])? null: $s[3];
2760            }
2761            while ('' !== ($header = $this->_sock->readLine())) {
2762                $this->_processHeader($header);
2763            }
2764        } while (100 == $this->_code);
2765
2766        $this->_notify('gotHeaders', $this->_headers);
2767
2768        // RFC 2616, section 4.4:
2769        // 1. Any response message which "MUST NOT" include a message-body ...
2770        // is always terminated by the first empty line after the header fields
2771        // 3. ... If a message is received with both a
2772        // Transfer-Encoding header field and a Content-Length header field,
2773        // the latter MUST be ignored.
2774        $canHaveBody = $canHaveBody && $this->_code >= 200 &&
2775                       $this->_code != 204 && $this->_code != 304;
2776
2777        // If response body is present, read it and decode
2778        $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
2779        $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
2780        $hasBody = false;
2781        if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) ||
2782                0 != $this->_headers['content-length']))
2783        {
2784            if ($chunked || !isset($this->_headers['content-length'])) {
2785                $this->_toRead = null;
2786            } else {
2787                $this->_toRead = $this->_headers['content-length'];
2788            }
2789            while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
2790                if ($chunked) {
2791                    $data = $this->_readChunked();
2792                } elseif (is_null($this->_toRead)) {
2793                    $data = $this->_sock->read(4096);
2794                } else {
2795                    $data = $this->_sock->read(min(4096, $this->_toRead));
2796                    $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
2797                }
2798                if ('' == $data && (!$this->_chunkLength || $this->_sock->eof())) {
2799                    break;
2800                } else {
2801                    $hasBody = true;
2802                    if ($saveBody || $gzipped) {
2803                        $this->_body .= $data;
2804                    }
2805                    $this->_notify($gzipped? 'gzTick': 'tick', $data);
2806                }
2807            }
2808        }
2809
2810        if ($hasBody) {
2811            // Uncompress the body if needed
2812            if ($gzipped) {
2813                $body = $this->_decodeGzip($this->_body);
2814                if (PEAR::isError($body)) {
2815                    return $body;
2816                }
2817                $this->_body = $body;
2818                $this->_notify('gotBody', $this->_body);
2819            } else {
2820                $this->_notify('gotBody');
2821            }
2822        }
2823        return true;
2824    }
2825
2826
2827   /**
2828    * Processes the response header
2829    *
2830    * @access private
2831    * @param  string    HTTP header
2832    */
2833    function _processHeader($header)
2834    {
2835        if (false === strpos($header, ':')) {
2836            return;
2837        }
2838        list($headername, $headervalue) = explode(':', $header, 2);
2839        $headername  = strtolower($headername);
2840        $headervalue = ltrim($headervalue);
2841
2842        if ('set-cookie' != $headername) {
2843            if (isset($this->_headers[$headername])) {
2844                $this->_headers[$headername] .= ',' . $headervalue;
2845            } else {
2846                $this->_headers[$headername]  = $headervalue;
2847            }
2848        } else {
2849            $this->_parseCookie($headervalue);
2850        }
2851    }
2852
2853
2854   /**
2855    * Parse a Set-Cookie header to fill $_cookies array
2856    *
2857    * @access private
2858    * @param  string    value of Set-Cookie header
2859    */
2860    function _parseCookie($headervalue)
2861    {
2862        $cookie = array(
2863            'expires' => null,
2864            'domain'  => null,
2865            'path'    => null,
2866            'secure'  => false
2867        );
2868
2869        // Only a name=value pair
2870        if (!strpos($headervalue, ';')) {
2871            $pos = strpos($headervalue, '=');
2872            $cookie['name']  = trim(substr($headervalue, 0, $pos));
2873            $cookie['value'] = trim(substr($headervalue, $pos + 1));
2874
2875        // Some optional parameters are supplied
2876        } else {
2877            $elements = explode(';', $headervalue);
2878            $pos = strpos($elements[0], '=');
2879            $cookie['name']  = trim(substr($elements[0], 0, $pos));
2880            $cookie['value'] = trim(substr($elements[0], $pos + 1));
2881
2882            for ($i = 1; $i < count($elements); $i++) {
2883                if (false === strpos($elements[$i], '=')) {
2884                    $elName  = trim($elements[$i]);
2885                    $elValue = null;
2886                } else {
2887                    list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
2888                }
2889                $elName = strtolower($elName);
2890                if ('secure' == $elName) {
2891                    $cookie['secure'] = true;
2892                } elseif ('expires' == $elName) {
2893                    $cookie['expires'] = str_replace('"', '', $elValue);
2894                } elseif ('path' == $elName || 'domain' == $elName) {
2895                    $cookie[$elName] = urldecode($elValue);
2896                } else {
2897                    $cookie[$elName] = $elValue;
2898                }
2899            }
2900        }
2901        $this->_cookies[] = $cookie;
2902    }
2903
2904
2905   /**
2906    * Read a part of response body encoded with chunked Transfer-Encoding
2907    *
2908    * @access private
2909    * @return string
2910    */
2911    function _readChunked()
2912    {
2913        // at start of the next chunk?
2914        if (0 == $this->_chunkLength) {
2915            $line = $this->_sock->readLine();
2916            if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
2917                $this->_chunkLength = hexdec($matches[1]);
2918                // Chunk with zero length indicates the end
2919                if (0 == $this->_chunkLength) {
2920                    $this->_sock->readLine(); // make this an eof()
2921                    return '';
2922                }
2923            } else {
2924                return '';
2925            }
2926        }
2927        $data = $this->_sock->read($this->_chunkLength);
2928        $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
2929        if (0 == $this->_chunkLength) {
2930            $this->_sock->readLine(); // Trailing CRLF
2931        }
2932        return $data;
2933    }
2934
2935
2936   /**
2937    * Notifies all registered listeners of an event.
2938    *
2939    * @param    string  Event name
2940    * @param    mixed   Additional data
2941    * @access   private
2942    * @see HTTP_Request::_notify()
2943    */
2944    function _notify($event, $data = null)
2945    {
2946        foreach (array_keys($this->_listeners) as $id) {
2947            $this->_listeners[$id]->update($this, $event, $data);
2948        }
2949    }
2950
2951
2952   /**
2953    * Decodes the message-body encoded by gzip
2954    *
2955    * The real decoding work is done by gzinflate() built-in function, this
2956    * method only parses the header and checks data for compliance with
2957    * RFC 1952
2958    *
2959    * @access   private
2960    * @param    string  gzip-encoded data
2961    * @return   string  decoded data
2962    */
2963    function _decodeGzip($data)
2964    {
2965        if (HTTP_REQUEST_MBSTRING) {
2966            $oldEncoding = mb_internal_encoding();
2967            mb_internal_encoding('iso-8859-1');
2968        }
2969        $length = strlen($data);
2970        // If it doesn't look like gzip-encoded data, don't bother
2971        if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
2972            return $data;
2973        }
2974        $method = ord(substr($data, 2, 1));
2975        if (8 != $method) {
2976            return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD);
2977        }
2978        $flags = ord(substr($data, 3, 1));
2979        if ($flags & 224) {
2980            return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA);
2981        }
2982
2983        // header is 10 bytes minimum. may be longer, though.
2984        $headerLength = 10;
2985        // extra fields, need to skip 'em
2986        if ($flags & 4) {
2987            if ($length - $headerLength - 2 < 8) {
2988                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
2989            }
2990            $extraLength = unpack('v', substr($data, 10, 2));
2991            if ($length - $headerLength - 2 - $extraLength[1] < 8) {
2992                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
2993            }
2994            $headerLength += $extraLength[1] + 2;
2995        }
2996        // file name, need to skip that
2997        if ($flags & 8) {
2998            if ($length - $headerLength - 1 < 8) {
2999                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
3000            }
3001            $filenameLength = strpos(substr($data, $headerLength), chr(0));
3002            if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
3003                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
3004            }
3005            $headerLength += $filenameLength + 1;
3006        }
3007        // comment, need to skip that also
3008        if ($flags & 16) {
3009            if ($length - $headerLength - 1 < 8) {
3010                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
3011            }
3012            $commentLength = strpos(substr($data, $headerLength), chr(0));
3013            if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
3014                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
3015            }
3016            $headerLength += $commentLength + 1;
3017        }
3018        // have a CRC for header. let's check
3019        if ($flags & 1) {
3020            if ($length - $headerLength - 2 < 8) {
3021                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
3022            }
3023            $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));
3024            $crcStored = unpack('v', substr($data, $headerLength, 2));
3025            if ($crcReal != $crcStored[1]) {
3026                return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
3027            }
3028            $headerLength += 2;
3029        }
3030        // unpacked data CRC and size at the end of encoded data
3031        $tmp = unpack('V2', substr($data, -8));
3032        $dataCrc  = $tmp[1];
3033        $dataSize = $tmp[2];
3034
3035        // finally, call the gzinflate() function
3036        // don't pass $dataSize to gzinflate, see bugs #13135, #14370
3037        $unpacked = gzinflate(substr($data, $headerLength, -8));
3038        if (false === $unpacked) {
3039            return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ);
3040        } elseif ($dataSize != strlen($unpacked)) {
3041            return PEAR::raiseError('_decodeGzip(): data size check failed', HTTP_REQUEST_ERROR_GZIP_READ);
3042        } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
3043            return PEAR::raiseError('_decodeGzip(): data CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
3044        }
3045        if (HTTP_REQUEST_MBSTRING) {
3046            mb_internal_encoding($oldEncoding);
3047        }
3048        return $unpacked;
3049    }
3050} // End class HTTP_Response
3051?>
3052>>>>>>> .merge-right.r23124
Note: See TracBrowser for help on using the repository browser.