Response.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. <?php
  2. namespace PhpXmlRpc;
  3. use PhpXmlRpc\Exception\StateErrorException;
  4. use PhpXmlRpc\Traits\CharsetEncoderAware;
  5. use PhpXmlRpc\Traits\DeprecationLogger;
  6. use PhpXmlRpc\Traits\PayloadBearer;
  7. /**
  8. * This class provides the representation of the response of an XML-RPC server.
  9. * Server-side, a server method handler will construct a Response and pass it as its return value.
  10. * An identical Response object will be returned by the result of an invocation of the send() method of the Client class.
  11. *
  12. * @property Value|string|mixed $val deprecated - public access left in purely for BC. Access via value()/__construct()
  13. * @property string $valtyp deprecated - public access left in purely for BC. Access via valueType()/__construct()
  14. * @property int $errno deprecated - public access left in purely for BC. Access via faultCode()/__construct()
  15. * @property string $errstr deprecated - public access left in purely for BC. Access faultString()/__construct()
  16. * @property string $payload deprecated - public access left in purely for BC. Access via getPayload()/setPayload()
  17. * @property string $content_type deprecated - public access left in purely for BC. Access via getContentType()/setPayload()
  18. * @property array $hdrs deprecated. Access via httpResponse()['headers'], set via $httpResponse['headers']
  19. * @property array _cookies deprecated. Access via httpResponse()['cookies'], set via $httpResponse['cookies']
  20. * @property string $raw_data deprecated. Access via httpResponse()['raw_data'], set via $httpResponse['raw_data']
  21. */
  22. class Response
  23. {
  24. use CharsetEncoderAware;
  25. use DeprecationLogger;
  26. use PayloadBearer;
  27. /** @var Value|string|mixed */
  28. protected $val = 0;
  29. /** @var string */
  30. protected $valtyp;
  31. /** @var int */
  32. protected $errno = 0;
  33. /** @var string */
  34. protected $errstr = '';
  35. protected $httpResponse = array('headers' => array(), 'cookies' => array(), 'raw_data' => '', 'status_code' => null);
  36. /**
  37. * @param Value|string|mixed $val either a Value object, a php value or the xml serialization of an xml-rpc value (a string).
  38. * Note that using anything other than a Value object wll have an impact on serialization.
  39. * @param integer $fCode set it to anything but 0 to create an error response. In that case, $val is discarded
  40. * @param string $fString the error string, in case of an error response
  41. * @param string $valType The type of $val passed in. Either 'xmlrpcvals', 'phpvals' or 'xml'. Leave empty to let
  42. * the code guess the correct type by looking at $val - in which case strings are assumed
  43. * to be serialized xml
  44. * @param array|null $httpResponse this should be set when the response is being built out of data received from
  45. * http (i.e. not when programmatically building a Response server-side). Array
  46. * keys should include, if known: headers, cookies, raw_data, status_code
  47. *
  48. * @todo add check that $val / $fCode / $fString is of correct type? We could at least log a warning for fishy cases...
  49. * 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
  50. * xml chunk, depending on usage of Client::send() inside which the constructor is called.
  51. */
  52. public function __construct($val, $fCode = 0, $fString = '', $valType = '', $httpResponse = null)
  53. {
  54. if ($fCode != 0) {
  55. // error response
  56. $this->errno = $fCode;
  57. $this->errstr = $fString;
  58. } else {
  59. // successful response
  60. $this->val = $val;
  61. if ($valType == '') {
  62. // user did not declare type of response value: try to guess it
  63. if (is_object($this->val) && is_a($this->val, 'PhpXmlRpc\Value')) {
  64. $this->valtyp = 'xmlrpcvals';
  65. } elseif (is_string($this->val)) {
  66. $this->valtyp = 'xml';
  67. } else {
  68. $this->valtyp = 'phpvals';
  69. }
  70. } else {
  71. $this->valtyp = $valType;
  72. // user declares the type of resp value: we "almost" trust it... but log errors just in case
  73. if (($this->valtyp == 'xmlrpcvals' && (!is_a($this->val, 'PhpXmlRpc\Value'))) ||
  74. ($this->valtyp == 'xml' && (!is_string($this->val)))) {
  75. $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': value passed in does not match type ' . $valType);
  76. }
  77. }
  78. }
  79. if (is_array($httpResponse)) {
  80. $this->httpResponse = array_merge(array('headers' => array(), 'cookies' => array(), 'raw_data' => '', 'status_code' => null), $httpResponse);
  81. }
  82. }
  83. /**
  84. * Returns the error code of the response.
  85. *
  86. * @return integer the error code of this response (0 for not-error responses)
  87. */
  88. public function faultCode()
  89. {
  90. return $this->errno;
  91. }
  92. /**
  93. * Returns the error code of the response.
  94. *
  95. * @return string the error string of this response ('' for not-error responses)
  96. */
  97. public function faultString()
  98. {
  99. return $this->errstr;
  100. }
  101. /**
  102. * Returns the value received by the server. If the Response's faultCode is non-zero then the value returned by this
  103. * method should not be used (it may not even be an object).
  104. *
  105. * @return Value|string|mixed the Value object returned by the server. Might be an xml string or plain php value
  106. * depending on the convention adopted when creating the Response
  107. */
  108. public function value()
  109. {
  110. return $this->val;
  111. }
  112. /**
  113. * @return string
  114. */
  115. public function valueType()
  116. {
  117. return $this->valtyp;
  118. }
  119. /**
  120. * Returns an array with the cookies received from the server.
  121. * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 => $val2, ...)
  122. * with attributes being e.g. 'expires', 'path', domain'.
  123. * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past) are still present in the array.
  124. * It is up to the user-defined code to decide how to use the received cookies, and whether they have to be sent back
  125. * with the next request to the server (using $client->setCookie) or not.
  126. * The values are filled in at constructor time, and might not be set for specific debug values used.
  127. *
  128. * @return array[] array of cookies received from the server
  129. */
  130. public function cookies()
  131. {
  132. return $this->httpResponse['cookies'];
  133. }
  134. /**
  135. * Returns an array with info about the http response received from the server.
  136. * The values are filled in at constructor time, and might not be set for specific debug values used.
  137. *
  138. * @return array array with keys 'headers', 'cookies', 'raw_data' and 'status_code'.
  139. */
  140. public function httpResponse()
  141. {
  142. return $this->httpResponse;
  143. }
  144. /**
  145. * Returns xml representation of the response, XML prologue _not_ included. Sets `payload` and `content_type` properties
  146. *
  147. * @param string $charsetEncoding the charset to be used for serialization. If null, US-ASCII is assumed
  148. * @return string the xml representation of the response
  149. * @throws StateErrorException if the response was built out of a value of an unsupported type
  150. */
  151. public function serialize($charsetEncoding = '')
  152. {
  153. if ($charsetEncoding != '') {
  154. $this->content_type = 'text/xml; charset=' . $charsetEncoding;
  155. } else {
  156. $this->content_type = 'text/xml';
  157. }
  158. if (PhpXmlRpc::$xmlrpc_null_apache_encoding) {
  159. $result = "<methodResponse xmlns:ex=\"" . PhpXmlRpc::$xmlrpc_null_apache_encoding_ns . "\">\n";
  160. } else {
  161. $result = "<methodResponse>\n";
  162. }
  163. if ($this->errno) {
  164. // Let non-ASCII response messages be tolerated by clients by xml-encoding non ascii chars
  165. $result .= "<fault>\n" .
  166. "<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
  167. "</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
  168. $this->getCharsetEncoder()->encodeEntities($this->errstr, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) .
  169. "</string></value>\n</member>\n</struct>\n</value>\n</fault>";
  170. } else {
  171. if (is_object($this->val) && is_a($this->val, 'PhpXmlRpc\Value')) {
  172. $result .= "<params>\n<param>\n" . $this->val->serialize($charsetEncoding) . "</param>\n</params>";
  173. } else if (is_string($this->val) && $this->valtyp == 'xml') {
  174. $result .= "<params>\n<param>\n" .
  175. $this->val .
  176. "</param>\n</params>";
  177. } else if ($this->valtyp == 'phpvals') {
  178. $encoder = new Encoder();
  179. $val = $encoder->encode($this->val);
  180. $result .= "<params>\n<param>\n" . $val->serialize($charsetEncoding) . "</param>\n</params>";
  181. } else {
  182. throw new StateErrorException('cannot serialize xmlrpc response objects whose content is native php values');
  183. }
  184. }
  185. $result .= "\n</methodResponse>";
  186. $this->payload = $result;
  187. return $result;
  188. }
  189. /**
  190. * @param string $charsetEncoding
  191. * @return string
  192. */
  193. public function xml_header($charsetEncoding = '')
  194. {
  195. if ($charsetEncoding != '') {
  196. return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n";
  197. } else {
  198. return "<?xml version=\"1.0\"?" . ">\n";
  199. }
  200. }
  201. // *** BC layer ***
  202. // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];`
  203. public function &__get($name)
  204. {
  205. switch ($name) {
  206. case 'val':
  207. case 'valtyp':
  208. case 'errno':
  209. case 'errstr':
  210. case 'payload':
  211. case 'content_type':
  212. $this->logDeprecation('Getting property Response::' . $name . ' is deprecated');
  213. return $this->$name;
  214. case 'hdrs':
  215. $this->logDeprecation('Getting property Response::' . $name . ' is deprecated');
  216. return $this->httpResponse['headers'];
  217. case '_cookies':
  218. $this->logDeprecation('Getting property Response::' . $name . ' is deprecated');
  219. return $this->httpResponse['cookies'];
  220. case 'raw_data':
  221. $this->logDeprecation('Getting property Response::' . $name . ' is deprecated');
  222. return $this->httpResponse['raw_data'];
  223. default:
  224. /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
  225. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
  226. trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
  227. $result = null;
  228. return $result;
  229. }
  230. }
  231. public function __set($name, $value)
  232. {
  233. switch ($name) {
  234. case 'val':
  235. case 'valtyp':
  236. case 'errno':
  237. case 'errstr':
  238. case 'payload':
  239. case 'content_type':
  240. $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
  241. $this->$name = $value;
  242. break;
  243. case 'hdrs':
  244. $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
  245. $this->httpResponse['headers'] = $value;
  246. break;
  247. case '_cookies':
  248. $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
  249. $this->httpResponse['cookies'] = $value;
  250. break;
  251. case 'raw_data':
  252. $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
  253. $this->httpResponse['raw_data'] = $value;
  254. break;
  255. default:
  256. /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
  257. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
  258. trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
  259. }
  260. }
  261. public function __isset($name)
  262. {
  263. switch ($name) {
  264. case 'val':
  265. case 'valtyp':
  266. case 'errno':
  267. case 'errstr':
  268. case 'payload':
  269. case 'content_type':
  270. $this->logDeprecation('Checking property Response::' . $name . ' is deprecated');
  271. return isset($this->$name);
  272. case 'hdrs':
  273. $this->logDeprecation('Checking property Response::' . $name . ' is deprecated');
  274. return isset($this->httpResponse['headers']);
  275. case '_cookies':
  276. $this->logDeprecation('Checking property Response::' . $name . ' is deprecated');
  277. return isset($this->httpResponse['cookies']);
  278. case 'raw_data':
  279. $this->logDeprecation('Checking property Response::' . $name . ' is deprecated');
  280. return isset($this->httpResponse['raw_data']);
  281. default:
  282. return false;
  283. }
  284. }
  285. public function __unset($name)
  286. {
  287. switch ($name) {
  288. case 'val':
  289. case 'valtyp':
  290. case 'errno':
  291. case 'errstr':
  292. case 'payload':
  293. case 'content_type':
  294. $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
  295. unset($this->$name);
  296. break;
  297. case 'hdrs':
  298. $this->logDeprecation('Unsetting property Response::' . $name . ' is deprecated');
  299. unset($this->httpResponse['headers']);
  300. break;
  301. case '_cookies':
  302. $this->logDeprecation('Unsetting property Response::' . $name . ' is deprecated');
  303. unset($this->httpResponse['cookies']);
  304. break;
  305. case 'raw_data':
  306. $this->logDeprecation('Unsetting property Response::' . $name . ' is deprecated');
  307. unset($this->httpResponse['raw_data']);
  308. break;
  309. default:
  310. /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
  311. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
  312. trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
  313. }
  314. }
  315. }