]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - extlib/HTTP/Request2/Adapter/Socket.php
Merge branch '1.0.x' into testing
[quix0rs-gnu-social.git] / extlib / HTTP / Request2 / Adapter / Socket.php
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