Refactoring ongoing: move http headers parsing away from response into http class...
[plcapi.git] / src / Request.php
index 77567ab..17aae84 100644 (file)
@@ -153,201 +153,9 @@ class Request
         while ($data = fread($fp, 32768)) {
             $ipd .= $data;
         }
-        //fclose($fp);
         return $this->parseResponse($ipd);
     }
 
-    /**
-     * Parses HTTP headers and separates them from data.
-     *
-     * @return null|Response null on success, or a Response on error
-     */
-    private function parseResponseHeaders(&$data, $headers_processed = false)
-    {
-        $this->httpResponse['headers'] = array();
-        $this->httpResponse['cookies'] = array();
-
-        // Support "web-proxy-tunelling" connections for https through proxies
-        if (preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data)) {
-            // Look for CR/LF or simple LF as line separator,
-            // (even though it is not valid http)
-            $pos = strpos($data, "\r\n\r\n");
-            if ($pos || is_int($pos)) {
-                $bd = $pos + 4;
-            } else {
-                $pos = strpos($data, "\n\n");
-                if ($pos || is_int($pos)) {
-                    $bd = $pos + 2;
-                } else {
-                    // No separation between response headers and body: fault?
-                    $bd = 0;
-                }
-            }
-            if ($bd) {
-                // this filters out all http headers from proxy.
-                // maybe we could take them into account, too?
-                $data = substr($data, $bd);
-            } else {
-                error_log('XML-RPC: ' . __METHOD__ . ': HTTPS via proxy error, tunnel connection possibly failed');
-                $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], PhpXmlRpc::$xmlrpcstr['http_error'] . ' (HTTPS via proxy error, tunnel connection possibly failed)');
-
-                return $r;
-            }
-        }
-
-        // Strip HTTP 1.1 100 Continue header if present
-        while (preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data)) {
-            $pos = strpos($data, 'HTTP', 12);
-            // server sent a Continue header without any (valid) content following...
-            // give the client a chance to know it
-            if (!$pos && !is_int($pos)) {
-                // works fine in php 3, 4 and 5
-
-                break;
-            }
-            $data = substr($data, $pos);
-        }
-        if (!preg_match('/^HTTP\/[0-9.]+ 200 /', $data)) {
-            $errstr = substr($data, 0, strpos($data, "\n") - 1);
-            error_log('XML-RPC: ' . __METHOD__ . ': HTTP error, got response: ' . $errstr);
-            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], PhpXmlRpc::$xmlrpcstr['http_error'] . ' (' . $errstr . ')');
-
-            return $r;
-        }
-
-        // be tolerant to usage of \n instead of \r\n to separate headers and data
-        // (even though it is not valid http)
-        $pos = strpos($data, "\r\n\r\n");
-        if ($pos || is_int($pos)) {
-            $bd = $pos + 4;
-        } else {
-            $pos = strpos($data, "\n\n");
-            if ($pos || is_int($pos)) {
-                $bd = $pos + 2;
-            } else {
-                // No separation between response headers and body: fault?
-                // we could take some action here instead of going on...
-                $bd = 0;
-            }
-        }
-        // be tolerant to line endings, and extra empty lines
-        $ar = preg_split("/\r?\n/", trim(substr($data, 0, $pos)));
-        while (list(, $line) = @each($ar)) {
-            // take care of multi-line headers and cookies
-            $arr = explode(':', $line, 2);
-            if (count($arr) > 1) {
-                $header_name = strtolower(trim($arr[0]));
-                /// @todo some other headers (the ones that allow a CSV list of values)
-                /// do allow many values to be passed using multiple header lines.
-                /// We should add content to $xmlrpc->_xh['headers'][$header_name]
-                /// instead of replacing it for those...
-                if ($header_name == 'set-cookie' || $header_name == 'set-cookie2') {
-                    if ($header_name == 'set-cookie2') {
-                        // version 2 cookies:
-                        // there could be many cookies on one line, comma separated
-                        $cookies = explode(',', $arr[1]);
-                    } else {
-                        $cookies = array($arr[1]);
-                    }
-                    foreach ($cookies as $cookie) {
-                        // glue together all received cookies, using a comma to separate them
-                        // (same as php does with getallheaders())
-                        if (isset($this->httpResponse['headers'][$header_name])) {
-                            $this->httpResponse['headers'][$header_name] .= ', ' . trim($cookie);
-                        } else {
-                            $this->httpResponse['headers'][$header_name] = trim($cookie);
-                        }
-                        // parse cookie attributes, in case user wants to correctly honour them
-                        // feature creep: only allow rfc-compliant cookie attributes?
-                        // @todo support for server sending multiple time cookie with same name, but using different PATHs
-                        $cookie = explode(';', $cookie);
-                        foreach ($cookie as $pos => $val) {
-                            $val = explode('=', $val, 2);
-                            $tag = trim($val[0]);
-                            $val = trim(@$val[1]);
-                            /// @todo with version 1 cookies, we should strip leading and trailing " chars
-                            if ($pos == 0) {
-                                $cookiename = $tag;
-                                $this->httpResponse['cookies'][$tag] = array();
-                                $this->httpResponse['cookies'][$cookiename]['value'] = urldecode($val);
-                            } else {
-                                if ($tag != 'value') {
-                                    $this->httpResponse['cookies'][$cookiename][$tag] = $val;
-                                }
-                            }
-                        }
-                    }
-                } else {
-                    $this->httpResponse['headers'][$header_name] = trim($arr[1]);
-                }
-            } elseif (isset($header_name)) {
-                /// @todo version1 cookies might span multiple lines, thus breaking the parsing above
-                $this->httpResponse['headers'][$header_name] .= ' ' . trim($line);
-            }
-        }
-
-        $data = substr($data, $bd);
-
-        if ($this->debug && count($this->httpResponse['headers'])) {
-            $msg = '';
-            foreach ($this->httpResponse['headers'] as $header => $value) {
-                $msg .= "HEADER: $header: $value\n";
-            }
-            foreach ($this->httpResponse['cookies'] as $header => $value) {
-                $msg .= "COOKIE: $header={$value['value']}\n";
-            }
-            $this->debugMessage($msg);
-        }
-
-        // if CURL was used for the call, http headers have been processed,
-        // and dechunking + reinflating have been carried out
-        if (!$headers_processed) {
-            // Decode chunked encoding sent by http 1.1 servers
-            if (isset($this->httpResponse['headers']['transfer-encoding']) && $this->httpResponse['headers']['transfer-encoding'] == 'chunked') {
-                if (!$data = Http::decode_chunked($data)) {
-                    error_log('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to rebuild the chunked data received from server');
-                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['dechunk_fail'], PhpXmlRpc::$xmlrpcstr['dechunk_fail']);
-
-                    return $r;
-                }
-            }
-
-            // Decode gzip-compressed stuff
-            // code shamelessly inspired from nusoap library by Dietrich Ayala
-            if (isset($this->httpResponse['headers']['content-encoding'])) {
-                $this->httpResponse['headers']['content-encoding'] = str_replace('x-', '', $this->httpResponse['headers']['content-encoding']);
-                if ($this->httpResponse['headers']['content-encoding'] == 'deflate' || $this->httpResponse['headers']['content-encoding'] == 'gzip') {
-                    // if decoding works, use it. else assume data wasn't gzencoded
-                    if (function_exists('gzinflate')) {
-                        if ($this->httpResponse['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
-                            $data = $degzdata;
-                            if ($this->debug) {
-                                $this->debugMessage("---INFLATED RESPONSE---[" . strlen($data) . " chars]---\n$data\n---END---");
-                            }
-                        } elseif ($this->httpResponse['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
-                            $data = $degzdata;
-                            if ($this->debug) {
-                                $this->debugMessage("---INFLATED RESPONSE---[" . strlen($data) . " chars]---\n$data\n---END---");
-                            }
-                        } else {
-                            error_log('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to decode the deflated data received from server');
-                            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['decompress_fail'], PhpXmlRpc::$xmlrpcstr['decompress_fail']);
-
-                            return $r;
-                        }
-                    } else {
-                        error_log('XML-RPC: ' . __METHOD__ . ': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
-                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['cannot_decompress'], PhpXmlRpc::$xmlrpcstr['cannot_decompress']);
-
-                        return $r;
-                    }
-                }
-            }
-        } // end of 'if needed, de-chunk, re-inflate response'
-
-        return;
-    }
-
     /**
      * Parse the xmlrpc response contained in the string $data and return a Response object.
      *
@@ -364,22 +172,20 @@ class Request
             $this->debugMessage("---GOT---\n$data\n---END---");
         }
 
-        $this->httpResponse = array();
-        $this->httpResponse['raw_data'] = $data;
-        $this->httpResponse['headers'] = array();
-        $this->httpResponse['cookies'] = array();
+        $this->httpResponse = array('raw_data' => $data, 'headers' => array(), 'cookies' => array());
 
         if ($data == '') {
             error_log('XML-RPC: ' . __METHOD__ . ': no response received from server.');
-            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['no_data'], PhpXmlRpc::$xmlrpcstr['no_data']);
-
-            return $r;
+            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_data'], PhpXmlRpc::$xmlrpcstr['no_data']);
         }
 
         // parse the HTTP headers of the response, if present, and separate them from data
         if (substr($data, 0, 4) == 'HTTP') {
-            $r = $this->parseResponseHeaders($data, $headers_processed);
-            if ($r) {
+            $httpParser = new Http();
+            try {
+                $this->httpResponse = $httpParser->parseResponseHeaders($data, $headers_processed, $this->debug);
+            } catch(\Exception $e) {
+                $r = new Response(0, $e->getCode(), $e->getMessage());
                 // failed processing of HTTP response headers
                 // save into response obj the full payload received, for debugging
                 $r->raw_data = $data;