Client.php 83 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058
  1. <?php
  2. namespace PhpXmlRpc;
  3. use PhpXmlRpc\Exception\ValueErrorException;
  4. use PhpXmlRpc\Helper\XMLParser;
  5. use PhpXmlRpc\Traits\CharsetEncoderAware;
  6. use PhpXmlRpc\Traits\DeprecationLogger;
  7. /**
  8. * Used to represent a client of an XML-RPC server.
  9. *
  10. * @property int $errno deprecated - public access left in purely for BC.
  11. * @property string $errstr deprecated - public access left in purely for BC.
  12. * @property string $method deprecated - public access left in purely for BC. Access via getUrl()/__construct()
  13. * @property string $server deprecated - public access left in purely for BC. Access via getUrl()/__construct()
  14. * @property int $port deprecated - public access left in purely for BC. Access via getUrl()/__construct()
  15. * @property string $path deprecated - public access left in purely for BC. Access via getUrl()/__construct()
  16. */
  17. class Client
  18. {
  19. use DeprecationLogger;
  20. //use CharsetEncoderAware;
  21. const USE_CURL_NEVER = 0;
  22. const USE_CURL_ALWAYS = 1;
  23. const USE_CURL_AUTO = 2;
  24. const OPT_ACCEPTED_CHARSET_ENCODINGS = 'accepted_charset_encodings';
  25. const OPT_ACCEPTED_COMPRESSION = 'accepted_compression';
  26. const OPT_AUTH_TYPE = 'authtype';
  27. const OPT_CA_CERT = 'cacert';
  28. const OPT_CA_CERT_DIR = 'cacertdir';
  29. const OPT_CERT = 'cert';
  30. const OPT_CERT_PASS = 'certpass';
  31. const OPT_COOKIES = 'cookies';
  32. const OPT_DEBUG = 'debug';
  33. const OPT_EXTRA_CURL_OPTS = 'extracurlopts';
  34. const OPT_EXTRA_SOCKET_OPTS = 'extrasockopts';
  35. const OPT_KEEPALIVE = 'keepalive';
  36. const OPT_KEY = 'key';
  37. const OPT_KEY_PASS = 'keypass';
  38. const OPT_NO_MULTICALL = 'no_multicall';
  39. const OPT_PASSWORD = 'password';
  40. const OPT_PROXY = 'proxy';
  41. const OPT_PROXY_AUTH_TYPE = 'proxy_authtype';
  42. const OPT_PROXY_PASS = 'proxy_pass';
  43. const OPT_PROXY_PORT = 'proxyport';
  44. const OPT_PROXY_USER = 'proxy_user';
  45. const OPT_REQUEST_CHARSET_ENCODING = 'request_charset_encoding';
  46. const OPT_REQUEST_COMPRESSION = 'request_compression';
  47. const OPT_RETURN_TYPE = 'return_type';
  48. const OPT_SSL_VERSION = 'sslversion';
  49. const OPT_TIMEOUT = 'timeout';
  50. const OPT_USERNAME = 'username';
  51. const OPT_USER_AGENT = 'user_agent';
  52. const OPT_USE_CURL = 'use_curl';
  53. const OPT_VERIFY_HOST = 'verifyhost';
  54. const OPT_VERIFY_PEER = 'verifypeer';
  55. /** @var string */
  56. protected static $requestClass = '\\PhpXmlRpc\\Request';
  57. /** @var string */
  58. protected static $responseClass = '\\PhpXmlRpc\\Response';
  59. /**
  60. * @var int
  61. * @deprecated will be removed in the future
  62. */
  63. protected $errno;
  64. /**
  65. * @var string
  66. * @deprecated will be removed in the future
  67. */
  68. protected $errstr;
  69. /// @todo: do all the ones below need to be public?
  70. /**
  71. * @var string
  72. */
  73. protected $method = 'http';
  74. /**
  75. * @var string
  76. */
  77. protected $server;
  78. /**
  79. * @var int
  80. */
  81. protected $port = 0;
  82. /**
  83. * @var string
  84. */
  85. protected $path;
  86. /**
  87. * @var int
  88. */
  89. protected $debug = 0;
  90. /**
  91. * @var string
  92. */
  93. protected $username = '';
  94. /**
  95. * @var string
  96. */
  97. protected $password = '';
  98. /**
  99. * @var int
  100. */
  101. protected $authtype = 1;
  102. /**
  103. * @var string
  104. */
  105. protected $cert = '';
  106. /**
  107. * @var string
  108. */
  109. protected $certpass = '';
  110. /**
  111. * @var string
  112. */
  113. protected $cacert = '';
  114. /**
  115. * @var string
  116. */
  117. protected $cacertdir = '';
  118. /**
  119. * @var string
  120. */
  121. protected $key = '';
  122. /**
  123. * @var string
  124. */
  125. protected $keypass = '';
  126. /**
  127. * @var bool
  128. */
  129. protected $verifypeer = true;
  130. /**
  131. * @var int
  132. */
  133. protected $verifyhost = 2;
  134. /**
  135. * @var int
  136. */
  137. protected $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT. Other CURL_SSLVERSION_ values are supported
  138. /**
  139. * @var string
  140. */
  141. protected $proxy = '';
  142. /**
  143. * @var int
  144. */
  145. protected $proxyport = 0;
  146. /**
  147. * @var string
  148. */
  149. protected $proxy_user = '';
  150. /**
  151. * @var string
  152. */
  153. protected $proxy_pass = '';
  154. /**
  155. * @var int
  156. */
  157. protected $proxy_authtype = 1;
  158. /**
  159. * @var array
  160. */
  161. protected $cookies = array();
  162. /**
  163. * @var array
  164. */
  165. protected $extrasockopts = array();
  166. /**
  167. * @var array
  168. */
  169. protected $extracurlopts = array();
  170. /**
  171. * @var int
  172. */
  173. protected $timeout = 0;
  174. /**
  175. * @var int
  176. */
  177. protected $use_curl = self::USE_CURL_AUTO;
  178. /**
  179. * @var bool
  180. *
  181. * This determines whether the multicall() method will try to take advantage of the system.multicall xml-rpc method
  182. * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
  183. * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
  184. * system.multicall.
  185. */
  186. protected $no_multicall = false;
  187. /**
  188. * @var array
  189. *
  190. * List of http compression methods accepted by the client for responses.
  191. * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
  192. *
  193. * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since in those cases it will be up to CURL to
  194. * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of
  195. * curl_version() to determine whether compression is supported or not
  196. */
  197. protected $accepted_compression = array();
  198. /**
  199. * @var string|null
  200. *
  201. * Name of compression scheme to be used for sending requests.
  202. * Either null, 'gzip' or 'deflate'.
  203. */
  204. protected $request_compression = '';
  205. /**
  206. * @var bool
  207. *
  208. * Whether to use persistent connections for http 1.1 and https. Value set at constructor time.
  209. */
  210. protected $keepalive = false;
  211. /**
  212. * @var string[]
  213. *
  214. * Charset encodings that can be decoded without problems by the client. Value set at constructor time
  215. */
  216. protected $accepted_charset_encodings = array();
  217. /**
  218. * @var string
  219. *
  220. * The charset encoding that will be used for serializing request sent by the client.
  221. * It defaults to NULL, which means using US-ASCII and encoding all characters outside the ASCII printable range
  222. * using their xml character entity representation (this has the benefit that line end characters will not be mangled
  223. * in the transfer, a CR-LF will be preserved as well as a singe LF).
  224. * Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'.
  225. * For the fastest mode of operation, set your both your app internal encoding and this to UTF-8.
  226. */
  227. protected $request_charset_encoding = '';
  228. /**
  229. * @var string
  230. *
  231. * Decides the content of Response objects returned by calls to send() and multicall().
  232. * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
  233. *
  234. * Determines whether the value returned inside a Response object as results of calls to the send() and multicall()
  235. * methods will be a Value object, a plain php value or a raw xml string.
  236. * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
  237. * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
  238. * Response objects in any case.
  239. * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
  240. * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
  241. * server as an xml-rpc string or base64 value.
  242. */
  243. protected $return_type = XMLParser::RETURN_XMLRPCVALS;
  244. /**
  245. * @var string
  246. *
  247. * Sent to servers in http headers. Value set at constructor time.
  248. */
  249. protected $user_agent;
  250. /**
  251. * CURL handle: used for keep-alive
  252. * @internal
  253. */
  254. public $xmlrpc_curl_handle = null;
  255. /**
  256. * @var array
  257. */
  258. protected static $options = array(
  259. self::OPT_ACCEPTED_CHARSET_ENCODINGS,
  260. self::OPT_ACCEPTED_COMPRESSION,
  261. self::OPT_AUTH_TYPE,
  262. self::OPT_CA_CERT,
  263. self::OPT_CA_CERT_DIR,
  264. self::OPT_CERT,
  265. self::OPT_CERT_PASS,
  266. self::OPT_COOKIES,
  267. self::OPT_DEBUG,
  268. self::OPT_EXTRA_CURL_OPTS,
  269. self::OPT_EXTRA_SOCKET_OPTS,
  270. self::OPT_KEEPALIVE,
  271. self::OPT_KEY,
  272. self::OPT_KEY_PASS,
  273. self::OPT_NO_MULTICALL,
  274. self::OPT_PASSWORD,
  275. self::OPT_PROXY,
  276. self::OPT_PROXY_AUTH_TYPE,
  277. self::OPT_PROXY_PASS,
  278. self::OPT_PROXY_USER,
  279. self::OPT_PROXY_PORT,
  280. self::OPT_REQUEST_CHARSET_ENCODING,
  281. self::OPT_REQUEST_COMPRESSION,
  282. self::OPT_RETURN_TYPE,
  283. self::OPT_SSL_VERSION,
  284. self::OPT_TIMEOUT,
  285. self::OPT_USE_CURL,
  286. self::OPT_USER_AGENT,
  287. self::OPT_USERNAME,
  288. self::OPT_VERIFY_HOST,
  289. self::OPT_VERIFY_PEER,
  290. );
  291. /**
  292. * @param string $path either the PATH part of the xml-rpc server URL, or complete server URL (in which case you
  293. * should use an empty string for all other parameters)
  294. * e.g. /xmlrpc/server.php
  295. * e.g. http://phpxmlrpc.sourceforge.net/server.php
  296. * e.g. https://james:[email protected]:444/xmlrpcserver?agent=007
  297. * e.g. h2://fast-and-secure-services.org/endpoint
  298. * @param string $server the server name / ip address
  299. * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
  300. * protocol used
  301. * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can
  302. * be used if CURL is installed. The value set here can be overridden in any call to $this->send().
  303. * Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
  304. * for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
  305. * thus incompatible with any server/proxy not supporting http/2. This is because POST
  306. * request are not compatible with h2c upgrade.
  307. */
  308. public function __construct($path, $server = '', $port = '', $method = '')
  309. {
  310. // allow user to specify all params in $path
  311. if ($server == '' && $port == '' && $method == '') {
  312. $parts = parse_url($path);
  313. $server = $parts['host'];
  314. $path = isset($parts['path']) ? $parts['path'] : '';
  315. if (isset($parts['query'])) {
  316. $path .= '?' . $parts['query'];
  317. }
  318. if (isset($parts['fragment'])) {
  319. $path .= '#' . $parts['fragment'];
  320. }
  321. if (isset($parts['port'])) {
  322. $port = $parts['port'];
  323. }
  324. if (isset($parts['scheme'])) {
  325. $method = $parts['scheme'];
  326. }
  327. if (isset($parts['user'])) {
  328. $this->username = $parts['user'];
  329. }
  330. if (isset($parts['pass'])) {
  331. $this->password = $parts['pass'];
  332. }
  333. }
  334. if ($path == '' || $path[0] != '/') {
  335. $this->path = '/' . $path;
  336. } else {
  337. $this->path = $path;
  338. }
  339. $this->server = $server;
  340. if ($port != '') {
  341. $this->port = $port;
  342. }
  343. if ($method != '') {
  344. $this->method = $method;
  345. }
  346. // if ZLIB is enabled, let the client by default accept compressed responses
  347. if (function_exists('gzinflate') || (
  348. function_exists('curl_version') && (($info = curl_version()) &&
  349. ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
  350. )
  351. ) {
  352. $this->accepted_compression = array('gzip', 'deflate');
  353. }
  354. // keepalives: enabled by default
  355. $this->keepalive = true;
  356. // by default the xml parser can support these 3 charset encodings
  357. $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
  358. // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
  359. //$ch = $this->getCharsetEncoder();
  360. //$this->accepted_charset_encodings = $ch->knownCharsets();
  361. // initialize user_agent string
  362. $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
  363. }
  364. /**
  365. * @param string $name see all the OPT_ constants
  366. * @param mixed $value
  367. * @return $this
  368. * @throws ValueErrorException on unsupported option
  369. */
  370. public function setOption($name, $value)
  371. {
  372. if (in_array($name, static::$options)) {
  373. $this->$name = $value;
  374. return $this;
  375. }
  376. throw new ValueErrorException("Unsupported option '$name'");
  377. }
  378. /**
  379. * @param string $name see all the OPT_ constants
  380. * @return mixed
  381. * @throws ValueErrorException on unsupported option
  382. */
  383. public function getOption($name)
  384. {
  385. if (in_array($name, static::$options)) {
  386. return $this->$name;
  387. }
  388. throw new ValueErrorException("Unsupported option '$name'");
  389. }
  390. /**
  391. * Returns the complete list of Client options, with their value.
  392. * @return array
  393. */
  394. public function getOptions()
  395. {
  396. $values = array();
  397. foreach (static::$options as $opt) {
  398. $values[$opt] = $this->getOption($opt);
  399. }
  400. return $values;
  401. }
  402. /**
  403. * @param array $options key: any valid option (see all the OPT_ constants)
  404. * @return $this
  405. * @throws ValueErrorException on unsupported option
  406. */
  407. public function setOptions($options)
  408. {
  409. foreach ($options as $name => $value) {
  410. $this->setOption($name, $value);
  411. }
  412. return $this;
  413. }
  414. /**
  415. * Enable/disable the echoing to screen of the xml-rpc responses received. The default is not to output anything.
  416. *
  417. * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
  418. * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
  419. * represent the value returned by the server.
  420. * At level 2, the complete payload of the xml-rpc request is also printed, before being sent to the server.
  421. * At level -1, the Response objects returned by send() calls will not carry information about the http response's
  422. * cookies, headers and body, which might save some memory
  423. *
  424. * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
  425. * the server returns. Never leave it enabled for production!
  426. *
  427. * @param integer $level values -1, 0, 1 and 2 are supported
  428. * @return $this
  429. */
  430. public function setDebug($level)
  431. {
  432. $this->debug = $level;
  433. return $this;
  434. }
  435. /**
  436. * Sets the username and password for authorizing the client to the server.
  437. *
  438. * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
  439. * Note that username and password can also be set using the class constructor.
  440. * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
  441. * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
  442. *
  443. * @param string $user username
  444. * @param string $password password
  445. * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
  446. * (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
  447. * extension is enabled.
  448. * @return $this
  449. */
  450. public function setCredentials($user, $password, $authType = 1)
  451. {
  452. $this->username = $user;
  453. $this->password = $password;
  454. $this->authtype = $authType;
  455. return $this;
  456. }
  457. /**
  458. * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
  459. *
  460. * Note: to retrieve information about the client certificate on the server side, you will need to look into the
  461. * environment variables which are set up by the webserver. Different webservers will typically set up different
  462. * variables.
  463. *
  464. * @param string $cert the name of a file containing a PEM formatted certificate
  465. * @param string $certPass the password required to use it
  466. * @return $this
  467. */
  468. public function setCertificate($cert, $certPass = '')
  469. {
  470. $this->cert = $cert;
  471. $this->certpass = $certPass;
  472. return $this;
  473. }
  474. /**
  475. * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
  476. *
  477. * See the php manual page about CURLOPT_CAINFO for more details.
  478. *
  479. * @param string $caCert certificate file name (or dir holding certificates)
  480. * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
  481. * @return $this
  482. */
  483. public function setCaCertificate($caCert, $isDir = false)
  484. {
  485. if ($isDir) {
  486. $this->cacertdir = $caCert;
  487. } else {
  488. $this->cacert = $caCert;
  489. }
  490. return $this;
  491. }
  492. /**
  493. * Set attributes for SSL communication: private SSL key.
  494. *
  495. * NB: does not work in older php/curl installs.
  496. * Thanks to Daniel Convissor.
  497. *
  498. * @param string $key The name of a file containing a private SSL key
  499. * @param string $keyPass The secret password needed to use the private SSL key
  500. * @return $this
  501. */
  502. public function setKey($key, $keyPass)
  503. {
  504. $this->key = $key;
  505. $this->keypass = $keyPass;
  506. return $this;
  507. }
  508. /**
  509. * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
  510. * if the cert verification fails.
  511. *
  512. * By default, verification is enabled.
  513. * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
  514. *
  515. * @param bool $i enable/disable verification of peer certificate
  516. * @return $this
  517. * @deprecated use setOption
  518. */
  519. public function setSSLVerifyPeer($i)
  520. {
  521. $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
  522. $this->verifypeer = $i;
  523. return $this;
  524. }
  525. /**
  526. * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
  527. *
  528. * Note that support for value 1 has been removed in cURL 7.28.1
  529. *
  530. * @param int $i Set to 1 to only the existence of a CN, not that it matches
  531. * @return $this
  532. * @deprecated use setOption
  533. */
  534. public function setSSLVerifyHost($i)
  535. {
  536. $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
  537. $this->verifyhost = $i;
  538. return $this;
  539. }
  540. /**
  541. * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
  542. *
  543. * @param int $i see CURL_SSLVERSION_ constants
  544. * @return $this
  545. * @deprecated use setOption
  546. */
  547. public function setSSLVersion($i)
  548. {
  549. $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
  550. $this->sslversion = $i;
  551. return $this;
  552. }
  553. /**
  554. * Set proxy info.
  555. *
  556. * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
  557. *
  558. * @param string $proxyHost
  559. * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
  560. * @param string $proxyUsername Leave blank if proxy has public access
  561. * @param string $proxyPassword Leave blank if proxy has public access
  562. * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
  563. * to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
  564. * @return $this
  565. */
  566. public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
  567. {
  568. $this->proxy = $proxyHost;
  569. $this->proxyport = $proxyPort;
  570. $this->proxy_user = $proxyUsername;
  571. $this->proxy_pass = $proxyPassword;
  572. $this->proxy_authtype = $proxyAuthType;
  573. return $this;
  574. }
  575. /**
  576. * Enables/disables reception of compressed xml-rpc responses.
  577. *
  578. * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
  579. * instances will enable reception of compressed content.
  580. * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests.
  581. * It is up to the xml-rpc server to return compressed responses when receiving such requests.
  582. *
  583. * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
  584. * @return $this
  585. */
  586. public function setAcceptedCompression($compMethod)
  587. {
  588. if ($compMethod == 'any') {
  589. $this->accepted_compression = array('gzip', 'deflate');
  590. } elseif ($compMethod == false) {
  591. $this->accepted_compression = array();
  592. } else {
  593. $this->accepted_compression = array($compMethod);
  594. }
  595. return $this;
  596. }
  597. /**
  598. * Enables/disables http compression of xml-rpc request.
  599. *
  600. * This requires the "zlib" extension to be enabled in your php install.
  601. * Take care when sending compressed requests: servers might not support them (and automatic fallback to
  602. * uncompressed requests is not yet implemented).
  603. *
  604. * @param string $compMethod either 'gzip', 'deflate' or ''
  605. * @return $this
  606. * @deprecated use setOption
  607. */
  608. public function setRequestCompression($compMethod)
  609. {
  610. $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
  611. $this->request_compression = $compMethod;
  612. return $this;
  613. }
  614. /**
  615. * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
  616. * session info outside the xml-rpc payload).
  617. *
  618. * NB: by default all cookies set via this method are sent to the server, regardless of path/domain/port. Taking
  619. * advantage of those values is left to the single developer.
  620. *
  621. * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
  622. * separators!
  623. * @param string $value
  624. * @param string $path
  625. * @param string $domain
  626. * @param int $port do not use! Cookies are not separated by port
  627. * @return $this
  628. *
  629. * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
  630. * response not requests. We do the opposite...)
  631. * @todo strip invalid chars from cookie name? As per RFC 6265, we should follow RFC 2616, Section 2.2
  632. * @todo drop/rename $port parameter. Cookies are not isolated by port!
  633. * @todo feature-creep allow storing 'expires', 'secure', 'httponly' and 'samesite' cookie attributes (we could do
  634. * as php, and allow $path to be an array of attributes...)
  635. */
  636. public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
  637. {
  638. $this->cookies[$name]['value'] = rawurlencode($value);
  639. if ($path || $domain || $port) {
  640. $this->cookies[$name]['path'] = $path;
  641. $this->cookies[$name]['domain'] = $domain;
  642. $this->cookies[$name]['port'] = $port;
  643. }
  644. return $this;
  645. }
  646. /**
  647. * Directly set cURL options, for extra flexibility (when in cURL mode).
  648. *
  649. * It allows e.g. to bind client to a specific IP interface / address.
  650. *
  651. * @param array $options
  652. * @return $this
  653. * @deprecated use setOption
  654. */
  655. public function setCurlOptions($options)
  656. {
  657. $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
  658. $this->extracurlopts = $options;
  659. return $this;
  660. }
  661. /**
  662. * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
  663. * @return $this
  664. * @deprecated use setOption
  665. */
  666. public function setUseCurl($useCurlMode)
  667. {
  668. $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
  669. $this->use_curl = $useCurlMode;
  670. return $this;
  671. }
  672. /**
  673. * Set user-agent string that will be used by this client instance in http headers sent to the server.
  674. *
  675. * The default user agent string includes the name of this library and the version number.
  676. *
  677. * @param string $agentString
  678. * @return $this
  679. * @deprecated use setOption
  680. */
  681. public function setUserAgent($agentString)
  682. {
  683. $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
  684. $this->user_agent = $agentString;
  685. return $this;
  686. }
  687. /**
  688. * @param null|int $component allowed values: PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PORT, PHP_URL_PATH
  689. * @return string|int Notes: the path component will include query string and fragment; NULL is a valid value for port
  690. * (in which case the default port for http/https will be used);
  691. * @throws ValueErrorException on unsupported component
  692. */
  693. public function getUrl($component = null)
  694. {
  695. if (is_int($component) || ctype_digit($component)) {
  696. switch ($component) {
  697. case PHP_URL_SCHEME:
  698. return $this->method;
  699. case PHP_URL_HOST:
  700. return $this->server;
  701. case PHP_URL_PORT:
  702. return $this->port;
  703. case PHP_URL_PATH:
  704. return $this->path;
  705. case '':
  706. default:
  707. throw new ValueErrorException("Unsupported component '$component'");
  708. }
  709. }
  710. $url = $this->method . '://' . $this->server;
  711. if ($this->port == 0 || ($this->port == 80 && in_array($this->method, array('http', 'http10', 'http11', 'h2c'))) ||
  712. ($this->port == 443 && in_array($this->method, array('https', 'h2')))) {
  713. return $url . $this->path;
  714. } else {
  715. return $url . ':' . $this->port . $this->path;
  716. }
  717. }
  718. /**
  719. * Send an xml-rpc request to the server.
  720. *
  721. * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
  722. * complete xml representation of a request.
  723. * When sending an array of Request objects, the client will try to make use of
  724. * a single 'system.multicall' xml-rpc method call to forward to the server all
  725. * the requests in a single HTTP round trip, unless $this->no_multicall has
  726. * been previously set to TRUE (see the multicall method below), in which case
  727. * many consecutive xml-rpc requests will be sent. The method will return an
  728. * array of Response objects in both cases.
  729. * The third variant allows to build by hand (or any other means) a complete
  730. * xml-rpc request message, and send it to the server. $req should be a string
  731. * containing the complete xml representation of the request. It is e.g. useful
  732. * when, for maximal speed of execution, the request is serialized into a
  733. * string using the native php xml-rpc functions (see http://www.php.net/xmlrpc)
  734. * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setOption
  735. * will be used. If that is 0, a platform specific timeout will apply.
  736. * This timeout value is passed to fsockopen(). It is also used for detecting server
  737. * timeouts during communication (i.e. if the server does not send anything to the client
  738. * for $timeout seconds, the connection will be closed).
  739. * @param string $method deprecated. Use the same value in the constructor instead.
  740. * Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
  741. * the http protocol chosen during creation of the object will be used.
  742. * Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
  743. * for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
  744. * thus incompatible with any server/proxy not supporting http/2. This is because POST
  745. * request are not compatible with h2c upgrade.
  746. * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
  747. *
  748. * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
  749. * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
  750. */
  751. public function send($req, $timeout = 0, $method = '')
  752. {
  753. if ($method !== '' || $timeout !== 0) {
  754. $this->logDeprecation("Using non-default values for arguments 'method' and 'timeout' when calling method " . __METHOD__ . ' is deprecated');
  755. }
  756. // if user does not specify http protocol, use native method of this client
  757. // (i.e. method set during call to constructor)
  758. if ($method == '') {
  759. $method = $this->method;
  760. }
  761. if ($timeout == 0) {
  762. $timeout = $this->timeout;
  763. }
  764. if (is_array($req)) {
  765. // $req is an array of Requests
  766. /// @todo switch to the new syntax for multicall
  767. return $this->multicall($req, $timeout, $method);
  768. } elseif (is_string($req)) {
  769. $n = new static::$requestClass('');
  770. /// @todo we should somehow allow the caller to declare a custom contenttype too, esp. for the charset declaration
  771. $n->setPayload($req);
  772. $req = $n;
  773. }
  774. // where req is a Request
  775. $req->setDebug($this->debug);
  776. /// @todo we could be smarter about this and not force usage of curl for https if not present as well as use the
  777. /// presence of curl_extra_opts or socket_extra_opts as a hint
  778. $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO && (
  779. in_array($method, array('https', 'http11', 'h2c', 'h2')) ||
  780. ($this->username != '' && $this->authtype != 1) ||
  781. ($this->proxy != '' && $this->proxy_user != '' && $this->proxy_authtype != 1)
  782. ));
  783. // BC - we go through sendPayloadCURL/sendPayloadSocket in case some subclass reimplemented those
  784. if ($useCurl) {
  785. $r = $this->sendPayloadCURL(
  786. $req,
  787. $this->server,
  788. $this->port,
  789. $timeout,
  790. $this->username,
  791. $this->password,
  792. $this->authtype,
  793. $this->cert,
  794. $this->certpass,
  795. $this->cacert,
  796. $this->cacertdir,
  797. $this->proxy,
  798. $this->proxyport,
  799. $this->proxy_user,
  800. $this->proxy_pass,
  801. $this->proxy_authtype,
  802. // BC
  803. $method == 'http11' ? 'http' : $method,
  804. $this->keepalive,
  805. $this->key,
  806. $this->keypass,
  807. $this->sslversion
  808. );
  809. } else {
  810. $r = $this->sendPayloadSocket(
  811. $req,
  812. $this->server,
  813. $this->port,
  814. $timeout,
  815. $this->username,
  816. $this->password,
  817. $this->authtype,
  818. $this->cert,
  819. $this->certpass,
  820. $this->cacert,
  821. $this->cacertdir,
  822. $this->proxy,
  823. $this->proxyport,
  824. $this->proxy_user,
  825. $this->proxy_pass,
  826. $this->proxy_authtype,
  827. $method,
  828. $this->key,
  829. $this->keypass,
  830. $this->sslversion
  831. );
  832. }
  833. return $r;
  834. }
  835. /**
  836. * @param Request $req
  837. * @param string $method
  838. * @param string $server
  839. * @param int $port
  840. * @param string $path
  841. * @param array $opts
  842. * @return Response
  843. */
  844. protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
  845. {
  846. /// @todo log a warning if passed an unsupported method
  847. // Only create the payload if it was not created previously
  848. /// @todo what if the request's payload was created with a different encoding?
  849. /// Also, if we do not call serialize(), the request will not set its content-type to have the charset declared
  850. $payload = $req->getPayload();
  851. if (empty($payload)) {
  852. $payload = $req->serialize($opts['request_charset_encoding']);
  853. }
  854. // Deflate request body and set appropriate request headers
  855. $encodingHdr = '';
  856. if ($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate') {
  857. if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
  858. $a = @gzencode($payload);
  859. if ($a) {
  860. $payload = $a;
  861. $encodingHdr = "Content-Encoding: gzip\r\n";
  862. } else {
  863. $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
  864. }
  865. } else if (function_exists('gzcompress')) {
  866. $a = @gzcompress($payload);
  867. if ($a) {
  868. $payload = $a;
  869. $encodingHdr = "Content-Encoding: deflate\r\n";
  870. } else {
  871. $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
  872. }
  873. } else {
  874. $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
  875. }
  876. } else {
  877. if ($opts['request_compression'] != '') {
  878. $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
  879. }
  880. }
  881. // thanks to Grant Rauscher
  882. $credentials = '';
  883. if ($opts['username'] != '') {
  884. if ($opts['authtype'] != 1) {
  885. $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
  886. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
  887. PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth is supported with HTTP 1.0');
  888. }
  889. $credentials = 'Authorization: Basic ' . base64_encode($opts['username'] . ':' . $opts['password']) . "\r\n";
  890. }
  891. $acceptedEncoding = '';
  892. if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
  893. $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $opts['accepted_compression']) . "\r\n";
  894. }
  895. if ($port == 0) {
  896. $port = ($method === 'https') ? 443 : 80;
  897. }
  898. $proxyCredentials = '';
  899. if ($opts['proxy']) {
  900. if ($opts['proxyport'] == 0) {
  901. $opts['proxyport'] = 8080;
  902. }
  903. $connectServer = $opts['proxy'];
  904. $connectPort = $opts['proxyport'];
  905. $transport = 'tcp';
  906. /// @todo check: should we not use https in some cases?
  907. $uri = 'http://' . $server . ':' . $port . $path;
  908. if ($opts['proxy_user'] != '') {
  909. if ($opts['proxy_authtype'] != 1) {
  910. $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
  911. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
  912. PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth to proxy is supported with HTTP 1.0');
  913. }
  914. $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($opts['proxy_user'] . ':' .
  915. $opts['proxy_pass']) . "\r\n";
  916. }
  917. } else {
  918. $connectServer = $server;
  919. $connectPort = $port;
  920. $transport = ($method === 'https') ? 'tls' : 'tcp';
  921. $uri = $path;
  922. }
  923. // Cookie generation, as per RFC 6265
  924. // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
  925. $cookieHeader = '';
  926. if (count($opts['cookies'])) {
  927. $version = '';
  928. foreach ($opts['cookies'] as $name => $cookie) {
  929. /// @todo should we sanitize the cookie value on behalf of the user? See setCookie comments
  930. $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
  931. }
  932. $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
  933. }
  934. // omit port if default
  935. if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
  936. $port = '';
  937. } else {
  938. $port = ':' . $port;
  939. }
  940. $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
  941. 'User-Agent: ' . $opts['user_agent'] . "\r\n" .
  942. 'Host: ' . $server . $port . "\r\n" .
  943. $credentials .
  944. $proxyCredentials .
  945. $acceptedEncoding .
  946. $encodingHdr .
  947. 'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']) . "\r\n" .
  948. $cookieHeader .
  949. 'Content-Type: ' . $req->getContentType() . "\r\nContent-Length: " .
  950. strlen($payload) . "\r\n\r\n" .
  951. $payload;
  952. if ($opts['debug'] > 1) {
  953. $this->getLogger()->debug("---SENDING---\n$op\n---END---");
  954. }
  955. $contextOptions = array();
  956. if ($method == 'https') {
  957. if ($opts['cert'] != '') {
  958. $contextOptions['ssl']['local_cert'] = $opts['cert'];
  959. if ($opts['certpass'] != '') {
  960. $contextOptions['ssl']['passphrase'] = $opts['certpass'];
  961. }
  962. }
  963. if ($opts['cacert'] != '') {
  964. $contextOptions['ssl']['cafile'] = $opts['cacert'];
  965. }
  966. if ($opts['cacertdir'] != '') {
  967. $contextOptions['ssl']['capath'] = $opts['cacertdir'];
  968. }
  969. if ($opts['key'] != '') {
  970. $contextOptions['ssl']['local_pk'] = $opts['key'];
  971. }
  972. $contextOptions['ssl']['verify_peer'] = $opts['verifypeer'];
  973. $contextOptions['ssl']['verify_peer_name'] = $opts['verifypeer'];
  974. if ($opts['sslversion'] != 0) {
  975. /// @see https://www.php.net/manual/en/function.curl-setopt.php, https://www.php.net/manual/en/migration56.openssl.php
  976. switch($opts['sslversion']) {
  977. /// @todo what does this map to? 1.0-1.3?
  978. //case 1: // TLSv1
  979. // break;
  980. case 2: // SSLv2
  981. $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
  982. break;
  983. case 3: // SSLv3
  984. $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
  985. break;
  986. case 4: // TLSv1.0
  987. $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
  988. break;
  989. case 5: // TLSv1.1
  990. $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
  991. break;
  992. case 6: // TLSv1.2
  993. $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
  994. break;
  995. case 7: // TLSv1.3
  996. if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) {
  997. $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
  998. } else {
  999. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
  1000. PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-1.3 only is supported with PHP 7.4 or later');
  1001. }
  1002. break;
  1003. default:
  1004. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
  1005. PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': Unsupported required TLS version');
  1006. }
  1007. }
  1008. }
  1009. foreach ($opts['extracurlopts'] as $proto => $protoOpts) {
  1010. foreach ($protoOpts as $key => $val) {
  1011. $contextOptions[$proto][$key] = $val;
  1012. }
  1013. }
  1014. $context = stream_context_create($contextOptions);
  1015. if ($opts['timeout'] <= 0) {
  1016. $connectTimeout = ini_get('default_socket_timeout');
  1017. } else {
  1018. $connectTimeout = $opts['timeout'];
  1019. }
  1020. $this->errno = 0;
  1021. $this->errstr = '';
  1022. $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
  1023. STREAM_CLIENT_CONNECT, $context);
  1024. if ($fp) {
  1025. if ($opts['timeout'] > 0) {
  1026. stream_set_timeout($fp, $opts['timeout'], 0);
  1027. }
  1028. } else {
  1029. if ($this->errstr == '') {
  1030. $err = error_get_last();
  1031. $this->errstr = $err['message'];
  1032. }
  1033. $this->errstr = 'Connect error: ' . $this->errstr;
  1034. $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
  1035. return $r;
  1036. }
  1037. if (!fputs($fp, $op, strlen($op))) {
  1038. fclose($fp);
  1039. $this->errstr = 'Write error';
  1040. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
  1041. }
  1042. // Close socket before parsing.
  1043. // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
  1044. $ipd = '';
  1045. do {
  1046. // shall we check for $data === FALSE?
  1047. // as per the manual, it signals an error
  1048. $ipd .= fread($fp, 32768);
  1049. } while (!feof($fp));
  1050. fclose($fp);
  1051. return $req->parseResponse($ipd, false, $opts['return_type']);
  1052. }
  1053. /**
  1054. * Contributed by Justin Miller
  1055. * Requires curl to be built into PHP
  1056. * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
  1057. *
  1058. * @param Request $req
  1059. * @param string $method
  1060. * @param string $server
  1061. * @param int $port
  1062. * @param string $path
  1063. * @param array $opts the keys/values match self::getOptions
  1064. * @return Response
  1065. *
  1066. * @todo the $path arg atm is ignored. What to do if it is != $this->path?
  1067. */
  1068. protected function sendViaCURL($req, $method, $server, $port, $path, $opts)
  1069. {
  1070. if (!function_exists('curl_init')) {
  1071. $this->errstr = 'CURL unavailable on this install';
  1072. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
  1073. }
  1074. if ($method == 'https' || $method == 'h2') {
  1075. // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
  1076. if (($info = curl_version()) &&
  1077. ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))
  1078. ) {
  1079. $this->errstr = 'SSL unavailable on this install';
  1080. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
  1081. }
  1082. }
  1083. if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
  1084. ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
  1085. $this->errstr = 'HTTP/2 unavailable on this install';
  1086. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
  1087. }
  1088. // BC - we go through prepareCurlHandle in case some subclass reimplemented it
  1089. $curl = $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
  1090. $opts['authtype'], $opts['cert'], $opts['certpass'], $opts['cacert'], $opts['cacertdir'], $opts['proxy'],
  1091. $opts['proxyport'], $opts['proxy_user'], $opts['proxy_pass'], $opts['proxy_authtype'], $method,
  1092. $opts['keepalive'], $opts['key'], $opts['keypass'], $opts['sslversion']);
  1093. if (!$curl) {
  1094. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
  1095. ': error during curl initialization. Check php error log for details');
  1096. }
  1097. $result = curl_exec($curl);
  1098. if ($opts['debug'] > 1) {
  1099. $message = "---CURL INFO---\n";
  1100. foreach (curl_getinfo($curl) as $name => $val) {
  1101. if (is_array($val)) {
  1102. $val = implode("\n", $val);
  1103. }
  1104. $message .= $name . ': ' . $val . "\n";
  1105. }
  1106. $message .= '---END---';
  1107. $this->getLogger()->debug($message);
  1108. }
  1109. if (!$result) {
  1110. /// @todo we should use a better check here - what if we get back '' or '0'?
  1111. $this->errstr = 'no response';
  1112. $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
  1113. ': ' . curl_error($curl));
  1114. curl_close($curl);
  1115. if ($opts['keepalive']) {
  1116. $this->xmlrpc_curl_handle = null;
  1117. }
  1118. } else {
  1119. if (!$opts['keepalive']) {
  1120. curl_close($curl);
  1121. }
  1122. $resp = $req->parseResponse($result, true, $opts['return_type']);
  1123. if ($opts['keepalive']) {
  1124. /// @todo if we got back a 302 or 308, we should not reuse the curl handle for later calls
  1125. if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error']) {
  1126. curl_close($curl);
  1127. $this->xmlrpc_curl_handle = null;
  1128. }
  1129. }
  1130. }
  1131. return $resp;
  1132. }
  1133. /**
  1134. * @param Request $req
  1135. * @param string $method
  1136. * @param string $server
  1137. * @param int $port
  1138. * @param string $path
  1139. * @param array $opts the keys/values match self::getOptions
  1140. * @return \CurlHandle|resource|false
  1141. *
  1142. * @todo allow this method to either throw or return a Response, so that we can pass back to caller more info on errors
  1143. */
  1144. protected function createCURLHandle($req, $method, $server, $port, $path, $opts)
  1145. {
  1146. if ($port == 0) {
  1147. if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
  1148. $port = 80;
  1149. } else {
  1150. $port = 443;
  1151. }
  1152. }
  1153. // Only create the payload if it was not created previously
  1154. $payload = $req->getPayload();
  1155. if (empty($payload)) {
  1156. $payload = $req->serialize($opts['request_charset_encoding']);
  1157. }
  1158. // Deflate request body and set appropriate request headers
  1159. $encodingHdr = '';
  1160. if (($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate')) {
  1161. if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
  1162. $a = @gzencode($payload);
  1163. if ($a) {
  1164. $payload = $a;
  1165. $encodingHdr = 'Content-Encoding: gzip';
  1166. } else {
  1167. $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
  1168. }
  1169. } else if (function_exists('gzcompress')) {
  1170. $a = @gzcompress($payload);
  1171. if ($a) {
  1172. $payload = $a;
  1173. $encodingHdr = 'Content-Encoding: deflate';
  1174. } else {
  1175. $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
  1176. }
  1177. } else {
  1178. $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
  1179. }
  1180. } else {
  1181. if ($opts['request_compression'] != '') {
  1182. $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
  1183. }
  1184. }
  1185. if (!$opts['keepalive'] || !$this->xmlrpc_curl_handle) {
  1186. if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
  1187. $protocol = 'http';
  1188. } else {
  1189. if ($method == 'h2') {
  1190. $protocol = 'https';
  1191. } else {
  1192. // http, https
  1193. $protocol = $method;
  1194. if (strpos($protocol, ':') !== false) {
  1195. $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The curl protocol requested for the call is: '$protocol'");
  1196. return false;
  1197. }
  1198. }
  1199. }
  1200. $curl = curl_init($protocol . '://' . $server . ':' . $port . $path);
  1201. if (!$curl) {
  1202. return false;
  1203. }
  1204. if ($opts['keepalive']) {
  1205. $this->xmlrpc_curl_handle = $curl;
  1206. }
  1207. } else {
  1208. $curl = $this->xmlrpc_curl_handle;
  1209. }
  1210. // results into variable
  1211. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  1212. if ($opts['debug'] > 1) {
  1213. curl_setopt($curl, CURLOPT_VERBOSE, true);
  1214. /// @todo redirect curlopt_stderr to some stream which can be piped to the logger
  1215. }
  1216. curl_setopt($curl, CURLOPT_USERAGENT, $opts['user_agent']);
  1217. // required for XMLRPC: post the data
  1218. curl_setopt($curl, CURLOPT_POST, 1);
  1219. // the data
  1220. curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
  1221. // return the header too
  1222. curl_setopt($curl, CURLOPT_HEADER, 1);
  1223. // NB: if we set an empty string, CURL will add http header indicating
  1224. // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
  1225. if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
  1226. //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $opts['accepted_compression']));
  1227. // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
  1228. if (count($opts['accepted_compression']) == 1) {
  1229. curl_setopt($curl, CURLOPT_ENCODING, $opts['accepted_compression'][0]);
  1230. } else {
  1231. curl_setopt($curl, CURLOPT_ENCODING, '');
  1232. }
  1233. }
  1234. // extra headers
  1235. $headers = array('Content-Type: ' . $req->getContentType(), 'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']));
  1236. // if no keepalive is wanted, let the server know it in advance
  1237. if (!$opts['keepalive']) {
  1238. $headers[] = 'Connection: close';
  1239. }
  1240. // request compression header
  1241. if ($encodingHdr) {
  1242. $headers[] = $encodingHdr;
  1243. }
  1244. // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
  1245. // size exceeds 1025 bytes, apparently)
  1246. $headers[] = 'Expect:';
  1247. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  1248. // timeout is borked
  1249. if ($opts['timeout']) {
  1250. curl_setopt($curl, CURLOPT_TIMEOUT, $opts['timeout'] == 1 ? 1 : $opts['timeout'] - 1);
  1251. }
  1252. switch ($method) {
  1253. case 'http10':
  1254. curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
  1255. break;
  1256. case 'http11':
  1257. curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  1258. break;
  1259. case 'h2c':
  1260. if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
  1261. curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
  1262. } else {
  1263. $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
  1264. curl_close($curl);
  1265. return false;
  1266. }
  1267. break;
  1268. case 'h2':
  1269. curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
  1270. break;
  1271. }
  1272. if ($opts['username'] && $opts['password']) {
  1273. curl_setopt($curl, CURLOPT_USERPWD, $opts['username'] . ':' . $opts['password']);
  1274. if (defined('CURLOPT_HTTPAUTH')) {
  1275. curl_setopt($curl, CURLOPT_HTTPAUTH, $opts['authtype']);
  1276. } elseif ($opts['authtype'] != 1) {
  1277. $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
  1278. curl_close($curl);
  1279. return false;
  1280. }
  1281. }
  1282. // note: h2c is http2 without the https. No need to have it in this IF
  1283. if ($method == 'https' || $method == 'h2') {
  1284. // set cert file
  1285. if ($opts['cert']) {
  1286. curl_setopt($curl, CURLOPT_SSLCERT, $opts['cert']);
  1287. }
  1288. // set cert password
  1289. if ($opts['certpass']) {
  1290. curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $opts['certpass']);
  1291. }
  1292. // whether to verify remote host's cert
  1293. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $opts['verifypeer']);
  1294. // set ca certificates file/dir
  1295. if ($opts['cacert']) {
  1296. curl_setopt($curl, CURLOPT_CAINFO, $opts['cacert']);
  1297. }
  1298. if ($opts['cacertdir']) {
  1299. curl_setopt($curl, CURLOPT_CAPATH, $opts['cacertdir']);
  1300. }
  1301. // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
  1302. if ($opts['key']) {
  1303. curl_setopt($curl, CURLOPT_SSLKEY, $opts['key']);
  1304. }
  1305. // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
  1306. if ($opts['keypass']) {
  1307. curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $opts['keypass']);
  1308. }
  1309. // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
  1310. // it matches the hostname used
  1311. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $opts['verifyhost']);
  1312. // allow usage of different SSL versions
  1313. curl_setopt($curl, CURLOPT_SSLVERSION, $opts['sslversion']);
  1314. }
  1315. // proxy info
  1316. if ($opts['proxy']) {
  1317. if ($opts['proxyport'] == 0) {
  1318. $opts['proxyport'] = 8080; // NB: even for HTTPS, local connection is on port 8080
  1319. }
  1320. curl_setopt($curl, CURLOPT_PROXY, $opts['proxy'] . ':' . $opts['proxyport']);
  1321. if ($opts['proxy_user']) {
  1322. curl_setopt($curl, CURLOPT_PROXYUSERPWD, $opts['proxy_user'] . ':' . $opts['proxy_pass']);
  1323. if (defined('CURLOPT_PROXYAUTH')) {
  1324. curl_setopt($curl, CURLOPT_PROXYAUTH, $opts['proxy_authtype']);
  1325. } elseif ($opts['proxy_authtype'] != 1) {
  1326. $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
  1327. curl_close($curl);
  1328. return false;
  1329. }
  1330. }
  1331. }
  1332. // NB: should we build cookie http headers by hand rather than let CURL do it?
  1333. // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
  1334. if (count($opts['cookies'])) {
  1335. $cookieHeader = '';
  1336. foreach ($opts['cookies'] as $name => $cookie) {
  1337. $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
  1338. }
  1339. curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
  1340. }
  1341. foreach ($opts['extracurlopts'] as $opt => $val) {
  1342. curl_setopt($curl, $opt, $val);
  1343. }
  1344. if ($opts['debug'] > 1) {
  1345. $this->getLogger()->debug("---SENDING---\n$payload\n---END---");
  1346. }
  1347. return $curl;
  1348. }
  1349. /**
  1350. * Send an array of requests and return an array of responses.
  1351. *
  1352. * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
  1353. * system.multicall, and revert to sending many successive calls in case of failure.
  1354. * This failure is also stored in $this->no_multicall for subsequent calls.
  1355. * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
  1356. * so there is no way to reliably distinguish between that and a temporary failure.
  1357. * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
  1358. * 2np parameter to FALSE.
  1359. *
  1360. * NB: trying to shoehorn extra functionality into existing syntax has resulted
  1361. * in pretty much convoluted code...
  1362. *
  1363. * @param Request[] $reqs an array of Request objects
  1364. * @param bool $noFallback When true, upon receiving an error during multicall, multiple single calls will not be
  1365. * attempted.
  1366. * Deprecated alternative, was: int - "connection timeout (in seconds). See the details in the
  1367. * docs for the send() method". Please use setOption instead to set a timeout
  1368. * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method."
  1369. * Please use the constructor to set an http protocol variant.
  1370. * @param boolean $fallback deprecated. Was: "w"hen true, upon receiving an error during multicall, multiple single
  1371. * calls will be attempted"
  1372. * @return Response[]
  1373. */
  1374. public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
  1375. {
  1376. // BC
  1377. if (is_bool($timeout) && $fallback === true) {
  1378. $fallback = !$timeout;
  1379. $timeout = 0;
  1380. }
  1381. if ($method == '') {
  1382. $method = $this->method;
  1383. }
  1384. if (!$this->no_multicall) {
  1385. $results = $this->_try_multicall($reqs, $timeout, $method);
  1386. /// @todo how to handle the case of $this->return_type = xml?
  1387. if (is_array($results)) {
  1388. // System.multicall succeeded
  1389. return $results;
  1390. } else {
  1391. // either system.multicall is unsupported by server, or the call failed for some other reason.
  1392. // Feature creep: is there a way to tell apart unsupported multicall from other faults?
  1393. if ($fallback) {
  1394. // Don't try it next time...
  1395. $this->no_multicall = true;
  1396. } else {
  1397. $result = $results;
  1398. }
  1399. }
  1400. } else {
  1401. // override fallback, in case careless user tries to do two
  1402. // opposite things at the same time
  1403. $fallback = true;
  1404. }
  1405. $results = array();
  1406. if ($fallback) {
  1407. // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
  1408. /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
  1409. foreach ($reqs as $req) {
  1410. $results[] = $this->send($req, $timeout, $method);
  1411. }
  1412. } else {
  1413. // user does NOT want to fallback on many single calls: since we should always return an array of responses,
  1414. // we return an array with the same error repeated n times
  1415. foreach ($reqs as $req) {
  1416. $results[] = $result;
  1417. }
  1418. }
  1419. return $results;
  1420. }
  1421. /**
  1422. * Attempt to boxcar $reqs via system.multicall.
  1423. *
  1424. * @param Request[] $reqs
  1425. * @param int $timeout
  1426. * @param string $method
  1427. * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
  1428. * from a multicall response
  1429. */
  1430. private function _try_multicall($reqs, $timeout, $method)
  1431. {
  1432. // Construct multicall request
  1433. $calls = array();
  1434. foreach ($reqs as $req) {
  1435. $call['methodName'] = new Value($req->method(), 'string');
  1436. $numParams = $req->getNumParams();
  1437. $params = array();
  1438. for ($i = 0; $i < $numParams; $i++) {
  1439. $params[$i] = $req->getParam($i);
  1440. }
  1441. $call['params'] = new Value($params, 'array');
  1442. $calls[] = new Value($call, 'struct');
  1443. }
  1444. $multiCall = new static::$requestClass('system.multicall');
  1445. $multiCall->addParam(new Value($calls, 'array'));
  1446. // Attempt RPC call
  1447. $result = $this->send($multiCall, $timeout, $method);
  1448. if ($result->faultCode() != 0) {
  1449. // call to system.multicall failed
  1450. return $result;
  1451. }
  1452. // Unpack responses.
  1453. $rets = $result->value();
  1454. $response = array();
  1455. if ($this->return_type == 'xml') {
  1456. for ($i = 0; $i < count($reqs); $i++) {
  1457. $response[] = new static::$responseClass($rets, 0, '', 'xml', $result->httpResponse());
  1458. }
  1459. } elseif ($this->return_type == 'phpvals') {
  1460. if (!is_array($rets)) {
  1461. // bad return type from system.multicall
  1462. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1463. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
  1464. }
  1465. $numRets = count($rets);
  1466. if ($numRets != count($reqs)) {
  1467. // wrong number of return values.
  1468. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1469. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
  1470. $result->httpResponse());
  1471. }
  1472. for ($i = 0; $i < $numRets; $i++) {
  1473. $val = $rets[$i];
  1474. if (!is_array($val)) {
  1475. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1476. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
  1477. 'phpvals', $result->httpResponse());
  1478. }
  1479. switch (count($val)) {
  1480. case 1:
  1481. if (!isset($val[0])) {
  1482. // Bad value
  1483. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1484. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
  1485. 'phpvals', $result->httpResponse());
  1486. }
  1487. // Normal return value
  1488. $response[$i] = new static::$responseClass($val[0], 0, '', 'phpvals', $result->httpResponse());
  1489. break;
  1490. case 2:
  1491. /// @todo remove usage of @: it is apparently quite slow
  1492. $code = @$val['faultCode'];
  1493. if (!is_int($code)) {
  1494. /// @todo should we check that it is != 0?
  1495. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1496. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
  1497. 'phpvals', $result->httpResponse());
  1498. }
  1499. $str = @$val['faultString'];
  1500. if (!is_string($str)) {
  1501. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1502. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
  1503. 'phpvals', $result->httpResponse());
  1504. }
  1505. $response[$i] = new static::$responseClass(0, $code, $str, 'phpvals', $result->httpResponse());
  1506. break;
  1507. default:
  1508. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1509. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
  1510. 'phpvals', $result->httpResponse());
  1511. }
  1512. }
  1513. } else {
  1514. // return type == 'xmlrpcvals'
  1515. if ($rets->kindOf() != 'array') {
  1516. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1517. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array", 'xmlrpcvals',
  1518. $result->httpResponse());
  1519. }
  1520. $numRets = $rets->count();
  1521. if ($numRets != count($reqs)) {
  1522. // wrong number of return values.
  1523. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1524. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
  1525. $result->httpResponse());
  1526. }
  1527. foreach ($rets as $i => $val) {
  1528. switch ($val->kindOf()) {
  1529. case 'array':
  1530. if ($val->count() != 1) {
  1531. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1532. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
  1533. 'phpvals', $result->httpResponse());
  1534. }
  1535. // Normal return value
  1536. $response[] = new static::$responseClass($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
  1537. break;
  1538. case 'struct':
  1539. if ($val->count() != 2) {
  1540. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1541. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
  1542. 'phpvals', $result->httpResponse());
  1543. }
  1544. /** @var Value $code */
  1545. $code = $val['faultCode'];
  1546. if ($code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') {
  1547. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1548. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
  1549. 'xmlrpcvals', $result->httpResponse());
  1550. }
  1551. /** @var Value $str */
  1552. $str = $val['faultString'];
  1553. if ($str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') {
  1554. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1555. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
  1556. 'xmlrpcvals', $result->httpResponse());
  1557. }
  1558. $response[] = new static::$responseClass(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse());
  1559. break;
  1560. default:
  1561. return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
  1562. PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
  1563. 'xmlrpcvals', $result->httpResponse());
  1564. }
  1565. }
  1566. }
  1567. return $response;
  1568. }
  1569. // *** BC layer ***
  1570. /**
  1571. * @deprecated
  1572. *
  1573. * @param Request $req
  1574. * @param string $server
  1575. * @param int $port
  1576. * @param int $timeout
  1577. * @param string $username
  1578. * @param string $password
  1579. * @param int $authType
  1580. * @param string $proxyHost
  1581. * @param int $proxyPort
  1582. * @param string $proxyUsername
  1583. * @param string $proxyPassword
  1584. * @param int $proxyAuthType
  1585. * @param string $method
  1586. * @return Response
  1587. */
  1588. protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
  1589. $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
  1590. $method = 'http')
  1591. {
  1592. $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
  1593. return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
  1594. null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
  1595. }
  1596. /**
  1597. * @deprecated
  1598. *
  1599. * @param Request $req
  1600. * @param string $server
  1601. * @param int $port
  1602. * @param int $timeout
  1603. * @param string $username
  1604. * @param string $password
  1605. * @param int $authType
  1606. * @param string $cert
  1607. * @param string $certPass
  1608. * @param string $caCert
  1609. * @param string $caCertDir
  1610. * @param string $proxyHost
  1611. * @param int $proxyPort
  1612. * @param string $proxyUsername
  1613. * @param string $proxyPassword
  1614. * @param int $proxyAuthType
  1615. * @param bool $keepAlive
  1616. * @param string $key
  1617. * @param string $keyPass
  1618. * @param int $sslVersion
  1619. * @return Response
  1620. */
  1621. protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '',
  1622. $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
  1623. $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
  1624. $sslVersion = 0)
  1625. {
  1626. $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
  1627. return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
  1628. $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
  1629. $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
  1630. }
  1631. /**
  1632. * @deprecated
  1633. *
  1634. * @param Request $req
  1635. * @param string $server
  1636. * @param int $port
  1637. * @param int $timeout
  1638. * @param string $username
  1639. * @param string $password
  1640. * @param int $authType only value supported is 1
  1641. * @param string $cert
  1642. * @param string $certPass
  1643. * @param string $caCert
  1644. * @param string $caCertDir
  1645. * @param string $proxyHost
  1646. * @param int $proxyPort
  1647. * @param string $proxyUsername
  1648. * @param string $proxyPassword
  1649. * @param int $proxyAuthType only value supported is 1
  1650. * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
  1651. * @param string $key
  1652. * @param string $keyPass @todo not implemented yet.
  1653. * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
  1654. * @return Response
  1655. */
  1656. protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
  1657. $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
  1658. $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'http', $key = '', $keyPass = '',
  1659. $sslVersion = 0)
  1660. {
  1661. $this->logDeprecationUnlessCalledBy('send');
  1662. return $this->sendViaSocket($req, $method, $server, $port, $this->path, array(
  1663. 'accepted_charset_encodings' => $this->accepted_charset_encodings,
  1664. 'accepted_compression' => $this->accepted_compression,
  1665. 'authtype' => $authType,
  1666. 'cacert' => $caCert,
  1667. 'cacertdir' => $caCertDir,
  1668. 'cert' => $cert,
  1669. 'certpass' => $certPass,
  1670. 'cookies' => $this->cookies,
  1671. 'debug' => $this->debug,
  1672. 'extracurlopts' => $this->extracurlopts,
  1673. 'extrasockopts' => $this->extrasockopts,
  1674. 'keepalive' => $this->keepalive,
  1675. 'key' => $key,
  1676. 'keypass' => $keyPass,
  1677. 'no_multicall' => $this->no_multicall,
  1678. 'password' => $password,
  1679. 'proxy' => $proxyHost,
  1680. 'proxy_authtype' => $proxyAuthType,
  1681. 'proxy_pass' => $proxyPassword,
  1682. 'proxyport' => $proxyPort,
  1683. 'proxy_user' => $proxyUsername,
  1684. 'request_charset_encoding' => $this->request_charset_encoding,
  1685. 'request_compression' => $this->request_compression,
  1686. 'return_type' => $this->return_type,
  1687. 'sslversion' => $sslVersion,
  1688. 'timeout' => $timeout,
  1689. 'username' => $username,
  1690. 'user_agent' => $this->user_agent,
  1691. 'use_curl' => $this->use_curl,
  1692. 'verifyhost' => $this->verifyhost,
  1693. 'verifypeer' => $this->verifypeer,
  1694. ));
  1695. }
  1696. /**
  1697. * @deprecated
  1698. *
  1699. * @param Request $req
  1700. * @param string $server
  1701. * @param int $port
  1702. * @param int $timeout
  1703. * @param string $username
  1704. * @param string $password
  1705. * @param int $authType
  1706. * @param string $cert
  1707. * @param string $certPass
  1708. * @param string $caCert
  1709. * @param string $caCertDir
  1710. * @param string $proxyHost
  1711. * @param int $proxyPort
  1712. * @param string $proxyUsername
  1713. * @param string $proxyPassword
  1714. * @param int $proxyAuthType
  1715. * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
  1716. * @param bool $keepAlive
  1717. * @param string $key
  1718. * @param string $keyPass
  1719. * @param int $sslVersion
  1720. * @return Response
  1721. */
  1722. protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
  1723. $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
  1724. $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
  1725. $keyPass = '', $sslVersion = 0)
  1726. {
  1727. $this->logDeprecationUnlessCalledBy('send');
  1728. return $this->sendViaCURL($req, $method, $server, $port, $this->path, array(
  1729. 'accepted_charset_encodings' => $this->accepted_charset_encodings,
  1730. 'accepted_compression' => $this->accepted_compression,
  1731. 'authtype' => $authType,
  1732. 'cacert' => $caCert,
  1733. 'cacertdir' => $caCertDir,
  1734. 'cert' => $cert,
  1735. 'certpass' => $certPass,
  1736. 'cookies' => $this->cookies,
  1737. 'debug' => $this->debug,
  1738. 'extracurlopts' => $this->extracurlopts,
  1739. 'extrasockopts' => $this->extrasockopts,
  1740. 'keepalive' => $keepAlive,
  1741. 'key' => $key,
  1742. 'keypass' => $keyPass,
  1743. 'no_multicall' => $this->no_multicall,
  1744. 'password' => $password,
  1745. 'proxy' => $proxyHost,
  1746. 'proxy_authtype' => $proxyAuthType,
  1747. 'proxy_pass' => $proxyPassword,
  1748. 'proxyport' => $proxyPort,
  1749. 'proxy_user' => $proxyUsername,
  1750. 'request_charset_encoding' => $this->request_charset_encoding,
  1751. 'request_compression' => $this->request_compression,
  1752. 'return_type' => $this->return_type,
  1753. 'sslversion' => $sslVersion,
  1754. 'timeout' => $timeout,
  1755. 'username' => $username,
  1756. 'user_agent' => $this->user_agent,
  1757. 'use_curl' => $this->use_curl,
  1758. 'verifyhost' => $this->verifyhost,
  1759. 'verifypeer' => $this->verifypeer,
  1760. ));
  1761. }
  1762. /**
  1763. * @deprecated
  1764. *
  1765. * @param $req
  1766. * @param $server
  1767. * @param $port
  1768. * @param $timeout
  1769. * @param $username
  1770. * @param $password
  1771. * @param $authType
  1772. * @param $cert
  1773. * @param $certPass
  1774. * @param $caCert
  1775. * @param $caCertDir
  1776. * @param $proxyHost
  1777. * @param $proxyPort
  1778. * @param $proxyUsername
  1779. * @param $proxyPassword
  1780. * @param $proxyAuthType
  1781. * @param $method
  1782. * @param $keepAlive
  1783. * @param $key
  1784. * @param $keyPass
  1785. * @param $sslVersion
  1786. * @return false|\CurlHandle|resource
  1787. */
  1788. protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
  1789. $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
  1790. $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
  1791. $keyPass = '', $sslVersion = 0)
  1792. {
  1793. $this->logDeprecationUnlessCalledBy('sendViaCURL');
  1794. return $this->createCURLHandle($req, $method, $server, $port, $this->path, array(
  1795. 'accepted_charset_encodings' => $this->accepted_charset_encodings,
  1796. 'accepted_compression' => $this->accepted_compression,
  1797. 'authtype' => $authType,
  1798. 'cacert' => $caCert,
  1799. 'cacertdir' => $caCertDir,
  1800. 'cert' => $cert,
  1801. 'certpass' => $certPass,
  1802. 'cookies' => $this->cookies,
  1803. 'debug' => $this->debug,
  1804. 'extracurlopts' => $this->extracurlopts,
  1805. 'keepalive' => $keepAlive,
  1806. 'key' => $key,
  1807. 'keypass' => $keyPass,
  1808. 'no_multicall' => $this->no_multicall,
  1809. 'password' => $password,
  1810. 'proxy' => $proxyHost,
  1811. 'proxy_authtype' => $proxyAuthType,
  1812. 'proxy_pass' => $proxyPassword,
  1813. 'proxyport' => $proxyPort,
  1814. 'proxy_user' => $proxyUsername,
  1815. 'request_charset_encoding' => $this->request_charset_encoding,
  1816. 'request_compression' => $this->request_compression,
  1817. 'return_type' => $this->return_type,
  1818. 'sslversion' => $sslVersion,
  1819. 'timeout' => $timeout,
  1820. 'username' => $username,
  1821. 'user_agent' => $this->user_agent,
  1822. 'use_curl' => $this->use_curl,
  1823. 'verifyhost' => $this->verifyhost,
  1824. 'verifypeer' => $this->verifypeer,
  1825. ));
  1826. }
  1827. // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];`
  1828. public function &__get($name)
  1829. {
  1830. if (in_array($name, static::$options)) {
  1831. $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
  1832. return $this->$name;
  1833. }
  1834. switch ($name) {
  1835. case 'errno':
  1836. case 'errstr':
  1837. case 'method':
  1838. case 'server':
  1839. case 'port':
  1840. case 'path':
  1841. $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
  1842. return $this->$name;
  1843. default:
  1844. /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
  1845. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
  1846. trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
  1847. $result = null;
  1848. return $result;
  1849. }
  1850. }
  1851. public function __set($name, $value)
  1852. {
  1853. if (in_array($name, static::$options)) {
  1854. $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
  1855. $this->$name = $value;
  1856. return;
  1857. }
  1858. switch ($name) {
  1859. case 'errno':
  1860. case 'errstr':
  1861. case 'method':
  1862. case 'server':
  1863. case 'port':
  1864. case 'path':
  1865. $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
  1866. $this->$name = $value;
  1867. return;
  1868. default:
  1869. /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
  1870. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
  1871. trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
  1872. }
  1873. }
  1874. public function __isset($name)
  1875. {
  1876. if (in_array($name, static::$options)) {
  1877. $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
  1878. return isset($this->$name);
  1879. }
  1880. switch ($name) {
  1881. case 'errno':
  1882. case 'errstr':
  1883. case 'method':
  1884. case 'server':
  1885. case 'port':
  1886. case 'path':
  1887. $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
  1888. return isset($this->$name);
  1889. default:
  1890. return false;
  1891. }
  1892. }
  1893. public function __unset($name)
  1894. {
  1895. if (in_array($name, static::$options)) {
  1896. $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
  1897. unset($this->$name);
  1898. return;
  1899. }
  1900. switch ($name) {
  1901. case 'errno':
  1902. case 'errstr':
  1903. case 'method':
  1904. case 'server':
  1905. case 'port':
  1906. case 'path':
  1907. $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
  1908. unset($this->$name);
  1909. return;
  1910. default:
  1911. /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
  1912. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
  1913. trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
  1914. }
  1915. }
  1916. }