]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/HTTP/Request2.php
Rebuilt HTTPClient class as an extension of PEAR HTTP_Request2 package, adding redire...
[quix0rs-gnu-social.git] / extlib / HTTP / Request2.php
1 <?php\r
2 /**\r
3  * Class representing a HTTP request\r
4  *\r
5  * PHP version 5\r
6  *\r
7  * LICENSE:\r
8  *\r
9  * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
10  * All rights reserved.\r
11  *\r
12  * Redistribution and use in source and binary forms, with or without\r
13  * modification, are permitted provided that the following conditions\r
14  * are met:\r
15  *\r
16  *    * Redistributions of source code must retain the above copyright\r
17  *      notice, this list of conditions and the following disclaimer.\r
18  *    * Redistributions in binary form must reproduce the above copyright\r
19  *      notice, this list of conditions and the following disclaimer in the\r
20  *      documentation and/or other materials provided with the distribution.\r
21  *    * The names of the authors may not be used to endorse or promote products\r
22  *      derived from this software without specific prior written permission.\r
23  *\r
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
25  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
26  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
27  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
28  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
29  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
30  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
31  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
32  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
35  *\r
36  * @category   HTTP\r
37  * @package    HTTP_Request2\r
38  * @author     Alexey Borzov <avb@php.net>\r
39  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
40  * @version    CVS: $Id: Request2.php 278226 2009-04-03 21:32:48Z avb $\r
41  * @link       http://pear.php.net/package/HTTP_Request2\r
42  */\r
43 \r
44 /**\r
45  * A class representing an URL as per RFC 3986.\r
46  */\r
47 require_once 'Net/URL2.php';\r
48 \r
49 /**\r
50  * Exception class for HTTP_Request2 package\r
51  */ \r
52 require_once 'HTTP/Request2/Exception.php';\r
53 \r
54 /**\r
55  * Class representing a HTTP request\r
56  *\r
57  * @category   HTTP\r
58  * @package    HTTP_Request2\r
59  * @author     Alexey Borzov <avb@php.net>\r
60  * @version    Release: 0.4.1\r
61  * @link       http://tools.ietf.org/html/rfc2616#section-5\r
62  */\r
63 class HTTP_Request2 implements SplSubject\r
64 {\r
65    /**#@+\r
66     * Constants for HTTP request methods\r
67     *\r
68     * @link http://tools.ietf.org/html/rfc2616#section-5.1.1\r
69     */\r
70     const METHOD_OPTIONS = 'OPTIONS';\r
71     const METHOD_GET     = 'GET';\r
72     const METHOD_HEAD    = 'HEAD';\r
73     const METHOD_POST    = 'POST';\r
74     const METHOD_PUT     = 'PUT';\r
75     const METHOD_DELETE  = 'DELETE';\r
76     const METHOD_TRACE   = 'TRACE';\r
77     const METHOD_CONNECT = 'CONNECT';\r
78    /**#@-*/\r
79 \r
80    /**#@+\r
81     * Constants for HTTP authentication schemes \r
82     *\r
83     * @link http://tools.ietf.org/html/rfc2617\r
84     */\r
85     const AUTH_BASIC  = 'basic';\r
86     const AUTH_DIGEST = 'digest';\r
87    /**#@-*/\r
88 \r
89    /**\r
90     * Regular expression used to check for invalid symbols in RFC 2616 tokens\r
91     * @link http://pear.php.net/bugs/bug.php?id=15630\r
92     */\r
93     const REGEXP_INVALID_TOKEN = '![\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]!';\r
94 \r
95    /**\r
96     * Regular expression used to check for invalid symbols in cookie strings\r
97     * @link http://pear.php.net/bugs/bug.php?id=15630\r
98     * @link http://cgi.netscape.com/newsref/std/cookie_spec.html\r
99     */\r
100     const REGEXP_INVALID_COOKIE = '/[\s,;]/';\r
101 \r
102    /**\r
103     * Fileinfo magic database resource\r
104     * @var  resource\r
105     * @see  detectMimeType()\r
106     */\r
107     private static $_fileinfoDb;\r
108 \r
109    /**\r
110     * Observers attached to the request (instances of SplObserver)\r
111     * @var  array\r
112     */\r
113     protected $observers = array();\r
114 \r
115    /**\r
116     * Request URL\r
117     * @var  Net_URL2\r
118     */\r
119     protected $url;\r
120 \r
121    /**\r
122     * Request method\r
123     * @var  string\r
124     */\r
125     protected $method = self::METHOD_GET;\r
126 \r
127    /**\r
128     * Authentication data\r
129     * @var  array\r
130     * @see  getAuth()\r
131     */\r
132     protected $auth;\r
133 \r
134    /**\r
135     * Request headers\r
136     * @var  array\r
137     */\r
138     protected $headers = array();\r
139 \r
140    /**\r
141     * Configuration parameters\r
142     * @var  array\r
143     * @see  setConfig()\r
144     */\r
145     protected $config = array(\r
146         'adapter'           => 'HTTP_Request2_Adapter_Socket',\r
147         'connect_timeout'   => 10,\r
148         'timeout'           => 0,\r
149         'use_brackets'      => true,\r
150         'protocol_version'  => '1.1',\r
151         'buffer_size'       => 16384,\r
152         'store_body'        => true,\r
153 \r
154         'proxy_host'        => '',\r
155         'proxy_port'        => '',\r
156         'proxy_user'        => '',\r
157         'proxy_password'    => '',\r
158         'proxy_auth_scheme' => self::AUTH_BASIC,\r
159 \r
160         'ssl_verify_peer'   => true,\r
161         'ssl_verify_host'   => true,\r
162         'ssl_cafile'        => null,\r
163         'ssl_capath'        => null,\r
164         'ssl_local_cert'    => null,\r
165         'ssl_passphrase'    => null,\r
166 \r
167         'digest_compat_ie'  => false\r
168     );\r
169 \r
170    /**\r
171     * Last event in request / response handling, intended for observers\r
172     * @var  array\r
173     * @see  getLastEvent()\r
174     */\r
175     protected $lastEvent = array(\r
176         'name' => 'start',\r
177         'data' => null\r
178     );\r
179 \r
180    /**\r
181     * Request body\r
182     * @var  string|resource\r
183     * @see  setBody()\r
184     */\r
185     protected $body = '';\r
186 \r
187    /**\r
188     * Array of POST parameters\r
189     * @var  array\r
190     */\r
191     protected $postParams = array();\r
192 \r
193    /**\r
194     * Array of file uploads (for multipart/form-data POST requests) \r
195     * @var  array\r
196     */\r
197     protected $uploads = array();\r
198 \r
199    /**\r
200     * Adapter used to perform actual HTTP request\r
201     * @var  HTTP_Request2_Adapter\r
202     */\r
203     protected $adapter;\r
204 \r
205 \r
206    /**\r
207     * Constructor. Can set request URL, method and configuration array.\r
208     *\r
209     * Also sets a default value for User-Agent header. \r
210     *\r
211     * @param    string|Net_Url2     Request URL\r
212     * @param    string              Request method\r
213     * @param    array               Configuration for this Request instance\r
214     */\r
215     public function __construct($url = null, $method = self::METHOD_GET, array $config = array())\r
216     {\r
217         if (!empty($url)) {\r
218             $this->setUrl($url);\r
219         }\r
220         if (!empty($method)) {\r
221             $this->setMethod($method);\r
222         }\r
223         $this->setConfig($config);\r
224         $this->setHeader('user-agent', 'HTTP_Request2/0.4.1 ' .\r
225                          '(http://pear.php.net/package/http_request2) ' .\r
226                          'PHP/' . phpversion());\r
227     }\r
228 \r
229    /**\r
230     * Sets the URL for this request\r
231     *\r
232     * If the URL has userinfo part (username & password) these will be removed\r
233     * and converted to auth data. If the URL does not have a path component,\r
234     * that will be set to '/'.\r
235     *\r
236     * @param    string|Net_URL2 Request URL\r
237     * @return   HTTP_Request2\r
238     * @throws   HTTP_Request2_Exception\r
239     */\r
240     public function setUrl($url)\r
241     {\r
242         if (is_string($url)) {\r
243             $url = new Net_URL2($url);\r
244         }\r
245         if (!$url instanceof Net_URL2) {\r
246             throw new HTTP_Request2_Exception('Parameter is not a valid HTTP URL');\r
247         }\r
248         // URL contains username / password?\r
249         if ($url->getUserinfo()) {\r
250             $username = $url->getUser();\r
251             $password = $url->getPassword();\r
252             $this->setAuth(rawurldecode($username), $password? rawurldecode($password): '');\r
253             $url->setUserinfo('');\r
254         }\r
255         if ('' == $url->getPath()) {\r
256             $url->setPath('/');\r
257         }\r
258         $this->url = $url;\r
259 \r
260         return $this;\r
261     }\r
262 \r
263    /**\r
264     * Returns the request URL\r
265     *\r
266     * @return   Net_URL2\r
267     */\r
268     public function getUrl()\r
269     {\r
270         return $this->url;\r
271     }\r
272 \r
273    /**\r
274     * Sets the request method\r
275     *\r
276     * @param    string\r
277     * @return   HTTP_Request2\r
278     * @throws   HTTP_Request2_Exception if the method name is invalid\r
279     */\r
280     public function setMethod($method)\r
281     {\r
282         // Method name should be a token: http://tools.ietf.org/html/rfc2616#section-5.1.1\r
283         if (preg_match(self::REGEXP_INVALID_TOKEN, $method)) {\r
284             throw new HTTP_Request2_Exception("Invalid request method '{$method}'");\r
285         }\r
286         $this->method = $method;\r
287 \r
288         return $this;\r
289     }\r
290 \r
291    /**\r
292     * Returns the request method\r
293     *\r
294     * @return   string\r
295     */\r
296     public function getMethod()\r
297     {\r
298         return $this->method;\r
299     }\r
300 \r
301    /**\r
302     * Sets the configuration parameter(s)\r
303     *\r
304     * The following parameters are available:\r
305     * <ul>\r
306     *   <li> 'adapter'           - adapter to use (string)</li>\r
307     *   <li> 'connect_timeout'   - Connection timeout in seconds (integer)</li>\r
308     *   <li> 'timeout'           - Total number of seconds a request can take.\r
309     *                              Use 0 for no limit, should be greater than \r
310     *                              'connect_timeout' if set (integer)</li>\r
311     *   <li> 'use_brackets'      - Whether to append [] to array variable names (bool)</li>\r
312     *   <li> 'protocol_version'  - HTTP Version to use, '1.0' or '1.1' (string)</li>\r
313     *   <li> 'buffer_size'       - Buffer size to use for reading and writing (int)</li>\r
314     *   <li> 'store_body'        - Whether to store response body in response object.\r
315     *                              Set to false if receiving a huge response and\r
316     *                              using an Observer to save it (boolean)</li>\r
317     *   <li> 'proxy_host'        - Proxy server host (string)</li>\r
318     *   <li> 'proxy_port'        - Proxy server port (integer)</li>\r
319     *   <li> 'proxy_user'        - Proxy auth username (string)</li>\r
320     *   <li> 'proxy_password'    - Proxy auth password (string)</li>\r
321     *   <li> 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)</li>\r
322     *   <li> 'ssl_verify_peer'   - Whether to verify peer's SSL certificate (bool)</li>\r
323     *   <li> 'ssl_verify_host'   - Whether to check that Common Name in SSL\r
324     *                              certificate matches host name (bool)</li>\r
325     *   <li> 'ssl_cafile'        - Cerificate Authority file to verify the peer\r
326     *                              with (use with 'ssl_verify_peer') (string)</li>\r
327     *   <li> 'ssl_capath'        - Directory holding multiple Certificate \r
328     *                              Authority files (string)</li>\r
329     *   <li> 'ssl_local_cert'    - Name of a file containing local cerificate (string)</li>\r
330     *   <li> 'ssl_passphrase'    - Passphrase with which local certificate\r
331     *                              was encoded (string)</li>\r
332     *   <li> 'digest_compat_ie'  - Whether to imitate behaviour of MSIE 5 and 6\r
333     *                              in using URL without query string in digest\r
334     *                              authentication (boolean)</li>\r
335     * </ul>\r
336     *\r
337     * @param    string|array    configuration parameter name or array\r
338     *                           ('parameter name' => 'parameter value')\r
339     * @param    mixed           parameter value if $nameOrConfig is not an array\r
340     * @return   HTTP_Request2\r
341     * @throws   HTTP_Request2_Exception If the parameter is unknown\r
342     */\r
343     public function setConfig($nameOrConfig, $value = null)\r
344     {\r
345         if (is_array($nameOrConfig)) {\r
346             foreach ($nameOrConfig as $name => $value) {\r
347                 $this->setConfig($name, $value);\r
348             }\r
349 \r
350         } else {\r
351             if (!array_key_exists($nameOrConfig, $this->config)) {\r
352                 throw new HTTP_Request2_Exception(\r
353                     "Unknown configuration parameter '{$nameOrConfig}'"\r
354                 );\r
355             }\r
356             $this->config[$nameOrConfig] = $value;\r
357         }\r
358 \r
359         return $this;\r
360     }\r
361 \r
362    /**\r
363     * Returns the value(s) of the configuration parameter(s)\r
364     *\r
365     * @param    string  parameter name\r
366     * @return   mixed   value of $name parameter, array of all configuration \r
367     *                   parameters if $name is not given\r
368     * @throws   HTTP_Request2_Exception If the parameter is unknown\r
369     */\r
370     public function getConfig($name = null)\r
371     {\r
372         if (null === $name) {\r
373             return $this->config;\r
374         } elseif (!array_key_exists($name, $this->config)) {\r
375             throw new HTTP_Request2_Exception(\r
376                 "Unknown configuration parameter '{$name}'"\r
377             );\r
378         }\r
379         return $this->config[$name];\r
380     }\r
381 \r
382    /**\r
383     * Sets the autentification data\r
384     *\r
385     * @param    string  user name\r
386     * @param    string  password\r
387     * @param    string  authentication scheme\r
388     * @return   HTTP_Request2\r
389     */ \r
390     public function setAuth($user, $password = '', $scheme = self::AUTH_BASIC)\r
391     {\r
392         if (empty($user)) {\r
393             $this->auth = null;\r
394         } else {\r
395             $this->auth = array(\r
396                 'user'     => (string)$user,\r
397                 'password' => (string)$password,\r
398                 'scheme'   => $scheme\r
399             );\r
400         }\r
401 \r
402         return $this;\r
403     }\r
404 \r
405    /**\r
406     * Returns the authentication data\r
407     *\r
408     * The array has the keys 'user', 'password' and 'scheme', where 'scheme'\r
409     * is one of the HTTP_Request2::AUTH_* constants.\r
410     *\r
411     * @return   array\r
412     */\r
413     public function getAuth()\r
414     {\r
415         return $this->auth;\r
416     }\r
417 \r
418    /**\r
419     * Sets request header(s)\r
420     *\r
421     * The first parameter may be either a full header string 'header: value' or\r
422     * header name. In the former case $value parameter is ignored, in the latter \r
423     * the header's value will either be set to $value or the header will be\r
424     * removed if $value is null. The first parameter can also be an array of\r
425     * headers, in that case method will be called recursively.\r
426     *\r
427     * Note that headers are treated case insensitively as per RFC 2616.\r
428     * \r
429     * <code>\r
430     * $req->setHeader('Foo: Bar'); // sets the value of 'Foo' header to 'Bar'\r
431     * $req->setHeader('FoO', 'Baz'); // sets the value of 'Foo' header to 'Baz'\r
432     * $req->setHeader(array('foo' => 'Quux')); // sets the value of 'Foo' header to 'Quux'\r
433     * $req->setHeader('FOO'); // removes 'Foo' header from request\r
434     * </code>\r
435     *\r
436     * @param    string|array    header name, header string ('Header: value')\r
437     *                           or an array of headers\r
438     * @param    string|null     header value, header will be removed if null\r
439     * @return   HTTP_Request2\r
440     * @throws   HTTP_Request2_Exception\r
441     */\r
442     public function setHeader($name, $value = null)\r
443     {\r
444         if (is_array($name)) {\r
445             foreach ($name as $k => $v) {\r
446                 if (is_string($k)) {\r
447                     $this->setHeader($k, $v);\r
448                 } else {\r
449                     $this->setHeader($v);\r
450                 }\r
451             }\r
452         } else {\r
453             if (null === $value && strpos($name, ':')) {\r
454                 list($name, $value) = array_map('trim', explode(':', $name, 2));\r
455             }\r
456             // Header name should be a token: http://tools.ietf.org/html/rfc2616#section-4.2\r
457             if (preg_match(self::REGEXP_INVALID_TOKEN, $name)) {\r
458                 throw new HTTP_Request2_Exception("Invalid header name '{$name}'");\r
459             }\r
460             // Header names are case insensitive anyway\r
461             $name = strtolower($name);\r
462             if (null === $value) {\r
463                 unset($this->headers[$name]);\r
464             } else {\r
465                 $this->headers[$name] = $value;\r
466             }\r
467         }\r
468         \r
469         return $this;\r
470     }\r
471 \r
472    /**\r
473     * Returns the request headers\r
474     *\r
475     * The array is of the form ('header name' => 'header value'), header names\r
476     * are lowercased\r
477     *\r
478     * @return   array\r
479     */\r
480     public function getHeaders()\r
481     {\r
482         return $this->headers;\r
483     }\r
484 \r
485    /**\r
486     * Appends a cookie to "Cookie:" header\r
487     *\r
488     * @param    string  cookie name\r
489     * @param    string  cookie value\r
490     * @return   HTTP_Request2\r
491     * @throws   HTTP_Request2_Exception\r
492     */\r
493     public function addCookie($name, $value)\r
494     {\r
495         $cookie = $name . '=' . $value;\r
496         if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) {\r
497             throw new HTTP_Request2_Exception("Invalid cookie: '{$cookie}'");\r
498         }\r
499         $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; ';\r
500         $this->setHeader('cookie', $cookies . $cookie);\r
501 \r
502         return $this;\r
503     }\r
504 \r
505    /**\r
506     * Sets the request body\r
507     *\r
508     * @param    string  Either a string with the body or filename containing body\r
509     * @param    bool    Whether first parameter is a filename\r
510     * @return   HTTP_Request2\r
511     * @throws   HTTP_Request2_Exception\r
512     */\r
513     public function setBody($body, $isFilename = false)\r
514     {\r
515         if (!$isFilename) {\r
516             $this->body = (string)$body;\r
517         } else {\r
518             if (!($fp = @fopen($body, 'rb'))) {\r
519                 throw new HTTP_Request2_Exception("Cannot open file {$body}");\r
520             }\r
521             $this->body = $fp;\r
522             if (empty($this->headers['content-type'])) {\r
523                 $this->setHeader('content-type', self::detectMimeType($body));\r
524             }\r
525         }\r
526 \r
527         return $this;\r
528     }\r
529 \r
530    /**\r
531     * Returns the request body\r
532     *\r
533     * @return   string|resource|HTTP_Request2_MultipartBody\r
534     */\r
535     public function getBody()\r
536     {\r
537         if (self::METHOD_POST == $this->method && \r
538             (!empty($this->postParams) || !empty($this->uploads))\r
539         ) {\r
540             if ('application/x-www-form-urlencoded' == $this->headers['content-type']) {\r
541                 $body = http_build_query($this->postParams, '', '&');\r
542                 if (!$this->getConfig('use_brackets')) {\r
543                     $body = preg_replace('/%5B\d+%5D=/', '=', $body);\r
544                 }\r
545                 // support RFC 3986 by not encoding '~' symbol (request #15368)\r
546                 return str_replace('%7E', '~', $body);\r
547 \r
548             } elseif ('multipart/form-data' == $this->headers['content-type']) {\r
549                 require_once 'HTTP/Request2/MultipartBody.php';\r
550                 return new HTTP_Request2_MultipartBody(\r
551                     $this->postParams, $this->uploads, $this->getConfig('use_brackets')\r
552                 );\r
553             }\r
554         }\r
555         return $this->body;\r
556     }\r
557 \r
558    /**\r
559     * Adds a file to form-based file upload\r
560     *\r
561     * Used to emulate file upload via a HTML form. The method also sets\r
562     * Content-Type of HTTP request to 'multipart/form-data'.\r
563     *\r
564     * If you just want to send the contents of a file as the body of HTTP\r
565     * request you should use setBody() method.\r
566     *\r
567     * @param    string  name of file-upload field\r
568     * @param    mixed   full name of local file\r
569     * @param    string  filename to send in the request \r
570     * @param    string  content-type of file being uploaded\r
571     * @return   HTTP_Request2\r
572     * @throws   HTTP_Request2_Exception\r
573     */\r
574     public function addUpload($fieldName, $filename, $sendFilename = null,\r
575                               $contentType = null)\r
576     {\r
577         if (!is_array($filename)) {\r
578             if (!($fp = @fopen($filename, 'rb'))) {\r
579                 throw new HTTP_Request2_Exception("Cannot open file {$filename}");\r
580             }\r
581             $this->uploads[$fieldName] = array(\r
582                 'fp'        => $fp,\r
583                 'filename'  => empty($sendFilename)? basename($filename): $sendFilename,\r
584                 'size'      => filesize($filename),\r
585                 'type'      => empty($contentType)? self::detectMimeType($filename): $contentType\r
586             );\r
587         } else {\r
588             $fps = $names = $sizes = $types = array();\r
589             foreach ($filename as $f) {\r
590                 if (!is_array($f)) {\r
591                     $f = array($f);\r
592                 }\r
593                 if (!($fp = @fopen($f[0], 'rb'))) {\r
594                     throw new HTTP_Request2_Exception("Cannot open file {$f[0]}");\r
595                 }\r
596                 $fps[]   = $fp;\r
597                 $names[] = empty($f[1])? basename($f[0]): $f[1];\r
598                 $sizes[] = filesize($f[0]);\r
599                 $types[] = empty($f[2])? self::detectMimeType($f[0]): $f[2];\r
600             }\r
601             $this->uploads[$fieldName] = array(\r
602                 'fp' => $fps, 'filename' => $names, 'size' => $sizes, 'type' => $types\r
603             );\r
604         }\r
605         if (empty($this->headers['content-type']) ||\r
606             'application/x-www-form-urlencoded' == $this->headers['content-type']\r
607         ) {\r
608             $this->setHeader('content-type', 'multipart/form-data');\r
609         }\r
610 \r
611         return $this;\r
612     }\r
613 \r
614    /**\r
615     * Adds POST parameter(s) to the request.\r
616     *\r
617     * @param    string|array    parameter name or array ('name' => 'value')\r
618     * @param    mixed           parameter value (can be an array)\r
619     * @return   HTTP_Request2\r
620     */\r
621     public function addPostParameter($name, $value = null)\r
622     {\r
623         if (!is_array($name)) {\r
624             $this->postParams[$name] = $value;\r
625         } else {\r
626             foreach ($name as $k => $v) {\r
627                 $this->addPostParameter($k, $v);\r
628             }\r
629         }\r
630         if (empty($this->headers['content-type'])) {\r
631             $this->setHeader('content-type', 'application/x-www-form-urlencoded');\r
632         }\r
633 \r
634         return $this;\r
635     }\r
636 \r
637    /**\r
638     * Attaches a new observer\r
639     *\r
640     * @param    SplObserver\r
641     */\r
642     public function attach(SplObserver $observer)\r
643     {\r
644         foreach ($this->observers as $attached) {\r
645             if ($attached === $observer) {\r
646                 return;\r
647             }\r
648         }\r
649         $this->observers[] = $observer;\r
650     }\r
651 \r
652    /**\r
653     * Detaches an existing observer\r
654     *\r
655     * @param    SplObserver\r
656     */\r
657     public function detach(SplObserver $observer)\r
658     {\r
659         foreach ($this->observers as $key => $attached) {\r
660             if ($attached === $observer) {\r
661                 unset($this->observers[$key]);\r
662                 return;\r
663             }\r
664         }\r
665     }\r
666 \r
667    /**\r
668     * Notifies all observers\r
669     */\r
670     public function notify()\r
671     {\r
672         foreach ($this->observers as $observer) {\r
673             $observer->update($this);\r
674         }\r
675     }\r
676 \r
677    /**\r
678     * Sets the last event\r
679     *\r
680     * Adapters should use this method to set the current state of the request\r
681     * and notify the observers.\r
682     *\r
683     * @param    string  event name\r
684     * @param    mixed   event data\r
685     */\r
686     public function setLastEvent($name, $data = null)\r
687     {\r
688         $this->lastEvent = array(\r
689             'name' => $name,\r
690             'data' => $data\r
691         );\r
692         $this->notify();\r
693     }\r
694 \r
695    /**\r
696     * Returns the last event\r
697     *\r
698     * Observers should use this method to access the last change in request.\r
699     * The following event names are possible:\r
700     * <ul>\r
701     *   <li>'connect'                 - after connection to remote server,\r
702     *                                   data is the destination (string)</li>\r
703     *   <li>'disconnect'              - after disconnection from server</li>\r
704     *   <li>'sentHeaders'             - after sending the request headers,\r
705     *                                   data is the headers sent (string)</li>\r
706     *   <li>'sentBodyPart'            - after sending a part of the request body, \r
707     *                                   data is the length of that part (int)</li>\r
708     *   <li>'receivedHeaders'         - after receiving the response headers,\r
709     *                                   data is HTTP_Request2_Response object</li>\r
710     *   <li>'receivedBodyPart'        - after receiving a part of the response\r
711     *                                   body, data is that part (string)</li>\r
712     *   <li>'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still\r
713     *                                   encoded by Content-Encoding</li>\r
714     *   <li>'receivedBody'            - after receiving the complete response\r
715     *                                   body, data is HTTP_Request2_Response object</li>\r
716     * </ul>\r
717     * Different adapters may not send all the event types. Mock adapter does\r
718     * not send any events to the observers.\r
719     *\r
720     * @return   array   The array has two keys: 'name' and 'data'\r
721     */\r
722     public function getLastEvent()\r
723     {\r
724         return $this->lastEvent;\r
725     }\r
726 \r
727    /**\r
728     * Sets the adapter used to actually perform the request\r
729     *\r
730     * You can pass either an instance of a class implementing HTTP_Request2_Adapter\r
731     * or a class name. The method will only try to include a file if the class\r
732     * name starts with HTTP_Request2_Adapter_, it will also try to prepend this\r
733     * prefix to the class name if it doesn't contain any underscores, so that\r
734     * <code>\r
735     * $request->setAdapter('curl');\r
736     * </code>\r
737     * will work.\r
738     *\r
739     * @param    string|HTTP_Request2_Adapter\r
740     * @return   HTTP_Request2\r
741     * @throws   HTTP_Request2_Exception\r
742     */\r
743     public function setAdapter($adapter)\r
744     {\r
745         if (is_string($adapter)) {\r
746             if (!class_exists($adapter, false)) {\r
747                 if (false === strpos($adapter, '_')) {\r
748                     $adapter = 'HTTP_Request2_Adapter_' . ucfirst($adapter);\r
749                 }\r
750                 if (preg_match('/^HTTP_Request2_Adapter_([a-zA-Z0-9]+)$/', $adapter)) {\r
751                     include_once str_replace('_', DIRECTORY_SEPARATOR, $adapter) . '.php';\r
752                 }\r
753                 if (!class_exists($adapter, false)) {\r
754                     throw new HTTP_Request2_Exception("Class {$adapter} not found");\r
755                 }\r
756             }\r
757             $adapter = new $adapter;\r
758         }\r
759         if (!$adapter instanceof HTTP_Request2_Adapter) {\r
760             throw new HTTP_Request2_Exception('Parameter is not a HTTP request adapter');\r
761         }\r
762         $this->adapter = $adapter;\r
763 \r
764         return $this;\r
765     }\r
766 \r
767    /**\r
768     * Sends the request and returns the response\r
769     *\r
770     * @throws   HTTP_Request2_Exception\r
771     * @return   HTTP_Request2_Response\r
772     */\r
773     public function send()\r
774     {\r
775         // Sanity check for URL\r
776         if (!$this->url instanceof Net_URL2) {\r
777             throw new HTTP_Request2_Exception('No URL given');\r
778         } elseif (!$this->url->isAbsolute()) {\r
779             throw new HTTP_Request2_Exception('Absolute URL required');\r
780         } elseif (!in_array(strtolower($this->url->getScheme()), array('https', 'http'))) {\r
781             throw new HTTP_Request2_Exception('Not a HTTP URL');\r
782         }\r
783         if (empty($this->adapter)) {\r
784             $this->setAdapter($this->getConfig('adapter'));\r
785         }\r
786         // magic_quotes_runtime may break file uploads and chunked response\r
787         // processing; see bug #4543\r
788         if ($magicQuotes = ini_get('magic_quotes_runtime')) {\r
789             ini_set('magic_quotes_runtime', false);\r
790         }\r
791         // force using single byte encoding if mbstring extension overloads\r
792         // strlen() and substr(); see bug #1781, bug #10605\r
793         if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {\r
794             $oldEncoding = mb_internal_encoding();\r
795             mb_internal_encoding('iso-8859-1');\r
796         }\r
797 \r
798         try {\r
799             $response = $this->adapter->sendRequest($this);\r
800         } catch (Exception $e) {\r
801         }\r
802         // cleanup in either case (poor man's "finally" clause)\r
803         if ($magicQuotes) {\r
804             ini_set('magic_quotes_runtime', true);\r
805         }\r
806         if (!empty($oldEncoding)) {\r
807             mb_internal_encoding($oldEncoding);\r
808         }\r
809         // rethrow the exception\r
810         if (!empty($e)) {\r
811             throw $e;\r
812         }\r
813         return $response;\r
814     }\r
815 \r
816    /**\r
817     * Tries to detect MIME type of a file\r
818     *\r
819     * The method will try to use fileinfo extension if it is available,\r
820     * deprecated mime_content_type() function in the other case. If neither\r
821     * works, default 'application/octet-stream' MIME type is returned\r
822     *\r
823     * @param    string  filename\r
824     * @return   string  file MIME type\r
825     */\r
826     protected static function detectMimeType($filename)\r
827     {\r
828         // finfo extension from PECL available \r
829         if (function_exists('finfo_open')) {\r
830             if (!isset(self::$_fileinfoDb)) {\r
831                 self::$_fileinfoDb = @finfo_open(FILEINFO_MIME);\r
832             }\r
833             if (self::$_fileinfoDb) { \r
834                 $info = finfo_file(self::$_fileinfoDb, $filename);\r
835             }\r
836         }\r
837         // (deprecated) mime_content_type function available\r
838         if (empty($info) && function_exists('mime_content_type')) {\r
839             return mime_content_type($filename);\r
840         }\r
841         return empty($info)? 'application/octet-stream': $info;\r
842     }\r
843 }\r
844 ?>