case 'MEMBER':
// set member name to null, in case we do not find in the xml later on
+ /// @todo we could reject structs missing a NAME in the MEMBER element
$this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = '';
//$this->_xh['ac']='';
// Drop trough intentionally
// We translate boolean 1 or 0 into PHP constants true or false. Strings 'true' and 'false' are accepted,
// even though the spec never mentions them (see e.g. Blogger api docs)
// NB: this simple checks helps a lot sanitizing input, i.e. no security problems around here
- if ($this->_xh['ac'] == '1' || strcasecmp($this->_xh['ac'], 'true') == 0) {
+ // Note the non-strict type check: it will allow ' 1 '
+ /// @todo feature-creep: use a flexible regexp, the same as we do with int, double and datetime.
+ /// Note that using a regexp would also make this test less sensitive to phpunit shenanigans
+ if ($this->_xh['ac'] == '1' || strcasecmp($this->_xh['ac'], 'true') === 0) {
$this->_xh['value'] = true;
} else {
// log if receiving something strange, even though we set the value to false anyway
/// @todo to be consistent with the other types, we should use a value outside the good-value domain, e.g. NULL
- if ($this->_xh['ac'] != '0' && strcasecmp($this->_xh['ac'], 'false') != 0) {
+ if ($this->_xh['ac'] != '0' && strcasecmp($this->_xh['ac'], 'false') !== 0) {
$this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid data received in BOOLEAN value: ' . $this->_xh['ac']);
if ($this->current_parsing_options['xmlrpc_reject_invalid_values'])
{
// `Value::scalartyp()` function will do some normalization of the data
$this->_xh['vt'] = strtolower($name);
$this->_xh['lv'] = 3; // indicate we've found a value
- // we must check that only 0123456789-<space> are characters here
- if (!preg_match('/^[+-]?[0123456789 \t]+$/', $this->_xh['ac'])) {
+ if (!preg_match(PhpXmlRpc::$xmlrpc_int_format, $this->_xh['ac'])) {
$this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric data received in INT: ' . $this->_xh['ac']);
if ($this->current_parsing_options['xmlrpc_reject_invalid_values'])
{
case 'DOUBLE':
$this->_xh['vt'] = Value::$xmlrpcDouble;
$this->_xh['lv'] = 3; // indicate we've found a value
- // we must check that only 0123456789-.<space> are characters here
- // NOTE: regexp could be much stricter than this...
- if (!preg_match('/^[+-eE0123456789 \t.]+$/', $this->_xh['ac'])) {
+ if (!preg_match(PhpXmlRpc::$xmlrpc_double_format, $this->_xh['ac'])) {
$this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric data received in DOUBLE value: ' . $this->_xh['ac']);
if ($this->current_parsing_options['xmlrpc_reject_invalid_values'])
{
case 'BASE64':
$this->_xh['vt'] = Value::$xmlrpcBase64;
$this->_xh['lv'] = 3; // indicate we've found a value
- /// @todo check: should we silence warnings here?
- $v = base64_decode($this->_xh['ac']);
- if ($v === false) {
- $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid data received in BASE64 value');
- if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
+ if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
+ $v = base64_decode($this->_xh['ac'], true);
+ if ($v === false) {
+ $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid data received in BASE64 value');
$this->_xh['isf'] = 2;
$this->_xh['isf_reason'] = 'Invalid data received in BASE64 value';
return;
}
+ } else {
+ $v = base64_decode($this->_xh['ac']);
+ if ($v === '' && $this->_xh['ac'] !== '') {
+ // only the empty string should decode to the empty string
+ $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid data received in BASE64 value');
+ }
}
$this->_xh['value'] = $v;
break;
// add to array in the stack the last element built, unless no VALUE was found
if ($this->_xh['vt']) {
$vscount = count($this->_xh['valuestack']);
- if (!isset($this->_xh['valuestack'][$vscount - 1]['name'])) {
- /// @todo handle the case of the NAME element actually following the VALUE in the xml!!!
- $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': missing NAME inside STRUCT in received xml');
- }
- $this->_xh['valuestack'][$vscount - 1]['values'][$this->_xh['valuestack'][$vscount - 1]['name']] = $this->_xh['value'];
+ // NB: atm we always initialize members with an empty name in xmlrpc__ee, so no need for this check.
+ // We could make parsing stricter though...
+ //if (isset($this->_xh['valuestack'][$vscount - 1]['name'])) {
+ $this->_xh['valuestack'][$vscount - 1]['values'][$this->_xh['valuestack'][$vscount - 1]['name']] = $this->_xh['value'];
+ //} else {
+ // /// @todo return a parsing error if $this->current_parsing_options['xmlrpc_reject_invalid_values']?
+ // $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': missing NAME inside STRUCT in received xml');
+ //}
} else {
- /// @todo return a parsing error?
+ /// @todo return a parsing error $this->current_parsing_options['xmlrpc_reject_invalid_values']?
$this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside STRUCT in received xml');
}
break;
use PHPUnit\Runner\BaseTestRunner;
/**
- * Tests involving the Request and Response classes.
+ * Tests involving xml parsing.
*
- * @todo many tests are here only because we use a Response to trigger parsing of xml for a single Value, but they
- * logically belong elsewhere...
+ * @todo some tests are here even though they logically belong elsewhere...
*/
-class MessageTests extends PhpXmlRpc_PolyfillTestCase
+class ParsingTests extends PhpXmlRpc_PolyfillTestCase
{
public $args = array();
}
}
- protected function newMsg($methodName, $params = array())
+ protected function newRequest($methodName, $params = array())
{
$msg = new xmlrpcmsg($methodName, $params);
$msg->setDebug($this->args['DEBUG']);
public function testValidNumbers()
{
- $m = $this->newMsg('dummy');
+ $m = $this->newRequest('dummy');
$fp =
'<?xml version="1.0"?>
<methodResponse>
<param>
<value>
<struct>
-<member>
-<name>integer1</name>
-<value><int>01</int></value>
-</member>
-<member>
-<name>integer2</name>
-<value><int>+1</int></value>
-</member>
-<member>
-<name>integer3</name>
-<value><i4>1</i4></value>
-</member>
-<member>
-<name>float1</name>
-<value><double>01.10</double></value>
-</member>
-<member>
-<name>float2</name>
-<value><double>+1.10</double></value>
-</member>
-<member>
-<name>float3</name>
-<value><double>-1.10e2</double></value>
-</member>
+<member><name>integer1</name><value><int>01</int></value></member>
+<member><name>integer2</name><value><int>+1</int></value></member>
+<member><name>integer3</name><value><i4>1</i4></value></member>
+<member><name>integer4</name><value><int> 1 </int></value></member>
+<member><name>float1</name><value><double>01.10</double></value></member>
+<member><name>float2</name><value><double>+1.10</double></value></member>
+<member><name>float3</name><value><double>-1.10e2</double></value></member>
+<member><name>float4</name><value><double> -1.10e2 </double></value></member>
</struct>
</value>
</param>
$s = $v->structmem('integer1');
$t = $v->structmem('integer2');
$u = $v->structmem('integer3');
+ $u2 = $v->structmem('integer4');
$x = $v->structmem('float1');
$y = $v->structmem('float2');
$z = $v->structmem('float3');
+ $z2 = $v->structmem('float4');
$this->assertEquals(1, $s->scalarval());
$this->assertEquals(1, $t->scalarval());
$this->assertEquals(1, $u->scalarval());
+ $this->assertEquals(1, $u2->scalarval());
+ $this->assertEquals('int', $u->scalartyp());
$this->assertEquals(1.1, $x->scalarval());
$this->assertEquals(1.1, $y->scalarval());
$this->assertEquals(-110.0, $z->scalarval());
+ $this->assertEquals(-110.0, $z2->scalarval());
+ }
+
+ public function testBooleans()
+ {
+ $m = $this->newRequest('dummy');
+ $fp =
+ '<?xml version="1.0"?>
+<methodResponse><params><param><value><struct>
+<member><name>b1</name>
+<value><boolean>1</boolean></value></member>
+<member><name>b2</name>
+<value><boolean> 1 </boolean></value></member>
+<member><name>b3</name>
+<value><boolean>tRuE</boolean></value></member>
+<member><name>b4</name>
+<value><boolean>0</boolean></value></member>
+<member><name>b5</name>
+<value><boolean> 0 </boolean></value></member>
+<member><name>b6</name>
+<value><boolean>fAlSe</boolean></value></member>
+</struct></value></param></params></methodResponse>';
+ $r = $m->parseResponse($fp);
+ $v = $r->value();
+
+ $s = $v->structmem('b1');
+ $t = $v->structmem('b2');
+ $u = $v->structmem('b3');
+ $x = $v->structmem('b4');
+ $y = $v->structmem('b5');
+ $z = $v->structmem('b6');
+
+ /// @todo this test fails with phpunit, but the same code works elsewhere!
+ $this->assertEquals(true, $s->scalarval());
+ //$this->assertEquals(true, $t->scalarval());
+ $this->assertEquals(true, $u->scalarval());
+ $this->assertEquals(false, $x->scalarval());
+ //$this->assertEquals(false, $y->scalarval());
+ $this->assertEquals(false, $z->scalarval());
}
public function testI8()
return;
}
- $m = $this->newMsg('dummy');
+ $m = $this->newRequest('dummy');
$fp =
'<?xml version="1.0"?>
<methodResponse>
<name>integer1</name>
<value><i8>1</i8></value>
</member>
+<member>
+<name>integer2</name>
+<value><ex:i8>1</ex:i8></value>
+</member>
</struct>
</value>
</param>
$v = $r->value();
$s = $v->structmem('integer1');
$this->assertEquals(1, $s->scalarval());
+ $s = $v->structmem('integer2');
+ $this->assertEquals(1, $s->scalarval());
+ $this->assertEquals('i8', $s->scalartyp());
+ }
+
+ // struct with value before name, with no name, with no value, etc...
+ public function testQuirkyStruct()
+ {
+ $m = $this->newRequest('dummy');
+ $fp =
+ '<?xml version="1.0"?>
+<methodResponse>
+<params>
+<param>
+<value>
+<struct>
+<member>
+<value><int>1</int></value>
+<name>Gollum</name>
+</member>
+<member>
+<name>Bilbo</name>
+</member>
+<member>
+<value><int>9</int></value>
+</member>
+<member>
+<value><int>1</int></value>
+</member>
+</struct>
+</value>
+</param>
+</params>
+</methodResponse>';
+ $r = $m->parseResponse($fp);
+ $v = $r->value();
+ $this->assertEquals(2, count($v));
+ $s = $v['Gollum'];
+ $this->assertEquals(1, $s->scalarval());
+ $s = $v[''];
+ $this->assertEquals(1, $s->scalarval());
}
public function testUnicodeInMemberName()
$v = array($str => new xmlrpcval(1));
$r = new xmlrpcresp(new xmlrpcval($v, 'struct'));
$r = $r->serialize();
- $m = $this->newMsg('dummy');
+ $m = $this->newRequest('dummy');
$r = $m->parseResponse($r);
$v = $r->value();
$this->assertEquals(true, $v->structmemexists($str));
</value>
</fault>
</methodResponse>');
- $m = $this->newMsg('dummy');
+ $m = $this->newRequest('dummy');
$r = $m->parseResponse($response);
$v = $r->faultString();
$this->assertEquals(chr(224) . chr(252) . chr(232) . chr(224) . chr(252) . chr(232), $v);
public function testBrokenResponses()
{
- $m = $this->newMsg('dummy');
+ $m = $this->newRequest('dummy');
// omitting the 'params' tag: no more tolerated by the lib...
$f = '<?xml version="1.0"?>
<methodResponse>
public function testBuggyHttp()
{
- $s = $this->newMsg('dummy');
+ $s = $this->newRequest('dummy');
$f = 'HTTP/1.1 100 Welcome to the jungle
HTTP/1.0 200 OK
public function testStringBug()
{
- $s = $this->newMsg('dummy');
+ $s = $this->newRequest('dummy');
$f = '<?xml version="1.0"?>
<!-- found by 2z69xks7bpy001@sneakemail.com, amongst others covers what happens when there\'s character data after </string>
and before </value> -->
$this->assertEquals('S300510007I', $s->scalarval());
}
- public function testWhiteSpace()
+ public function testBase64()
{
- $s = $this->newMsg('dummy');
+ $s = $this->newRequest('dummy');
+ $f = '<?xml version="1.0"?><methodResponse><params><param><value><base64>
+aGk=
+</base64></value></param></params></methodResponse> ';
+ $r = $s->parseResponse($f);
+ $v = $r->value();
+ $this->assertEquals('hi', $v->scalarval());
+ }
+
+ public function testInvalidValues()
+ {
+ $s = $this->newRequest('dummy');
+ $f = '<?xml version="1.0"?><methodResponse><params><param><value><struct>
+<member>
+<name>bool</name>
+<value><boolean>
+1
+</boolean></value>
+</member>
+<member>
+<name>double</name>
+<value><double>
+1.01
+</double></value>
+</member>
+<member>
+<name>int</name>
+<value><int>
+1
+</int></value>
+</member>
+<member>
+<name>date</name>
+<value><dateTime.iso8601>
+20011126T09:17:52
+</dateTime.iso8601></value>
+</member>
+<member>
+<name>base64</name>
+<value><base64>
+!
+</base64></value>
+</member>
+</struct></value></param></params></methodResponse> ';
+ $r = $s->parseResponse($f);
+ $v = $r->value();
+ // NB: this is the status-quo of the xml parser, rather than something we want the library to always be returning...
+ $this->assertEquals(false, $v['bool']->scalarval());
+ $this->assertEquals("ERROR_NON_NUMERIC_FOUND", $v['double']->scalarval());
+ $this->assertEquals("ERROR_NON_NUMERIC_FOUND", $v['int']->scalarval());
+ $this->assertEquals("\n20011126T09:17:52\n", $v['date']->scalarval());
+ $this->assertEquals("", $v['base64']->scalarval());
+ }
+
+ public function testInvalidValuesStrictMode()
+ {
+ $s = $this->newRequest('dummy');
+
+ $values = array(
+ '<boolean>x</boolean>',
+ '<double>x</double>',
+ '<double>1..</double>',
+ '<double>..1</double>',
+ '<double>1.0.1</double>',
+ '<int>x</int>',
+ '<int>1.0</int>',
+ '<dateTime.iso8601> 20011126T09:17:52</dateTime.iso8601>',
+ '<dateTime.iso8601>20011126T09:17:52 </dateTime.iso8601>',
+ '<base64>!</base64>'
+ );
+
+ $i = \PhpXmlRpc\PhpXmlRpc::$xmlrpc_reject_invalid_values;
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_reject_invalid_values = true;
+
+ foreach($values as $value) {
+ $f = '<?xml version="1.0"?><methodResponse><params><param><value>' . $value . '</value></param></params></methodResponse> ';
+ $r = $s->parseResponse($f);
+ $v = $r->faultCode();
+ $this->assertEquals(2, $v, "Testing $value");
+ }
+
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_reject_invalid_values = $i;
+ }
+
+ public function testNewlines()
+ {
+ $s = $this->newRequest('dummy');
$f = '<?xml version="1.0"?><methodResponse><params><param><value><struct><member><name>userid</name><value>311127</value></member>
<member><name>dateCreated</name><value><dateTime.iso8601>20011126T09:17:52</dateTime.iso8601></value></member><member><name>content</name><value>hello world. 2 newlines follow
public function testDoubleDataInArrayTag()
{
- $s = $this->newMsg('dummy');
+ $s = $this->newRequest('dummy');
$f = '<?xml version="1.0"?><methodResponse><params><param><value><array>
<data></data>
<data></data>
public function testDoubleStuffInValueTag()
{
- $s = $this->newMsg('dummy');
+ $s = $this->newRequest('dummy');
$f = '<?xml version="1.0"?><methodResponse><params><param><value>
<string>hello world</string>
<array><data></data></array>
public function testAutoDecodeResponse()
{
- $s = $this->newMsg('dummy');
+ $s = $this->newRequest('dummy');
$f = '<?xml version="1.0"?><methodResponse><params><param><value><struct><member><name>userid</name><value>311127</value></member>
<member><name>dateCreated</name><value><dateTime.iso8601>20011126T09:17:52</dateTime.iso8601></value></member><member><name>content</name><value>hello world. 3 newlines follow
public function testNoDecodeResponse()
{
- $s = $this->newMsg('dummy');
+ $s = $this->newRequest('dummy');
$f = '<?xml version="1.0"?><methodResponse><params><param><value><struct><member><name>userid</name><value>311127</value></member>
<member><name>dateCreated</name><value><dateTime.iso8601>20011126T09:17:52</dateTime.iso8601></value></member><member><name>content</name><value>hello world. 3 newlines follow
$this->assertEquals($f, $v);
}
- public function testUTF8Request()
- {
- $sendstring = 'κόσμε'; // Greek word 'kosme'
- $GLOBALS['xmlrpc_internalencoding'] = 'UTF-8';
- \PhpXmlRpc\PhpXmlRpc::importGlobals();
- $f = new xmlrpcval($sendstring, 'string');
- $v = $f->serialize();
- $this->assertEquals("<value><string>κόσμε</string></value>\n", $v);
- $GLOBALS['xmlrpc_internalencoding'] = 'ISO-8859-1';
- \PhpXmlRpc\PhpXmlRpc::importGlobals();
- }
-
public function testUTF8Response()
{
$string = chr(224) . chr(252) . chr(232);
- $s = $this->newMsg('dummy');
+ $s = $this->newRequest('dummy');
$f = "HTTP/1.1 200 OK\r\nContent-type: text/xml; charset=UTF-8\r\n\r\n" . '<?xml version="1.0"?><methodResponse><params><param><value><struct><member><name>userid</name><value>311127</value></member>
<member><name>dateCreated</name><value><dateTime.iso8601>20011126T09:17:52</dateTime.iso8601></value></member><member><name>content</name><value>' . @utf8_encode($string) . '</value></member><member><name>postid</name><value>7414222</value></member></struct></value></param></params></methodResponse>
';
{
$string = chr(224) . chr(252) . chr(232);
- $s = $this->newMsg('dummy');
+ $s = $this->newRequest('dummy');
$f = "HTTP/1.1 200 OK\r\nContent-type: text/xml; charset=ISO-8859-1\r\n\r\n" . '<?xml version="1.0"?><methodResponse><params><param><value><struct><member><name>userid</name><value>311127</value></member>
<member><name>dateCreated</name><value><dateTime.iso8601>20011126T09:17:52</dateTime.iso8601></value></member><member><name>content</name><value>' . $string . '</value></member><member><name>postid</name><value>7414222</value></member></struct></value></param></params></methodResponse>
';
$this->assertEquals($string, $v);
}
+ public function testDatetimeAsObject()
+ {
+ $s = $this->newRequest('dummy');
+ $f = '<?xml version="1.0"?>
+<methodResponse><params><param><value>
+<dateTime.iso8601>20011126T09:17:52</dateTime.iso8601>
+</value></param></params></methodResponse>';
+
+ $o = \PhpXmlRpc\PhpXmlRpc::$xmlrpc_return_datetimes;
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_return_datetimes = true;
+
+ $r = $s->parseResponse($f);
+ $v = $r->value();
+ $this->assertInstanceOf('\DateTime', $v->scalarval());
+
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_return_datetimes = $o;
+ }
+
+ public function testCustomDatetimeFormat()
+ {
+ $s = $this->newRequest('dummy');
+ $f = '<?xml version="1.0"?>
+<methodResponse><params><param><value>
+<dateTime.iso8601>20011126T09:17:52+01:00</dateTime.iso8601>
+</value></param></params></methodResponse>';
+
+ $o = \PhpXmlRpc\PhpXmlRpc::$xmlrpc_return_datetimes;
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_return_datetimes = true;
+ $i = \PhpXmlRpc\PhpXmlRpc::$xmlrpc_reject_invalid_values;
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_reject_invalid_values = true;
+
+ $r = $s->parseResponse($f);
+ $v = $r->faultCode();
+ $this->assertNotEquals(0, $v);
+
+ $d = \PhpXmlRpc\PhpXmlRpc::$xmlrpc_datetime_format;
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_datetime_format = '/^([0-9]{4})(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-4]):([0-5][0-9]):([0-5][0-9]|60)(Z|[+-][0-9]{2}(:?[0-9]{2})?)?$/';
+
+ $r = $s->parseResponse($f);
+ $v = $r->value();
+ $this->assertInstanceOf('\DateTime', $v->scalarval());
+
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_return_datetimes = $o;
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_reject_invalid_values = $i;
+ \PhpXmlRpc\PhpXmlRpc::$xmlrpc_datetime_format = $d;
+ }
+
/// @todo can we change this test to purely using the Value class ?
- public function testNilvalue()
+ /// @todo move test to its own class
+ public function testNilSupport()
{
// default case: we do not accept nil values received
$v = new xmlrpcval('hello', 'null');
$r = new xmlrpcresp($v);
$s = $r->serialize();
- $m = $this->newMsg('dummy');
+ $m = $this->newRequest('dummy');
$r = $m->parseresponse($s);
$this->assertequals(2, $r->faultCode());
// enable reception of nil values