]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
update HTTP_Request2 to 2.0.0RC1
authorEvan Prodromou <evan@status.net>
Wed, 22 Jun 2011 19:56:27 +0000 (15:56 -0400)
committerEvan Prodromou <evan@status.net>
Wed, 22 Jun 2011 19:56:27 +0000 (15:56 -0400)
extlib/HTTP/Request2.php
extlib/HTTP/Request2/Adapter.php
extlib/HTTP/Request2/Adapter/Curl.php
extlib/HTTP/Request2/Adapter/Mock.php
extlib/HTTP/Request2/Adapter/Socket.php
extlib/HTTP/Request2/Exception.php
extlib/HTTP/Request2/MultipartBody.php
extlib/HTTP/Request2/Observer/Log.php
extlib/HTTP/Request2/Response.php

index e06bb86bca0ca270915ccb28c269464cae35a822..60beeaf2ef5f88f547b33e04f7c89ef32026714c 100644 (file)
@@ -1,12 +1,12 @@
 <?php\r
 /**\r
- * Class representing a HTTP request\r
+ * Class representing a HTTP request message\r
  *\r
  * PHP version 5\r
  *\r
  * LICENSE:\r
  *\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
@@ -37,7 +37,7 @@
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version    CVS: $Id: Request2.php 278226 2009-04-03 21:32:48Z avb $\r
+ * @version    SVN: $Id: Request2.php 308735 2011-02-27 20:31:28Z avb $\r
  * @link       http://pear.php.net/package/HTTP_Request2\r
  */\r
 \r
@@ -48,16 +48,16 @@ require_once 'Net/URL2.php';
 \r
 /**\r
  * Exception class for HTTP_Request2 package\r
- */ \r
+ */\r
 require_once 'HTTP/Request2/Exception.php';\r
 \r
 /**\r
- * Class representing a HTTP request\r
+ * Class representing a HTTP request message\r
  *\r
  * @category   HTTP\r
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
- * @version    Release: 0.4.1\r
+ * @version    Release: 2.0.0RC1\r
  * @link       http://tools.ietf.org/html/rfc2616#section-5\r
  */\r
 class HTTP_Request2 implements SplSubject\r
@@ -78,7 +78,7 @@ class HTTP_Request2 implements SplSubject
    /**#@-*/\r
 \r
    /**#@+\r
-    * Constants for HTTP authentication schemes \r
+    * Constants for HTTP authentication schemes\r
     *\r
     * @link http://tools.ietf.org/html/rfc2617\r
     */\r
@@ -95,7 +95,7 @@ class HTTP_Request2 implements SplSubject
    /**\r
     * Regular expression used to check for invalid symbols in cookie strings\r
     * @link http://pear.php.net/bugs/bug.php?id=15630\r
-    * @link http://cgi.netscape.com/newsref/std/cookie_spec.html\r
+    * @link http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html\r
     */\r
     const REGEXP_INVALID_COOKIE = '/[\s,;]/';\r
 \r
@@ -164,7 +164,11 @@ class HTTP_Request2 implements SplSubject
         'ssl_local_cert'    => null,\r
         'ssl_passphrase'    => null,\r
 \r
-        'digest_compat_ie'  => false\r
+        'digest_compat_ie'  => false,\r
+\r
+        'follow_redirects'  => false,\r
+        'max_redirects'     => 5,\r
+        'strict_redirects'  => false\r
     );\r
 \r
    /**\r
@@ -191,7 +195,7 @@ class HTTP_Request2 implements SplSubject
     protected $postParams = array();\r
 \r
    /**\r
-    * Array of file uploads (for multipart/form-data POST requests) \r
+    * Array of file uploads (for multipart/form-data POST requests)\r
     * @var  array\r
     */\r
     protected $uploads = array();\r
@@ -202,11 +206,16 @@ class HTTP_Request2 implements SplSubject
     */\r
     protected $adapter;\r
 \r
+   /**\r
+    * Cookie jar to persist cookies between requests\r
+    * @var HTTP_Request2_CookieJar\r
+    */\r
+    protected $cookieJar = null;\r
 \r
    /**\r
     * Constructor. Can set request URL, method and configuration array.\r
     *\r
-    * Also sets a default value for User-Agent header. \r
+    * Also sets a default value for User-Agent header.\r
     *\r
     * @param    string|Net_Url2     Request URL\r
     * @param    string              Request method\r
@@ -214,14 +223,14 @@ class HTTP_Request2 implements SplSubject
     */\r
     public function __construct($url = null, $method = self::METHOD_GET, array $config = array())\r
     {\r
+        $this->setConfig($config);\r
         if (!empty($url)) {\r
             $this->setUrl($url);\r
         }\r
         if (!empty($method)) {\r
             $this->setMethod($method);\r
         }\r
-        $this->setConfig($config);\r
-        $this->setHeader('user-agent', 'HTTP_Request2/0.4.1 ' .\r
+        $this->setHeader('user-agent', 'HTTP_Request2/2.0.0RC1 ' .\r
                          '(http://pear.php.net/package/http_request2) ' .\r
                          'PHP/' . phpversion());\r
     }\r
@@ -235,15 +244,20 @@ class HTTP_Request2 implements SplSubject
     *\r
     * @param    string|Net_URL2 Request URL\r
     * @return   HTTP_Request2\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_LogicException\r
     */\r
     public function setUrl($url)\r
     {\r
         if (is_string($url)) {\r
-            $url = new Net_URL2($url);\r
+            $url = new Net_URL2(\r
+                $url, array(Net_URL2::OPTION_USE_BRACKETS => $this->config['use_brackets'])\r
+            );\r
         }\r
         if (!$url instanceof Net_URL2) {\r
-            throw new HTTP_Request2_Exception('Parameter is not a valid HTTP URL');\r
+            throw new HTTP_Request2_LogicException(\r
+                'Parameter is not a valid HTTP URL',\r
+                HTTP_Request2_Exception::INVALID_ARGUMENT\r
+            );\r
         }\r
         // URL contains username / password?\r
         if ($url->getUserinfo()) {\r
@@ -275,13 +289,16 @@ class HTTP_Request2 implements SplSubject
     *\r
     * @param    string\r
     * @return   HTTP_Request2\r
-    * @throws   HTTP_Request2_Exception if the method name is invalid\r
+    * @throws   HTTP_Request2_LogicException if the method name is invalid\r
     */\r
     public function setMethod($method)\r
     {\r
         // Method name should be a token: http://tools.ietf.org/html/rfc2616#section-5.1.1\r
         if (preg_match(self::REGEXP_INVALID_TOKEN, $method)) {\r
-            throw new HTTP_Request2_Exception("Invalid request method '{$method}'");\r
+            throw new HTTP_Request2_LogicException(\r
+                "Invalid request method '{$method}'",\r
+                HTTP_Request2_Exception::INVALID_ARGUMENT\r
+            );\r
         }\r
         $this->method = $method;\r
 \r
@@ -306,7 +323,7 @@ class HTTP_Request2 implements SplSubject
     *   <li> 'adapter'           - adapter to use (string)</li>\r
     *   <li> 'connect_timeout'   - Connection timeout in seconds (integer)</li>\r
     *   <li> 'timeout'           - Total number of seconds a request can take.\r
-    *                              Use 0 for no limit, should be greater than \r
+    *                              Use 0 for no limit, should be greater than\r
     *                              'connect_timeout' if set (integer)</li>\r
     *   <li> 'use_brackets'      - Whether to append [] to array variable names (bool)</li>\r
     *   <li> 'protocol_version'  - HTTP Version to use, '1.0' or '1.1' (string)</li>\r
@@ -324,7 +341,7 @@ class HTTP_Request2 implements SplSubject
     *                              certificate matches host name (bool)</li>\r
     *   <li> 'ssl_cafile'        - Cerificate Authority file to verify the peer\r
     *                              with (use with 'ssl_verify_peer') (string)</li>\r
-    *   <li> 'ssl_capath'        - Directory holding multiple Certificate \r
+    *   <li> 'ssl_capath'        - Directory holding multiple Certificate\r
     *                              Authority files (string)</li>\r
     *   <li> 'ssl_local_cert'    - Name of a file containing local cerificate (string)</li>\r
     *   <li> 'ssl_passphrase'    - Passphrase with which local certificate\r
@@ -332,13 +349,19 @@ class HTTP_Request2 implements SplSubject
     *   <li> 'digest_compat_ie'  - Whether to imitate behaviour of MSIE 5 and 6\r
     *                              in using URL without query string in digest\r
     *                              authentication (boolean)</li>\r
+    *   <li> 'follow_redirects'  - Whether to automatically follow HTTP Redirects (boolean)</li>\r
+    *   <li> 'max_redirects'     - Maximum number of redirects to follow (integer)</li>\r
+    *   <li> 'strict_redirects'  - Whether to keep request method on redirects via status 301 and\r
+    *                              302 (true, needed for compatibility with RFC 2616)\r
+    *                              or switch to GET (false, needed for compatibility with most\r
+    *                              browsers) (boolean)</li>\r
     * </ul>\r
     *\r
     * @param    string|array    configuration parameter name or array\r
     *                           ('parameter name' => 'parameter value')\r
     * @param    mixed           parameter value if $nameOrConfig is not an array\r
     * @return   HTTP_Request2\r
-    * @throws   HTTP_Request2_Exception If the parameter is unknown\r
+    * @throws   HTTP_Request2_LogicException If the parameter is unknown\r
     */\r
     public function setConfig($nameOrConfig, $value = null)\r
     {\r
@@ -349,8 +372,9 @@ class HTTP_Request2 implements SplSubject
 \r
         } else {\r
             if (!array_key_exists($nameOrConfig, $this->config)) {\r
-                throw new HTTP_Request2_Exception(\r
-                    "Unknown configuration parameter '{$nameOrConfig}'"\r
+                throw new HTTP_Request2_LogicException(\r
+                    "Unknown configuration parameter '{$nameOrConfig}'",\r
+                    HTTP_Request2_Exception::INVALID_ARGUMENT\r
                 );\r
             }\r
             $this->config[$nameOrConfig] = $value;\r
@@ -363,17 +387,18 @@ class HTTP_Request2 implements SplSubject
     * Returns the value(s) of the configuration parameter(s)\r
     *\r
     * @param    string  parameter name\r
-    * @return   mixed   value of $name parameter, array of all configuration \r
+    * @return   mixed   value of $name parameter, array of all configuration\r
     *                   parameters if $name is not given\r
-    * @throws   HTTP_Request2_Exception If the parameter is unknown\r
+    * @throws   HTTP_Request2_LogicException If the parameter is unknown\r
     */\r
     public function getConfig($name = null)\r
     {\r
         if (null === $name) {\r
             return $this->config;\r
         } elseif (!array_key_exists($name, $this->config)) {\r
-            throw new HTTP_Request2_Exception(\r
-                "Unknown configuration parameter '{$name}'"\r
+            throw new HTTP_Request2_LogicException(\r
+                "Unknown configuration parameter '{$name}'",\r
+                HTTP_Request2_Exception::INVALID_ARGUMENT\r
             );\r
         }\r
         return $this->config[$name];\r
@@ -386,7 +411,7 @@ class HTTP_Request2 implements SplSubject
     * @param    string  password\r
     * @param    string  authentication scheme\r
     * @return   HTTP_Request2\r
-    */ \r
+    */\r
     public function setAuth($user, $password = '', $scheme = self::AUTH_BASIC)\r
     {\r
         if (empty($user)) {\r
@@ -419,13 +444,13 @@ class HTTP_Request2 implements SplSubject
     * Sets request header(s)\r
     *\r
     * The first parameter may be either a full header string 'header: value' or\r
-    * header name. In the former case $value parameter is ignored, in the latter \r
+    * header name. In the former case $value parameter is ignored, in the latter\r
     * the header's value will either be set to $value or the header will be\r
     * removed if $value is null. The first parameter can also be an array of\r
     * headers, in that case method will be called recursively.\r
     *\r
     * Note that headers are treated case insensitively as per RFC 2616.\r
-    * \r
+    *\r
     * <code>\r
     * $req->setHeader('Foo: Bar'); // sets the value of 'Foo' header to 'Bar'\r
     * $req->setHeader('FoO', 'Baz'); // sets the value of 'Foo' header to 'Baz'\r
@@ -435,18 +460,21 @@ class HTTP_Request2 implements SplSubject
     *\r
     * @param    string|array    header name, header string ('Header: value')\r
     *                           or an array of headers\r
-    * @param    string|null     header value, header will be removed if null\r
+    * @param    string|array|null header value if $name is not an array,\r
+    *                           header will be removed if value is null\r
+    * @param    bool            whether to replace previous header with the\r
+    *                           same name or append to its value\r
     * @return   HTTP_Request2\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_LogicException\r
     */\r
-    public function setHeader($name, $value = null)\r
+    public function setHeader($name, $value = null, $replace = true)\r
     {\r
         if (is_array($name)) {\r
             foreach ($name as $k => $v) {\r
                 if (is_string($k)) {\r
-                    $this->setHeader($k, $v);\r
+                    $this->setHeader($k, $v, $replace);\r
                 } else {\r
-                    $this->setHeader($v);\r
+                    $this->setHeader($v, null, $replace);\r
                 }\r
             }\r
         } else {\r
@@ -455,17 +483,30 @@ class HTTP_Request2 implements SplSubject
             }\r
             // Header name should be a token: http://tools.ietf.org/html/rfc2616#section-4.2\r
             if (preg_match(self::REGEXP_INVALID_TOKEN, $name)) {\r
-                throw new HTTP_Request2_Exception("Invalid header name '{$name}'");\r
+                throw new HTTP_Request2_LogicException(\r
+                    "Invalid header name '{$name}'",\r
+                    HTTP_Request2_Exception::INVALID_ARGUMENT\r
+                );\r
             }\r
             // Header names are case insensitive anyway\r
             $name = strtolower($name);\r
             if (null === $value) {\r
                 unset($this->headers[$name]);\r
+\r
             } else {\r
-                $this->headers[$name] = $value;\r
+                if (is_array($value)) {\r
+                    $value = implode(', ', array_map('trim', $value));\r
+                } elseif (is_string($value)) {\r
+                    $value = trim($value);\r
+                }\r
+                if (!isset($this->headers[$name]) || $replace) {\r
+                    $this->headers[$name] = $value;\r
+                } else {\r
+                    $this->headers[$name] .= ', ' . $value;\r
+                }\r
             }\r
         }\r
-        \r
+\r
         return $this;\r
     }\r
 \r
@@ -483,21 +524,39 @@ class HTTP_Request2 implements SplSubject
     }\r
 \r
    /**\r
-    * Appends a cookie to "Cookie:" header\r
+    * Adds a cookie to the request\r
+    *\r
+    * If the request does not have a CookieJar object set, this method simply\r
+    * appends a cookie to "Cookie:" header.\r
+    *\r
+    * If a CookieJar object is available, the cookie is stored in that object.\r
+    * Data from request URL will be used for setting its 'domain' and 'path'\r
+    * parameters, 'expires' and 'secure' will be set to null and false,\r
+    * respectively. If you need further control, use CookieJar's methods.\r
     *\r
     * @param    string  cookie name\r
     * @param    string  cookie value\r
     * @return   HTTP_Request2\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_LogicException\r
+    * @see      setCookieJar()\r
     */\r
     public function addCookie($name, $value)\r
     {\r
-        $cookie = $name . '=' . $value;\r
-        if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) {\r
-            throw new HTTP_Request2_Exception("Invalid cookie: '{$cookie}'");\r
+        if (!empty($this->cookieJar)) {\r
+            $this->cookieJar->store(array('name' => $name, 'value' => $value),\r
+                                    $this->url);\r
+\r
+        } else {\r
+            $cookie = $name . '=' . $value;\r
+            if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) {\r
+                throw new HTTP_Request2_LogicException(\r
+                    "Invalid cookie: '{$cookie}'",\r
+                    HTTP_Request2_Exception::INVALID_ARGUMENT\r
+                );\r
+            }\r
+            $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; ';\r
+            $this->setHeader('cookie', $cookies . $cookie);\r
         }\r
-        $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; ';\r
-        $this->setHeader('cookie', $cookies . $cookie);\r
 \r
         return $this;\r
     }\r
@@ -505,24 +564,32 @@ class HTTP_Request2 implements SplSubject
    /**\r
     * Sets the request body\r
     *\r
-    * @param    string  Either a string with the body or filename containing body\r
+    * If you provide file pointer rather than file name, it should support\r
+    * fstat() and rewind() operations.\r
+    *\r
+    * @param    string|resource|HTTP_Request2_MultipartBody  Either a string\r
+    *               with the body or filename containing body or pointer to\r
+    *               an open file or object with multipart body data\r
     * @param    bool    Whether first parameter is a filename\r
     * @return   HTTP_Request2\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_LogicException\r
     */\r
     public function setBody($body, $isFilename = false)\r
     {\r
-        if (!$isFilename) {\r
-            $this->body = (string)$body;\r
-        } else {\r
-            if (!($fp = @fopen($body, 'rb'))) {\r
-                throw new HTTP_Request2_Exception("Cannot open file {$body}");\r
+        if (!$isFilename && !is_resource($body)) {\r
+            if (!$body instanceof HTTP_Request2_MultipartBody) {\r
+                $this->body = (string)$body;\r
+            } else {\r
+                $this->body = $body;\r
             }\r
-            $this->body = $fp;\r
+        } else {\r
+            $fileData = $this->fopenWrapper($body, empty($this->headers['content-type']));\r
+            $this->body = $fileData['fp'];\r
             if (empty($this->headers['content-type'])) {\r
-                $this->setHeader('content-type', self::detectMimeType($body));\r
+                $this->setHeader('content-type', $fileData['type']);\r
             }\r
         }\r
+        $this->postParams = $this->uploads = array();\r
 \r
         return $this;\r
     }\r
@@ -534,10 +601,10 @@ class HTTP_Request2 implements SplSubject
     */\r
     public function getBody()\r
     {\r
-        if (self::METHOD_POST == $this->method && \r
+        if (self::METHOD_POST == $this->method &&\r
             (!empty($this->postParams) || !empty($this->uploads))\r
         ) {\r
-            if ('application/x-www-form-urlencoded' == $this->headers['content-type']) {\r
+            if (0 === strpos($this->headers['content-type'], 'application/x-www-form-urlencoded')) {\r
                 $body = http_build_query($this->postParams, '', '&');\r
                 if (!$this->getConfig('use_brackets')) {\r
                     $body = preg_replace('/%5B\d+%5D=/', '=', $body);\r
@@ -545,7 +612,7 @@ class HTTP_Request2 implements SplSubject
                 // support RFC 3986 by not encoding '~' symbol (request #15368)\r
                 return str_replace('%7E', '~', $body);\r
 \r
-            } elseif ('multipart/form-data' == $this->headers['content-type']) {\r
+            } elseif (0 === strpos($this->headers['content-type'], 'multipart/form-data')) {\r
                 require_once 'HTTP/Request2/MultipartBody.php';\r
                 return new HTTP_Request2_MultipartBody(\r
                     $this->postParams, $this->uploads, $this->getConfig('use_brackets')\r
@@ -564,25 +631,28 @@ class HTTP_Request2 implements SplSubject
     * If you just want to send the contents of a file as the body of HTTP\r
     * request you should use setBody() method.\r
     *\r
+    * If you provide file pointers rather than file names, they should support\r
+    * fstat() and rewind() operations.\r
+    *\r
     * @param    string  name of file-upload field\r
-    * @param    mixed   full name of local file\r
-    * @param    string  filename to send in the request \r
+    * @param    string|resource|array   full name of local file, pointer to\r
+    *               open file or an array of files\r
+    * @param    string  filename to send in the request\r
     * @param    string  content-type of file being uploaded\r
     * @return   HTTP_Request2\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_LogicException\r
     */\r
     public function addUpload($fieldName, $filename, $sendFilename = null,\r
                               $contentType = null)\r
     {\r
         if (!is_array($filename)) {\r
-            if (!($fp = @fopen($filename, 'rb'))) {\r
-                throw new HTTP_Request2_Exception("Cannot open file {$filename}");\r
-            }\r
+            $fileData = $this->fopenWrapper($filename, empty($contentType));\r
             $this->uploads[$fieldName] = array(\r
-                'fp'        => $fp,\r
-                'filename'  => empty($sendFilename)? basename($filename): $sendFilename,\r
-                'size'      => filesize($filename),\r
-                'type'      => empty($contentType)? self::detectMimeType($filename): $contentType\r
+                'fp'        => $fileData['fp'],\r
+                'filename'  => !empty($sendFilename)? $sendFilename\r
+                                :(is_string($filename)? basename($filename): 'anonymous.blob') ,\r
+                'size'      => $fileData['size'],\r
+                'type'      => empty($contentType)? $fileData['type']: $contentType\r
             );\r
         } else {\r
             $fps = $names = $sizes = $types = array();\r
@@ -590,13 +660,12 @@ class HTTP_Request2 implements SplSubject
                 if (!is_array($f)) {\r
                     $f = array($f);\r
                 }\r
-                if (!($fp = @fopen($f[0], 'rb'))) {\r
-                    throw new HTTP_Request2_Exception("Cannot open file {$f[0]}");\r
-                }\r
-                $fps[]   = $fp;\r
-                $names[] = empty($f[1])? basename($f[0]): $f[1];\r
-                $sizes[] = filesize($f[0]);\r
-                $types[] = empty($f[2])? self::detectMimeType($f[0]): $f[2];\r
+                $fileData = $this->fopenWrapper($f[0], empty($f[2]));\r
+                $fps[]   = $fileData['fp'];\r
+                $names[] = !empty($f[1])? $f[1]\r
+                            :(is_string($f[0])? basename($f[0]): 'anonymous.blob');\r
+                $sizes[] = $fileData['size'];\r
+                $types[] = empty($f[2])? $fileData['type']: $f[2];\r
             }\r
             $this->uploads[$fieldName] = array(\r
                 'fp' => $fps, 'filename' => $names, 'size' => $sizes, 'type' => $types\r
@@ -703,8 +772,10 @@ class HTTP_Request2 implements SplSubject
     *   <li>'disconnect'              - after disconnection from server</li>\r
     *   <li>'sentHeaders'             - after sending the request headers,\r
     *                                   data is the headers sent (string)</li>\r
-    *   <li>'sentBodyPart'            - after sending a part of the request body, \r
+    *   <li>'sentBodyPart'            - after sending a part of the request body,\r
     *                                   data is the length of that part (int)</li>\r
+    *   <li>'sentBody'                - after sending the whole request body,\r
+    *                                   data is request body length (int)</li>\r
     *   <li>'receivedHeaders'         - after receiving the response headers,\r
     *                                   data is HTTP_Request2_Response object</li>\r
     *   <li>'receivedBodyPart'        - after receiving a part of the response\r
@@ -738,7 +809,7 @@ class HTTP_Request2 implements SplSubject
     *\r
     * @param    string|HTTP_Request2_Adapter\r
     * @return   HTTP_Request2\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_LogicException\r
     */\r
     public function setAdapter($adapter)\r
     {\r
@@ -751,19 +822,67 @@ class HTTP_Request2 implements SplSubject
                     include_once str_replace('_', DIRECTORY_SEPARATOR, $adapter) . '.php';\r
                 }\r
                 if (!class_exists($adapter, false)) {\r
-                    throw new HTTP_Request2_Exception("Class {$adapter} not found");\r
+                    throw new HTTP_Request2_LogicException(\r
+                        "Class {$adapter} not found",\r
+                        HTTP_Request2_Exception::MISSING_VALUE\r
+                    );\r
                 }\r
             }\r
             $adapter = new $adapter;\r
         }\r
         if (!$adapter instanceof HTTP_Request2_Adapter) {\r
-            throw new HTTP_Request2_Exception('Parameter is not a HTTP request adapter');\r
+            throw new HTTP_Request2_LogicException(\r
+                'Parameter is not a HTTP request adapter',\r
+                HTTP_Request2_Exception::INVALID_ARGUMENT\r
+            );\r
         }\r
         $this->adapter = $adapter;\r
 \r
         return $this;\r
     }\r
 \r
+   /**\r
+    * Sets the cookie jar\r
+    *\r
+    * A cookie jar is used to maintain cookies across HTTP requests and\r
+    * responses. Cookies from jar will be automatically added to the request\r
+    * headers based on request URL.\r
+    *\r
+    * @param HTTP_Request2_CookieJar|bool   Existing CookieJar object, true to\r
+    *                                       create a new one, false to remove\r
+    */\r
+    public function setCookieJar($jar = true)\r
+    {\r
+        if (!class_exists('HTTP_Request2_CookieJar', false)) {\r
+            require_once 'HTTP/Request2/CookieJar.php';\r
+        }\r
+\r
+        if ($jar instanceof HTTP_Request2_CookieJar) {\r
+            $this->cookieJar = $jar;\r
+        } elseif (true === $jar) {\r
+            $this->cookieJar = new HTTP_Request2_CookieJar();\r
+        } elseif (!$jar) {\r
+            $this->cookieJar = null;\r
+        } else {\r
+            throw new HTTP_Request2_LogicException(\r
+                'Invalid parameter passed to setCookieJar()',\r
+                HTTP_Request2_Exception::INVALID_ARGUMENT\r
+            );\r
+        }\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Returns current CookieJar object or null if none\r
+    *\r
+    * @return HTTP_Request2_CookieJar|null\r
+    */\r
+    public function getCookieJar()\r
+    {\r
+        return $this->cookieJar;\r
+    }\r
+\r
    /**\r
     * Sends the request and returns the response\r
     *\r
@@ -773,20 +892,25 @@ class HTTP_Request2 implements SplSubject
     public function send()\r
     {\r
         // Sanity check for URL\r
-        if (!$this->url instanceof Net_URL2) {\r
-            throw new HTTP_Request2_Exception('No URL given');\r
-        } elseif (!$this->url->isAbsolute()) {\r
-            throw new HTTP_Request2_Exception('Absolute URL required');\r
-        } elseif (!in_array(strtolower($this->url->getScheme()), array('https', 'http'))) {\r
-            throw new HTTP_Request2_Exception('Not a HTTP URL');\r
+        if (!$this->url instanceof Net_URL2\r
+            || !$this->url->isAbsolute()\r
+            || !in_array(strtolower($this->url->getScheme()), array('https', 'http'))\r
+        ) {\r
+            throw new HTTP_Request2_LogicException(\r
+                'HTTP_Request2 needs an absolute HTTP(S) request URL, '\r
+                . ($this->url instanceof Net_URL2\r
+                   ? 'none' : "'" . $this->url->__toString() . "'")\r
+                . ' given',\r
+                HTTP_Request2_Exception::INVALID_ARGUMENT\r
+            );\r
         }\r
         if (empty($this->adapter)) {\r
             $this->setAdapter($this->getConfig('adapter'));\r
         }\r
         // magic_quotes_runtime may break file uploads and chunked response\r
-        // processing; see bug #4543\r
-        if ($magicQuotes = ini_get('magic_quotes_runtime')) {\r
-            ini_set('magic_quotes_runtime', false);\r
+        // processing; see bug #4543. Don't use ini_get() here; see bug #16440.\r
+        if ($magicQuotes = get_magic_quotes_runtime()) {\r
+            set_magic_quotes_runtime(false);\r
         }\r
         // force using single byte encoding if mbstring extension overloads\r
         // strlen() and substr(); see bug #1781, bug #10605\r
@@ -801,7 +925,7 @@ class HTTP_Request2 implements SplSubject
         }\r
         // cleanup in either case (poor man's "finally" clause)\r
         if ($magicQuotes) {\r
-            ini_set('magic_quotes_runtime', true);\r
+            set_magic_quotes_runtime(true);\r
         }\r
         if (!empty($oldEncoding)) {\r
             mb_internal_encoding($oldEncoding);\r
@@ -813,6 +937,53 @@ class HTTP_Request2 implements SplSubject
         return $response;\r
     }\r
 \r
+   /**\r
+    * Wrapper around fopen()/fstat() used by setBody() and addUpload()\r
+    *\r
+    * @param  string|resource file name or pointer to open file\r
+    * @param  bool            whether to try autodetecting MIME type of file,\r
+    *                         will only work if $file is a filename, not pointer\r
+    * @return array array('fp' => file pointer, 'size' => file size, 'type' => MIME type)\r
+    * @throws HTTP_Request2_LogicException\r
+    */\r
+    protected function fopenWrapper($file, $detectType = false)\r
+    {\r
+        if (!is_string($file) && !is_resource($file)) {\r
+            throw new HTTP_Request2_LogicException(\r
+                "Filename or file pointer resource expected",\r
+                HTTP_Request2_Exception::INVALID_ARGUMENT\r
+            );\r
+        }\r
+        $fileData = array(\r
+            'fp'   => is_string($file)? null: $file,\r
+            'type' => 'application/octet-stream',\r
+            'size' => 0\r
+        );\r
+        if (is_string($file)) {\r
+            $track = @ini_set('track_errors', 1);\r
+            if (!($fileData['fp'] = @fopen($file, 'rb'))) {\r
+                $e = new HTTP_Request2_LogicException(\r
+                    $php_errormsg, HTTP_Request2_Exception::READ_ERROR\r
+                );\r
+            }\r
+            @ini_set('track_errors', $track);\r
+            if (isset($e)) {\r
+                throw $e;\r
+            }\r
+            if ($detectType) {\r
+                $fileData['type'] = self::detectMimeType($file);\r
+            }\r
+        }\r
+        if (!($stat = fstat($fileData['fp']))) {\r
+            throw new HTTP_Request2_LogicException(\r
+                "fstat() call failed", HTTP_Request2_Exception::READ_ERROR\r
+            );\r
+        }\r
+        $fileData['size'] = $stat['size'];\r
+\r
+        return $fileData;\r
+    }\r
+\r
    /**\r
     * Tries to detect MIME type of a file\r
     *\r
@@ -825,12 +996,12 @@ class HTTP_Request2 implements SplSubject
     */\r
     protected static function detectMimeType($filename)\r
     {\r
-        // finfo extension from PECL available \r
+        // finfo extension from PECL available\r
         if (function_exists('finfo_open')) {\r
             if (!isset(self::$_fileinfoDb)) {\r
                 self::$_fileinfoDb = @finfo_open(FILEINFO_MIME);\r
             }\r
-            if (self::$_fileinfoDb) { \r
+            if (self::$_fileinfoDb) {\r
                 $info = finfo_file(self::$_fileinfoDb, $filename);\r
             }\r
         }\r
index 39b092b346e451f962412ea4f51d81e674efa7c6..2cabbf897b8ddcbe3e2a54d549461d3d7e70ac3a 100644 (file)
@@ -6,7 +6,7 @@
  *\r
  * LICENSE:\r
  *\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
@@ -37,7 +37,7 @@
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version    CVS: $Id: Adapter.php 274684 2009-01-26 23:07:27Z avb $\r
+ * @version    SVN: $Id: Adapter.php 308322 2011-02-14 13:58:03Z avb $\r
  * @link       http://pear.php.net/package/HTTP_Request2\r
  */\r
 \r
@@ -50,13 +50,13 @@ require_once 'HTTP/Request2/Response.php';
  * Base class for HTTP_Request2 adapters\r
  *\r
  * HTTP_Request2 class itself only defines methods for aggregating the request\r
- * data, all actual work of sending the request to the remote server and \r
+ * data, all actual work of sending the request to the remote server and\r
  * receiving its response is performed by adapters.\r
  *\r
  * @category   HTTP\r
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
- * @version    Release: 0.4.1\r
+ * @version    Release: 2.0.0RC1\r
  */\r
 abstract class HTTP_Request2_Adapter\r
 {\r
@@ -109,8 +109,8 @@ abstract class HTTP_Request2_Adapter
    /**\r
     * Calculates length of the request body, adds proper headers\r
     *\r
-    * @param    array   associative array of request headers, this method will \r
-    *                   add proper 'Content-Length' and 'Content-Type' headers \r
+    * @param    array   associative array of request headers, this method will\r
+    *                   add proper 'Content-Length' and 'Content-Type' headers\r
     *                   to this array (or remove them if not needed)\r
     */\r
     protected function calculateRequestLength(&$headers)\r
@@ -133,13 +133,15 @@ abstract class HTTP_Request2_Adapter
         if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||\r
             0 == $this->contentLength\r
         ) {\r
-            unset($headers['content-type']);\r
             // No body: send a Content-Length header nonetheless (request #12900),\r
             // but do that only for methods that require a body (bug #14740)\r
             if (in_array($this->request->getMethod(), self::$bodyRequired)) {\r
                 $headers['content-length'] = 0;\r
             } else {\r
                 unset($headers['content-length']);\r
+                // if the method doesn't require a body and doesn't have a\r
+                // body, don't send a Content-Type header. (request #16799)\r
+                unset($headers['content-type']);\r
             }\r
         } else {\r
             if (empty($headers['content-type'])) {\r
index 4d4de0dcc7c1740dc1c64981257496974989494f..fecfbd7abceb7d48f39de33d90eaa55923a2486d 100644 (file)
@@ -6,7 +6,7 @@
  *\r
  * LICENSE:\r
  *\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
@@ -37,7 +37,7 @@
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version    CVS: $Id: Curl.php 278226 2009-04-03 21:32:48Z avb $\r
+ * @version    SVN: $Id: Curl.php 310800 2011-05-06 07:29:56Z avb $\r
  * @link       http://pear.php.net/package/HTTP_Request2\r
  */\r
 \r
@@ -52,7 +52,7 @@ require_once 'HTTP/Request2/Adapter.php';
  * @category    HTTP\r
  * @package     HTTP_Request2\r
  * @author      Alexey Borzov <avb@php.net>\r
- * @version     Release: 0.4.1\r
+ * @version     Release: 2.0.0RC1\r
  */\r
 class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter\r
 {\r
@@ -79,6 +79,46 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
         'ssl_passphrase'  => CURLOPT_SSLCERTPASSWD\r
    );\r
 \r
+   /**\r
+    * Mapping of CURLE_* constants to Exception subclasses and error codes\r
+    * @var  array\r
+    */\r
+    protected static $errorMap = array(\r
+        CURLE_UNSUPPORTED_PROTOCOL  => array('HTTP_Request2_MessageException',\r
+                                             HTTP_Request2_Exception::NON_HTTP_REDIRECT),\r
+        CURLE_COULDNT_RESOLVE_PROXY => array('HTTP_Request2_ConnectionException'),\r
+        CURLE_COULDNT_RESOLVE_HOST  => array('HTTP_Request2_ConnectionException'),\r
+        CURLE_COULDNT_CONNECT       => array('HTTP_Request2_ConnectionException'),\r
+        // error returned from write callback\r
+        CURLE_WRITE_ERROR           => array('HTTP_Request2_MessageException',\r
+                                             HTTP_Request2_Exception::NON_HTTP_REDIRECT),\r
+        CURLE_OPERATION_TIMEOUTED   => array('HTTP_Request2_MessageException',\r
+                                             HTTP_Request2_Exception::TIMEOUT),\r
+        CURLE_HTTP_RANGE_ERROR      => array('HTTP_Request2_MessageException'),\r
+        CURLE_SSL_CONNECT_ERROR     => array('HTTP_Request2_ConnectionException'),\r
+        CURLE_LIBRARY_NOT_FOUND     => array('HTTP_Request2_LogicException',\r
+                                             HTTP_Request2_Exception::MISCONFIGURATION),\r
+        CURLE_FUNCTION_NOT_FOUND    => array('HTTP_Request2_LogicException',\r
+                                             HTTP_Request2_Exception::MISCONFIGURATION),\r
+        CURLE_ABORTED_BY_CALLBACK   => array('HTTP_Request2_MessageException',\r
+                                             HTTP_Request2_Exception::NON_HTTP_REDIRECT),\r
+        CURLE_TOO_MANY_REDIRECTS    => array('HTTP_Request2_MessageException',\r
+                                             HTTP_Request2_Exception::TOO_MANY_REDIRECTS),\r
+        CURLE_SSL_PEER_CERTIFICATE  => array('HTTP_Request2_ConnectionException'),\r
+        CURLE_GOT_NOTHING           => array('HTTP_Request2_MessageException'),\r
+        CURLE_SSL_ENGINE_NOTFOUND   => array('HTTP_Request2_LogicException',\r
+                                             HTTP_Request2_Exception::MISCONFIGURATION),\r
+        CURLE_SSL_ENGINE_SETFAILED  => array('HTTP_Request2_LogicException',\r
+                                             HTTP_Request2_Exception::MISCONFIGURATION),\r
+        CURLE_SEND_ERROR            => array('HTTP_Request2_MessageException'),\r
+        CURLE_RECV_ERROR            => array('HTTP_Request2_MessageException'),\r
+        CURLE_SSL_CERTPROBLEM       => array('HTTP_Request2_LogicException',\r
+                                             HTTP_Request2_Exception::INVALID_ARGUMENT),\r
+        CURLE_SSL_CIPHER            => array('HTTP_Request2_ConnectionException'),\r
+        CURLE_SSL_CACERT            => array('HTTP_Request2_ConnectionException'),\r
+        CURLE_BAD_CONTENT_ENCODING  => array('HTTP_Request2_MessageException'),\r
+    );\r
+\r
    /**\r
     * Response being received\r
     * @var  HTTP_Request2_Response\r
@@ -110,6 +150,26 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
     */\r
     protected $lastInfo;\r
 \r
+   /**\r
+    * Creates a subclass of HTTP_Request2_Exception from curl error data\r
+    *\r
+    * @param resource curl handle\r
+    * @return HTTP_Request2_Exception\r
+    */\r
+    protected static function wrapCurlError($ch)\r
+    {\r
+        $nativeCode = curl_errno($ch);\r
+        $message    = 'Curl error: ' . curl_error($ch);\r
+        if (!isset(self::$errorMap[$nativeCode])) {\r
+            return new HTTP_Request2_Exception($message, 0, $nativeCode);\r
+        } else {\r
+            $class = self::$errorMap[$nativeCode][0];\r
+            $code  = empty(self::$errorMap[$nativeCode][1])\r
+                     ? 0 : self::$errorMap[$nativeCode][1];\r
+            return new $class($message, $code, $nativeCode);\r
+        }\r
+    }\r
+\r
    /**\r
     * Sends request to the remote server and returns its response\r
     *\r
@@ -120,7 +180,9 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
     public function sendRequest(HTTP_Request2 $request)\r
     {\r
         if (!extension_loaded('curl')) {\r
-            throw new HTTP_Request2_Exception('cURL extension not available');\r
+            throw new HTTP_Request2_LogicException(\r
+                'cURL extension not available', HTTP_Request2_Exception::MISCONFIGURATION\r
+            );\r
         }\r
 \r
         $this->request              = $request;\r
@@ -131,24 +193,30 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
 \r
         try {\r
             if (false === curl_exec($ch = $this->createCurlHandle())) {\r
-                $errorMessage = 'Error sending request: #' . curl_errno($ch) .\r
-                                                       ' ' . curl_error($ch);\r
+                $e = self::wrapCurlError($ch);\r
             }\r
         } catch (Exception $e) {\r
         }\r
-        $this->lastInfo = curl_getinfo($ch);\r
-        curl_close($ch);\r
+        if (isset($ch)) {\r
+            $this->lastInfo = curl_getinfo($ch);\r
+            curl_close($ch);\r
+        }\r
+\r
+        $response = $this->response;\r
+        unset($this->request, $this->requestBody, $this->response);\r
 \r
         if (!empty($e)) {\r
             throw $e;\r
-        } elseif (!empty($errorMessage)) {\r
-            throw new HTTP_Request2_Exception($errorMessage);\r
+        }\r
+\r
+        if ($jar = $request->getCookieJar()) {\r
+            $jar->addCookiesFromResponse($response, $request->getUrl());\r
         }\r
 \r
         if (0 < $this->lastInfo['size_download']) {\r
-            $this->request->setLastEvent('receivedBody', $this->response);\r
+            $request->setLastEvent('receivedBody', $response);\r
         }\r
-        return $this->response;\r
+        return $response;\r
     }\r
 \r
    /**\r
@@ -165,19 +233,16 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
     * Creates a new cURL handle and populates it with data from the request\r
     *\r
     * @return   resource    a cURL handle, as created by curl_init()\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_LogicException\r
     */\r
     protected function createCurlHandle()\r
     {\r
         $ch = curl_init();\r
 \r
         curl_setopt_array($ch, array(\r
-            // setup callbacks\r
-            CURLOPT_READFUNCTION   => array($this, 'callbackReadBody'),\r
+            // setup write callbacks\r
             CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'),\r
             CURLOPT_WRITEFUNCTION  => array($this, 'callbackWriteBody'),\r
-            // disallow redirects\r
-            CURLOPT_FOLLOWLOCATION => false,\r
             // buffer size\r
             CURLOPT_BUFFERSIZE     => $this->request->getConfig('buffer_size'),\r
             // connection timeout\r
@@ -188,6 +253,27 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
             CURLOPT_URL            => $this->request->getUrl()->getUrl()\r
         ));\r
 \r
+        // set up redirects\r
+        if (!$this->request->getConfig('follow_redirects')) {\r
+            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);\r
+        } else {\r
+            if (!@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true)) {\r
+                throw new HTTP_Request2_LogicException(\r
+                    'Redirect support in curl is unavailable due to open_basedir or safe_mode setting',\r
+                    HTTP_Request2_Exception::MISCONFIGURATION\r
+                );\r
+            }\r
+            curl_setopt($ch, CURLOPT_MAXREDIRS, $this->request->getConfig('max_redirects'));\r
+            // limit redirects to http(s), works in 5.2.10+\r
+            if (defined('CURLOPT_REDIR_PROTOCOLS')) {\r
+                curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);\r
+            }\r
+            // works in 5.3.2+, http://bugs.php.net/bug.php?id=49571\r
+            if ($this->request->getConfig('strict_redirects') && defined('CURLOPT_POSTREDIR')) {\r
+                curl_setopt($ch, CURLOPT_POSTREDIR, 3);\r
+            }\r
+        }\r
+\r
         // request timeout\r
         if ($timeout = $this->request->getConfig('timeout')) {\r
             curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);\r
@@ -210,6 +296,12 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
             case HTTP_Request2::METHOD_POST:\r
                 curl_setopt($ch, CURLOPT_POST, true);\r
                 break;\r
+            case HTTP_Request2::METHOD_HEAD:\r
+                curl_setopt($ch, CURLOPT_NOBODY, true);\r
+                break;\r
+            case HTTP_Request2::METHOD_PUT:\r
+                curl_setopt($ch, CURLOPT_UPLOAD, true);\r
+                break;\r
             default:\r
                 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());\r
         }\r
@@ -217,7 +309,9 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
         // set proxy, if needed\r
         if ($host = $this->request->getConfig('proxy_host')) {\r
             if (!($port = $this->request->getConfig('proxy_port'))) {\r
-                throw new HTTP_Request2_Exception('Proxy port not provided');\r
+                throw new HTTP_Request2_LogicException(\r
+                    'Proxy port not provided', HTTP_Request2_Exception::MISSING_VALUE\r
+                );\r
             }\r
             curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port);\r
             if ($user = $this->request->getConfig('proxy_user')) {\r
@@ -246,13 +340,11 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
         }\r
 \r
         // set SSL options\r
-        if (0 == strcasecmp($this->request->getUrl()->getScheme(), 'https')) {\r
-            foreach ($this->request->getConfig() as $name => $value) {\r
-                if ('ssl_verify_host' == $name && null !== $value) {\r
-                    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);\r
-                } elseif (isset(self::$sslContextMap[$name]) && null !== $value) {\r
-                    curl_setopt($ch, self::$sslContextMap[$name], $value);\r
-                }\r
+        foreach ($this->request->getConfig() as $name => $value) {\r
+            if ('ssl_verify_host' == $name && null !== $value) {\r
+                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);\r
+            } elseif (isset(self::$sslContextMap[$name]) && null !== $value) {\r
+                curl_setopt($ch, self::$sslContextMap[$name], $value);\r
             }\r
         }\r
 \r
@@ -262,6 +354,12 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
             $headers['accept-encoding'] = '';\r
         }\r
 \r
+        if (($jar = $this->request->getCookieJar())\r
+            && ($cookies = $jar->getMatching($this->request->getUrl(), true))\r
+        ) {\r
+            $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;\r
+        }\r
+\r
         // set headers having special cURL keys\r
         foreach (self::$headerMap as $name => $option) {\r
             if (isset($headers[$name])) {\r
@@ -271,6 +369,9 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
         }\r
 \r
         $this->calculateRequestLength($headers);\r
+        if (isset($headers['content-length'])) {\r
+            $this->workaroundPhpBug47204($ch, $headers);\r
+        }\r
 \r
         // set headers not having special keys\r
         $headersFmt = array();\r
@@ -283,13 +384,50 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
         return $ch;\r
     }\r
 \r
+   /**\r
+    * Workaround for PHP bug #47204 that prevents rewinding request body\r
+    *\r
+    * The workaround consists of reading the entire request body into memory\r
+    * and setting it as CURLOPT_POSTFIELDS, so it isn't recommended for large\r
+    * file uploads, use Socket adapter instead.\r
+    *\r
+    * @param    resource    cURL handle\r
+    * @param    array       Request headers\r
+    */\r
+    protected function workaroundPhpBug47204($ch, &$headers)\r
+    {\r
+        // no redirects, no digest auth -> probably no rewind needed\r
+        if (!$this->request->getConfig('follow_redirects')\r
+            && (!($auth = $this->request->getAuth())\r
+                || HTTP_Request2::AUTH_DIGEST != $auth['scheme'])\r
+        ) {\r
+            curl_setopt($ch, CURLOPT_READFUNCTION, array($this, 'callbackReadBody'));\r
+\r
+        // rewind may be needed, read the whole body into memory\r
+        } else {\r
+            if ($this->requestBody instanceof HTTP_Request2_MultipartBody) {\r
+                $this->requestBody = $this->requestBody->__toString();\r
+\r
+            } elseif (is_resource($this->requestBody)) {\r
+                $fp = $this->requestBody;\r
+                $this->requestBody = '';\r
+                while (!feof($fp)) {\r
+                    $this->requestBody .= fread($fp, 16384);\r
+                }\r
+            }\r
+            // curl hangs up if content-length is present\r
+            unset($headers['content-length']);\r
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->requestBody);\r
+        }\r
+    }\r
+\r
    /**\r
     * Callback function called by cURL for reading the request body\r
     *\r
     * @param    resource    cURL handle\r
     * @param    resource    file descriptor (not used)\r
     * @param    integer     maximum length of data to return\r
-    * @return   string      part of the request body, up to $length bytes \r
+    * @return   string      part of the request body, up to $length bytes\r
     */\r
     protected function callbackReadBody($ch, $fd, $length)\r
     {\r
@@ -336,6 +474,19 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
                     'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)\r
                 );\r
             }\r
+            $upload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD);\r
+            // if body wasn't read by a callback, send event with total body size\r
+            if ($upload > $this->position) {\r
+                $this->request->setLastEvent(\r
+                    'sentBodyPart', $upload - $this->position\r
+                );\r
+                $this->position = $upload;\r
+            }\r
+            if ($upload && (!$this->eventSentHeaders\r
+                            || $this->response->getStatus() >= 200)\r
+            ) {\r
+                $this->request->setLastEvent('sentBody', $upload);\r
+            }\r
             $this->eventSentHeaders = true;\r
             // we'll need a new response object\r
             if ($this->eventReceivedHeaders) {\r
@@ -344,7 +495,9 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
             }\r
         }\r
         if (empty($this->response)) {\r
-            $this->response = new HTTP_Request2_Response($string, false);\r
+            $this->response = new HTTP_Request2_Response(\r
+                $string, false, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)\r
+            );\r
         } else {\r
             $this->response->parseHeaderLine($string);\r
             if ('' == trim($string)) {\r
@@ -352,6 +505,27 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
                 if (200 <= $this->response->getStatus()) {\r
                     $this->request->setLastEvent('receivedHeaders', $this->response);\r
                 }\r
+\r
+                if ($this->request->getConfig('follow_redirects') && $this->response->isRedirect()) {\r
+                    $redirectUrl = new Net_URL2($this->response->getHeader('location'));\r
+\r
+                    // for versions lower than 5.2.10, check the redirection URL protocol\r
+                    if (!defined('CURLOPT_REDIR_PROTOCOLS') && $redirectUrl->isAbsolute()\r
+                        && !in_array($redirectUrl->getScheme(), array('http', 'https'))\r
+                    ) {\r
+                        return -1;\r
+                    }\r
+\r
+                    if ($jar = $this->request->getCookieJar()) {\r
+                        $jar->addCookiesFromResponse($this->response, $this->request->getUrl());\r
+                        if (!$redirectUrl->isAbsolute()) {\r
+                            $redirectUrl = $this->request->getUrl()->resolve($redirectUrl);\r
+                        }\r
+                        if ($cookies = $jar->getMatching($redirectUrl, true)) {\r
+                            curl_setopt($ch, CURLOPT_COOKIE, $cookies);\r
+                        }\r
+                    }\r
+                }\r
                 $this->eventReceivedHeaders = true;\r
             }\r
         }\r
@@ -368,10 +542,13 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
     */\r
     protected function callbackWriteBody($ch, $string)\r
     {\r
-        // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if \r
+        // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if\r
         // response doesn't start with proper HTTP status line (see bug #15716)\r
         if (empty($this->response)) {\r
-            throw new HTTP_Request2_Exception("Malformed response: {$string}");\r
+            throw new HTTP_Request2_MessageException(\r
+                "Malformed response: {$string}",\r
+                HTTP_Request2_Exception::MALFORMED_RESPONSE\r
+            );\r
         }\r
         if ($this->request->getConfig('store_body')) {\r
             $this->response->appendBody($string);\r
index 89688003b2352a57dc6618e1327394a135f9e0fd..c99defb899dc3a67feaec14803cfeaade3225c06 100644 (file)
@@ -6,7 +6,7 @@
  *\r
  * LICENSE:\r
  *\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
@@ -37,7 +37,7 @@
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version    CVS: $Id: Mock.php 274406 2009-01-23 18:01:57Z avb $\r
+ * @version    SVN: $Id: Mock.php 308322 2011-02-14 13:58:03Z avb $\r
  * @link       http://pear.php.net/package/HTTP_Request2\r
  */\r
 \r
@@ -55,31 +55,31 @@ require_once 'HTTP/Request2/Adapter.php';
  * <code>\r
  * $mock = new HTTP_Request2_Adapter_Mock();\r
  * $mock->addResponse("HTTP/1.1 ... ");\r
- * \r
+ *\r
  * $request = new HTTP_Request2();\r
  * $request->setAdapter($mock);\r
- * \r
+ *\r
  * // This will return the response set above\r
  * $response = $req->send();\r
- * </code> \r
+ * </code>\r
  *\r
  * @category   HTTP\r
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
- * @version    Release: 0.4.1\r
+ * @version    Release: 2.0.0RC1\r
  */\r
 class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter\r
 {\r
    /**\r
     * A queue of responses to be returned by sendRequest()\r
-    * @var  array \r
+    * @var  array\r
     */\r
     protected $responses = array();\r
 \r
    /**\r
     * Returns the next response from the queue built by addResponse()\r
     *\r
-    * If the queue is empty will return default empty response with status 400,\r
+    * If the queue is empty it will return default empty response with status 400,\r
     * if an Exception object was added to the queue it will be thrown.\r
     *\r
     * @param    HTTP_Request2\r
@@ -93,7 +93,7 @@ class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter
             if ($response instanceof HTTP_Request2_Response) {\r
                 return $response;\r
             } else {\r
-                // rethrow the exception,\r
+                // rethrow the exception\r
                 $class   = get_class($response);\r
                 $message = $response->getMessage();\r
                 $code    = $response->getCode();\r
@@ -108,7 +108,7 @@ class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter
     * Adds response to the queue\r
     *\r
     * @param    mixed   either a string, a pointer to an open file,\r
-    *                   a HTTP_Request2_Response or Exception object\r
+    *                   an instance of HTTP_Request2_Response or Exception\r
     * @throws   HTTP_Request2_Exception\r
     */\r
     public function addResponse($response)\r
@@ -135,7 +135,7 @@ class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter
     public static function createResponseFromString($str)\r
     {\r
         $parts       = preg_split('!(\r?\n){2}!m', $str, 2);\r
-        $headerLines = explode("\n", $parts[0]); \r
+        $headerLines = explode("\n", $parts[0]);\r
         $response    = new HTTP_Request2_Response(array_shift($headerLines));\r
         foreach ($headerLines as $headerLine) {\r
             $response->parseHeaderLine($headerLine);\r
index ff44d4959402e68e64da7d1a49a2a694c0ef419a..05cc4c715bb6b8f83adc502694d49dd3ae5337ef 100644 (file)
@@ -6,7 +6,7 @@
  *\r
  * LICENSE:\r
  *\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
@@ -37,7 +37,7 @@
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version    CVS: $Id: Socket.php 279760 2009-05-03 10:46:42Z avb $\r
+ * @version    SVN: $Id: Socket.php 309921 2011-04-03 16:43:02Z avb $\r
  * @link       http://pear.php.net/package/HTTP_Request2\r
  */\r
 \r
@@ -55,13 +55,13 @@ require_once 'HTTP/Request2/Adapter.php';
  * @category    HTTP\r
  * @package     HTTP_Request2\r
  * @author      Alexey Borzov <avb@php.net>\r
- * @version     Release: 0.4.1\r
+ * @version     Release: 2.0.0RC1\r
  */\r
 class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter\r
 {\r
    /**\r
     * Regular expression for 'token' rule from RFC 2616\r
-    */ \r
+    */\r
     const REGEXP_TOKEN = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+';\r
 \r
    /**\r
@@ -79,11 +79,11 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
    /**\r
     * Data for digest authentication scheme\r
     *\r
-    * The keys for the array are URL prefixes. \r
+    * The keys for the array are URL prefixes.\r
     *\r
-    * The values are associative arrays with data (realm, nonce, nonce-count, \r
-    * opaque...) needed for digest authentication. Stored here to prevent making \r
-    * duplicate requests to digest-protected resources after we have already \r
+    * The values are associative arrays with data (realm, nonce, nonce-count,\r
+    * opaque...) needed for digest authentication. Stored here to prevent making\r
+    * duplicate requests to digest-protected resources after we have already\r
     * received the challenge.\r
     *\r
     * @var  array\r
@@ -110,18 +110,28 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     protected $proxyChallenge;\r
 \r
    /**\r
-    * Global timeout, exception will be raised if request continues past this time\r
+    * Sum of start time and global timeout, exception will be thrown if request continues past this time\r
     * @var  integer\r
     */\r
-    protected $timeout = null;\r
+    protected $deadline = null;\r
 \r
    /**\r
     * Remaining length of the current chunk, when reading chunked response\r
     * @var  integer\r
     * @see  readChunked()\r
-    */ \r
+    */\r
     protected $chunkLength = 0;\r
 \r
+   /**\r
+    * Remaining amount of redirections to follow\r
+    *\r
+    * Starts at 'max_redirects' configuration parameter and is reduced on each\r
+    * subsequent redirect. An Exception will be thrown once it reaches zero.\r
+    *\r
+    * @var  integer\r
+    */\r
+    protected $redirectCountdown = null;\r
+\r
    /**\r
     * Sends request to the remote server and returns its response\r
     *\r
@@ -132,33 +142,38 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     public function sendRequest(HTTP_Request2 $request)\r
     {\r
         $this->request = $request;\r
-        $keepAlive     = $this->connect();\r
-        $headers       = $this->prepareHeaders();\r
 \r
-        // Use global request timeout if given, see feature requests #5735, #8964 \r
+        // Use global request timeout if given, see feature requests #5735, #8964\r
         if ($timeout = $request->getConfig('timeout')) {\r
-            $this->timeout = time() + $timeout;\r
+            $this->deadline = time() + $timeout;\r
         } else {\r
-            $this->timeout = null;\r
+            $this->deadline = null;\r
         }\r
 \r
         try {\r
+            $keepAlive = $this->connect();\r
+            $headers   = $this->prepareHeaders();\r
             if (false === @fwrite($this->socket, $headers, strlen($headers))) {\r
-                throw new HTTP_Request2_Exception('Error writing request');\r
+                throw new HTTP_Request2_MessageException('Error writing request');\r
             }\r
             // provide request headers to the observer, see request #7633\r
             $this->request->setLastEvent('sentHeaders', $headers);\r
             $this->writeBody();\r
 \r
-            if ($this->timeout && time() > $this->timeout) {\r
-                throw new HTTP_Request2_Exception(\r
-                    'Request timed out after ' . \r
-                    $request->getConfig('timeout') . ' second(s)'\r
+            if ($this->deadline && time() > $this->deadline) {\r
+                throw new HTTP_Request2_MessageException(\r
+                    'Request timed out after ' .\r
+                    $request->getConfig('timeout') . ' second(s)',\r
+                    HTTP_Request2_Exception::TIMEOUT\r
                 );\r
             }\r
 \r
             $response = $this->readResponse();\r
 \r
+            if ($jar = $request->getCookieJar()) {\r
+                $jar->addCookiesFromResponse($response, $request->getUrl());\r
+            }\r
+\r
             if (!$this->canKeepAlive($keepAlive, $response)) {\r
                 $this->disconnect();\r
             }\r
@@ -178,10 +193,21 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
 \r
         } catch (Exception $e) {\r
             $this->disconnect();\r
+        }\r
+\r
+        unset($this->request, $this->requestBody);\r
+\r
+        if (!empty($e)) {\r
+            $this->redirectCountdown = null;\r
             throw $e;\r
         }\r
 \r
-        return $response;\r
+        if (!$request->getConfig('follow_redirects') || !$response->isRedirect()) {\r
+            $this->redirectCountdown = null;\r
+            return $response;\r
+        } else {\r
+            return $this->handleRedirect($request, $response);\r
+        }\r
     }\r
 \r
    /**\r
@@ -202,7 +228,10 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
 \r
         if ($host = $this->request->getConfig('proxy_host')) {\r
             if (!($port = $this->request->getConfig('proxy_port'))) {\r
-                throw new HTTP_Request2_Exception('Proxy port not provided');\r
+                throw new HTTP_Request2_LogicException(\r
+                    'Proxy port not provided',\r
+                    HTTP_Request2_Exception::MISSING_VALUE\r
+                );\r
             }\r
             $proxy = true;\r
         } else {\r
@@ -212,25 +241,27 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
         }\r
 \r
         if ($tunnel && !$proxy) {\r
-            throw new HTTP_Request2_Exception(\r
-                "Trying to perform CONNECT request without proxy"\r
+            throw new HTTP_Request2_LogicException(\r
+                "Trying to perform CONNECT request without proxy",\r
+                HTTP_Request2_Exception::MISSING_VALUE\r
             );\r
         }\r
         if ($secure && !in_array('ssl', stream_get_transports())) {\r
-            throw new HTTP_Request2_Exception(\r
-                'Need OpenSSL support for https:// requests'\r
+            throw new HTTP_Request2_LogicException(\r
+                'Need OpenSSL support for https:// requests',\r
+                HTTP_Request2_Exception::MISCONFIGURATION\r
             );\r
         }\r
 \r
         // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive\r
         // connection token to a proxy server...\r
-        if ($proxy && !$secure && \r
+        if ($proxy && !$secure &&\r
             !empty($headers['connection']) && 'Keep-Alive' == $headers['connection']\r
         ) {\r
             $this->request->setHeader('connection');\r
         }\r
 \r
-        $keepAlive = ('1.1' == $this->request->getConfig('protocol_version') && \r
+        $keepAlive = ('1.1' == $this->request->getConfig('protocol_version') &&\r
                       empty($headers['connection'])) ||\r
                      (!empty($headers['connection']) &&\r
                       'Keep-Alive' == $headers['connection']);\r
@@ -278,21 +309,27 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
             $context = stream_context_create();\r
             foreach ($options as $name => $value) {\r
                 if (!stream_context_set_option($context, 'ssl', $name, $value)) {\r
-                    throw new HTTP_Request2_Exception(\r
+                    throw new HTTP_Request2_LogicException(\r
                         "Error setting SSL context option '{$name}'"\r
                     );\r
                 }\r
             }\r
+            $track = @ini_set('track_errors', 1);\r
             $this->socket = @stream_socket_client(\r
                 $remote, $errno, $errstr,\r
                 $this->request->getConfig('connect_timeout'),\r
                 STREAM_CLIENT_CONNECT, $context\r
             );\r
             if (!$this->socket) {\r
-                throw new HTTP_Request2_Exception(\r
-                    "Unable to connect to {$remote}. Error #{$errno}: {$errstr}"\r
+                $e = new HTTP_Request2_ConnectionException(\r
+                    "Unable to connect to {$remote}. Error: "\r
+                     . (empty($errstr)? $php_errormsg: $errstr), 0, $errno\r
                 );\r
             }\r
+            @ini_set('track_errors', $track);\r
+            if (isset($e)) {\r
+                throw $e;\r
+            }\r
             $this->request->setLastEvent('connect', $remote);\r
             self::$sockets[$socketKey] =& $this->socket;\r
         }\r
@@ -320,7 +357,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
         $response = $connect->send();\r
         // Need any successful (2XX) response\r
         if (200 > $response->getStatus() || 300 <= $response->getStatus()) {\r
-            throw new HTTP_Request2_Exception(\r
+            throw new HTTP_Request2_ConnectionException(\r
                 'Failed to connect via HTTPS proxy. Proxy response: ' .\r
                 $response->getStatus() . ' ' . $response->getReasonPhrase()\r
             );\r
@@ -328,10 +365,10 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
         $this->socket = $donor->socket;\r
 \r
         $modes = array(\r
-            STREAM_CRYPTO_METHOD_TLS_CLIENT, \r
+            STREAM_CRYPTO_METHOD_TLS_CLIENT,\r
             STREAM_CRYPTO_METHOD_SSLv3_CLIENT,\r
             STREAM_CRYPTO_METHOD_SSLv23_CLIENT,\r
-            STREAM_CRYPTO_METHOD_SSLv2_CLIENT \r
+            STREAM_CRYPTO_METHOD_SSLv2_CLIENT\r
         );\r
 \r
         foreach ($modes as $mode) {\r
@@ -339,7 +376,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
                 return;\r
             }\r
         }\r
-        throw new HTTP_Request2_Exception(\r
+        throw new HTTP_Request2_ConnectionException(\r
             'Failed to enable secure connection when connecting through proxy'\r
         );\r
     }\r
@@ -347,7 +384,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
    /**\r
     * Checks whether current connection may be reused or should be closed\r
     *\r
-    * @param    boolean                 whether connection could be persistent \r
+    * @param    boolean                 whether connection could be persistent\r
     *                                   in the first place\r
     * @param    HTTP_Request2_Response  response object to check\r
     * @return   boolean\r
@@ -361,8 +398,11 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
             return true;\r
         }\r
 \r
-        $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding')) ||\r
-                       null !== $response->getHeader('content-length');\r
+        $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding'))\r
+                       || null !== $response->getHeader('content-length')\r
+                       // no body possible for such responses, see also request #17031\r
+                       || HTTP_Request2::METHOD_HEAD == $this->request->getMethod()\r
+                       || in_array($response->getStatus(), array(204, 304));\r
         $persistent  = 'keep-alive' == strtolower($response->getHeader('connection')) ||\r
                        (null === $response->getHeader('connection') &&\r
                         '1.1' == $response->getVersion());\r
@@ -381,6 +421,66 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
         }\r
     }\r
 \r
+   /**\r
+    * Handles HTTP redirection\r
+    *\r
+    * This method will throw an Exception if redirect to a non-HTTP(S) location\r
+    * is attempted, also if number of redirects performed already is equal to\r
+    * 'max_redirects' configuration parameter.\r
+    *\r
+    * @param    HTTP_Request2               Original request\r
+    * @param    HTTP_Request2_Response      Response containing redirect\r
+    * @return   HTTP_Request2_Response      Response from a new location\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function handleRedirect(HTTP_Request2 $request,\r
+                                      HTTP_Request2_Response $response)\r
+    {\r
+        if (is_null($this->redirectCountdown)) {\r
+            $this->redirectCountdown = $request->getConfig('max_redirects');\r
+        }\r
+        if (0 == $this->redirectCountdown) {\r
+            $this->redirectCountdown = null;\r
+            // Copying cURL behaviour\r
+            throw new HTTP_Request2_MessageException (\r
+                'Maximum (' . $request->getConfig('max_redirects') . ') redirects followed',\r
+                HTTP_Request2_Exception::TOO_MANY_REDIRECTS\r
+            );\r
+        }\r
+        $redirectUrl = new Net_URL2(\r
+            $response->getHeader('location'),\r
+            array(Net_URL2::OPTION_USE_BRACKETS => $request->getConfig('use_brackets'))\r
+        );\r
+        // refuse non-HTTP redirect\r
+        if ($redirectUrl->isAbsolute()\r
+            && !in_array($redirectUrl->getScheme(), array('http', 'https'))\r
+        ) {\r
+            $this->redirectCountdown = null;\r
+            throw new HTTP_Request2_MessageException(\r
+                'Refusing to redirect to a non-HTTP URL ' . $redirectUrl->__toString(),\r
+                HTTP_Request2_Exception::NON_HTTP_REDIRECT\r
+            );\r
+        }\r
+        // Theoretically URL should be absolute (see http://tools.ietf.org/html/rfc2616#section-14.30),\r
+        // but in practice it is often not\r
+        if (!$redirectUrl->isAbsolute()) {\r
+            $redirectUrl = $request->getUrl()->resolve($redirectUrl);\r
+        }\r
+        $redirect = clone $request;\r
+        $redirect->setUrl($redirectUrl);\r
+        if (303 == $response->getStatus() || (!$request->getConfig('strict_redirects')\r
+             && in_array($response->getStatus(), array(301, 302)))\r
+        ) {\r
+            $redirect->setMethod(HTTP_Request2::METHOD_GET);\r
+            $redirect->setBody('');\r
+        }\r
+\r
+        if (0 < $this->redirectCountdown) {\r
+            $this->redirectCountdown--;\r
+        }\r
+        return $this->sendRequest($redirect);\r
+    }\r
+\r
    /**\r
     * Checks whether another request should be performed with server digest auth\r
     *\r
@@ -389,7 +489,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     *   - auth credentials should be set in the request object\r
     *   - response should contain WWW-Authenticate header with digest challenge\r
     *   - there is either no challenge stored for this URL or new challenge\r
-    *     contains stale=true parameter (in other case we probably just failed \r
+    *     contains stale=true parameter (in other case we probably just failed\r
     *     due to invalid username / password)\r
     *\r
     * The method stores challenge values in $challenges static property\r
@@ -453,7 +553,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     *   - proxy auth credentials should be set in the request object\r
     *   - response should contain Proxy-Authenticate header with digest challenge\r
     *   - there is either no challenge stored for this proxy or new challenge\r
-    *     contains stale=true parameter (in other case we probably just failed \r
+    *     contains stale=true parameter (in other case we probably just failed\r
     *     due to invalid username / password)\r
     *\r
     * The method stores challenge values in $challenges static property\r
@@ -489,7 +589,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     * Extracts digest method challenge from (WWW|Proxy)-Authenticate header value\r
     *\r
     * There is a problem with implementation of RFC 2617: several of the parameters\r
-    * here are defined as quoted-string and thus may contain backslash escaped\r
+    * are defined as quoted-string there and thus may contain backslash escaped\r
     * double quotes (RFC 2616, section 2.2). However, RFC 2617 defines unq(X) as\r
     * just value of quoted-string X without surrounding quotes, it doesn't speak\r
     * about removing backslash escaping.\r
@@ -501,17 +601,17 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     *   - Squid allows (manually escaped) quotes there, but it is impossible to\r
     *     authorize with either escaped or unescaped quotes used in digest,\r
     *     probably it can't parse the response (?)\r
-    *   - Both IE and Firefox display realm value with backslashes in \r
+    *   - Both IE and Firefox display realm value with backslashes in\r
     *     the password popup and apparently use the same value for digest\r
     *\r
     * HTTP_Request2 follows IE and Firefox (and hopefully RFC 2617) in\r
-    * quoted-string handling, unfortunately that means failure to authorize \r
+    * quoted-string handling, unfortunately that means failure to authorize\r
     * sometimes\r
     *\r
     * @param    string  value of WWW-Authenticate or Proxy-Authenticate header\r
     * @return   mixed   associative array with challenge parameters, false if\r
     *                   no challenge is present in header value\r
-    * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters\r
+    * @throws   HTTP_Request2_NotImplementedException in case of unsupported challenge parameters\r
     */\r
     protected function parseDigestChallenge($headerValue)\r
     {\r
@@ -537,23 +637,23 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
             }\r
         }\r
         // we only support qop=auth\r
-        if (!empty($paramsAry['qop']) && \r
+        if (!empty($paramsAry['qop']) &&\r
             !in_array('auth', array_map('trim', explode(',', $paramsAry['qop'])))\r
         ) {\r
-            throw new HTTP_Request2_Exception(\r
+            throw new HTTP_Request2_NotImplementedException(\r
                 "Only 'auth' qop is currently supported in digest authentication, " .\r
                 "server requested '{$paramsAry['qop']}'"\r
             );\r
         }\r
         // we only support algorithm=MD5\r
         if (!empty($paramsAry['algorithm']) && 'MD5' != $paramsAry['algorithm']) {\r
-            throw new HTTP_Request2_Exception(\r
+            throw new HTTP_Request2_NotImplementedException(\r
                 "Only 'MD5' algorithm is currently supported in digest authentication, " .\r
                 "server requested '{$paramsAry['algorithm']}'"\r
             );\r
         }\r
 \r
-        return $paramsAry; \r
+        return $paramsAry;\r
     }\r
 \r
    /**\r
@@ -562,7 +662,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     * @param    array   challenge to update\r
     * @param    string  value of [Proxy-]Authentication-Info header\r
     * @todo     validate server rspauth response\r
-    */ \r
+    */\r
     protected function updateChallenge(&$challenge, $headerValue)\r
     {\r
         $authParam   = '!(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .\r
@@ -593,10 +693,10 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     * @param    array   digest challenge parameters\r
     * @return   string  value of [Proxy-]Authorization request header\r
     * @link     http://tools.ietf.org/html/rfc2617#section-3.2.2\r
-    */ \r
+    */\r
     protected function createDigestResponse($user, $password, $url, &$challenge)\r
     {\r
-        if (false !== ($q = strpos($url, '?')) && \r
+        if (false !== ($q = strpos($url, '?')) &&\r
             $this->request->getConfig('digest_compat_ie')\r
         ) {\r
             $url = substr($url, 0, $q);\r
@@ -621,7 +721,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
                'nonce="' . $challenge['nonce'] . '", ' .\r
                'uri="' . $url . '", ' .\r
                'response="' . $digest . '"' .\r
-               (!empty($challenge['opaque'])? \r
+               (!empty($challenge['opaque'])?\r
                 ', opaque="' . $challenge['opaque'] . '"':\r
                 '') .\r
                (!empty($challenge['qop'])?\r
@@ -635,7 +735,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     * @param    array   request headers\r
     * @param    string  request host (needed for digest authentication)\r
     * @param    string  request URL (needed for digest authentication)\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_NotImplementedException\r
     */\r
     protected function addAuthorizationHeader(&$headers, $requestHost, $requestUrl)\r
     {\r
@@ -644,7 +744,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
         }\r
         switch ($auth['scheme']) {\r
             case HTTP_Request2::AUTH_BASIC:\r
-                $headers['authorization'] = \r
+                $headers['authorization'] =\r
                     'Basic ' . base64_encode($auth['user'] . ':' . $auth['password']);\r
                 break;\r
 \r
@@ -657,7 +757,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
                 foreach (array_keys(self::$challenges) as $key) {\r
                     if ($key == substr($fullUrl, 0, strlen($key))) {\r
                         $headers['authorization'] = $this->createDigestResponse(\r
-                            $auth['user'], $auth['password'], \r
+                            $auth['user'], $auth['password'],\r
                             $requestUrl, self::$challenges[$key]\r
                         );\r
                         $this->serverChallenge =& self::$challenges[$key];\r
@@ -667,7 +767,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
                 break;\r
 \r
             default:\r
-                throw new HTTP_Request2_Exception(\r
+                throw new HTTP_Request2_NotImplementedException(\r
                     "Unknown HTTP authentication scheme '{$auth['scheme']}'"\r
                 );\r
         }\r
@@ -678,7 +778,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     *\r
     * @param    array   request headers\r
     * @param    string  request URL (needed for digest authentication)\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_NotImplementedException\r
     */\r
     protected function addProxyAuthorizationHeader(&$headers, $requestUrl)\r
     {\r
@@ -711,7 +811,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
                 break;\r
 \r
             default:\r
-                throw new HTTP_Request2_Exception(\r
+                throw new HTTP_Request2_NotImplementedException(\r
                     "Unknown HTTP authentication scheme '" .\r
                     $this->request->getConfig('proxy_auth_scheme') . "'"\r
                 );\r
@@ -762,6 +862,11 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
         ) {\r
             $headers['accept-encoding'] = 'gzip, deflate';\r
         }\r
+        if (($jar = $this->request->getCookieJar())\r
+            && ($cookies = $jar->getMatching($this->request->getUrl(), true))\r
+        ) {\r
+            $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;\r
+        }\r
 \r
         $this->addAuthorizationHeader($headers, $host, $requestUrl);\r
         $this->addProxyAuthorizationHeader($headers, $requestUrl);\r
@@ -779,7 +884,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
    /**\r
     * Sends the request body\r
     *\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_MessageException\r
     */\r
     protected function writeBody()\r
     {\r
@@ -800,12 +905,13 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
                 $str = $this->requestBody->read($bufferSize);\r
             }\r
             if (false === @fwrite($this->socket, $str, strlen($str))) {\r
-                throw new HTTP_Request2_Exception('Error writing request');\r
+                throw new HTTP_Request2_MessageException('Error writing request');\r
             }\r
             // Provide the length of written string to the observer, request #7630\r
             $this->request->setLastEvent('sentBodyPart', strlen($str));\r
-            $position += strlen($str); \r
+            $position += strlen($str);\r
         }\r
+        $this->request->setLastEvent('sentBody', $this->contentLength);\r
     }\r
 \r
    /**\r
@@ -819,7 +925,9 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
         $bufferSize = $this->request->getConfig('buffer_size');\r
 \r
         do {\r
-            $response = new HTTP_Request2_Response($this->readLine($bufferSize), true);\r
+            $response = new HTTP_Request2_Response(\r
+                $this->readLine($bufferSize), true, $this->request->getUrl()\r
+            );\r
             do {\r
                 $headerLine = $this->readLine($bufferSize);\r
                 $response->parseHeaderLine($headerLine);\r
@@ -880,28 +988,30 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     }\r
 \r
    /**\r
-    * Reads until either the end of the socket or a newline, whichever comes first \r
+    * Reads until either the end of the socket or a newline, whichever comes first\r
     *\r
-    * Strips the trailing newline from the returned data, handles global \r
-    * request timeout. Method idea borrowed from Net_Socket PEAR package. \r
+    * Strips the trailing newline from the returned data, handles global\r
+    * request timeout. Method idea borrowed from Net_Socket PEAR package.\r
     *\r
     * @param    int     buffer size to use for reading\r
     * @return   Available data up to the newline (not including newline)\r
-    * @throws   HTTP_Request2_Exception     In case of timeout\r
+    * @throws   HTTP_Request2_MessageException     In case of timeout\r
     */\r
     protected function readLine($bufferSize)\r
     {\r
         $line = '';\r
         while (!feof($this->socket)) {\r
-            if ($this->timeout) {\r
-                stream_set_timeout($this->socket, max($this->timeout - time(), 1));\r
+            if ($this->deadline) {\r
+                stream_set_timeout($this->socket, max($this->deadline - time(), 1));\r
             }\r
             $line .= @fgets($this->socket, $bufferSize);\r
             $info  = stream_get_meta_data($this->socket);\r
-            if ($info['timed_out'] || $this->timeout && time() > $this->timeout) {\r
-                throw new HTTP_Request2_Exception(\r
-                    'Request timed out after ' . \r
-                    $this->request->getConfig('timeout') . ' second(s)'\r
+            if ($info['timed_out'] || $this->deadline && time() > $this->deadline) {\r
+                $reason = $this->deadline\r
+                          ? 'after ' . $this->request->getConfig('timeout') . ' second(s)'\r
+                          : 'due to default_socket_timeout php.ini setting';\r
+                throw new HTTP_Request2_MessageException(\r
+                    "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT\r
                 );\r
             }\r
             if (substr($line, -1) == "\n") {\r
@@ -916,19 +1026,21 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     *\r
     * @param    int     Reads up to this number of bytes\r
     * @return   Data read from socket\r
-    * @throws   HTTP_Request2_Exception     In case of timeout\r
+    * @throws   HTTP_Request2_MessageException     In case of timeout\r
     */\r
     protected function fread($length)\r
     {\r
-        if ($this->timeout) {\r
-            stream_set_timeout($this->socket, max($this->timeout - time(), 1));\r
+        if ($this->deadline) {\r
+            stream_set_timeout($this->socket, max($this->deadline - time(), 1));\r
         }\r
         $data = fread($this->socket, $length);\r
         $info = stream_get_meta_data($this->socket);\r
-        if ($info['timed_out'] || $this->timeout && time() > $this->timeout) {\r
-            throw new HTTP_Request2_Exception(\r
-                'Request timed out after ' . \r
-                $this->request->getConfig('timeout') . ' second(s)'\r
+        if ($info['timed_out'] || $this->deadline && time() > $this->deadline) {\r
+            $reason = $this->deadline\r
+                      ? 'after ' . $this->request->getConfig('timeout') . ' second(s)'\r
+                      : 'due to default_socket_timeout php.ini setting';\r
+            throw new HTTP_Request2_MessageException(\r
+                "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT\r
             );\r
         }\r
         return $data;\r
@@ -939,7 +1051,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
     *\r
     * @param    int     buffer size to use for reading\r
     * @return   string\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_MessageException\r
     */\r
     protected function readChunked($bufferSize)\r
     {\r
@@ -947,8 +1059,9 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
         if (0 == $this->chunkLength) {\r
             $line = $this->readLine($bufferSize);\r
             if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {\r
-                throw new HTTP_Request2_Exception(\r
-                    "Cannot decode chunked response, invalid chunk length '{$line}'"\r
+                throw new HTTP_Request2_MessageException(\r
+                    "Cannot decode chunked response, invalid chunk length '{$line}'",\r
+                    HTTP_Request2_Exception::DECODE_ERROR\r
                 );\r
             } else {\r
                 $this->chunkLength = hexdec($matches[1]);\r
index bfef7d6c22c066d00c39f5aee5c61a1293b98922..530c23b9ceca81bff1a6eabae0ba5f2df644b36d 100644 (file)
@@ -1,12 +1,12 @@
 <?php\r
 /**\r
- * Exception class for HTTP_Request2 package\r
+ * Exception classes for HTTP_Request2 package\r
  *\r
  * PHP version 5\r
  *\r
  * LICENSE:\r
  *\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
@@ -37,7 +37,7 @@
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version    CVS: $Id: Exception.php 273003 2009-01-07 19:28:22Z avb $\r
+ * @version    SVN: $Id: Exception.php 308629 2011-02-24 17:34:24Z avb $\r
  * @link       http://pear.php.net/package/HTTP_Request2\r
  */\r
 \r
 require_once 'PEAR/Exception.php';\r
 \r
 /**\r
- * Exception class for HTTP_Request2 package\r
- *\r
- * Such a class is required by the Exception RFC:\r
- * http://pear.php.net/pepr/pepr-proposal-show.php?id=132\r
+ * Base exception class for HTTP_Request2 package\r
  *\r
  * @category   HTTP\r
  * @package    HTTP_Request2\r
- * @version    Release: 0.4.1\r
+ * @version    Release: 2.0.0RC1\r
+ * @link       http://pear.php.net/pepr/pepr-proposal-show.php?id=132\r
  */\r
 class HTTP_Request2_Exception extends PEAR_Exception\r
 {\r
+    /** An invalid argument was passed to a method */\r
+    const INVALID_ARGUMENT   = 1;\r
+    /** Some required value was not available */\r
+    const MISSING_VALUE      = 2;\r
+    /** Request cannot be processed due to errors in PHP configuration */\r
+    const MISCONFIGURATION   = 3;\r
+    /** Error reading the local file */\r
+    const READ_ERROR         = 4;\r
+\r
+    /** Server returned a response that does not conform to HTTP protocol */\r
+    const MALFORMED_RESPONSE = 10;\r
+    /** Failure decoding Content-Encoding or Transfer-Encoding of response */\r
+    const DECODE_ERROR       = 20;\r
+    /** Operation timed out */\r
+    const TIMEOUT            = 30;\r
+    /** Number of redirects exceeded 'max_redirects' configuration parameter */\r
+    const TOO_MANY_REDIRECTS = 40;\r
+    /** Redirect to a protocol other than http(s):// */\r
+    const NON_HTTP_REDIRECT  = 50;\r
+\r
+   /**\r
+    * Native error code\r
+    * @var int\r
+    */\r
+    private $_nativeCode;\r
+\r
+   /**\r
+    * Constructor, can set package error code and native error code\r
+    *\r
+    * @param string exception message\r
+    * @param int    package error code, one of class constants\r
+    * @param int    error code from underlying PHP extension\r
+    */\r
+    public function __construct($message = null, $code = null, $nativeCode = null)\r
+    {\r
+        parent::__construct($message, $code);\r
+        $this->_nativeCode = $nativeCode;\r
+    }\r
+\r
+   /**\r
+    * Returns error code produced by underlying PHP extension\r
+    *\r
+    * For Socket Adapter this may contain error number returned by\r
+    * stream_socket_client(), for Curl Adapter this will contain error number\r
+    * returned by curl_errno()\r
+    *\r
+    * @return integer\r
+    */\r
+    public function getNativeCode()\r
+    {\r
+        return $this->_nativeCode;\r
+    }\r
 }\r
+\r
+/**\r
+ * Exception thrown in case of missing features\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @version    Release: 2.0.0RC1\r
+ */\r
+class HTTP_Request2_NotImplementedException extends HTTP_Request2_Exception {}\r
+\r
+/**\r
+ * Exception that represents error in the program logic\r
+ *\r
+ * This exception usually implies a programmer's error, like passing invalid\r
+ * data to methods or trying to use PHP extensions that weren't installed or\r
+ * enabled. Usually exceptions of this kind will be thrown before request even\r
+ * starts.\r
+ *\r
+ * The exception will usually contain a package error code.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @version    Release: 2.0.0RC1\r
+ */\r
+class HTTP_Request2_LogicException extends HTTP_Request2_Exception {}\r
+\r
+/**\r
+ * Exception thrown when connection to a web or proxy server fails\r
+ *\r
+ * The exception will not contain a package error code, but will contain\r
+ * native error code, as returned by stream_socket_client() or curl_errno().\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @version    Release: 2.0.0RC1\r
+ */\r
+class HTTP_Request2_ConnectionException extends HTTP_Request2_Exception {}\r
+\r
+/**\r
+ * Exception thrown when sending or receiving HTTP message fails\r
+ *\r
+ * The exception may contain both package error code and native error code.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @version    Release: 2.0.0RC1\r
+ */\r
+class HTTP_Request2_MessageException extends HTTP_Request2_Exception {}\r
 ?>
\ No newline at end of file
index d8afd8344ced33963714a68889df0e50cb2b99ee..a7bd948bafe4e90bc7295b8f83e7ac64dff6a857 100644 (file)
@@ -6,7 +6,7 @@
  *\r
  * LICENSE:\r
  *\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
@@ -37,7 +37,7 @@
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version    CVS: $Id: MultipartBody.php 287306 2009-08-14 15:22:52Z avb $\r
+ * @version    SVN: $Id: MultipartBody.php 308322 2011-02-14 13:58:03Z avb $\r
  * @link       http://pear.php.net/package/HTTP_Request2\r
  */\r
 \r
@@ -50,7 +50,7 @@
  * @category   HTTP\r
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
- * @version    Release: 0.4.1\r
+ * @version    Release: 2.0.0RC1\r
  * @link       http://tools.ietf.org/html/rfc1867\r
  */\r
 class HTTP_Request2_MultipartBody\r
@@ -172,7 +172,7 @@ class HTTP_Request2_MultipartBody
         while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) {\r
             $oldLength = $length;\r
             if ($this->_pos[0] < $paramCount) {\r
-                $param = sprintf($this->_headerParam, $boundary, \r
+                $param = sprintf($this->_headerParam, $boundary,\r
                                  $this->_params[$this->_pos[0]][0]) .\r
                          $this->_params[$this->_pos[0]][1] . "\r\n";\r
                 $ret    .= substr($param, $this->_pos[1], $length);\r
index b1a0552780db9ea98a4e83ca4eaeb652d82e5108..7865906f658208cf6f1d67cfcef3f5d33a133e05 100644 (file)
@@ -6,7 +6,7 @@
  *\r
  * LICENSE:\r
  *\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
  * @author   David Jean Louis <izi@php.net>\r
  * @author   Alexey Borzov <avb@php.net>\r
  * @license  http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version  CVS: $Id: Log.php 272593 2009-01-02 16:27:14Z avb $\r
+ * @version  SVN: $Id: Log.php 308680 2011-02-25 17:40:17Z avb $\r
  * @link     http://pear.php.net/package/HTTP_Request2\r
  */\r
 \r
 /**\r
  * Exception class for HTTP_Request2 package\r
- */ \r
+ */\r
 require_once 'HTTP/Request2/Exception.php';\r
 \r
 /**\r
  * A debug observer useful for debugging / testing.\r
  *\r
- * This observer logs to a log target data corresponding to the various request \r
+ * This observer logs to a log target data corresponding to the various request\r
  * and response events, it logs by default to php://output but can be configured\r
  * to log to a file or via the PEAR Log package.\r
  *\r
@@ -87,7 +87,7 @@ require_once 'HTTP/Request2/Exception.php';
  * @author   David Jean Louis <izi@php.net>\r
  * @author   Alexey Borzov <avb@php.net>\r
  * @license  http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version  Release: 0.4.1\r
+ * @version  Release: 2.0.0RC1\r
  * @link     http://pear.php.net/package/HTTP_Request2\r
  */\r
 class HTTP_Request2_Observer_Log implements SplObserver\r
@@ -109,7 +109,7 @@ class HTTP_Request2_Observer_Log implements SplObserver
     public $events = array(\r
         'connect',\r
         'sentHeaders',\r
-        'sentBodyPart',\r
+        'sentBody',\r
         'receivedHeaders',\r
         'receivedBody',\r
         'disconnect',\r
@@ -134,7 +134,7 @@ class HTTP_Request2_Observer_Log implements SplObserver
         }\r
         if (is_resource($target) || $target instanceof Log) {\r
             $this->target = $target;\r
-        } elseif (false === ($this->target = @fopen($target, 'w'))) {\r
+        } elseif (false === ($this->target = @fopen($target, 'ab'))) {\r
             throw new HTTP_Request2_Exception("Unable to open '{$target}'");\r
         }\r
     }\r
@@ -143,7 +143,7 @@ class HTTP_Request2_Observer_Log implements SplObserver
     // update() {{{\r
 \r
     /**\r
-     * Called when the request notify us of an event.\r
+     * Called when the request notifies us of an event.\r
      *\r
      * @param HTTP_Request2 $subject The HTTP_Request2 instance\r
      *\r
@@ -167,8 +167,8 @@ class HTTP_Request2_Observer_Log implements SplObserver
                 $this->log('> ' . $header);\r
             }\r
             break;\r
-        case 'sentBodyPart':\r
-            $this->log('> ' . $event['data']);\r
+        case 'sentBody':\r
+            $this->log('> ' . $event['data'] . ' byte(s) sent');\r
             break;\r
         case 'receivedHeaders':\r
             $this->log(sprintf('< HTTP/%s %s %s',\r
@@ -189,12 +189,12 @@ class HTTP_Request2_Observer_Log implements SplObserver
             break;\r
         }\r
     }\r
-    \r
+\r
     // }}}\r
     // log() {{{\r
 \r
     /**\r
-     * Log the given message to the configured target.\r
+     * Logs the given message to the configured target.\r
      *\r
      * @param string $message Message to display\r
      *\r
index c7c1021fbbd11af81f3da84aaf2ecaf1a2619690..73e9a5dc82c64699c63995426f9615539fd49577 100644 (file)
@@ -6,7 +6,7 @@
  *\r
  * LICENSE:\r
  *\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version    CVS: $Id: Response.php 287948 2009-09-01 17:12:18Z avb $\r
+ * @version    SVN: $Id: Response.php 309921 2011-04-03 16:43:02Z avb $\r
  * @link       http://pear.php.net/package/HTTP_Request2\r
  */\r
 \r
 /**\r
  * Exception class for HTTP_Request2 package\r
- */ \r
+ */\r
 require_once 'HTTP/Request2/Exception.php';\r
 \r
 /**\r
@@ -58,11 +58,11 @@ require_once 'HTTP/Request2/Exception.php';
  *     $headerLine = read_header_line();\r
  *     $response->parseHeaderLine($headerLine);\r
  * } while ($headerLine != '');\r
- * \r
+ *\r
  * while ($chunk = read_body()) {\r
  *     $response->appendBody($chunk);\r
  * }\r
- * \r
+ *\r
  * var_dump($response->getHeader(), $response->getCookies(), $response->getBody());\r
  * </code>\r
  *\r
@@ -70,7 +70,7 @@ require_once 'HTTP/Request2/Exception.php';
  * @category   HTTP\r
  * @package    HTTP_Request2\r
  * @author     Alexey Borzov <avb@php.net>\r
- * @version    Release: 0.4.1\r
+ * @version    Release: 2.0.0RC1\r
  * @link       http://tools.ietf.org/html/rfc2616#section-6\r
  */\r
 class HTTP_Request2_Response\r
@@ -95,6 +95,12 @@ class HTTP_Request2_Response
     */\r
     protected $reasonPhrase;\r
 \r
+   /**\r
+    * Effective URL (may be different from original request URL in case of redirects)\r
+    * @var  string\r
+    */\r
+    protected $effectiveUrl;\r
+\r
    /**\r
     * Associative array of response headers\r
     * @var  array\r
@@ -164,7 +170,7 @@ class HTTP_Request2_Response
         305 => 'Use Proxy',\r
         307 => 'Temporary Redirect',\r
 \r
-        // 4xx: Client Error - The request contains bad syntax or cannot be \r
+        // 4xx: Client Error - The request contains bad syntax or cannot be\r
         // fulfilled\r
         400 => 'Bad Request',\r
         401 => 'Unauthorized',\r
@@ -200,14 +206,18 @@ class HTTP_Request2_Response
    /**\r
     * Constructor, parses the response status line\r
     *\r
-    * @param    string  Response status line (e.g. "HTTP/1.1 200 OK")\r
-    * @param    bool    Whether body is still encoded by Content-Encoding\r
-    * @throws   HTTP_Request2_Exception if status line is invalid according to spec\r
+    * @param    string Response status line (e.g. "HTTP/1.1 200 OK")\r
+    * @param    bool   Whether body is still encoded by Content-Encoding\r
+    * @param    string Effective URL of the response\r
+    * @throws   HTTP_Request2_MessageException if status line is invalid according to spec\r
     */\r
-    public function __construct($statusLine, $bodyEncoded = true)\r
+    public function __construct($statusLine, $bodyEncoded = true, $effectiveUrl = null)\r
     {\r
         if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) {\r
-            throw new HTTP_Request2_Exception("Malformed response: {$statusLine}");\r
+            throw new HTTP_Request2_MessageException(\r
+                "Malformed response: {$statusLine}",\r
+                HTTP_Request2_Exception::MALFORMED_RESPONSE\r
+            );\r
         }\r
         $this->version = $m[1];\r
         $this->code    = intval($m[2]);\r
@@ -216,13 +226,14 @@ class HTTP_Request2_Response
         } elseif (!empty(self::$phrases[$this->code])) {\r
             $this->reasonPhrase = self::$phrases[$this->code];\r
         }\r
-        $this->bodyEncoded = (bool)$bodyEncoded;\r
+        $this->bodyEncoded  = (bool)$bodyEncoded;\r
+        $this->effectiveUrl = (string)$effectiveUrl;\r
     }\r
 \r
    /**\r
     * Parses the line from HTTP response filling $headers array\r
     *\r
-    * The method should be called after reading the line from socket or receiving \r
+    * The method should be called after reading the line from socket or receiving\r
     * it into cURL callback. Passing an empty string here indicates the end of\r
     * response headers and triggers additional processing, so be sure to pass an\r
     * empty string in the end.\r
@@ -264,7 +275,7 @@ class HTTP_Request2_Response
             }\r
             $this->lastHeader = $name;\r
 \r
-        // string \r
+        // continuation of a previous header\r
         } elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) {\r
             if (!is_array($this->headers[$this->lastHeader])) {\r
                 $this->headers[$this->lastHeader] .= ' ' . trim($m[1]);\r
@@ -273,13 +284,13 @@ class HTTP_Request2_Response
                 $this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]);\r
             }\r
         }\r
-    } \r
+    }\r
 \r
    /**\r
     * Parses a Set-Cookie header to fill $cookies array\r
     *\r
     * @param    string    value of Set-Cookie header\r
-    * @link     http://cgi.netscape.com/newsref/std/cookie_spec.html\r
+    * @link     http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html\r
     */\r
     protected function parseCookie($cookieString)\r
     {\r
@@ -334,9 +345,22 @@ class HTTP_Request2_Response
         $this->body .= $bodyChunk;\r
     }\r
 \r
+   /**\r
+    * Returns the effective URL of the response\r
+    *\r
+    * This may be different from the request URL if redirects were followed.\r
+    *\r
+    * @return string\r
+    * @link   http://pear.php.net/bugs/bug.php?id=18412\r
+    */\r
+    public function getEffectiveUrl()\r
+    {\r
+        return $this->effectiveUrl;\r
+    }\r
+\r
    /**\r
     * Returns the status code\r
-    * @return   integer \r
+    * @return   integer\r
     */\r
     public function getStatus()\r
     {\r
@@ -352,6 +376,16 @@ class HTTP_Request2_Response
         return $this->reasonPhrase;\r
     }\r
 \r
+   /**\r
+    * Whether response is a redirect that can be automatically handled by HTTP_Request2\r
+    * @return   bool\r
+    */\r
+    public function isRedirect()\r
+    {\r
+        return in_array($this->code, array(300, 301, 302, 303, 307))\r
+               && isset($this->headers['location']);\r
+    }\r
+\r
    /**\r
     * Returns either the named header or all response headers\r
     *\r
@@ -388,7 +422,7 @@ class HTTP_Request2_Response
     */\r
     public function getBody()\r
     {\r
-        if (!$this->bodyEncoded ||\r
+        if (0 == strlen($this->body) || !$this->bodyEncoded ||\r
             !in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate'))\r
         ) {\r
             return $this->body;\r
@@ -424,7 +458,7 @@ class HTTP_Request2_Response
     * Get the HTTP version of the response\r
     *\r
     * @return   string\r
-    */ \r
+    */\r
     public function getVersion()\r
     {\r
         return $this->version;\r
@@ -439,7 +473,8 @@ class HTTP_Request2_Response
     *\r
     * @param    string  gzip-encoded data\r
     * @return   string  decoded data\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_LogicException\r
+    * @throws   HTTP_Request2_MessageException\r
     * @link     http://tools.ietf.org/html/rfc1952\r
     */\r
     public static function decodeGzip($data)\r
@@ -450,15 +485,24 @@ class HTTP_Request2_Response
             return $data;\r
         }\r
         if (!function_exists('gzinflate')) {\r
-            throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available');\r
+            throw new HTTP_Request2_LogicException(\r
+                'Unable to decode body: gzip extension not available',\r
+                HTTP_Request2_Exception::MISCONFIGURATION\r
+            );\r
         }\r
         $method = ord(substr($data, 2, 1));\r
         if (8 != $method) {\r
-            throw new HTTP_Request2_Exception('Error parsing gzip header: unknown compression method');\r
+            throw new HTTP_Request2_MessageException(\r
+                'Error parsing gzip header: unknown compression method',\r
+                HTTP_Request2_Exception::DECODE_ERROR\r
+            );\r
         }\r
         $flags = ord(substr($data, 3, 1));\r
         if ($flags & 224) {\r
-            throw new HTTP_Request2_Exception('Error parsing gzip header: reserved bits are set');\r
+            throw new HTTP_Request2_MessageException(\r
+                'Error parsing gzip header: reserved bits are set',\r
+                HTTP_Request2_Exception::DECODE_ERROR\r
+            );\r
         }\r
 \r
         // header is 10 bytes minimum. may be longer, though.\r
@@ -466,45 +510,69 @@ class HTTP_Request2_Response
         // extra fields, need to skip 'em\r
         if ($flags & 4) {\r
             if ($length - $headerLength - 2 < 8) {\r
-                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+                throw new HTTP_Request2_MessageException(\r
+                    'Error parsing gzip header: data too short',\r
+                    HTTP_Request2_Exception::DECODE_ERROR\r
+                );\r
             }\r
             $extraLength = unpack('v', substr($data, 10, 2));\r
             if ($length - $headerLength - 2 - $extraLength[1] < 8) {\r
-                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+                throw new HTTP_Request2_MessageException(\r
+                    'Error parsing gzip header: data too short',\r
+                    HTTP_Request2_Exception::DECODE_ERROR\r
+                );\r
             }\r
             $headerLength += $extraLength[1] + 2;\r
         }\r
         // file name, need to skip that\r
         if ($flags & 8) {\r
             if ($length - $headerLength - 1 < 8) {\r
-                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+                throw new HTTP_Request2_MessageException(\r
+                    'Error parsing gzip header: data too short',\r
+                    HTTP_Request2_Exception::DECODE_ERROR\r
+                );\r
             }\r
             $filenameLength = strpos(substr($data, $headerLength), chr(0));\r
             if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {\r
-                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+                throw new HTTP_Request2_MessageException(\r
+                    'Error parsing gzip header: data too short',\r
+                    HTTP_Request2_Exception::DECODE_ERROR\r
+                );\r
             }\r
             $headerLength += $filenameLength + 1;\r
         }\r
         // comment, need to skip that also\r
         if ($flags & 16) {\r
             if ($length - $headerLength - 1 < 8) {\r
-                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+                throw new HTTP_Request2_MessageException(\r
+                    'Error parsing gzip header: data too short',\r
+                    HTTP_Request2_Exception::DECODE_ERROR\r
+                );\r
             }\r
             $commentLength = strpos(substr($data, $headerLength), chr(0));\r
             if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {\r
-                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+                throw new HTTP_Request2_MessageException(\r
+                    'Error parsing gzip header: data too short',\r
+                    HTTP_Request2_Exception::DECODE_ERROR\r
+                );\r
             }\r
             $headerLength += $commentLength + 1;\r
         }\r
         // have a CRC for header. let's check\r
         if ($flags & 2) {\r
             if ($length - $headerLength - 2 < 8) {\r
-                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+                throw new HTTP_Request2_MessageException(\r
+                    'Error parsing gzip header: data too short',\r
+                    HTTP_Request2_Exception::DECODE_ERROR\r
+                );\r
             }\r
             $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));\r
             $crcStored = unpack('v', substr($data, $headerLength, 2));\r
             if ($crcReal != $crcStored[1]) {\r
-                throw new HTTP_Request2_Exception('Header CRC check failed');\r
+                throw new HTTP_Request2_MessageException(\r
+                    'Header CRC check failed',\r
+                    HTTP_Request2_Exception::DECODE_ERROR\r
+                );\r
             }\r
             $headerLength += 2;\r
         }\r
@@ -517,11 +585,20 @@ class HTTP_Request2_Response
         // don't pass $dataSize to gzinflate, see bugs #13135, #14370\r
         $unpacked = gzinflate(substr($data, $headerLength, -8));\r
         if (false === $unpacked) {\r
-            throw new HTTP_Request2_Exception('gzinflate() call failed');\r
+            throw new HTTP_Request2_MessageException(\r
+                'gzinflate() call failed',\r
+                HTTP_Request2_Exception::DECODE_ERROR\r
+            );\r
         } elseif ($dataSize != strlen($unpacked)) {\r
-            throw new HTTP_Request2_Exception('Data size check failed');\r
+            throw new HTTP_Request2_MessageException(\r
+                'Data size check failed',\r
+                HTTP_Request2_Exception::DECODE_ERROR\r
+            );\r
         } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {\r
-            throw new HTTP_Request2_Exception('Data CRC check failed');\r
+            throw new HTTP_Request2_Exception(\r
+                'Data CRC check failed',\r
+                HTTP_Request2_Exception::DECODE_ERROR\r
+            );\r
         }\r
         return $unpacked;\r
     }\r
@@ -531,12 +608,15 @@ class HTTP_Request2_Response
     *\r
     * @param    string  deflate-encoded data\r
     * @return   string  decoded data\r
-    * @throws   HTTP_Request2_Exception\r
+    * @throws   HTTP_Request2_LogicException\r
     */\r
     public static function decodeDeflate($data)\r
     {\r
         if (!function_exists('gzuncompress')) {\r
-            throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available');\r
+            throw new HTTP_Request2_LogicException(\r
+                'Unable to decode body: gzip extension not available',\r
+                HTTP_Request2_Exception::MISCONFIGURATION\r
+            );\r
         }\r
         // RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950,\r
         // while many applications send raw deflate stream from RFC 1951.\r