1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\XmlRpc; 11 12use DOMDocument; 13use SimpleXMLElement; 14use Zend\Stdlib\ErrorHandler; 15 16/** 17 * XmlRpc Request object 18 * 19 * Encapsulates an XmlRpc request, holding the method call and all parameters. 20 * Provides accessors for these, as well as the ability to load from XML and to 21 * create the XML request string. 22 * 23 * Additionally, if errors occur setting the method or parsing XML, a fault is 24 * generated and stored in {@link $fault}; developers may check for it using 25 * {@link isFault()} and {@link getFault()}. 26 */ 27class Request 28{ 29 /** 30 * Request character encoding 31 * @var string 32 */ 33 protected $encoding = 'UTF-8'; 34 35 /** 36 * Method to call 37 * @var string 38 */ 39 protected $method; 40 41 /** 42 * XML request 43 * @var string 44 */ 45 protected $xml; 46 47 /** 48 * Method parameters 49 * @var array 50 */ 51 protected $params = array(); 52 53 /** 54 * Fault object, if any 55 * @var \Zend\XmlRpc\Fault 56 */ 57 protected $fault = null; 58 59 /** 60 * XML-RPC type for each param 61 * @var array 62 */ 63 protected $types = array(); 64 65 /** 66 * XML-RPC request params 67 * @var array 68 */ 69 protected $xmlRpcParams = array(); 70 71 /** 72 * Create a new XML-RPC request 73 * 74 * @param string $method (optional) 75 * @param array $params (optional) 76 */ 77 public function __construct($method = null, $params = null) 78 { 79 if ($method !== null) { 80 $this->setMethod($method); 81 } 82 83 if ($params !== null) { 84 $this->setParams($params); 85 } 86 } 87 88 /** 89 * Set encoding to use in request 90 * 91 * @param string $encoding 92 * @return \Zend\XmlRpc\Request 93 */ 94 public function setEncoding($encoding) 95 { 96 $this->encoding = $encoding; 97 AbstractValue::setEncoding($encoding); 98 return $this; 99 } 100 101 /** 102 * Retrieve current request encoding 103 * 104 * @return string 105 */ 106 public function getEncoding() 107 { 108 return $this->encoding; 109 } 110 111 /** 112 * Set method to call 113 * 114 * @param string $method 115 * @return bool Returns true on success, false if method name is invalid 116 */ 117 public function setMethod($method) 118 { 119 if (!is_string($method) || !preg_match('/^[a-z0-9_.:\\\\\/]+$/i', $method)) { 120 $this->fault = new Fault(634, 'Invalid method name ("' . $method . '")'); 121 $this->fault->setEncoding($this->getEncoding()); 122 return false; 123 } 124 125 $this->method = $method; 126 return true; 127 } 128 129 /** 130 * Retrieve call method 131 * 132 * @return string 133 */ 134 public function getMethod() 135 { 136 return $this->method; 137 } 138 139 /** 140 * Add a parameter to the parameter stack 141 * 142 * Adds a parameter to the parameter stack, associating it with the type 143 * $type if provided 144 * 145 * @param mixed $value 146 * @param string $type Optional; type hinting 147 * @return void 148 */ 149 public function addParam($value, $type = null) 150 { 151 $this->params[] = $value; 152 if (null === $type) { 153 // Detect type if not provided explicitly 154 if ($value instanceof AbstractValue) { 155 $type = $value->getType(); 156 } else { 157 $xmlRpcValue = AbstractValue::getXmlRpcValue($value); 158 $type = $xmlRpcValue->getType(); 159 } 160 } 161 $this->types[] = $type; 162 $this->xmlRpcParams[] = array('value' => $value, 'type' => $type); 163 } 164 165 /** 166 * Set the parameters array 167 * 168 * If called with a single, array value, that array is used to set the 169 * parameters stack. If called with multiple values or a single non-array 170 * value, the arguments are used to set the parameters stack. 171 * 172 * Best is to call with array of the format, in order to allow type hinting 173 * when creating the XMLRPC values for each parameter: 174 * <code> 175 * $array = array( 176 * array( 177 * 'value' => $value, 178 * 'type' => $type 179 * )[, ... ] 180 * ); 181 * </code> 182 * 183 * @access public 184 * @return void 185 */ 186 public function setParams() 187 { 188 $argc = func_num_args(); 189 $argv = func_get_args(); 190 if (0 == $argc) { 191 return; 192 } 193 194 if ((1 == $argc) && is_array($argv[0])) { 195 $params = array(); 196 $types = array(); 197 $wellFormed = true; 198 foreach ($argv[0] as $arg) { 199 if (!is_array($arg) || !isset($arg['value'])) { 200 $wellFormed = false; 201 break; 202 } 203 $params[] = $arg['value']; 204 205 if (!isset($arg['type'])) { 206 $xmlRpcValue = AbstractValue::getXmlRpcValue($arg['value']); 207 $arg['type'] = $xmlRpcValue->getType(); 208 } 209 $types[] = $arg['type']; 210 } 211 if ($wellFormed) { 212 $this->xmlRpcParams = $argv[0]; 213 $this->params = $params; 214 $this->types = $types; 215 } else { 216 $this->params = $argv[0]; 217 $this->types = array(); 218 $xmlRpcParams = array(); 219 foreach ($argv[0] as $arg) { 220 if ($arg instanceof AbstractValue) { 221 $type = $arg->getType(); 222 } else { 223 $xmlRpcValue = AbstractValue::getXmlRpcValue($arg); 224 $type = $xmlRpcValue->getType(); 225 } 226 $xmlRpcParams[] = array('value' => $arg, 'type' => $type); 227 $this->types[] = $type; 228 } 229 $this->xmlRpcParams = $xmlRpcParams; 230 } 231 return; 232 } 233 234 $this->params = $argv; 235 $this->types = array(); 236 $xmlRpcParams = array(); 237 foreach ($argv as $arg) { 238 if ($arg instanceof AbstractValue) { 239 $type = $arg->getType(); 240 } else { 241 $xmlRpcValue = AbstractValue::getXmlRpcValue($arg); 242 $type = $xmlRpcValue->getType(); 243 } 244 $xmlRpcParams[] = array('value' => $arg, 'type' => $type); 245 $this->types[] = $type; 246 } 247 $this->xmlRpcParams = $xmlRpcParams; 248 } 249 250 /** 251 * Retrieve the array of parameters 252 * 253 * @return array 254 */ 255 public function getParams() 256 { 257 return $this->params; 258 } 259 260 /** 261 * Return parameter types 262 * 263 * @return array 264 */ 265 public function getTypes() 266 { 267 return $this->types; 268 } 269 270 /** 271 * Load XML and parse into request components 272 * 273 * @param string $request 274 * @throws Exception\ValueException if invalid XML 275 * @return bool True on success, false if an error occurred. 276 */ 277 public function loadXml($request) 278 { 279 if (!is_string($request)) { 280 $this->fault = new Fault(635); 281 $this->fault->setEncoding($this->getEncoding()); 282 return false; 283 } 284 285 // @see ZF-12293 - disable external entities for security purposes 286 $loadEntities = libxml_disable_entity_loader(true); 287 $xmlErrorsFlag = libxml_use_internal_errors(true); 288 try { 289 $dom = new DOMDocument; 290 $dom->loadXML($request); 291 foreach ($dom->childNodes as $child) { 292 if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { 293 throw new Exception\ValueException( 294 'Invalid XML: Detected use of illegal DOCTYPE' 295 ); 296 } 297 } 298 ErrorHandler::start(); 299 $xml = simplexml_import_dom($dom); 300 $error = ErrorHandler::stop(); 301 libxml_disable_entity_loader($loadEntities); 302 libxml_use_internal_errors($xmlErrorsFlag); 303 } catch (\Exception $e) { 304 // Not valid XML 305 $this->fault = new Fault(631); 306 $this->fault->setEncoding($this->getEncoding()); 307 libxml_disable_entity_loader($loadEntities); 308 libxml_use_internal_errors($xmlErrorsFlag); 309 return false; 310 } 311 if (!$xml instanceof SimpleXMLElement || $error) { 312 // Not valid XML 313 $this->fault = new Fault(631); 314 $this->fault->setEncoding($this->getEncoding()); 315 libxml_use_internal_errors($xmlErrorsFlag); 316 return false; 317 } 318 319 // Check for method name 320 if (empty($xml->methodName)) { 321 // Missing method name 322 $this->fault = new Fault(632); 323 $this->fault->setEncoding($this->getEncoding()); 324 return false; 325 } 326 327 $this->method = (string) $xml->methodName; 328 329 // Check for parameters 330 if (!empty($xml->params)) { 331 $types = array(); 332 $argv = array(); 333 foreach ($xml->params->children() as $param) { 334 if (!isset($param->value)) { 335 $this->fault = new Fault(633); 336 $this->fault->setEncoding($this->getEncoding()); 337 return false; 338 } 339 340 try { 341 $param = AbstractValue::getXmlRpcValue($param->value, AbstractValue::XML_STRING); 342 $types[] = $param->getType(); 343 $argv[] = $param->getValue(); 344 } catch (\Exception $e) { 345 $this->fault = new Fault(636); 346 $this->fault->setEncoding($this->getEncoding()); 347 return false; 348 } 349 } 350 351 $this->types = $types; 352 $this->params = $argv; 353 } 354 355 $this->xml = $request; 356 357 return true; 358 } 359 360 /** 361 * Does the current request contain errors and should it return a fault 362 * response? 363 * 364 * @return bool 365 */ 366 public function isFault() 367 { 368 return $this->fault instanceof Fault; 369 } 370 371 /** 372 * Retrieve the fault response, if any 373 * 374 * @return null|\Zend\XmlRpc\Fault 375 */ 376 public function getFault() 377 { 378 return $this->fault; 379 } 380 381 /** 382 * Retrieve method parameters as XMLRPC values 383 * 384 * @return array 385 */ 386 protected function _getXmlRpcParams() 387 { 388 $params = array(); 389 if (is_array($this->xmlRpcParams)) { 390 foreach ($this->xmlRpcParams as $param) { 391 $value = $param['value']; 392 $type = $param['type'] ?: AbstractValue::AUTO_DETECT_TYPE; 393 394 if (!$value instanceof AbstractValue) { 395 $value = AbstractValue::getXmlRpcValue($value, $type); 396 } 397 $params[] = $value; 398 } 399 } 400 401 return $params; 402 } 403 404 /** 405 * Create XML request 406 * 407 * @return string 408 */ 409 public function saveXml() 410 { 411 $args = $this->_getXmlRpcParams(); 412 $method = $this->getMethod(); 413 414 $generator = AbstractValue::getGenerator(); 415 $generator->openElement('methodCall') 416 ->openElement('methodName', $method) 417 ->closeElement('methodName'); 418 419 if (is_array($args) && count($args)) { 420 $generator->openElement('params'); 421 422 foreach ($args as $arg) { 423 $generator->openElement('param'); 424 $arg->generateXml(); 425 $generator->closeElement('param'); 426 } 427 $generator->closeElement('params'); 428 } 429 $generator->closeElement('methodCall'); 430 431 return $generator->flush(); 432 } 433 434 /** 435 * Return XML request 436 * 437 * @return string 438 */ 439 public function __toString() 440 { 441 return $this->saveXML(); 442 } 443} 444