- /**
- * 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);
-
- /// @todo when in CLI mode, do not html-encode the output
- if ($this->debug && count($this->httpResponse['headers'])) {
- print "</PRE>\n";
- foreach ($this->httpResponse['headers'] as $header => $value) {
- print htmlentities("HEADER: $header: $value\n");
- }
- foreach ($this->httpResponse['cookies'] as $header => $value) {
- print htmlentities("COOKIE: $header={$value['value']}\n");
- }
- print "</PRE>\n";
- }
-
- // 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;
- }
-