NB: if the received strings are not parseable as dates, NULL will be returned instead of an object (but that can
be avoided by setting `PhpXmlRpc\PhpXmlRpc::$xmlrpc_reject_invalid_values = true`, see below).
+* improved: be more strict in the `Response` constructor and in `Request::addParam`: both of those will now generate
+ an error message in the log if passed unexpected values
+
* improved: be more strict in the data accepted as valid for dateTime xml-rpc values. Clearly invalid dates such as a
month '13', day '32' or hour '25' will cause an error message to be logged or the value to be rejected, depending
on configuration
* improved: made sure all debug output goes through the logger at response parsing time (there was one printf call left)
+* improved: all the Exceptions thrown by the library are now `\PhpXmlRpc\Exception` or subclasses thereof
+
* improved: all the Client's `setSomething()` methods now return the client object, allowing for usage of fluent style
calling. The same applies to `Request::setDebug`
if you subclassed id)
- traits have been introduced for all classes dealing with Logger, XMLParser and CharsetEncoder; method `setCharsetEncoder`
is now static
+ - exception `\PhpXmlRpc\Exception\PhpXmlRpcException` is deprecated. Use `\PhpXmlRpc\Exception` instead
## XML-RPC for PHP version 4.9.5 - 2023/01/11
*Note* when downloading the demo files, make sure that the demo folder is not directly accessible from the internet, i.e.
it is not within the webserver root directory.
+
+== Exception hierarchy
+
+ Exception
+ `-PhpXmlRpc/Exception
+ |-PhpXmlRpc/Exception/FaultResponseException.php
+ |-PhpXmlRpc/Exception/ParsingException.php
+ | |-PhpXmlRpc/Exception/XmlException.php
+ | `-PhpXmlRpc/Exception/XmlRpcException.php
+ |-PhpXmlRpc/Exception/TransportException.php
+ | `-PhpXmlRpc/Exception/HttpException.php
+ |-PhpXmlRpc/Exception/ServerException.php
+ | `-PhpXmlRpc/Exception/NoSuchMethodException.php
+ |-PhpXmlRpc/Exception/StateErrorException.php
+ |-PhpXmlRpc/Exception/TypeErrorException.php
+ `-PhpXmlRpc/Exception/ValueErrorException.php
+
+*Note* not all of the above exceptions are in use at the moment, but they might be in the future
include_once(__DIR__.'/../src/Request.php');
include_once(__DIR__.'/../src/Response.php');
include_once(__DIR__.'/../src/Value.php');
-include_once(__DIR__.'/../src/Exception/PhpXmlrpcException.php');
+include_once(__DIR__.'/../src/Exception.php');
+include_once(__DIR__.'/../src/Exception/FaultResponseException.php');
+include_once(__DIR__.'/../src/Exception/ParsingException.php');
+include_once(__DIR__.'/../src/Exception/XmlException.php');
+include_once(__DIR__.'/../src/Exception/XmlRpcException.php');
+include_once(__DIR__.'/../src/Exception/TransportException.php');
include_once(__DIR__.'/../src/Exception/HttpException.php');
+include_once(__DIR__.'/../src/Exception/ServerException.php');
+include_once(__DIR__.'/../src/Exception/NoSuchMethodException.php');
+include_once(__DIR__.'/../src/Exception/StateErrorException.php');
+include_once(__DIR__.'/../src/Exception/TypeErrorException.php');
+include_once(__DIR__.'/../src/Exception/ValueErrorException.php');
include_once(__DIR__.'/../src/Helper/Charset.php');
include_once(__DIR__.'/../src/Helper/Date.php');
include_once(__DIR__.'/../src/Helper/Http.php');
--- /dev/null
+<?php
+
+namespace PhpXmlRpc;
+
+class Exception extends \Exception
+{
+}
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+use PhpXmlRpc\Exception as BaseExtension;
+
+/**
+ * To be used when throwing exceptions instead of returning Response objects (future API?)
+ */
+class FaultResponseException extends BaseExtension
+{
+}
namespace PhpXmlRpc\Exception;
-class HttpException extends PhpXmlrpcException
+/**
+ * To be used for all errors related to parsing HTTP requests and responses
+ */
+class HttpException extends TransportException
{
protected $statusCode;
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+class NoSuchMethodException extends ServerException
+{
+}
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+use PhpXmlRpc\Exception as BaseExtension;
+
+/**
+ * Base exception for errors while parsing xml-rpc requests/responses: charset issue, xml issues, xml-rpc issues
+ */
+class ParsingException extends BaseExtension
+{
+}
<?php
-namespace PhpXmlRpc\Exception;
-
-class PhpXmlrpcException extends \Exception
-{
-}
+// deprecated. Kept around for BC
+class_alias('PhpXmlRpc\Exception', 'PhpXmlRpc\Exception\PhpXmlRpcException');
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+use PhpXmlRpc\Exception as BaseExtension;
+
+/**
+ * Base exception for errors thrown by the server while trying to handle the requests, such as errors with the dispatch map
+ */
+class ServerException extends BaseExtension
+{
+}
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+use PhpXmlRpc\Exception as BaseExtension;
+
+/**
+ * Exception thrown when an object is in such a state that it can not fulfill execution of a method
+ */
+class StateErrorException extends BaseExtension
+{
+}
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+use PhpXmlRpc\Exception as BaseExtension;
+
+/**
+ * To be used for all errors related to the transport, which are not related to specifically to HTTP. Eg: can not open socket
+ */
+class TransportException extends BaseExtension
+{
+}
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+use PhpXmlRpc\Exception as BaseExtension;
+
+/**
+ * Exception thrown when an argument passed to a function or method has an unsupported type
+ */
+class TypeErrorException extends BaseExtension
+{
+}
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+use PhpXmlRpc\Exception as BaseExtension;
+
+/**
+ * Exception thrown when an argument passed to a function or method has an unsupported value (but its type is ok)
+ */
+class ValueErrorException extends BaseExtension
+{
+}
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+class XmlException extends ParsingException
+{
+}
--- /dev/null
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+class XmlRpcException extends ParsingException
+{
+}
namespace PhpXmlRpc\Helper;
+use PhpXmlRpc\Exception\ValueErrorException;
use PhpXmlRpc\PhpXmlRpc;
use PhpXmlRpc\Traits\LoggerAware;
* @param string $tableName
* @return void
*
- * @throws \Exception for unsupported $tableName
+ * @throws ValueErrorException for unsupported $tableName
*
* @todo add support for cp1252 as well as latin-2 .. latin-10
* Optimization creep: instead of building all those tables on load, keep them ready-made php files
break;*/
default:
- throw new \Exception('Unsupported table: ' . $tableName);
+ throw new ValueErrorException('Unsupported table: ' . $tableName);
}
}
*
* @param string $charset
* @return array
- * @throws \Exception for unknown/unsupported charsets
+ * @throws ValueErrorException for unknown/unsupported charsets
*/
public function getEntities($charset)
{
case 'iso88591':
return $this->xml_iso88591_Entities;
default:
- throw new \Exception('Unsupported charset: ' . $charset);
+ throw new ValueErrorException('Unsupported charset: ' . $charset);
}
}
}
return;
}
- //$prevAccept = $this->accept;
- //$this->accept = $accept;
$this->current_parsing_options = array('accept' => $accept);
$mergedOptions = $this->parsing_options;
break;
}
}
+ /// @todo bump minimum php version to 5.5 and use a finally clause instead of doing cleanup 3 times
} catch (\Exception $e) {
xml_parser_free($parser);
$this->current_parsing_options = array();
- //$this->accept = $prevAccept;
/// @todo should we set $this->_xh['isf'] and $this->_xh['isf_reason'] ?
throw $e;
+ } catch (\Error $e) {
+ xml_parser_free($parser);
+ $this->current_parsing_options = array();
+ //$this->accept = $prevAccept;
+ /// @todo should we set $this->_xh['isf'] and $this->_xh['isf_reason'] ?
+ throw $e;
}
xml_parser_free($parser);
$this->current_parsing_options = array();
- //$this->accept = $prevAccept;
}
/**
return true;
} else {
+ $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': value passed in must be a PhpXmlRpc\Value');
return false;
}
}
namespace PhpXmlRpc;
+use PhpXmlRpc\Exception\StateErrorException;
use PhpXmlRpc\Traits\CharsetEncoderAware;
+use PhpXmlRpc\Traits\LoggerAware;
/**
* This class provides the representation of the response of an XML-RPC server.
class Response
{
use CharsetEncoderAware;
+ use LoggerAware;
/// @todo: do these need to be public?
/** @internal */
protected $httpResponse = array('headers' => array(), 'cookies' => array(), 'raw_data' => '', 'status_code' => null);
/**
- * @param Value|string|mixed $val either a Value object, a php value or the xml serialization of an xml-rpc value (a string)
+ * @param Value|string|mixed $val either a Value object, a php value or the xml serialization of an xml-rpc value (a string).
+ * Note that using anything other than a Value object wll have an impact on serialization.
* @param integer $fCode set it to anything but 0 to create an error response. In that case, $val is discarded
* @param string $fString the error string, in case of an error response
* @param string $valType The type of $val passed in. Either 'xmlrpcvals', 'phpvals' or 'xml'. Leave empty to let
- * the code guess the correct type.
+ * the code guess the correct type by looking at $val - in which case strings are assumed
+ * to be serialized xml
* @param array|null $httpResponse this should be set when the response is being built out of data received from
* http (i.e. not when programmatically building a Response server-side). Array
* keys should include, if known: headers, cookies, raw_data, status_code
*
- * @todo add check that $val / $fCode / $fString is of correct type???
+ * @todo add check that $val / $fCode / $fString is of correct type? We could at least log a warning for fishy cases...
* NB: as of now we do not do it, since it might be either an xml-rpc value or a plain php val, or a complete
- * xml chunk, depending on usage of Client::send() inside which the constructor is called...
+ * xml chunk, depending on usage of Client::send() inside which the constructor is called.
*/
public function __construct($val, $fCode = 0, $fString = '', $valType = '', $httpResponse = null)
{
$this->valtyp = 'phpvals';
}
} else {
- // user declares type of resp value: believe him
$this->valtyp = $valType;
+ // user declares the type of resp value: we "almost" trust it... but log errors just in case
+ if (($this->valtyp == 'xmlrpcvals' && (!is_a($this->val, 'PhpXmlRpc\Value'))) ||
+ ($this->valtyp == 'xml' && (!is_string($this->val)))) {
+ $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': value passed in does not match type ' . $valType);
+ }
}
}
*
* @param string $charsetEncoding the charset to be used for serialization. If null, US-ASCII is assumed
* @return string the xml representation of the response
- * @throws \Exception
+ * @throws StateErrorException if the response was built out of a value of an unsupported type
*/
public function serialize($charsetEncoding = '')
{
$result .= "<fault>\n" .
"<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
"</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
- $this->getCharsetEncoder()->encodeEntities($this->errstr, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</string></value>\n</member>\n" .
- "</struct>\n</value>\n</fault>";
+ $this->getCharsetEncoder()->encodeEntities($this->errstr, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) .
+ "</string></value>\n</member>\n</struct>\n</value>\n</fault>";
} else {
- if (!is_object($this->val) || !is_a($this->val, 'PhpXmlRpc\Value')) {
- if (is_string($this->val) && $this->valtyp == 'xml') {
- $result .= "<params>\n<param>\n" .
- $this->val .
- "</param>\n</params>";
- } else {
- /// @todo try to build something serializable using the Encoder...
- throw new \Exception('cannot serialize xmlrpc response objects whose content is native php values');
- }
- } else {
+ if (is_object($this->val) && is_a($this->val, 'PhpXmlRpc\Value')) {
+ $result .= "<params>\n<param>\n" . $this->val->serialize($charsetEncoding) . "</param>\n</params>";
+ } else if (is_string($this->val) && $this->valtyp == 'xml') {
$result .= "<params>\n<param>\n" .
- $this->val->serialize($charsetEncoding) .
+ $this->val .
"</param>\n</params>";
+ } else if ($this->valtyp == 'phpvals') {
+ $encoder = new Encoder();
+ $val = $encoder->encode($this->val);
+ $result .= "<params>\n<param>\n" . $val->serialize($charsetEncoding) . "</param>\n</params>";
+ } else {
+ throw new StateErrorException('cannot serialize xmlrpc response objects whose content is native php values');
}
}
$result .= "\n</methodResponse>";
namespace PhpXmlRpc;
-use PhpXmlRpc\Exception\PhpXmlrpcException;
+use PhpXmlRpc\Exception\NoSuchMethodException;
use PhpXmlRpc\Helper\Http;
use PhpXmlRpc\Helper\Interop;
use PhpXmlRpc\Helper\Logger;
$xmlRpcParser = $this->getParser();
try {
$xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST, $options);
- } catch (PhpXmlrpcException $e) {
+ } catch (NoSuchMethodException $e) {
return new Response(0, $e->getCode(), $e->getMessage());
}
$r = new Response($encoder->encode($r, $this->phpvals_encoding_options));
}
}
+ /// @todo bump minimum php version to 7.1 and use a single catch clause instead of the duplicate blocks
} catch (\Exception $e) {
// (barring errors in the lib) an uncaught exception happened in the called function, we wrap it in a
// proper error-response
* @param XMLParser $xmlParser
* @param resource $parser
* @return void
- * @throws PhpXmlrpcException
+ * @throws NoSuchMethodException
*
* @todo feature creep - we could validate here that the method in the dispatch map is valid, but that would mean
* dirtying a lot the logic, as we would have back to both parseRequest() and execute() methods the info
if (!isset($dmap[$methodName]['function'])) {
// No such method
- throw new PhpXmlrpcException(PhpXmlRpc::$xmlrpcstr['unknown_method'], PhpXmlRpc::$xmlrpcerr['unknown_method']);
+ throw new NoSuchMethodException(PhpXmlRpc::$xmlrpcstr['unknown_method'], PhpXmlRpc::$xmlrpcerr['unknown_method']);
}
// alter on-the-fly the config of the xml parser if needed
namespace PhpXmlRpc;
+use PhpXmlRpc\Exception\StateErrorException;
+use PhpXmlRpc\Exception\TypeErrorException;
+use PhpXmlRpc\Exception\ValueErrorException;
use PhpXmlRpc\Traits\CharsetEncoderAware;
use PhpXmlRpc\Traits\LoggerAware;
* @param mixed $value
* @return void
*
- * @throws \Exception
+ * @throws ValueErrorException|TypeErrorException
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
switch ($this->mytype) {
case 3:
- if (!($value instanceof \PhpXmlRpc\Value)) {
- throw new \Exception('It is only possible to add Value objects to an XML-RPC Struct');
+ if (!($value instanceof Value)) {
+ throw new TypeErrorException('It is only possible to add Value objects to an XML-RPC Struct');
}
if (is_null($offset)) {
// disallow struct members with empty names
- throw new \Exception('It is not possible to add anonymous members to an XML-RPC Struct');
+ throw new ValueErrorException('It is not possible to add anonymous members to an XML-RPC Struct');
} else {
$this->me['struct'][$offset] = $value;
}
return;
case 2:
- if (!($value instanceof \PhpXmlRpc\Value)) {
- throw new \Exception('It is only possible to add Value objects to an XML-RPC Array');
+ if (!($value instanceof Value)) {
+ throw new TypeErrorException('It is only possible to add Value objects to an XML-RPC Array');
}
if (is_null($offset)) {
$this->me['array'][] = $value;
reset($this->me);
$type = key($this->me);
if ($type != $offset) {
- throw new \Exception('');
+ throw new ValueErrorException('');
}
$this->me[$type] = $value;
return;
default:
// it would be nice to allow empty values to be turned into non-empty ones this way, but we miss info to do so
- throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be set using array index");
+ throw new ValueErrorException("XML-RPC Value is of type 'undef' and its value can not be set using array index");
}
}
* @param mixed $offset
* @return void
*
- * @throws \Exception
+ * @throws ValueErrorException|StateErrorException
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
return;
case 1:
// can not remove value from a scalar
- throw new \Exception("XML-RPC Value is of type 'scalar' and its value can not be unset using array index");
+ throw new StateErrorException("XML-RPC Value is of type 'scalar' and its value can not be unset using array index");
default:
- throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be unset using array index");
+ throw new StateErrorException("XML-RPC Value is of type 'undef' and its value can not be unset using array index");
}
}
*
* @param mixed $offset
* @return mixed|Value|null
- * @throws \Exception
+ * @throws StateErrorException
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
return $type == $offset ? $value : null;
default:
// return null or exception?
- throw new \Exception("XML-RPC Value is of type 'undef' and can not be accessed using array index");
+ throw new StateErrorException("XML-RPC Value is of type 'undef' and can not be accessed using array index");
}
}
}
namespace PhpXmlRpc;
+use PhpXmlRpc\Exception\ValueErrorException;
use PhpXmlRpc\Traits\LoggerAware;
/**
if (is_string($throwFault)) {
throw new $throwFault($resp->faultString(), $resp->faultCode());
} else {
- throw new \Exception($resp->faultString(), $resp->faultCode());
+ throw new \PhpXmlRpc\Exception($resp->faultString(), $resp->faultCode());
}
} else if ($decodeFault) {
if (is_string($faultResponse) && ((strpos($faultResponse, '%faultCode%') !== false) ||
$plist = implode(', ', $plist);
$mDesc .= ' * @return ' . $this->xmlrpc2PhpType($mSig[0]);
if ($throwFault) {
- $mDesc .= "\n * @throws " . (is_string($throwFault) ? $throwFault : '\\Exception');
+ $mDesc .= "\n * @throws " . (is_string($throwFault) ? $throwFault : '\\PhpXmlRpc\\Exception');
} else if ($decodeFault) {
$mDesc .= '|' . gettype($faultResponse) . " (a " . gettype($faultResponse) . " if call fails)";
} else {
$innerCode .= " \$res = \${$this_}client->send(\$req, $timeout, '$protocol');\n";
if ($throwFault) {
if (!is_string($throwFault)) {
- $throwFault = '\\Exception';
+ $throwFault = '\\PhpXmlRpc\\Exception';
}
$respCode = "throw new $throwFault(\$res->faultString(), \$res->faultCode())";
} else if ($decodeFault) {
/**
* @param string $index
* @return object
- * @throws \Exception
+ * @throws ValueErrorException
*/
public static function getHeldObject($index)
{
return self::$objHolder[$index];
}
- throw new \Exception("No object held for index '$index'");
+ throw new ValueErrorException("No object held for index '$index'");
}
}