- /**
- * 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)
- print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
- }
- elseif($this->httpResponse['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
- {
- $data = $degzdata;
- if($this->debug)
- print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
- }
- 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 null;
- }
-