Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * Class for performing HTTP requests
4
 *
5
 * PHP versions 4 and 5
6
 *
7
 * LICENSE:
8
 *
9
 * Copyright (c) 2002-2007, Richard Heyes
10
 * All rights reserved.
11
 *
12
 * Redistribution and use in source and binary forms, with or without
13
 * modification, are permitted provided that the following conditions
14
 * are met:
15
 *
16
 * o Redistributions of source code must retain the above copyright
17
 *   notice, this list of conditions and the following disclaimer.
18
 * o Redistributions in binary form must reproduce the above copyright
19
 *   notice, this list of conditions and the following disclaimer in the
20
 *   documentation and/or other materials provided with the distribution.
21
 * o The names of the authors may not be used to endorse or promote
22
 *   products derived from this software without specific prior written
23
 *   permission.
24
 *
25
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36
 *
37
 * @category    HTTP
38
 * @package     HTTP_Request
39
 * @author      Richard Heyes <richard@phpguru.org>
40
 * @author      Alexey Borzov <avb@php.net>
41
 * @copyright   2002-2007 Richard Heyes
42
 * @license     http://opensource.org/licenses/bsd-license.php New BSD License
43
 * @version     CVS: $Id: Request.php,v 1.63 2008/10/11 11:07:10 avb Exp $
44
 * @link        http://pear.php.net/package/HTTP_Request/
45
 */
46
 
47
/**
48
 * PEAR and PEAR_Error classes (for error handling)
49
 */
50
require_once 'PEAR.php';
51
/**
52
 * Socket class
53
 */
54
require_once 'Net/Socket.php';
55
/**
56
 * URL handling class
57
 */
58
require_once 'Net/URL.php';
59
 
60
/**#@+
61
 * Constants for HTTP request methods
62
 */
63
define('HTTP_REQUEST_METHOD_GET',     'GET',     true);
64
define('HTTP_REQUEST_METHOD_HEAD',    'HEAD',    true);
65
define('HTTP_REQUEST_METHOD_POST',    'POST',    true);
66
define('HTTP_REQUEST_METHOD_PUT',     'PUT',     true);
67
define('HTTP_REQUEST_METHOD_DELETE',  'DELETE',  true);
68
define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
69
define('HTTP_REQUEST_METHOD_TRACE',   'TRACE',   true);
70
/**#@-*/
71
 
72
/**#@+
73
 * Constants for HTTP request error codes
74
 */
75
define('HTTP_REQUEST_ERROR_FILE',             1);
76
define('HTTP_REQUEST_ERROR_URL',              2);
77
define('HTTP_REQUEST_ERROR_PROXY',            4);
78
define('HTTP_REQUEST_ERROR_REDIRECTS',        8);
79
define('HTTP_REQUEST_ERROR_RESPONSE',        16);
80
define('HTTP_REQUEST_ERROR_GZIP_METHOD',     32);
81
define('HTTP_REQUEST_ERROR_GZIP_READ',       64);
82
define('HTTP_REQUEST_ERROR_GZIP_DATA',      128);
83
define('HTTP_REQUEST_ERROR_GZIP_CRC',       256);
84
/**#@-*/
85
 
86
/**#@+
87
 * Constants for HTTP protocol versions
88
 */
89
define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
90
define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
91
/**#@-*/
92
 
93
if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
94
   /**
95
    * Whether string functions are overloaded by their mbstring equivalents
96
    */
97
    define('HTTP_REQUEST_MBSTRING', true);
98
} else {
99
   /**
100
    * @ignore
101
    */
102
    define('HTTP_REQUEST_MBSTRING', false);
103
}
104
 
105
/**
106
 * Class for performing HTTP requests
107
 *
108
 * Simple example (fetches yahoo.com and displays it):
109
 * <code>
110
 * $a = &new HTTP_Request('http://www.yahoo.com/');
111
 * $a->sendRequest();
112
 * echo $a->getResponseBody();
113
 * </code>
114
 *
115
 * @category    HTTP
116
 * @package     HTTP_Request
117
 * @author      Richard Heyes <richard@phpguru.org>
118
 * @author      Alexey Borzov <avb@php.net>
119
 * @version     Release: 1.4.4
120
 */
121
class HTTP_Request
122
{
123
   /**#@+
124
    * @access private
125
    */
126
    /**
127
    * Instance of Net_URL
128
    * @var Net_URL
129
    */
130
    var $_url;
131
 
132
    /**
133
    * Type of request
134
    * @var string
135
    */
136
    var $_method;
137
 
138
    /**
139
    * HTTP Version
140
    * @var string
141
    */
142
    var $_http;
143
 
144
    /**
145
    * Request headers
146
    * @var array
147
    */
148
    var $_requestHeaders;
149
 
150
    /**
151
    * Basic Auth Username
152
    * @var string
153
    */
154
    var $_user;
155
 
156
    /**
157
    * Basic Auth Password
158
    * @var string
159
    */
160
    var $_pass;
161
 
162
    /**
163
    * Socket object
164
    * @var Net_Socket
165
    */
166
    var $_sock;
167
 
168
    /**
169
    * Proxy server
170
    * @var string
171
    */
172
    var $_proxy_host;
173
 
174
    /**
175
    * Proxy port
176
    * @var integer
177
    */
178
    var $_proxy_port;
179
 
180
    /**
181
    * Proxy username
182
    * @var string
183
    */
184
    var $_proxy_user;
185
 
186
    /**
187
    * Proxy password
188
    * @var string
189
    */
190
    var $_proxy_pass;
191
 
192
    /**
193
    * Post data
194
    * @var array
195
    */
196
    var $_postData;
197
 
198
   /**
199
    * Request body
200
    * @var string
201
    */
202
    var $_body;
203
 
204
   /**
205
    * A list of methods that MUST NOT have a request body, per RFC 2616
206
    * @var array
207
    */
208
    var $_bodyDisallowed = array('TRACE');
209
 
210
   /**
211
    * Methods having defined semantics for request body
212
    *
213
    * Content-Length header (indicating that the body follows, section 4.3 of
214
    * RFC 2616) will be sent for these methods even if no body was added
215
    *
216
    * @var array
217
    */
218
    var $_bodyRequired = array('POST', 'PUT');
219
 
220
   /**
221
    * Files to post
222
    * @var array
223
    */
224
    var $_postFiles = array();
225
 
226
    /**
227
    * Connection timeout.
228
    * @var float
229
    */
230
    var $_timeout;
231
 
232
    /**
233
    * HTTP_Response object
234
    * @var HTTP_Response
235
    */
236
    var $_response;
237
 
238
    /**
239
    * Whether to allow redirects
240
    * @var boolean
241
    */
242
    var $_allowRedirects;
243
 
244
    /**
245
    * Maximum redirects allowed
246
    * @var integer
247
    */
248
    var $_maxRedirects;
249
 
250
    /**
251
    * Current number of redirects
252
    * @var integer
253
    */
254
    var $_redirects;
255
 
256
   /**
257
    * Whether to append brackets [] to array variables
258
    * @var bool
259
    */
260
    var $_useBrackets = true;
261
 
262
   /**
263
    * Attached listeners
264
    * @var array
265
    */
266
    var $_listeners = array();
267
 
268
   /**
269
    * Whether to save response body in response object property
270
    * @var bool
271
    */
272
    var $_saveBody = true;
273
 
274
   /**
275
    * Timeout for reading from socket (array(seconds, microseconds))
276
    * @var array
277
    */
278
    var $_readTimeout = null;
279
 
280
   /**
281
    * Options to pass to Net_Socket::connect. See stream_context_create
282
    * @var array
283
    */
284
    var $_socketOptions = null;
285
   /**#@-*/
286
 
287
    /**
288
    * Constructor
289
    *
290
    * Sets up the object
291
    * @param    string  The url to fetch/access
292
    * @param    array   Associative array of parameters which can have the following keys:
293
    * <ul>
294
    *   <li>method         - Method to use, GET, POST etc (string)</li>
295
    *   <li>http           - HTTP Version to use, 1.0 or 1.1 (string)</li>
296
    *   <li>user           - Basic Auth username (string)</li>
297
    *   <li>pass           - Basic Auth password (string)</li>
298
    *   <li>proxy_host     - Proxy server host (string)</li>
299
    *   <li>proxy_port     - Proxy server port (integer)</li>
300
    *   <li>proxy_user     - Proxy auth username (string)</li>
301
    *   <li>proxy_pass     - Proxy auth password (string)</li>
302
    *   <li>timeout        - Connection timeout in seconds (float)</li>
303
    *   <li>allowRedirects - Whether to follow redirects or not (bool)</li>
304
    *   <li>maxRedirects   - Max number of redirects to follow (integer)</li>
305
    *   <li>useBrackets    - Whether to append [] to array variable names (bool)</li>
306
    *   <li>saveBody       - Whether to save response body in response object property (bool)</li>
307
    *   <li>readTimeout    - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>
308
    *   <li>socketOptions  - Options to pass to Net_Socket object (array)</li>
309
    * </ul>
310
    * @access public
311
    */
312
    function HTTP_Request($url = '', $params = array())
313
    {
314
        $this->_method         =  HTTP_REQUEST_METHOD_GET;
315
        $this->_http           =  HTTP_REQUEST_HTTP_VER_1_1;
316
        $this->_requestHeaders = array();
317
        $this->_postData       = array();
318
        $this->_body           = null;
319
 
320
        $this->_user = null;
321
        $this->_pass = null;
322
 
323
        $this->_proxy_host = null;
324
        $this->_proxy_port = null;
325
        $this->_proxy_user = null;
326
        $this->_proxy_pass = null;
327
 
328
        $this->_allowRedirects = false;
329
        $this->_maxRedirects   = 3;
330
        $this->_redirects      = 0;
331
 
332
        $this->_timeout  = null;
333
        $this->_response = null;
334
 
335
        foreach ($params as $key => $value) {
336
            $this->{'_' . $key} = $value;
337
        }
338
 
339
        if (!empty($url)) {
340
            $this->setURL($url);
341
        }
342
 
343
        // Default useragent
344
        $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
345
 
346
        // We don't do keep-alives by default
347
        $this->addHeader('Connection', 'close');
348
 
349
        // Basic authentication
350
        if (!empty($this->_user)) {
351
            $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));
352
        }
353
 
354
        // Proxy authentication (see bug #5913)
355
        if (!empty($this->_proxy_user)) {
356
            $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));
357
        }
358
 
359
        // Use gzip encoding if possible
360
        if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) {
361
            $this->addHeader('Accept-Encoding', 'gzip');
362
        }
363
    }
364
 
365
    /**
366
    * Generates a Host header for HTTP/1.1 requests
367
    *
368
    * @access private
369
    * @return string
370
    */
371
    function _generateHostHeader()
372
    {
373
        if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
374
            $host = $this->_url->host . ':' . $this->_url->port;
375
 
376
        } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
377
            $host = $this->_url->host . ':' . $this->_url->port;
378
 
379
        } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
380
            $host = $this->_url->host . ':' . $this->_url->port;
381
 
382
        } else {
383
            $host = $this->_url->host;
384
        }
385
 
386
        return $host;
387
    }
388
 
389
    /**
390
    * Resets the object to its initial state (DEPRECATED).
391
    * Takes the same parameters as the constructor.
392
    *
393
    * @param  string $url    The url to be requested
394
    * @param  array  $params Associative array of parameters
395
    *                        (see constructor for details)
396
    * @access public
397
    * @deprecated deprecated since 1.2, call the constructor if this is necessary
398
    */
399
    function reset($url, $params = array())
400
    {
401
        $this->HTTP_Request($url, $params);
402
    }
403
 
404
    /**
405
    * Sets the URL to be requested
406
    *
407
    * @param  string The url to be requested
408
    * @access public
409
    */
410
    function setURL($url)
411
    {
412
        $this->_url = &new Net_URL($url, $this->_useBrackets);
413
 
414
        if (!empty($this->_url->user) || !empty($this->_url->pass)) {
415
            $this->setBasicAuth($this->_url->user, $this->_url->pass);
416
        }
417
 
418
        if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
419
            $this->addHeader('Host', $this->_generateHostHeader());
420
        }
421
 
422
        // set '/' instead of empty path rather than check later (see bug #8662)
423
        if (empty($this->_url->path)) {
424
            $this->_url->path = '/';
425
        }
426
    }
427
 
428
   /**
429
    * Returns the current request URL
430
    *
431
    * @return   string  Current request URL
432
    * @access   public
433
    */
434
    function getUrl()
435
    {
436
        return empty($this->_url)? '': $this->_url->getUrl();
437
    }
438
 
439
    /**
440
    * Sets a proxy to be used
441
    *
442
    * @param string     Proxy host
443
    * @param int        Proxy port
444
    * @param string     Proxy username
445
    * @param string     Proxy password
446
    * @access public
447
    */
448
    function setProxy($host, $port = 8080, $user = null, $pass = null)
449
    {
450
        $this->_proxy_host = $host;
451
        $this->_proxy_port = $port;
452
        $this->_proxy_user = $user;
453
        $this->_proxy_pass = $pass;
454
 
455
        if (!empty($user)) {
456
            $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
457
        }
458
    }
459
 
460
    /**
461
    * Sets basic authentication parameters
462
    *
463
    * @param string     Username
464
    * @param string     Password
465
    */
466
    function setBasicAuth($user, $pass)
467
    {
468
        $this->_user = $user;
469
        $this->_pass = $pass;
470
 
471
        $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
472
    }
473
 
474
    /**
475
    * Sets the method to be used, GET, POST etc.
476
    *
477
    * @param string     Method to use. Use the defined constants for this
478
    * @access public
479
    */
480
    function setMethod($method)
481
    {
482
        $this->_method = $method;
483
    }
484
 
485
    /**
486
    * Sets the HTTP version to use, 1.0 or 1.1
487
    *
488
    * @param string     Version to use. Use the defined constants for this
489
    * @access public
490
    */
491
    function setHttpVer($http)
492
    {
493
        $this->_http = $http;
494
    }
495
 
496
    /**
497
    * Adds a request header
498
    *
499
    * @param string     Header name
500
    * @param string     Header value
501
    * @access public
502
    */
503
    function addHeader($name, $value)
504
    {
505
        $this->_requestHeaders[strtolower($name)] = $value;
506
    }
507
 
508
    /**
509
    * Removes a request header
510
    *
511
    * @param string     Header name to remove
512
    * @access public
513
    */
514
    function removeHeader($name)
515
    {
516
        if (isset($this->_requestHeaders[strtolower($name)])) {
517
            unset($this->_requestHeaders[strtolower($name)]);
518
        }
519
    }
520
 
521
    /**
522
    * Adds a querystring parameter
523
    *
524
    * @param string     Querystring parameter name
525
    * @param string     Querystring parameter value
526
    * @param bool       Whether the value is already urlencoded or not, default = not
527
    * @access public
528
    */
529
    function addQueryString($name, $value, $preencoded = false)
530
    {
531
        $this->_url->addQueryString($name, $value, $preencoded);
532
    }
533
 
534
    /**
535
    * Sets the querystring to literally what you supply
536
    *
537
    * @param string     The querystring data. Should be of the format foo=bar&x=y etc
538
    * @param bool       Whether data is already urlencoded or not, default = already encoded
539
    * @access public
540
    */
541
    function addRawQueryString($querystring, $preencoded = true)
542
    {
543
        $this->_url->addRawQueryString($querystring, $preencoded);
544
    }
545
 
546
    /**
547
    * Adds postdata items
548
    *
549
    * @param string     Post data name
550
    * @param string     Post data value
551
    * @param bool       Whether data is already urlencoded or not, default = not
552
    * @access public
553
    */
554
    function addPostData($name, $value, $preencoded = false)
555
    {
556
        if ($preencoded) {
557
            $this->_postData[$name] = $value;
558
        } else {
559
            $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
560
        }
561
    }
562
 
563
   /**
564
    * Recursively applies the callback function to the value
565
    *
566
    * @param    mixed   Callback function
567
    * @param    mixed   Value to process
568
    * @access   private
569
    * @return   mixed   Processed value
570
    */
571
    function _arrayMapRecursive($callback, $value)
572
    {
573
        if (!is_array($value)) {
574
            return call_user_func($callback, $value);
575
        } else {
576
            $map = array();
577
            foreach ($value as $k => $v) {
578
                $map[$k] = $this->_arrayMapRecursive($callback, $v);
579
            }
580
            return $map;
581
        }
582
    }
583
 
584
   /**
585
    * Adds a file to form-based file upload
586
    *
587
    * Used to emulate file upload via a HTML form. The method also sets
588
    * Content-Type of HTTP request to 'multipart/form-data'.
589
    *
590
    * If you just want to send the contents of a file as the body of HTTP
591
    * request you should use setBody() method.
592
    *
593
    * @access public
594
    * @param  string    name of file-upload field
595
    * @param  mixed     file name(s)
596
    * @param  mixed     content-type(s) of file(s) being uploaded
597
    * @return bool      true on success
598
    * @throws PEAR_Error
599
    */
600
    function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
601
    {
602
        if (!is_array($fileName) && !is_readable($fileName)) {
603
            return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE);
604
        } elseif (is_array($fileName)) {
605
            foreach ($fileName as $name) {
606
                if (!is_readable($name)) {
607
                    return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE);
608
                }
609
            }
610
        }
611
        $this->addHeader('Content-Type', 'multipart/form-data');
612
        $this->_postFiles[$inputName] = array(
613
            'name' => $fileName,
614
            'type' => $contentType
615
        );
616
        return true;
617
    }
618
 
619
    /**
620
    * Adds raw postdata (DEPRECATED)
621
    *
622
    * @param string     The data
623
    * @param bool       Whether data is preencoded or not, default = already encoded
624
    * @access public
625
    * @deprecated       deprecated since 1.3.0, method setBody() should be used instead
626
    */
627
    function addRawPostData($postdata, $preencoded = true)
628
    {
629
        $this->_body = $preencoded ? $postdata : urlencode($postdata);
630
    }
631
 
632
   /**
633
    * Sets the request body (for POST, PUT and similar requests)
634
    *
635
    * @param    string  Request body
636
    * @access   public
637
    */
638
    function setBody($body)
639
    {
640
        $this->_body = $body;
641
    }
642
 
643
    /**
644
    * Clears any postdata that has been added (DEPRECATED).
645
    *
646
    * Useful for multiple request scenarios.
647
    *
648
    * @access public
649
    * @deprecated deprecated since 1.2
650
    */
651
    function clearPostData()
652
    {
653
        $this->_postData = null;
654
    }
655
 
656
    /**
657
    * Appends a cookie to "Cookie:" header
658
    *
659
    * @param string $name cookie name
660
    * @param string $value cookie value
661
    * @access public
662
    */
663
    function addCookie($name, $value)
664
    {
665
        $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';
666
        $this->addHeader('Cookie', $cookies . $name . '=' . $value);
667
    }
668
 
669
    /**
670
    * Clears any cookies that have been added (DEPRECATED).
671
    *
672
    * Useful for multiple request scenarios
673
    *
674
    * @access public
675
    * @deprecated deprecated since 1.2
676
    */
677
    function clearCookies()
678
    {
679
        $this->removeHeader('Cookie');
680
    }
681
 
682
    /**
683
    * Sends the request
684
    *
685
    * @access public
686
    * @param  bool   Whether to store response body in Response object property,
687
    *                set this to false if downloading a LARGE file and using a Listener
688
    * @return mixed  PEAR error on error, true otherwise
689
    */
690
    function sendRequest($saveBody = true)
691
    {
692
        if (!is_a($this->_url, 'Net_URL')) {
693
            return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL);
694
        }
695
 
696
        $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
697
        $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
698
 
699
        if (strcasecmp($this->_url->protocol, 'https') == 0) {
700
            // Bug #14127, don't try connecting to HTTPS sites without OpenSSL
701
            if (version_compare(PHP_VERSION, '4.3.0', '<') || !extension_loaded('openssl')) {
702
                return PEAR::raiseError('Need PHP 4.3.0 or later with OpenSSL support for https:// requests',
703
                                        HTTP_REQUEST_ERROR_URL);
704
            } elseif (isset($this->_proxy_host)) {
705
                return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY);
706
            }
707
            $host = 'ssl://' . $host;
708
        }
709
 
710
        // magic quotes may fuck up file uploads and chunked response processing
711
        $magicQuotes = ini_get('magic_quotes_runtime');
712
        ini_set('magic_quotes_runtime', false);
713
 
714
        // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
715
        // connection token to a proxy server...
716
        if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&
717
            'Keep-Alive' == $this->_requestHeaders['connection'])
718
        {
719
            $this->removeHeader('connection');
720
        }
721
 
722
        $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||
723
                     (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);
724
        $sockets   = &PEAR::getStaticProperty('HTTP_Request', 'sockets');
725
        $sockKey   = $host . ':' . $port;
726
        unset($this->_sock);
727
 
728
        // There is a connected socket in the "static" property?
729
        if ($keepAlive && !empty($sockets[$sockKey]) &&
730
            !empty($sockets[$sockKey]->fp))
731
        {
732
            $this->_sock =& $sockets[$sockKey];
733
            $err = null;
734
        } else {
735
            $this->_notify('connect');
736
            $this->_sock =& new Net_Socket();
737
            $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
738
        }
739
        PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
740
 
741
        if (!PEAR::isError($err)) {
742
            if (!empty($this->_readTimeout)) {
743
                $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
744
            }
745
 
746
            $this->_notify('sentRequest');
747
 
748
            // Read the response
749
            $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);
750
            $err = $this->_response->process(
751
                $this->_saveBody && $saveBody,
752
                HTTP_REQUEST_METHOD_HEAD != $this->_method
753
            );
754
 
755
            if ($keepAlive) {
756
                $keepAlive = (isset($this->_response->_headers['content-length'])
757
                              || (isset($this->_response->_headers['transfer-encoding'])
758
                                  && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));
759
                if ($keepAlive) {
760
                    if (isset($this->_response->_headers['connection'])) {
761
                        $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';
762
                    } else {
763
                        $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
764
                    }
765
                }
766
            }
767
        }
768
 
769
        ini_set('magic_quotes_runtime', $magicQuotes);
770
 
771
        if (PEAR::isError($err)) {
772
            return $err;
773
        }
774
 
775
        if (!$keepAlive) {
776
            $this->disconnect();
777
        // Store the connected socket in "static" property
778
        } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
779
            $sockets[$sockKey] =& $this->_sock;
780
        }
781
 
782
        // Check for redirection
783
        if (    $this->_allowRedirects
784
            AND $this->_redirects <= $this->_maxRedirects
785
            AND $this->getResponseCode() > 300
786
            AND $this->getResponseCode() < 399
787
            AND !empty($this->_response->_headers['location'])) {
788
 
789
 
790
            $redirect = $this->_response->_headers['location'];
791
 
792
            // Absolute URL
793
            if (preg_match('/^https?:\/\//i', $redirect)) {
794
                $this->_url = &new Net_URL($redirect);
795
                $this->addHeader('Host', $this->_generateHostHeader());
796
            // Absolute path
797
            } elseif ($redirect{0} == '/') {
798
                $this->_url->path = $redirect;
799
 
800
            // Relative path
801
            } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
802
                if (substr($this->_url->path, -1) == '/') {
803
                    $redirect = $this->_url->path . $redirect;
804
                } else {
805
                    $redirect = dirname($this->_url->path) . '/' . $redirect;
806
                }
807
                $redirect = Net_URL::resolvePath($redirect);
808
                $this->_url->path = $redirect;
809
 
810
            // Filename, no path
811
            } else {
812
                if (substr($this->_url->path, -1) == '/') {
813
                    $redirect = $this->_url->path . $redirect;
814
                } else {
815
                    $redirect = dirname($this->_url->path) . '/' . $redirect;
816
                }
817
                $this->_url->path = $redirect;
818
            }
819
 
820
            $this->_redirects++;
821
            return $this->sendRequest($saveBody);
822
 
823
        // Too many redirects
824
        } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
825
            return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS);
826
        }
827
 
828
        return true;
829
    }
830
 
831
    /**
832
     * Disconnect the socket, if connected. Only useful if using Keep-Alive.
833
     *
834
     * @access public
835
     */
836
    function disconnect()
837
    {
838
        if (!empty($this->_sock) && !empty($this->_sock->fp)) {
839
            $this->_notify('disconnect');
840
            $this->_sock->disconnect();
841
        }
842
    }
843
 
844
    /**
845
    * Returns the response code
846
    *
847
    * @access public
848
    * @return mixed     Response code, false if not set
849
    */
850
    function getResponseCode()
851
    {
852
        return isset($this->_response->_code) ? $this->_response->_code : false;
853
    }
854
 
855
    /**
856
    * Returns the response reason phrase
857
    *
858
    * @access public
859
    * @return mixed     Response reason phrase, false if not set
860
    */
861
    function getResponseReason()
862
    {
863
        return isset($this->_response->_reason) ? $this->_response->_reason : false;
864
    }
865
 
866
    /**
867
    * Returns either the named header or all if no name given
868
    *
869
    * @access public
870
    * @param string     The header name to return, do not set to get all headers
871
    * @return mixed     either the value of $headername (false if header is not present)
872
    *                   or an array of all headers
873
    */
874
    function getResponseHeader($headername = null)
875
    {
876
        if (!isset($headername)) {
877
            return isset($this->_response->_headers)? $this->_response->_headers: array();
878
        } else {
879
            $headername = strtolower($headername);
880
            return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
881
        }
882
    }
883
 
884
    /**
885
    * Returns the body of the response
886
    *
887
    * @access public
888
    * @return mixed     response body, false if not set
889
    */
890
    function getResponseBody()
891
    {
892
        return isset($this->_response->_body) ? $this->_response->_body : false;
893
    }
894
 
895
    /**
896
    * Returns cookies set in response
897
    *
898
    * @access public
899
    * @return mixed     array of response cookies, false if none are present
900
    */
901
    function getResponseCookies()
902
    {
903
        return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
904
    }
905
 
906
    /**
907
    * Builds the request string
908
    *
909
    * @access private
910
    * @return string The request string
911
    */
912
    function _buildRequest()
913
    {
914
        $separator = ini_get('arg_separator.output');
915
        ini_set('arg_separator.output', '&');
916
        $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
917
        ini_set('arg_separator.output', $separator);
918
 
919
        $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
920
        $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
921
        $path = $this->_url->path . $querystring;
922
        $url  = $host . $port . $path;
923
 
924
        if (!strlen($url)) {
925
            $url = '/';
926
        }
927
 
928
        $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
929
 
930
        if (in_array($this->_method, $this->_bodyDisallowed) ||
931
            (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||
932
             (empty($this->_postData) && empty($this->_postFiles)))))
933
        {
934
            $this->removeHeader('Content-Type');
935
        } else {
936
            if (empty($this->_requestHeaders['content-type'])) {
937
                // Add default content-type
938
                $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
939
            } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {
940
                $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
941
                $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
942
            }
943
        }
944
 
945
        // Request Headers
946
        if (!empty($this->_requestHeaders)) {
947
            foreach ($this->_requestHeaders as $name => $value) {
948
                $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
949
                $request      .= $canonicalName . ': ' . $value . "\r\n";
950
            }
951
        }
952
 
953
        // Method does not allow a body, simply add a final CRLF
954
        if (in_array($this->_method, $this->_bodyDisallowed)) {
955
 
956
            $request .= "\r\n";
957
 
958
        // Post data if it's an array
959
        } elseif (HTTP_REQUEST_METHOD_POST == $this->_method &&
960
                  (!empty($this->_postData) || !empty($this->_postFiles))) {
961
 
962
            // "normal" POST request
963
            if (!isset($boundary)) {
964
                $postdata = implode('&', array_map(
965
                    create_function('$a', 'return $a[0] . \'=\' . $a[1];'),
966
                    $this->_flattenArray('', $this->_postData)
967
                ));
968
 
969
            // multipart request, probably with file uploads
970
            } else {
971
                $postdata = '';
972
                if (!empty($this->_postData)) {
973
                    $flatData = $this->_flattenArray('', $this->_postData);
974
                    foreach ($flatData as $item) {
975
                        $postdata .= '--' . $boundary . "\r\n";
976
                        $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
977
                        $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
978
                    }
979
                }
980
                foreach ($this->_postFiles as $name => $value) {
981
                    if (is_array($value['name'])) {
982
                        $varname       = $name . ($this->_useBrackets? '[]': '');
983
                    } else {
984
                        $varname       = $name;
985
                        $value['name'] = array($value['name']);
986
                    }
987
                    foreach ($value['name'] as $key => $filename) {
988
                        $fp       = fopen($filename, 'r');
989
                        $basename = basename($filename);
990
                        $type     = is_array($value['type'])? @$value['type'][$key]: $value['type'];
991
 
992
                        $postdata .= '--' . $boundary . "\r\n";
993
                        $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
994
                        $postdata .= "\r\nContent-Type: " . $type;
995
                        $postdata .= "\r\n\r\n" . fread($fp, filesize($filename)) . "\r\n";
996
                        fclose($fp);
997
                    }
998
                }
999
                $postdata .= '--' . $boundary . "--\r\n";
1000
            }
1001
            $request .= 'Content-Length: ' .
1002
                        (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) .
1003
                        "\r\n\r\n";
1004
            $request .= $postdata;
1005
 
1006
        // Explicitly set request body
1007
        } elseif (0 < strlen($this->_body)) {
1008
 
1009
            $request .= 'Content-Length: ' .
1010
                        (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) .
1011
                        "\r\n\r\n";
1012
            $request .= $this->_body;
1013
 
1014
        // No body: send a Content-Length header nonetheless (request #12900),
1015
        // but do that only for methods that require a body (bug #14740)
1016
        } else {
1017
 
1018
            if (in_array($this->_method, $this->_bodyRequired)) {
1019
                $request .= "Content-Length: 0\r\n";
1020
            }
1021
            $request .= "\r\n";
1022
        }
1023
 
1024
        return $request;
1025
    }
1026
 
1027
   /**
1028
    * Helper function to change the (probably multidimensional) associative array
1029
    * into the simple one.
1030
    *
1031
    * @param    string  name for item
1032
    * @param    mixed   item's values
1033
    * @return   array   array with the following items: array('item name', 'item value');
1034
    * @access   private
1035
    */
1036
    function _flattenArray($name, $values)
1037
    {
1038
        if (!is_array($values)) {
1039
            return array(array($name, $values));
1040
        } else {
1041
            $ret = array();
1042
            foreach ($values as $k => $v) {
1043
                if (empty($name)) {
1044
                    $newName = $k;
1045
                } elseif ($this->_useBrackets) {
1046
                    $newName = $name . '[' . $k . ']';
1047
                } else {
1048
                    $newName = $name;
1049
                }
1050
                $ret = array_merge($ret, $this->_flattenArray($newName, $v));
1051
            }
1052
            return $ret;
1053
        }
1054
    }
1055
 
1056
 
1057
   /**
1058
    * Adds a Listener to the list of listeners that are notified of
1059
    * the object's events
1060
    *
1061
    * Events sent by HTTP_Request object
1062
    * - 'connect': on connection to server
1063
    * - 'sentRequest': after the request was sent
1064
    * - 'disconnect': on disconnection from server
1065
    *
1066
    * Events sent by HTTP_Response object
1067
    * - 'gotHeaders': after receiving response headers (headers are passed in $data)
1068
    * - 'tick': on receiving a part of response body (the part is passed in $data)
1069
    * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)
1070
    * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)
1071
    *
1072
    * @param    HTTP_Request_Listener   listener to attach
1073
    * @return   boolean                 whether the listener was successfully attached
1074
    * @access   public
1075
    */
1076
    function attach(&$listener)
1077
    {
1078
        if (!is_a($listener, 'HTTP_Request_Listener')) {
1079
            return false;
1080
        }
1081
        $this->_listeners[$listener->getId()] =& $listener;
1082
        return true;
1083
    }
1084
 
1085
 
1086
   /**
1087
    * Removes a Listener from the list of listeners
1088
    *
1089
    * @param    HTTP_Request_Listener   listener to detach
1090
    * @return   boolean                 whether the listener was successfully detached
1091
    * @access   public
1092
    */
1093
    function detach(&$listener)
1094
    {
1095
        if (!is_a($listener, 'HTTP_Request_Listener') ||
1096
            !isset($this->_listeners[$listener->getId()])) {
1097
            return false;
1098
        }
1099
        unset($this->_listeners[$listener->getId()]);
1100
        return true;
1101
    }
1102
 
1103
 
1104
   /**
1105
    * Notifies all registered listeners of an event.
1106
    *
1107
    * @param    string  Event name
1108
    * @param    mixed   Additional data
1109
    * @access   private
1110
    * @see      HTTP_Request::attach()
1111
    */
1112
    function _notify($event, $data = null)
1113
    {
1114
        foreach (array_keys($this->_listeners) as $id) {
1115
            $this->_listeners[$id]->update($this, $event, $data);
1116
        }
1117
    }
1118
}
1119
 
1120
 
1121
/**
1122
 * Response class to complement the Request class
1123
 *
1124
 * @category    HTTP
1125
 * @package     HTTP_Request
1126
 * @author      Richard Heyes <richard@phpguru.org>
1127
 * @author      Alexey Borzov <avb@php.net>
1128
 * @version     Release: 1.4.4
1129
 */
1130
class HTTP_Response
1131
{
1132
    /**
1133
    * Socket object
1134
    * @var Net_Socket
1135
    */
1136
    var $_sock;
1137
 
1138
    /**
1139
    * Protocol
1140
    * @var string
1141
    */
1142
    var $_protocol;
1143
 
1144
    /**
1145
    * Return code
1146
    * @var string
1147
    */
1148
    var $_code;
1149
 
1150
    /**
1151
    * Response reason phrase
1152
    * @var string
1153
    */
1154
    var $_reason;
1155
 
1156
    /**
1157
    * Response headers
1158
    * @var array
1159
    */
1160
    var $_headers;
1161
 
1162
    /**
1163
    * Cookies set in response
1164
    * @var array
1165
    */
1166
    var $_cookies;
1167
 
1168
    /**
1169
    * Response body
1170
    * @var string
1171
    */
1172
    var $_body = '';
1173
 
1174
   /**
1175
    * Used by _readChunked(): remaining length of the current chunk
1176
    * @var string
1177
    */
1178
    var $_chunkLength = 0;
1179
 
1180
   /**
1181
    * Attached listeners
1182
    * @var array
1183
    */
1184
    var $_listeners = array();
1185
 
1186
   /**
1187
    * Bytes left to read from message-body
1188
    * @var null|int
1189
    */
1190
    var $_toRead;
1191
 
1192
    /**
1193
    * Constructor
1194
    *
1195
    * @param  Net_Socket    socket to read the response from
1196
    * @param  array         listeners attached to request
1197
    */
1198
    function HTTP_Response(&$sock, &$listeners)
1199
    {
1200
        $this->_sock      =& $sock;
1201
        $this->_listeners =& $listeners;
1202
    }
1203
 
1204
 
1205
   /**
1206
    * Processes a HTTP response
1207
    *
1208
    * This extracts response code, headers, cookies and decodes body if it
1209
    * was encoded in some way
1210
    *
1211
    * @access public
1212
    * @param  bool      Whether to store response body in object property, set
1213
    *                   this to false if downloading a LARGE file and using a Listener.
1214
    *                   This is assumed to be true if body is gzip-encoded.
1215
    * @param  bool      Whether the response can actually have a message-body.
1216
    *                   Will be set to false for HEAD requests.
1217
    * @throws PEAR_Error
1218
    * @return mixed     true on success, PEAR_Error in case of malformed response
1219
    */
1220
    function process($saveBody = true, $canHaveBody = true)
1221
    {
1222
        do {
1223
            $line = $this->_sock->readLine();
1224
            if (!preg_match('!^(HTTP/\d\.\d) (\d{3})(?: (.+))?!', $line, $s)) {
1225
                return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE);
1226
            } else {
1227
                $this->_protocol = $s[1];
1228
                $this->_code     = intval($s[2]);
1229
                $this->_reason   = empty($s[3])? null: $s[3];
1230
            }
1231
            while ('' !== ($header = $this->_sock->readLine())) {
1232
                $this->_processHeader($header);
1233
            }
1234
        } while (100 == $this->_code);
1235
 
1236
        $this->_notify('gotHeaders', $this->_headers);
1237
 
1238
        // RFC 2616, section 4.4:
1239
        // 1. Any response message which "MUST NOT" include a message-body ...
1240
        // is always terminated by the first empty line after the header fields
1241
        // 3. ... If a message is received with both a
1242
        // Transfer-Encoding header field and a Content-Length header field,
1243
        // the latter MUST be ignored.
1244
        $canHaveBody = $canHaveBody && $this->_code >= 200 &&
1245
                       $this->_code != 204 && $this->_code != 304;
1246
 
1247
        // If response body is present, read it and decode
1248
        $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
1249
        $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
1250
        $hasBody = false;
1251
        if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) ||
1252
 
1253
        {
1254
            if ($chunked || !isset($this->_headers['content-length'])) {
1255
                $this->_toRead = null;
1256
            } else {
1257
                $this->_toRead = $this->_headers['content-length'];
1258
            }
1259
            while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
1260
                if ($chunked) {
1261
                    $data = $this->_readChunked();
1262
                } elseif (is_null($this->_toRead)) {
1263
                    $data = $this->_sock->read(4096);
1264
                } else {
1265
                    $data = $this->_sock->read(min(4096, $this->_toRead));
1266
                    $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
1267
                }
1268
                if ('' == $data && (!$this->_chunkLength || $this->_sock->eof())) {
1269
                    break;
1270
                } else {
1271
                    $hasBody = true;
1272
                    if ($saveBody || $gzipped) {
1273
                        $this->_body .= $data;
1274
                    }
1275
                    $this->_notify($gzipped? 'gzTick': 'tick', $data);
1276
                }
1277
            }
1278
        }
1279
 
1280
        if ($hasBody) {
1281
            // Uncompress the body if needed
1282
            if ($gzipped) {
1283
                $body = $this->_decodeGzip($this->_body);
1284
                if (PEAR::isError($body)) {
1285
                    return $body;
1286
                }
1287
                $this->_body = $body;
1288
                $this->_notify('gotBody', $this->_body);
1289
            } else {
1290
                $this->_notify('gotBody');
1291
            }
1292
        }
1293
        return true;
1294
    }
1295
 
1296
 
1297
   /**
1298
    * Processes the response header
1299
    *
1300
    * @access private
1301
    * @param  string    HTTP header
1302
    */
1303
    function _processHeader($header)
1304
    {
1305
        if (false === strpos($header, ':')) {
1306
            return;
1307
        }
1308
        list($headername, $headervalue) = explode(':', $header, 2);
1309
        $headername  = strtolower($headername);
1310
        $headervalue = ltrim($headervalue);
1311
 
1312
        if ('set-cookie' != $headername) {
1313
            if (isset($this->_headers[$headername])) {
1314
                $this->_headers[$headername] .= ',' . $headervalue;
1315
            } else {
1316
                $this->_headers[$headername]  = $headervalue;
1317
            }
1318
        } else {
1319
            $this->_parseCookie($headervalue);
1320
        }
1321
    }
1322
 
1323
 
1324
   /**
1325
    * Parse a Set-Cookie header to fill $_cookies array
1326
    *
1327
    * @access private
1328
    * @param  string    value of Set-Cookie header
1329
    */
1330
    function _parseCookie($headervalue)
1331
    {
1332
        $cookie = array(
1333
            'expires' => null,
1334
            'domain'  => null,
1335
            'path'    => null,
1336
            'secure'  => false
1337
        );
1338
 
1339
        // Only a name=value pair
1340
        if (!strpos($headervalue, ';')) {
1341
            $pos = strpos($headervalue, '=');
1342
            $cookie['name']  = trim(substr($headervalue, 0, $pos));
1343
            $cookie['value'] = trim(substr($headervalue, $pos + 1));
1344
 
1345
        // Some optional parameters are supplied
1346
        } else {
1347
            $elements = explode(';', $headervalue);
1348
            $pos = strpos($elements[0], '=');
1349
            $cookie['name']  = trim(substr($elements[0], 0, $pos));
1350
            $cookie['value'] = trim(substr($elements[0], $pos + 1));
1351
 
1352
            for ($i = 1; $i < count($elements); $i++) {
1353
                if (false === strpos($elements[$i], '=')) {
1354
                    $elName  = trim($elements[$i]);
1355
                    $elValue = null;
1356
                } else {
1357
                    list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
1358
                }
1359
                $elName = strtolower($elName);
1360
                if ('secure' == $elName) {
1361
                    $cookie['secure'] = true;
1362
                } elseif ('expires' == $elName) {
1363
                    $cookie['expires'] = str_replace('"', '', $elValue);
1364
                } elseif ('path' == $elName || 'domain' == $elName) {
1365
                    $cookie[$elName] = urldecode($elValue);
1366
                } else {
1367
                    $cookie[$elName] = $elValue;
1368
                }
1369
            }
1370
        }
1371
        $this->_cookies[] = $cookie;
1372
    }
1373
 
1374
 
1375
   /**
1376
    * Read a part of response body encoded with chunked Transfer-Encoding
1377
    *
1378
    * @access private
1379
    * @return string
1380
    */
1381
    function _readChunked()
1382
    {
1383
        // at start of the next chunk?
1384
        if (0 == $this->_chunkLength) {
1385
            $line = $this->_sock->readLine();
1386
            if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
1387
                $this->_chunkLength = hexdec($matches[1]);
1388
                // Chunk with zero length indicates the end
1389
                if (0 == $this->_chunkLength) {
1390
                    $this->_sock->readLine(); // make this an eof()
1391
                    return '';
1392
                }
1393
            } else {
1394
                return '';
1395
            }
1396
        }
1397
        $data = $this->_sock->read($this->_chunkLength);
1398
        $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
1399
        if (0 == $this->_chunkLength) {
1400
            $this->_sock->readLine(); // Trailing CRLF
1401
        }
1402
        return $data;
1403
    }
1404
 
1405
 
1406
   /**
1407
    * Notifies all registered listeners of an event.
1408
    *
1409
    * @param    string  Event name
1410
    * @param    mixed   Additional data
1411
    * @access   private
1412
    * @see HTTP_Request::_notify()
1413
    */
1414
    function _notify($event, $data = null)
1415
    {
1416
        foreach (array_keys($this->_listeners) as $id) {
1417
            $this->_listeners[$id]->update($this, $event, $data);
1418
        }
1419
    }
1420
 
1421
 
1422
   /**
1423
    * Decodes the message-body encoded by gzip
1424
    *
1425
    * The real decoding work is done by gzinflate() built-in function, this
1426
    * method only parses the header and checks data for compliance with
1427
    * RFC 1952
1428
    *
1429
    * @access   private
1430
    * @param    string  gzip-encoded data
1431
    * @return   string  decoded data
1432
    */
1433
    function _decodeGzip($data)
1434
    {
1435
        if (HTTP_REQUEST_MBSTRING) {
1436
            $oldEncoding = mb_internal_encoding();
1437
            mb_internal_encoding('iso-8859-1');
1438
        }
1439
        $length = strlen($data);
1440
        // If it doesn't look like gzip-encoded data, don't bother
1441
        if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
1442
            return $data;
1443
        }
1444
        $method = ord(substr($data, 2, 1));
1445
        if (8 != $method) {
1446
            return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD);
1447
        }
1448
        $flags = ord(substr($data, 3, 1));
1449
        if ($flags & 224) {
1450
            return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA);
1451
        }
1452
 
1453
        // header is 10 bytes minimum. may be longer, though.
1454
        $headerLength = 10;
1455
        // extra fields, need to skip 'em
1456
        if ($flags & 4) {
1457
            if ($length - $headerLength - 2 < 8) {
1458
                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1459
            }
1460
            $extraLength = unpack('v', substr($data, 10, 2));
1461
            if ($length - $headerLength - 2 - $extraLength[1] < 8) {
1462
                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1463
            }
1464
            $headerLength += $extraLength[1] + 2;
1465
        }
1466
        // file name, need to skip that
1467
        if ($flags & 8) {
1468
            if ($length - $headerLength - 1 < 8) {
1469
                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1470
            }
1471
            $filenameLength = strpos(substr($data, $headerLength), chr(0));
1472
            if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
1473
                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1474
            }
1475
            $headerLength += $filenameLength + 1;
1476
        }
1477
        // comment, need to skip that also
1478
        if ($flags & 16) {
1479
            if ($length - $headerLength - 1 < 8) {
1480
                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1481
            }
1482
            $commentLength = strpos(substr($data, $headerLength), chr(0));
1483
            if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
1484
                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1485
            }
1486
            $headerLength += $commentLength + 1;
1487
        }
1488
        // have a CRC for header. let's check
1489
        if ($flags & 1) {
1490
            if ($length - $headerLength - 2 < 8) {
1491
                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1492
            }
1493
            $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));
1494
            $crcStored = unpack('v', substr($data, $headerLength, 2));
1495
            if ($crcReal != $crcStored[1]) {
1496
                return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
1497
            }
1498
            $headerLength += 2;
1499
        }
1500
        // unpacked data CRC and size at the end of encoded data
1501
        $tmp = unpack('V2', substr($data, -8));
1502
        $dataCrc  = $tmp[1];
1503
        $dataSize = $tmp[2];
1504
 
1505
        // finally, call the gzinflate() function
1506
        // don't pass $dataSize to gzinflate, see bugs #13135, #14370
1507
        $unpacked = gzinflate(substr($data, $headerLength, -8));
1508
        if (false === $unpacked) {
1509
            return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ);
1510
        } elseif ($dataSize != strlen($unpacked)) {
1511
            return PEAR::raiseError('_decodeGzip(): data size check failed', HTTP_REQUEST_ERROR_GZIP_READ);
1512
        } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
1513
            return PEAR::raiseError('_decodeGzip(): data CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
1514
        }
1515
        if (HTTP_REQUEST_MBSTRING) {
1516
            mb_internal_encoding($oldEncoding);
1517
        }
1518
        return $unpacked;
1519
    }
1520
} // End class HTTP_Response
1521
?>