Skip to content

Commit

Permalink
make it possible to retrieve from Response error http codes; introduc…
Browse files Browse the repository at this point in the history
…e custom exceptions; deprecations and cleanups
  • Loading branch information
gggeek committed Dec 9, 2021
1 parent 9d2e97b commit bcf72dd
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 51 deletions.
11 changes: 11 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ XML-RPC for PHP version 4.6.0 - 2021/12/9
* new: method `Wrapper::wrapPhpClass` allows to customize the names of the phpxmlrpc methods by stripping the original
class name and accompanying namespace and replace it with a user-defined prefix, via option `replace_class_name`

* new: `Response` constructor gained a 4th argument

* deprecated: properties `Response::hdrs`, `Response::_cookies`, `Response::raw_data`. Use `Response::httpResponse()` instead.
That method returns an array which also holds the http response's status code - useful in case of http errors.

* deprecated: method `Request::createPayload`. Use `Request::serialize` instead

* deprecated: property `Request::httpResponse`

* improved: `Http::parseResponseHeaders` now throws a more specific exception in case of http errors

* improved: Continuous Integration is now running on Github Actions instead of Travis


Expand Down
2 changes: 2 additions & 0 deletions lib/xmlrpc.inc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ include_once(__DIR__.'/../src/PhpXmlRpc.php');
include_once(__DIR__.'/../src/Request.php');
include_once(__DIR__.'/../src/Response.php');
include_once(__DIR__.'/../src/Value.php');
include_once(__DIR__.'/../src/Exception/HttpException.php');
include_once(__DIR__.'/../src/Exception/PhpXmlrpcException.php');
include_once(__DIR__.'/../src/Helper/Charset.php');
include_once(__DIR__.'/../src/Helper/Date.php');
include_once(__DIR__.'/../src/Helper/Http.php');
Expand Down
8 changes: 4 additions & 4 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace PhpXmlRpc;

use PhpXmlRpc\Helper\Logger;

use PhpXmlRpc\Helper\XMLParser;
/**
* Used to represent a client of an XML-RPC server.
*/
Expand Down Expand Up @@ -111,7 +111,7 @@ class Client
* response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
* server as an xmlrpc string or base64 value.
*/
public $return_type = 'xmlrpcvals';
public $return_type = XMLParser::RETURN_XMLRPCVALS;

/**
* Sent to servers in http headers.
Expand Down Expand Up @@ -659,7 +659,7 @@ protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $userna

// Only create the payload if it was not created previously
if (empty($req->payload)) {
$req->createPayload($this->request_charset_encoding);
$req->serialize($this->request_charset_encoding);
}

$payload = $req->payload;
Expand Down Expand Up @@ -894,7 +894,7 @@ protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username

// Only create the payload if it was not created previously
if (empty($req->payload)) {
$req->createPayload($this->request_charset_encoding);
$req->serialize($this->request_charset_encoding);
}

// Deflate request body and set appropriate request headers
Expand Down
19 changes: 19 additions & 0 deletions src/Exception/HttpException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace PhpXmlRpc\Exception;

class HttpException extends PhpXmlrpcException
{
protected $statusCode;

public function __construct($message = "", $code = 0, $previous = null, $statusCode = null)
{
parent::__construct($message, $code, $previous);
$this->statusCode = $statusCode;
}

public function statusCode()
{
return $this->statusCode;
}
}
7 changes: 7 additions & 0 deletions src/Exception/PhpXmlrpcException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace PhpXmlRpc\Exception;

class PhpXmlrpcException extends \Exception
{
}
29 changes: 17 additions & 12 deletions src/Helper/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PhpXmlRpc\Helper;

use PhpXmlRpc\Exception\HttpException;
use PhpXmlRpc\PhpXmlRpc;

class Http
Expand Down Expand Up @@ -66,12 +67,12 @@ public static function decodeChunked($buffer)
* @param string $data the http response, headers and body. It will be stripped of headers
* @param bool $headersProcessed when true, we assume that response inflating and dechunking has been already carried out
*
* @return array with keys 'headers' and 'cookies'
* @throws \Exception
* @return array with keys 'headers', 'cookies', 'raw_data' and 'status_code'
* @throws HttpException
*/
public function parseResponseHeaders(&$data, $headersProcessed = false, $debug=0)
{
$httpResponse = array('raw_data' => $data, 'headers'=> array(), 'cookies' => array());
$httpResponse = array('raw_data' => $data, 'headers'=> array(), 'cookies' => array(), 'status_code' => null);

// Support "web-proxy-tunnelling" connections for https through proxies
if (preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data)) {
Expand All @@ -95,7 +96,7 @@ public function parseResponseHeaders(&$data, $headersProcessed = false, $debug=0
$data = substr($data, $bd);
} else {
Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': HTTPS via proxy error, tunnel connection possibly failed');
throw new \Exception(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (HTTPS via proxy error, tunnel connection possibly failed)', PhpXmlRpc::$xmlrpcerr['http_error']);
throw new HttpException(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (HTTPS via proxy error, tunnel connection possibly failed)', PhpXmlRpc::$xmlrpcerr['http_error']);
}
}

Expand All @@ -114,16 +115,20 @@ public function parseResponseHeaders(&$data, $headersProcessed = false, $debug=0

// When using Curl to query servers using Digest Auth, we get back a double set of http headers.
// We strip out the 1st...
if ($headersProcessed && preg_match('/^HTTP\/[0-9.]+ 401 /', $data)) {
if (preg_match('/(\r?\n){2}HTTP\/[0-9.]+ 200 /', $data)) {
$data = preg_replace('/^HTTP\/[0-9.]+ 401 .+?(?:\r?\n){2}(HTTP\/[0-9.]+ 200 )/s', '$1', $data, 1);
if ($headersProcessed && preg_match('/^HTTP\/[0-9]\.[0-9] 401 /', $data)) {
if (preg_match('/(\r?\n){2}HTTP\/[0-9]\.[0-9] 200 /', $data)) {
$data = preg_replace('/^HTTP\/[0-9]\.[0-9] 401 .+?(?:\r?\n){2}(HTTP\/[0-9.]+ 200 )/s', '$1', $data, 1);
}
}

if (!preg_match('/^HTTP\/[0-9.]+ 200 /', $data)) {
if (preg_match('/^HTTP\/[0-9]\.[0-9] ([0-9]{3}) /', $data, $matches)) {
$httpResponse['status_code'] = $matches[1];
}

if ($httpResponse['status_code'] !== '200') {
$errstr = substr($data, 0, strpos($data, "\n") - 1);
Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': HTTP error, got response: ' . $errstr);
throw new \Exception(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (' . $errstr . ')', PhpXmlRpc::$xmlrpcerr['http_error']);
throw new HttpException(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (' . $errstr . ')', PhpXmlRpc::$xmlrpcerr['http_error'], null, $httpResponse['status_code'] );
}

// be tolerant to usage of \n instead of \r\n to separate headers and data
Expand Down Expand Up @@ -220,7 +225,7 @@ public function parseResponseHeaders(&$data, $headersProcessed = false, $debug=0
if (isset($httpResponse['headers']['transfer-encoding']) && $httpResponse['headers']['transfer-encoding'] == 'chunked') {
if (!$data = static::decodeChunked($data)) {
Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to rebuild the chunked data received from server');
throw new \Exception(PhpXmlRpc::$xmlrpcstr['dechunk_fail'], PhpXmlRpc::$xmlrpcerr['dechunk_fail']);
throw new HttpException(PhpXmlRpc::$xmlrpcstr['dechunk_fail'], PhpXmlRpc::$xmlrpcerr['dechunk_fail'], null, $httpResponse['status_code']);
}
}

Expand All @@ -243,11 +248,11 @@ public function parseResponseHeaders(&$data, $headersProcessed = false, $debug=0
}
} else {
Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to decode the deflated data received from server');
throw new \Exception(PhpXmlRpc::$xmlrpcstr['decompress_fail'], PhpXmlRpc::$xmlrpcerr['decompress_fail']);
throw new HttpException(PhpXmlRpc::$xmlrpcstr['decompress_fail'], PhpXmlRpc::$xmlrpcerr['decompress_fail'], null, $httpResponse['status_code']);
}
} else {
Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
throw new \Exception(PhpXmlRpc::$xmlrpcstr['cannot_decompress'], PhpXmlRpc::$xmlrpcerr['cannot_decompress']);
throw new HttpException(PhpXmlRpc::$xmlrpcstr['cannot_decompress'], PhpXmlRpc::$xmlrpcerr['cannot_decompress'], null, $httpResponse['status_code']);
}
}
}
Expand Down
45 changes: 21 additions & 24 deletions src/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PhpXmlRpc;

use PhpXmlRpc\Exception\HttpException;
use PhpXmlRpc\Helper\Charset;
use PhpXmlRpc\Helper\Http;
use PhpXmlRpc\Helper\Logger;
Expand All @@ -27,6 +28,7 @@ class Request
public $content_type = 'text/xml';

// holds data while parsing the response. NB: Not a full Response object
/** @deprecated will be removed in a future release */
protected $httpResponse = array();

public function getLogger()
Expand Down Expand Up @@ -237,7 +239,7 @@ public function parseResponseFile($fp, $headersProcessed = false, $returnType =
*
* @todo parsing Responses is not really the responsibility of the Request class. Maybe of the Client...
*/
public function parseResponse($data = '', $headersProcessed = false, $returnType = 'xmlrpcvals')
public function parseResponse($data = '', $headersProcessed = false, $returnType = XMLParser::RETURN_XMLRPCVALS)
{
if ($this->debug) {
$this->getLogger()->debugMessage("---GOT---\n$data\n---END---");
Expand All @@ -255,13 +257,12 @@ public function parseResponse($data = '', $headersProcessed = false, $returnType
$httpParser = new Http();
try {
$this->httpResponse = $httpParser->parseResponseHeaders($data, $headersProcessed, $this->debug);
} catch(\Exception $e) {
$r = new Response(0, $e->getCode(), $e->getMessage());
} catch (HttpException $e) {
// failed processing of HTTP response headers
// save into response obj the full payload received, for debugging
$r->raw_data = $data;

return $r;
return new Response(0, $e->getCode(), $e->getMessage(), '', array('raw_data' => $data, 'status_code', $e->statusCode()));
} catch(\Exception $e) {
return new Response(0, $e->getCode(), $e->getMessage(), '', array('raw_data' => $data));
}
}

Expand Down Expand Up @@ -293,12 +294,7 @@ public function parseResponse($data = '', $headersProcessed = false, $returnType

// if user wants back raw xml, give it to her
if ($returnType == 'xml') {
$r = new Response($data, 0, '', 'xml');
$r->hdrs = $this->httpResponse['headers'];
$r->_cookies = $this->httpResponse['cookies'];
$r->raw_data = $this->httpResponse['raw_data'];

return $r;
return new Response($data, 0, '', 'xml', $this->httpResponse);
}

if ($respEncoding != '') {
Expand Down Expand Up @@ -341,7 +337,9 @@ public function parseResponse($data = '', $headersProcessed = false, $returnType
// BC break: in the past for some cases we used the error message: 'XML error at line 1, check URL'

$r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason'], '',
$this->httpResponse
);

if ($this->debug) {
print $xmlRpcParser->_xh['isf_reason'];
Expand All @@ -350,20 +348,23 @@ public function parseResponse($data = '', $headersProcessed = false, $returnType
// second error check: xml well formed but not xml-rpc compliant
elseif ($xmlRpcParser->_xh['isf'] == 2) {
$r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason'], '',
$this->httpResponse
);

if ($this->debug) {
/// @todo echo something for user?
}
}
// third error check: parsing of the response has somehow gone boink.
/// @todo shall we omit this check, since we trust the parsing code?
elseif ($returnType == 'xmlrpcvals' && !is_object($xmlRpcParser->_xh['value'])) {
elseif ($returnType == XMLParser::RETURN_XMLRPCVALS && !is_object($xmlRpcParser->_xh['value'])) {
// something odd has happened
// and it's time to generate a client side error
// indicating something odd went on
$r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
PhpXmlRpc::$xmlrpcstr['invalid_return']);
$r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], PhpXmlRpc::$xmlrpcstr['invalid_return'],
'', $this->httpResponse
);
} else {
if ($this->debug > 1) {
$this->getLogger()->debugMessage(
Expand All @@ -375,7 +376,7 @@ public function parseResponse($data = '', $headersProcessed = false, $returnType

if ($xmlRpcParser->_xh['isf']) {
/// @todo we should test here if server sent an int and a string, and/or coerce them into such...
if ($returnType == 'xmlrpcvals') {
if ($returnType == XMLParser::RETURN_XMLRPCVALS) {
$errNo_v = $v['faultCode'];
$errStr_v = $v['faultString'];
$errNo = $errNo_v->scalarval();
Expand All @@ -390,16 +391,12 @@ public function parseResponse($data = '', $headersProcessed = false, $returnType
$errNo = -1;
}

$r = new Response(0, $errNo, $errStr);
$r = new Response(0, $errNo, $errStr, '', $this->httpResponse);
} else {
$r = new Response($v, 0, '', $returnType);
$r = new Response($v, 0, '', $returnType, $this->httpResponse);
}
}

$r->hdrs = $this->httpResponse['headers'];
$r->_cookies = $this->httpResponse['cookies'];
$r->raw_data = $this->httpResponse['raw_data'];

return $r;
}

Expand Down
Loading

0 comments on commit bcf72dd

Please sign in to comment.