1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpXmlRpc; |
4
|
|
|
|
5
|
|
|
use PhpXmlRpc\Helper\Logger; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Used to represent a client of an XML-RPC server. |
9
|
|
|
*/ |
10
|
|
|
class Client |
11
|
|
|
{ |
12
|
|
|
const USE_CURL_NEVER = 0; |
13
|
|
|
const USE_CURL_ALWAYS = 1; |
14
|
|
|
const USE_CURL_AUTO = 2; |
15
|
|
|
|
16
|
|
|
/// @todo: do these need to be public? |
17
|
|
|
public $method = 'http'; |
18
|
|
|
public $server; |
19
|
|
|
public $port = 0; |
20
|
|
|
public $path; |
21
|
|
|
|
22
|
|
|
public $errno; |
23
|
|
|
public $errstr; |
24
|
|
|
public $debug = 0; |
25
|
|
|
|
26
|
|
|
public $username = ''; |
27
|
|
|
public $password = ''; |
28
|
|
|
public $authtype = 1; |
29
|
|
|
|
30
|
|
|
public $cert = ''; |
31
|
|
|
public $certpass = ''; |
32
|
|
|
public $cacert = ''; |
33
|
|
|
public $cacertdir = ''; |
34
|
|
|
public $key = ''; |
35
|
|
|
public $keypass = ''; |
36
|
|
|
public $verifypeer = true; |
37
|
|
|
public $verifyhost = 2; |
38
|
|
|
public $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT |
39
|
|
|
|
40
|
|
|
public $proxy = ''; |
41
|
|
|
public $proxyport = 0; |
42
|
|
|
public $proxy_user = ''; |
43
|
|
|
public $proxy_pass = ''; |
44
|
|
|
public $proxy_authtype = 1; |
45
|
|
|
|
46
|
|
|
public $cookies = array(); |
47
|
|
|
public $extracurlopts = array(); |
48
|
|
|
public $use_curl = self::USE_CURL_AUTO; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var bool |
52
|
|
|
* |
53
|
|
|
* This determines whether the multicall() method will try to take advantage of the system.multicall xmlrpc method |
54
|
|
|
* to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http |
55
|
|
|
* calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of |
56
|
|
|
* system.multicall. |
57
|
|
|
*/ |
58
|
|
|
public $no_multicall = false; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* List of http compression methods accepted by the client for responses. |
62
|
|
|
* NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib. |
63
|
|
|
* |
64
|
|
|
* NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since |
65
|
|
|
* in those cases it will be up to CURL to decide the compression methods |
66
|
|
|
* it supports. You might check for the presence of 'zlib' in the output of |
67
|
|
|
* curl_version() to determine wheter compression is supported or not |
68
|
|
|
*/ |
69
|
|
|
public $accepted_compression = array(); |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Name of compression scheme to be used for sending requests. |
73
|
|
|
* Either null, gzip or deflate. |
74
|
|
|
*/ |
75
|
|
|
|
76
|
|
|
public $request_compression = ''; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* CURL handle: used for keep-alive connections (PHP 4.3.8 up, see: |
80
|
|
|
* http://curl.haxx.se/docs/faq.html#7.3). |
81
|
|
|
*/ |
82
|
|
|
public $xmlrpc_curl_handle = null; |
83
|
|
|
|
84
|
|
|
/// Whether to use persistent connections for http 1.1 and https |
85
|
|
|
public $keepalive = false; |
86
|
|
|
|
87
|
|
|
/// Charset encodings that can be decoded without problems by the client |
88
|
|
|
public $accepted_charset_encodings = array(); |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* The charset encoding that will be used for serializing request sent by the client. |
92
|
|
|
* It defaults to NULL, which means using US-ASCII and encoding all characters outside of the ASCII range using |
93
|
|
|
* their xml character entity representation (this has the benefit that line end characters will not be mangled in |
94
|
|
|
* the transfer, a CR-LF will be preserved as well as a singe LF). |
95
|
|
|
* Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1' |
96
|
|
|
*/ |
97
|
|
|
public $request_charset_encoding = ''; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Decides the content of Response objects returned by calls to send() and multicall(). |
101
|
|
|
* Valid values are 'xmlrpcvals', 'phpvals' or 'xml'. |
102
|
|
|
* |
103
|
|
|
* Determines whether the value returned inside an Response object as results of calls to the send() and multicall() |
104
|
|
|
* methods will be a Value object, a plain php value or a raw xml string. |
105
|
|
|
* Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'. |
106
|
|
|
* To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as |
107
|
|
|
* Response objects in any case. |
108
|
|
|
* Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original |
109
|
|
|
* response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the |
110
|
|
|
* server as an xmlrpc string or base64 value. |
111
|
|
|
*/ |
112
|
|
|
public $return_type = 'xmlrpcvals'; |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Sent to servers in http headers. |
116
|
|
|
*/ |
117
|
|
|
public $user_agent; |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* @param string $path either the PATH part of the xmlrpc server URL, or complete server URL (in which case you |
121
|
|
|
* should use and empty string for all other parameters) |
122
|
|
|
* e.g. /xmlrpc/server.php |
123
|
|
|
* e.g. http://phpxmlrpc.sourceforge.net/server.php |
124
|
|
|
* e.g. https://james:[email protected]:443/xmlrpcserver?agent=007 |
125
|
|
|
* @param string $server the server name / ip address |
126
|
|
|
* @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on |
127
|
|
|
* protocol used |
128
|
|
|
* @param string $method the http protocol variant: defaults to 'http'; 'https' and 'http11' can be used if CURL is |
129
|
|
|
* installed. The value set here can be overridden in any call to $this->send(). |
130
|
|
|
*/ |
131
|
605 |
|
public function __construct($path, $server = '', $port = '', $method = '') |
132
|
|
|
{ |
133
|
|
|
// allow user to specify all params in $path |
134
|
605 |
|
if ($server == '' && $port == '' && $method == '') { |
135
|
|
|
$parts = parse_url($path); |
136
|
|
|
$server = $parts['host']; |
137
|
|
|
$path = isset($parts['path']) ? $parts['path'] : ''; |
138
|
|
|
if (isset($parts['query'])) { |
139
|
|
|
$path .= '?' . $parts['query']; |
140
|
|
|
} |
141
|
|
|
if (isset($parts['fragment'])) { |
142
|
|
|
$path .= '#' . $parts['fragment']; |
143
|
|
|
} |
144
|
|
|
if (isset($parts['port'])) { |
145
|
|
|
$port = $parts['port']; |
146
|
|
|
} |
147
|
|
|
if (isset($parts['scheme'])) { |
148
|
|
|
$method = $parts['scheme']; |
149
|
|
|
} |
150
|
|
|
if (isset($parts['user'])) { |
151
|
|
|
$this->username = $parts['user']; |
152
|
|
|
} |
153
|
|
|
if (isset($parts['pass'])) { |
154
|
|
|
$this->password = $parts['pass']; |
155
|
|
|
} |
156
|
|
|
} |
157
|
605 |
|
if ($path == '' || $path[0] != '/') { |
158
|
|
|
$this->path = '/' . $path; |
159
|
|
|
} else { |
160
|
605 |
|
$this->path = $path; |
161
|
|
|
} |
162
|
605 |
|
$this->server = $server; |
163
|
605 |
|
if ($port != '') { |
164
|
3 |
|
$this->port = $port; |
165
|
3 |
|
} |
166
|
605 |
|
if ($method != '') { |
167
|
|
|
$this->method = $method; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
// if ZLIB is enabled, let the client by default accept compressed responses |
171
|
605 |
|
if (function_exists('gzinflate') || ( |
172
|
|
|
function_exists('curl_init') && (($info = curl_version()) && |
173
|
|
|
((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version']))) |
174
|
|
|
) |
175
|
605 |
|
) { |
176
|
605 |
|
$this->accepted_compression = array('gzip', 'deflate'); |
177
|
605 |
|
} |
178
|
|
|
|
179
|
|
|
// keepalives: enabled by default |
180
|
605 |
|
$this->keepalive = true; |
181
|
|
|
|
182
|
|
|
// by default the xml parser can support these 3 charset encodings |
183
|
605 |
|
$this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII'); |
184
|
|
|
|
185
|
|
|
// Add all charsets which mbstring can handle, but remove junk not found in IANA registry at |
186
|
|
|
// http://www.iana.org/assignments/character-sets/character-sets.xhtml |
187
|
|
|
// NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets! |
188
|
|
|
/*if (function_exists('mb_list_encodings')) { |
|
|
|
|
189
|
|
|
|
190
|
|
|
$encodings = array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'BASE64', 'UUENCODE', 'ASCII', |
191
|
|
|
'HTML-ENTITIES', 'Quoted-Printable', '7bit','8bit', 'byte2be', 'byte2le', 'byte4be', 'byte4le')); |
192
|
|
|
$this->accepted_charset_encodings = array_unique(array_merge($this->accepted_charset_encodings, $encodings)); |
193
|
|
|
}*/ |
194
|
|
|
|
195
|
|
|
// initialize user_agent string |
196
|
605 |
|
$this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion; |
197
|
605 |
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Enable/disable the echoing to screen of the xmlrpc responses received. The default is not no output anything. |
201
|
|
|
* |
202
|
|
|
* The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying |
203
|
|
|
* (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to |
204
|
|
|
* represent the value returned by the server |
205
|
|
|
* At level2, the complete payload of the xmlrpc request is also printed, before being sent t the server. |
206
|
|
|
* |
207
|
|
|
* This option can be very useful when debugging servers as it allows you to see exactly what the client sends and |
208
|
|
|
* the server returns. |
209
|
|
|
* |
210
|
|
|
* @param integer $level values 0, 1 and 2 are supported (2 = echo sent msg too, before received response) |
211
|
|
|
*/ |
212
|
605 |
|
public function setDebug($level) |
213
|
|
|
{ |
214
|
605 |
|
$this->debug = $level; |
215
|
605 |
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Sets the username and password for authorizing the client to the server. |
219
|
|
|
* |
220
|
|
|
* With the default (HTTP) transport, this information is used for HTTP Basic authorization. |
221
|
|
|
* Note that username and password can also be set using the class constructor. |
222
|
|
|
* With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use |
223
|
|
|
* the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter. |
224
|
|
|
* |
225
|
|
|
* @param string $user username |
226
|
|
|
* @param string $password password |
227
|
|
|
* @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC |
228
|
|
|
* (basic auth). Note that auth types NTLM and Digest will only work if the Curl php |
229
|
|
|
* extension is enabled. |
230
|
|
|
*/ |
231
|
62 |
|
public function setCredentials($user, $password, $authType = 1) |
232
|
|
|
{ |
233
|
62 |
|
$this->username = $user; |
234
|
62 |
|
$this->password = $password; |
235
|
62 |
|
$this->authtype = $authType; |
236
|
62 |
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Set the optional certificate and passphrase used in SSL-enabled communication with a remote server. |
240
|
|
|
* |
241
|
|
|
* Note: to retrieve information about the client certificate on the server side, you will need to look into the |
242
|
|
|
* environment variables which are set up by the webserver. Different webservers will typically set up different |
243
|
|
|
* variables. |
244
|
|
|
* |
245
|
|
|
* @param string $cert the name of a file containing a PEM formatted certificate |
246
|
|
|
* @param string $certPass the password required to use it |
247
|
|
|
*/ |
248
|
|
|
public function setCertificate($cert, $certPass = '') |
249
|
|
|
{ |
250
|
|
|
$this->cert = $cert; |
251
|
|
|
$this->certpass = $certPass; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE. |
256
|
|
|
* |
257
|
|
|
* See the php manual page about CURLOPT_CAINFO for more details. |
258
|
|
|
* |
259
|
|
|
* @param string $caCert certificate file name (or dir holding certificates) |
260
|
|
|
* @param bool $isDir set to true to indicate cacert is a dir. defaults to false |
261
|
|
|
*/ |
262
|
|
|
public function setCaCertificate($caCert, $isDir = false) |
263
|
|
|
{ |
264
|
|
|
if ($isDir) { |
265
|
|
|
$this->cacertdir = $caCert; |
266
|
|
|
} else { |
267
|
|
|
$this->cacert = $caCert; |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Set attributes for SSL communication: private SSL key. |
273
|
|
|
* |
274
|
|
|
* NB: does not work in older php/curl installs. |
275
|
|
|
* Thanks to Daniel Convissor. |
276
|
|
|
* |
277
|
|
|
* @param string $key The name of a file containing a private SSL key |
278
|
|
|
* @param string $keyPass The secret password needed to use the private SSL key |
279
|
|
|
*/ |
280
|
|
|
public function setKey($key, $keyPass) |
281
|
|
|
{ |
282
|
|
|
$this->key = $key; |
283
|
|
|
$this->keypass = $keyPass; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail |
288
|
|
|
* if the cert verification fails. |
289
|
|
|
* |
290
|
|
|
* By default, verification is enabled. |
291
|
|
|
* To specify custom SSL certificates to validate the server with, use the setCaCertificate method. |
292
|
|
|
* |
293
|
|
|
* @param bool $i enable/disable verification of peer certificate |
294
|
|
|
*/ |
295
|
93 |
|
public function setSSLVerifyPeer($i) |
296
|
|
|
{ |
297
|
93 |
|
$this->verifypeer = $i; |
298
|
93 |
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN). |
302
|
|
|
* |
303
|
|
|
* Note that support for value 1 has been removed in cURL 7.28.1 |
304
|
|
|
* |
305
|
|
|
* @param int $i Set to 1 to only the existence of a CN, not that it matches |
306
|
|
|
*/ |
307
|
93 |
|
public function setSSLVerifyHost($i) |
308
|
|
|
{ |
309
|
93 |
|
$this->verifyhost = $i; |
310
|
93 |
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Set attributes for SSL communication: SSL version to use. Best left at 0 (default value ): let cURL decide |
314
|
|
|
* |
315
|
|
|
* @param int $i |
316
|
|
|
*/ |
317
|
93 |
|
public function setSSLVersion($i) |
318
|
|
|
{ |
319
|
93 |
|
$this->sslversion = $i; |
320
|
93 |
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Set proxy info. |
324
|
|
|
* |
325
|
|
|
* NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers. |
326
|
|
|
* |
327
|
|
|
* @param string $proxyHost |
328
|
|
|
* @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS |
329
|
|
|
* @param string $proxyUsername Leave blank if proxy has public access |
330
|
|
|
* @param string $proxyPassword Leave blank if proxy has public access |
331
|
|
|
* @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM |
332
|
|
|
* to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol) |
333
|
|
|
*/ |
334
|
93 |
|
public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1) |
335
|
|
|
{ |
336
|
93 |
|
$this->proxy = $proxyHost; |
337
|
93 |
|
$this->proxyport = $proxyPort; |
|
|
|
|
338
|
93 |
|
$this->proxy_user = $proxyUsername; |
339
|
93 |
|
$this->proxy_pass = $proxyPassword; |
340
|
93 |
|
$this->proxy_authtype = $proxyAuthType; |
341
|
93 |
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Enables/disables reception of compressed xmlrpc responses. |
345
|
|
|
* |
346
|
|
|
* This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client |
347
|
|
|
* instances will enable reception of compressed content. |
348
|
|
|
* Note that enabling reception of compressed responses merely adds some standard http headers to xmlrpc requests. |
349
|
|
|
* It is up to the xmlrpc server to return compressed responses when receiving such requests. |
350
|
|
|
* |
351
|
|
|
* @param string $compMethod either 'gzip', 'deflate', 'any' or '' |
352
|
|
|
*/ |
353
|
|
|
public function setAcceptedCompression($compMethod) |
354
|
|
|
{ |
355
|
|
|
if ($compMethod == 'any') { |
356
|
|
|
$this->accepted_compression = array('gzip', 'deflate'); |
357
|
|
|
} elseif ($compMethod == false) { |
|
|
|
|
358
|
|
|
$this->accepted_compression = array(); |
359
|
|
|
} else { |
360
|
|
|
$this->accepted_compression = array($compMethod); |
361
|
|
|
} |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* Enables/disables http compression of xmlrpc request. |
366
|
|
|
* |
367
|
|
|
* This requires the "zlib" extension to be enabled in your php install. |
368
|
|
|
* Take care when sending compressed requests: servers might not support them (and automatic fallback to |
369
|
|
|
* uncompressed requests is not yet implemented). |
370
|
|
|
* |
371
|
|
|
* @param string $compMethod either 'gzip', 'deflate' or '' |
372
|
|
|
*/ |
373
|
|
|
public function setRequestCompression($compMethod) |
374
|
|
|
{ |
375
|
|
|
$this->request_compression = $compMethod; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping |
380
|
|
|
* session info outside of the xml-rpc payload). |
381
|
|
|
* |
382
|
|
|
* NB: By default cookies are sent using the 'original/netscape' format, which is also the same as the RFC 2965; |
383
|
|
|
* setting any param but name and value will turn the cookie into a 'version 1' cookie (i.e. RFC 2109 cookie) that |
384
|
|
|
* might not be fully supported by the server. Note that RFC 2109 has currently 'historic' status... |
385
|
|
|
* |
386
|
|
|
* @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or |
387
|
|
|
* separators! |
388
|
|
|
* @param string $value |
389
|
|
|
* @param string $path leave this empty unless the xml-rpc server only accepts RFC 2109 cookies |
390
|
|
|
* @param string $domain leave this empty unless the xml-rpc server only accepts RFC 2109 cookies |
391
|
|
|
* @param int $port leave this empty unless the xml-rpc server only accepts RFC 2109 cookies |
392
|
|
|
* |
393
|
|
|
* @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending |
394
|
|
|
* response not requests. We do the opposite...) |
395
|
|
|
* @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2 |
396
|
|
|
*/ |
397
|
507 |
|
public function setCookie($name, $value = '', $path = '', $domain = '', $port = null) |
398
|
|
|
{ |
399
|
507 |
|
$this->cookies[$name]['value'] = urlencode($value); |
400
|
507 |
|
if ($path || $domain || $port) { |
|
|
|
|
401
|
|
|
$this->cookies[$name]['path'] = $path; |
402
|
|
|
$this->cookies[$name]['domain'] = $domain; |
403
|
|
|
$this->cookies[$name]['port'] = $port; |
404
|
|
|
$this->cookies[$name]['version'] = 1; |
405
|
|
|
} else { |
406
|
507 |
|
$this->cookies[$name]['version'] = 0; |
407
|
|
|
} |
408
|
507 |
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Directly set cURL options, for extra flexibility (when in cURL mode). |
412
|
|
|
* |
413
|
|
|
* It allows eg. to bind client to a specific IP interface / address. |
414
|
|
|
* |
415
|
|
|
* @param array $options |
416
|
|
|
*/ |
417
|
|
|
public function setCurlOptions($options) |
418
|
|
|
{ |
419
|
|
|
$this->extracurlopts = $options; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
/** |
423
|
|
|
* @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER |
424
|
|
|
*/ |
425
|
62 |
|
public function setUseCurl($useCurlMode) |
426
|
|
|
{ |
427
|
62 |
|
$this->use_curl = $useCurlMode; |
428
|
62 |
|
} |
429
|
|
|
|
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* Set user-agent string that will be used by this client instance in http headers sent to the server. |
433
|
|
|
* |
434
|
|
|
* The default user agent string includes the name of this library and the version number. |
435
|
|
|
* |
436
|
|
|
* @param string $agentString |
437
|
|
|
*/ |
438
|
|
|
public function setUserAgent($agentString) |
439
|
|
|
{ |
440
|
|
|
$this->user_agent = $agentString; |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
/** |
444
|
|
|
* Send an xmlrpc request to the server. |
445
|
|
|
* |
446
|
|
|
* @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the |
447
|
|
|
* complete xml representation of a request. |
448
|
|
|
* When sending an array of Request objects, the client will try to make use of |
449
|
|
|
* a single 'system.multicall' xml-rpc method call to forward to the server all |
450
|
|
|
* the requests in a single HTTP round trip, unless $this->no_multicall has |
451
|
|
|
* been previously set to TRUE (see the multicall method below), in which case |
452
|
|
|
* many consecutive xmlrpc requests will be sent. The method will return an |
453
|
|
|
* array of Response objects in both cases. |
454
|
|
|
* The third variant allows to build by hand (or any other means) a complete |
455
|
|
|
* xmlrpc request message, and send it to the server. $req should be a string |
456
|
|
|
* containing the complete xml representation of the request. It is e.g. useful |
457
|
|
|
* when, for maximal speed of execution, the request is serialized into a |
458
|
|
|
* string using the native php xmlrpc functions (see http://www.php.net/xmlrpc) |
459
|
|
|
* @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply. |
460
|
|
|
* This timeout value is passed to fsockopen(). It is also used for detecting server |
461
|
|
|
* timeouts during communication (i.e. if the server does not send anything to the client |
462
|
|
|
* for $timeout seconds, the connection will be closed). |
463
|
|
|
* @param string $method valid values are 'http', 'http11' and 'https'. If left unspecified, the http protocol |
464
|
|
|
* chosen during creation of the object will be used. |
465
|
|
|
* |
466
|
|
|
* @return Response|Response[] Note that the client will always return a Response object, even if the call fails |
467
|
|
|
*/ |
468
|
586 |
|
public function send($req, $timeout = 0, $method = '') |
469
|
|
|
{ |
470
|
|
|
// if user does not specify http protocol, use native method of this client |
471
|
|
|
// (i.e. method set during call to constructor) |
472
|
586 |
|
if ($method == '') { |
473
|
79 |
|
$method = $this->method; |
474
|
79 |
|
} |
475
|
|
|
|
476
|
586 |
|
if (is_array($req)) { |
477
|
|
|
// $req is an array of Requests |
478
|
58 |
|
$r = $this->multicall($req, $timeout, $method); |
479
|
|
|
|
480
|
58 |
|
return $r; |
481
|
586 |
|
} elseif (is_string($req)) { |
482
|
26 |
|
$n = new Request(''); |
483
|
26 |
|
$n->payload = $req; |
484
|
26 |
|
$req = $n; |
485
|
26 |
|
} |
486
|
|
|
|
487
|
|
|
// where req is a Request |
488
|
586 |
|
$req->setDebug($this->debug); |
489
|
|
|
|
490
|
|
|
/// @todo we could be smarter about this and force usage of curl in scenarios where it is both available and |
491
|
|
|
/// needed, such as digest or ntlm auth. Do not attempt to use it for https if not present |
492
|
586 |
|
$useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO && |
493
|
586 |
|
($method == 'https' || $method == 'http11')); |
494
|
|
|
|
495
|
586 |
|
if ($useCurl) { |
496
|
242 |
|
$r = $this->sendPayloadCURL( |
497
|
242 |
|
$req, |
498
|
242 |
|
$this->server, |
499
|
242 |
|
$this->port, |
500
|
242 |
|
$timeout, |
501
|
242 |
|
$this->username, |
502
|
242 |
|
$this->password, |
503
|
242 |
|
$this->authtype, |
504
|
242 |
|
$this->cert, |
505
|
242 |
|
$this->certpass, |
506
|
242 |
|
$this->cacert, |
507
|
242 |
|
$this->cacertdir, |
508
|
242 |
|
$this->proxy, |
509
|
242 |
|
$this->proxyport, |
510
|
242 |
|
$this->proxy_user, |
511
|
242 |
|
$this->proxy_pass, |
512
|
242 |
|
$this->proxy_authtype, |
513
|
|
|
// bc |
514
|
242 |
|
$method == 'http11' ? 'http' : $method, |
515
|
242 |
|
$this->keepalive, |
516
|
242 |
|
$this->key, |
517
|
242 |
|
$this->keypass, |
518
|
242 |
|
$this->sslversion |
519
|
242 |
|
); |
520
|
242 |
|
} else { |
521
|
|
|
// plain 'http 1.0': default to using socket |
522
|
344 |
|
$r = $this->sendPayloadSocket( |
523
|
344 |
|
$req, |
524
|
344 |
|
$this->server, |
525
|
344 |
|
$this->port, |
526
|
344 |
|
$timeout, |
527
|
344 |
|
$this->username, |
528
|
344 |
|
$this->password, |
529
|
344 |
|
$this->authtype, |
530
|
344 |
|
$this->cert, |
531
|
344 |
|
$this->certpass, |
532
|
344 |
|
$this->cacert, |
533
|
344 |
|
$this->cacertdir, |
534
|
344 |
|
$this->proxy, |
535
|
344 |
|
$this->proxyport, |
536
|
344 |
|
$this->proxy_user, |
537
|
344 |
|
$this->proxy_pass, |
538
|
344 |
|
$this->proxy_authtype, |
539
|
344 |
|
$method, |
540
|
344 |
|
$this->key, |
541
|
344 |
|
$this->keypass, |
542
|
344 |
|
$this->sslversion |
543
|
344 |
|
); |
544
|
|
|
} |
545
|
|
|
|
546
|
586 |
|
return $r; |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
/** |
550
|
|
|
* @deprecated |
551
|
|
|
* @param Request $req |
552
|
|
|
* @param string $server |
553
|
|
|
* @param int $port |
554
|
|
|
* @param int $timeout |
555
|
|
|
* @param string $username |
556
|
|
|
* @param string $password |
557
|
|
|
* @param int $authType |
558
|
|
|
* @param string $proxyHost |
559
|
|
|
* @param int $proxyPort |
560
|
|
|
* @param string $proxyUsername |
561
|
|
|
* @param string $proxyPassword |
562
|
|
|
* @param int $proxyAuthType |
563
|
|
|
* @param string $method |
564
|
|
|
* @return Response |
565
|
|
|
*/ |
566
|
|
|
protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '', |
567
|
|
|
$authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, |
568
|
|
|
$method='http') |
569
|
|
|
{ |
570
|
|
|
return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null, |
571
|
|
|
null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method); |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
/** |
575
|
|
|
* @deprecated |
576
|
|
|
* @param Request $req |
577
|
|
|
* @param string $server |
578
|
|
|
* @param int $port |
579
|
|
|
* @param int $timeout |
580
|
|
|
* @param string $username |
581
|
|
|
* @param string $password |
582
|
|
|
* @param int $authType |
583
|
|
|
* @param string $cert |
584
|
|
|
* @param string $certPass |
585
|
|
|
* @param string $caCert |
586
|
|
|
* @param string $caCertDir |
587
|
|
|
* @param string $proxyHost |
588
|
|
|
* @param int $proxyPort |
589
|
|
|
* @param string $proxyUsername |
590
|
|
|
* @param string $proxyPassword |
591
|
|
|
* @param int $proxyAuthType |
592
|
|
|
* @param bool $keepAlive |
593
|
|
|
* @param string $key |
594
|
|
|
* @param string $keyPass |
595
|
|
|
* @param int $sslVersion |
596
|
|
|
* @return Response |
597
|
|
|
*/ |
598
|
|
|
protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '', |
599
|
|
|
$authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0, |
600
|
|
|
$proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '', |
601
|
|
|
$sslVersion = 0) |
602
|
|
|
{ |
603
|
|
|
return $this->sendPayloadCURL($req, $server, $port, $timeout, $username, |
604
|
|
|
$password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort, |
605
|
|
|
$proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion); |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
/** |
609
|
|
|
* @param Request $req |
610
|
|
|
* @param string $server |
611
|
|
|
* @param int $port |
612
|
|
|
* @param int $timeout |
613
|
|
|
* @param string $username |
614
|
|
|
* @param string $password |
615
|
|
|
* @param int $authType only value supported is 1 |
616
|
|
|
* @param string $cert |
617
|
|
|
* @param string $certPass |
618
|
|
|
* @param string $caCert |
619
|
|
|
* @param string $caCertDir |
620
|
|
|
* @param string $proxyHost |
621
|
|
|
* @param int $proxyPort |
622
|
|
|
* @param string $proxyUsername |
623
|
|
|
* @param string $proxyPassword |
624
|
|
|
* @param int $proxyAuthType only value supported is 1 |
625
|
|
|
* @param string $method 'http' (synonym for 'http10'), 'http10' or 'https' |
626
|
|
|
* @param string $key |
627
|
|
|
* @param string $keyPass @todo not implemented yet. |
628
|
|
|
* @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php |
629
|
|
|
* @return Response |
630
|
|
|
*/ |
631
|
344 |
|
protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '', |
632
|
|
|
$authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0, |
633
|
|
|
$proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method='http', $key = '', $keyPass = '', |
|
|
|
|
634
|
|
|
$sslVersion = 0) |
|
|
|
|
635
|
|
|
{ |
636
|
344 |
|
if ($port == 0) { |
637
|
342 |
|
$port = ( $method === 'https' ) ? 443 : 80; |
638
|
342 |
|
} |
639
|
|
|
|
640
|
|
|
// Only create the payload if it was not created previously |
641
|
344 |
|
if (empty($req->payload)) { |
642
|
316 |
|
$req->createPayload($this->request_charset_encoding); |
643
|
316 |
|
} |
644
|
|
|
|
645
|
344 |
|
$payload = $req->payload; |
646
|
|
|
// Deflate request body and set appropriate request headers |
647
|
344 |
|
$encodingHdr = ''; |
648
|
344 |
View Code Duplication |
if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) { |
|
|
|
|
649
|
60 |
|
if ($this->request_compression == 'gzip') { |
650
|
30 |
|
$a = @gzencode($payload); |
651
|
30 |
|
if ($a) { |
652
|
30 |
|
$payload = $a; |
653
|
30 |
|
$encodingHdr = "Content-Encoding: gzip\r\n"; |
654
|
30 |
|
} |
655
|
30 |
|
} else { |
656
|
30 |
|
$a = @gzcompress($payload); |
657
|
30 |
|
if ($a) { |
658
|
30 |
|
$payload = $a; |
659
|
30 |
|
$encodingHdr = "Content-Encoding: deflate\r\n"; |
660
|
30 |
|
} |
661
|
|
|
} |
662
|
60 |
|
} |
663
|
|
|
|
664
|
|
|
// thanks to Grant Rauscher <[email protected]> for this |
665
|
344 |
|
$credentials = ''; |
666
|
344 |
|
if ($username != '') { |
667
|
30 |
|
$credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n"; |
668
|
30 |
|
if ($authType != 1) { |
669
|
|
|
error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0'); |
670
|
|
|
} |
671
|
30 |
|
} |
672
|
|
|
|
673
|
344 |
|
$acceptedEncoding = ''; |
674
|
344 |
|
if (is_array($this->accepted_compression) && count($this->accepted_compression)) { |
675
|
62 |
|
$acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n"; |
676
|
62 |
|
} |
677
|
|
|
|
678
|
344 |
|
$proxyCredentials = ''; |
679
|
344 |
|
if ($proxyHost) { |
680
|
30 |
|
if ($proxyPort == 0) { |
681
|
|
|
$proxyPort = 8080; |
682
|
|
|
} |
683
|
30 |
|
$connectServer = $proxyHost; |
684
|
30 |
|
$connectPort = $proxyPort; |
685
|
30 |
|
$transport = 'tcp'; |
686
|
30 |
|
$uri = 'http://' . $server . ':' . $port . $this->path; |
687
|
30 |
|
if ($proxyUsername != '') { |
688
|
|
|
if ($proxyAuthType != 1) { |
689
|
|
|
error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0'); |
690
|
|
|
} |
691
|
|
|
$proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n"; |
692
|
|
|
} |
693
|
30 |
|
} else { |
694
|
314 |
|
$connectServer = $server; |
695
|
314 |
|
$connectPort = $port; |
696
|
314 |
|
$transport = ( $method === 'https' ) ? 'tls' : 'tcp'; |
697
|
314 |
|
$uri = $this->path; |
698
|
|
|
} |
699
|
|
|
|
700
|
|
|
// Cookie generation, as per rfc2965 (version 1 cookies) or |
701
|
|
|
// netscape's rules (version 0 cookies) |
702
|
344 |
|
$cookieHeader = ''; |
703
|
344 |
|
if (count($this->cookies)) { |
704
|
298 |
|
$version = ''; |
705
|
298 |
|
foreach ($this->cookies as $name => $cookie) { |
706
|
298 |
|
if ($cookie['version']) { |
707
|
|
|
$version = ' $Version="' . $cookie['version'] . '";'; |
708
|
|
|
$cookieHeader .= ' ' . $name . '="' . $cookie['value'] . '";'; |
709
|
|
|
if ($cookie['path']) { |
710
|
|
|
$cookieHeader .= ' $Path="' . $cookie['path'] . '";'; |
711
|
|
|
} |
712
|
|
|
if ($cookie['domain']) { |
713
|
|
|
$cookieHeader .= ' $Domain="' . $cookie['domain'] . '";'; |
714
|
|
|
} |
715
|
|
|
if ($cookie['port']) { |
716
|
|
|
$cookieHeader .= ' $Port="' . $cookie['port'] . '";'; |
717
|
|
|
} |
718
|
|
|
} else { |
719
|
298 |
|
$cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";"; |
720
|
|
|
} |
721
|
298 |
|
} |
722
|
298 |
|
$cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n"; |
723
|
298 |
|
} |
724
|
|
|
|
725
|
|
|
// omit port if default |
726
|
344 |
|
if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) { |
727
|
344 |
|
$port = ''; |
728
|
344 |
|
} else { |
729
|
|
|
$port = ':' . $port; |
730
|
|
|
} |
731
|
|
|
|
732
|
344 |
|
$op = 'POST ' . $uri . " HTTP/1.0\r\n" . |
733
|
344 |
|
'User-Agent: ' . $this->user_agent . "\r\n" . |
734
|
344 |
|
'Host: ' . $server . $port . "\r\n" . |
735
|
344 |
|
$credentials . |
736
|
344 |
|
$proxyCredentials . |
737
|
344 |
|
$acceptedEncoding . |
738
|
344 |
|
$encodingHdr . |
739
|
344 |
|
'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" . |
740
|
344 |
|
$cookieHeader . |
741
|
344 |
|
'Content-Type: ' . $req->content_type . "\r\nContent-Length: " . |
742
|
344 |
|
strlen($payload) . "\r\n\r\n" . |
743
|
344 |
|
$payload; |
744
|
|
|
|
745
|
344 |
|
if ($this->debug > 1) { |
746
|
|
|
Logger::instance()->debugMessage("---SENDING---\n$op\n---END---"); |
747
|
|
|
} |
748
|
|
|
|
749
|
344 |
|
$contextOptions = array(); |
750
|
344 |
|
if ($method == 'https') { |
751
|
30 |
|
if ($cert != '') { |
752
|
|
|
$contextOptions['ssl']['local_cert'] = $cert; |
753
|
|
|
if ($certPass != '') { |
754
|
|
|
$contextOptions['ssl']['passphrase'] = $certPass; |
755
|
|
|
} |
756
|
|
|
} |
757
|
30 |
|
if ($caCert != '') { |
758
|
|
|
$contextOptions['ssl']['cafile'] = $caCert; |
759
|
|
|
} |
760
|
30 |
|
if ($caCertDir != '') { |
761
|
|
|
$contextOptions['ssl']['capath'] = $caCertDir; |
762
|
|
|
} |
763
|
30 |
|
if ($key != '') { |
764
|
|
|
$contextOptions['ssl']['local_pk'] = $key; |
765
|
|
|
} |
766
|
30 |
|
$contextOptions['ssl']['verify_peer'] = $this->verifypeer; |
767
|
30 |
|
$contextOptions['ssl']['verify_peer_name'] = $this->verifypeer; |
768
|
30 |
|
} |
769
|
344 |
|
$context = stream_context_create($contextOptions); |
770
|
|
|
|
771
|
344 |
|
if ($timeout <= 0) { |
772
|
44 |
|
$connectTimeout = ini_get('default_socket_timeout'); |
773
|
44 |
|
} else { |
774
|
300 |
|
$connectTimeout = $timeout; |
775
|
|
|
} |
776
|
|
|
|
777
|
344 |
|
$this->errno = 0; |
778
|
344 |
|
$this->errstr = ''; |
779
|
|
|
|
780
|
344 |
|
$fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout, |
781
|
344 |
|
STREAM_CLIENT_CONNECT, $context); |
782
|
344 |
|
if ($fp) { |
783
|
343 |
|
if ($timeout > 0) { |
784
|
299 |
|
stream_set_timeout($fp, $timeout); |
785
|
299 |
|
} |
786
|
343 |
|
} else { |
787
|
1 |
|
if ($this->errstr == '') { |
788
|
|
|
$err = error_get_last(); |
789
|
|
|
$this->errstr = $err['message']; |
790
|
|
|
} |
791
|
1 |
|
$this->errstr = 'Connect error: ' . $this->errstr; |
792
|
1 |
|
$r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')'); |
793
|
|
|
|
794
|
1 |
|
return $r; |
795
|
|
|
} |
796
|
|
|
|
797
|
343 |
|
if (!fputs($fp, $op, strlen($op))) { |
798
|
|
|
fclose($fp); |
799
|
|
|
$this->errstr = 'Write error'; |
800
|
|
|
$r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr); |
801
|
|
|
|
802
|
|
|
return $r; |
803
|
|
|
} |
804
|
|
|
|
805
|
|
|
// G. Giunta 2005/10/24: close socket before parsing. |
806
|
|
|
// should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects) |
807
|
343 |
|
$ipd = ''; |
808
|
|
|
do { |
809
|
|
|
// shall we check for $data === FALSE? |
810
|
|
|
// as per the manual, it signals an error |
811
|
343 |
|
$ipd .= fread($fp, 32768); |
812
|
343 |
|
} while (!feof($fp)); |
813
|
343 |
|
fclose($fp); |
814
|
|
|
|
815
|
343 |
|
$r = $req->parseResponse($ipd, false, $this->return_type); |
816
|
|
|
|
817
|
343 |
|
return $r; |
818
|
|
|
} |
819
|
|
|
|
820
|
|
|
/** |
821
|
|
|
* Contributed by Justin Miller <[email protected]> |
822
|
|
|
* Requires curl to be built into PHP |
823
|
|
|
* NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers! |
824
|
|
|
* |
825
|
|
|
* @param Request $req |
826
|
|
|
* @param string $server |
827
|
|
|
* @param int $port |
828
|
|
|
* @param int $timeout |
829
|
|
|
* @param string $username |
830
|
|
|
* @param string $password |
831
|
|
|
* @param int $authType |
832
|
|
|
* @param string $cert |
833
|
|
|
* @param string $certPass |
834
|
|
|
* @param string $caCert |
835
|
|
|
* @param string $caCertDir |
836
|
|
|
* @param string $proxyHost |
837
|
|
|
* @param int $proxyPort |
838
|
|
|
* @param string $proxyUsername |
839
|
|
|
* @param string $proxyPassword |
840
|
|
|
* @param int $proxyAuthType |
841
|
|
|
* @param string $method 'http' (let curl decide), 'http10', 'http11' or 'https' |
842
|
|
|
* @param bool $keepAlive |
843
|
|
|
* @param string $key |
844
|
|
|
* @param string $keyPass |
845
|
|
|
* @param int $sslVersion |
846
|
|
|
* @return Response |
847
|
|
|
*/ |
848
|
242 |
|
protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '', |
849
|
|
|
$authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0, |
850
|
|
|
$proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '', |
851
|
|
|
$keyPass = '', $sslVersion = 0) |
852
|
|
|
{ |
853
|
242 |
|
if (!function_exists('curl_init')) { |
854
|
|
|
$this->errstr = 'CURL unavailable on this install'; |
855
|
|
|
return new Response(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']); |
856
|
|
|
} |
857
|
242 |
|
if ($method == 'https') { |
858
|
60 |
|
if (($info = curl_version()) && |
859
|
60 |
|
((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version']))) |
860
|
60 |
|
) { |
861
|
|
|
$this->errstr = 'SSL unavailable on this install'; |
862
|
|
|
return new Response(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']); |
863
|
|
|
} |
864
|
60 |
|
} |
865
|
|
|
|
866
|
242 |
|
if ($port == 0) { |
867
|
241 |
|
if (in_array($method, array('http', 'http10', 'http11'))) { |
868
|
181 |
|
$port = 80; |
869
|
181 |
|
} else { |
870
|
60 |
|
$port = 443; |
871
|
|
|
} |
872
|
241 |
|
} |
873
|
|
|
|
874
|
|
|
// Only create the payload if it was not created previously |
875
|
242 |
|
if (empty($req->payload)) { |
876
|
226 |
|
$req->createPayload($this->request_charset_encoding); |
877
|
226 |
|
} |
878
|
|
|
|
879
|
|
|
// Deflate request body and set appropriate request headers |
880
|
242 |
|
$payload = $req->payload; |
881
|
242 |
View Code Duplication |
if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) { |
|
|
|
|
882
|
60 |
|
if ($this->request_compression == 'gzip') { |
883
|
30 |
|
$a = @gzencode($payload); |
884
|
30 |
|
if ($a) { |
885
|
30 |
|
$payload = $a; |
886
|
30 |
|
$encodingHdr = 'Content-Encoding: gzip'; |
887
|
30 |
|
} |
888
|
30 |
|
} else { |
889
|
30 |
|
$a = @gzcompress($payload); |
890
|
30 |
|
if ($a) { |
891
|
30 |
|
$payload = $a; |
892
|
30 |
|
$encodingHdr = 'Content-Encoding: deflate'; |
893
|
30 |
|
} |
894
|
|
|
} |
895
|
60 |
|
} else { |
896
|
182 |
|
$encodingHdr = ''; |
897
|
|
|
} |
898
|
|
|
|
899
|
242 |
|
if ($this->debug > 1) { |
900
|
|
|
Logger::instance()->debugMessage("---SENDING---\n$payload\n---END---"); |
901
|
|
|
} |
902
|
|
|
|
903
|
242 |
|
if (!$keepAlive || !$this->xmlrpc_curl_handle) { |
904
|
242 |
|
if ($method == 'http11' || $method == 'http10') { |
905
|
30 |
|
$protocol = 'http'; |
906
|
30 |
|
} else { |
907
|
212 |
|
$protocol = $method; |
908
|
|
|
} |
909
|
242 |
|
$curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path); |
910
|
242 |
|
if ($keepAlive) { |
911
|
92 |
|
$this->xmlrpc_curl_handle = $curl; |
912
|
92 |
|
} |
913
|
242 |
|
} else { |
914
|
34 |
|
$curl = $this->xmlrpc_curl_handle; |
915
|
|
|
} |
916
|
|
|
|
917
|
|
|
// results into variable |
918
|
242 |
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
919
|
|
|
|
920
|
242 |
|
if ($this->debug > 1) { |
921
|
|
|
curl_setopt($curl, CURLOPT_VERBOSE, true); |
922
|
|
|
/// @todo allow callers to redirect curlopt_stderr to some stream which can be buffered |
923
|
|
|
} |
924
|
242 |
|
curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent); |
925
|
|
|
// required for XMLRPC: post the data |
926
|
242 |
|
curl_setopt($curl, CURLOPT_POST, 1); |
927
|
|
|
// the data |
928
|
242 |
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $payload); |
929
|
|
|
|
930
|
|
|
// return the header too |
931
|
242 |
|
curl_setopt($curl, CURLOPT_HEADER, 1); |
932
|
|
|
|
933
|
|
|
// NB: if we set an empty string, CURL will add http header indicating |
934
|
|
|
// ALL methods it is supporting. This is possibly a better option than |
935
|
|
|
// letting the user tell what curl can / cannot do... |
936
|
242 |
|
if (is_array($this->accepted_compression) && count($this->accepted_compression)) { |
937
|
|
|
//curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression)); |
|
|
|
|
938
|
|
|
// empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?) |
939
|
61 |
|
if (count($this->accepted_compression) == 1) { |
940
|
60 |
|
curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]); |
941
|
60 |
|
} else { |
942
|
1 |
|
curl_setopt($curl, CURLOPT_ENCODING, ''); |
943
|
|
|
} |
944
|
61 |
|
} |
945
|
|
|
// extra headers |
946
|
242 |
|
$headers = array('Content-Type: ' . $req->content_type, 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings)); |
947
|
|
|
// if no keepalive is wanted, let the server know it in advance |
948
|
242 |
|
if (!$keepAlive) { |
949
|
150 |
|
$headers[] = 'Connection: close'; |
950
|
150 |
|
} |
951
|
|
|
// request compression header |
952
|
242 |
|
if ($encodingHdr) { |
953
|
60 |
|
$headers[] = $encodingHdr; |
|
|
|
|
954
|
60 |
|
} |
955
|
|
|
|
956
|
|
|
// Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST |
957
|
|
|
// size exceeds 1025 bytes, apparently) |
958
|
242 |
|
$headers[] = 'Expect:'; |
959
|
|
|
|
960
|
242 |
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); |
961
|
|
|
// timeout is borked |
962
|
242 |
|
if ($timeout) { |
963
|
210 |
|
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1); |
964
|
210 |
|
} |
965
|
|
|
|
966
|
242 |
|
if ($method == 'http10') { |
967
|
30 |
|
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); |
968
|
242 |
|
} elseif ($method == 'http11') { |
969
|
|
|
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); |
970
|
|
|
} |
971
|
|
|
|
972
|
242 |
View Code Duplication |
if ($username && $password) { |
|
|
|
|
973
|
30 |
|
curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password); |
974
|
30 |
|
if (defined('CURLOPT_HTTPAUTH')) { |
975
|
30 |
|
curl_setopt($curl, CURLOPT_HTTPAUTH, $authType); |
976
|
30 |
|
} elseif ($authType != 1) { |
977
|
|
|
error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install'); |
978
|
|
|
} |
979
|
30 |
|
} |
980
|
|
|
|
981
|
242 |
|
if ($method == 'https') { |
982
|
|
|
// set cert file |
983
|
60 |
|
if ($cert) { |
984
|
|
|
curl_setopt($curl, CURLOPT_SSLCERT, $cert); |
985
|
|
|
} |
986
|
|
|
// set cert password |
987
|
60 |
|
if ($certPass) { |
988
|
|
|
curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass); |
989
|
|
|
} |
990
|
|
|
// whether to verify remote host's cert |
991
|
60 |
|
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer); |
992
|
|
|
// set ca certificates file/dir |
993
|
60 |
|
if ($caCert) { |
994
|
|
|
curl_setopt($curl, CURLOPT_CAINFO, $caCert); |
995
|
|
|
} |
996
|
60 |
|
if ($caCertDir) { |
997
|
|
|
curl_setopt($curl, CURLOPT_CAPATH, $caCertDir); |
998
|
|
|
} |
999
|
|
|
// set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?) |
1000
|
60 |
|
if ($key) { |
1001
|
|
|
curl_setopt($curl, CURLOPT_SSLKEY, $key); |
1002
|
|
|
} |
1003
|
|
|
// set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?) |
1004
|
60 |
|
if ($keyPass) { |
1005
|
|
|
curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass); |
1006
|
|
|
} |
1007
|
|
|
// whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used |
1008
|
60 |
|
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost); |
1009
|
|
|
// allow usage of different SSL versions |
1010
|
60 |
|
curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion); |
1011
|
60 |
|
} |
1012
|
|
|
|
1013
|
|
|
// proxy info |
1014
|
242 |
|
if ($proxyHost) { |
1015
|
60 |
|
if ($proxyPort == 0) { |
1016
|
|
|
$proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080 |
1017
|
|
|
} |
1018
|
60 |
|
curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort); |
1019
|
60 |
View Code Duplication |
if ($proxyUsername) { |
|
|
|
|
1020
|
|
|
curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUsername . ':' . $proxyPassword); |
1021
|
|
|
if (defined('CURLOPT_PROXYAUTH')) { |
1022
|
|
|
curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType); |
1023
|
|
|
} elseif ($proxyAuthType != 1) { |
1024
|
|
|
error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install'); |
1025
|
|
|
} |
1026
|
|
|
} |
1027
|
60 |
|
} |
1028
|
|
|
|
1029
|
|
|
// NB: should we build cookie http headers by hand rather than let CURL do it? |
1030
|
|
|
// the following code does not honour 'expires', 'path' and 'domain' cookie attributes |
1031
|
|
|
// set to client obj the the user... |
1032
|
242 |
|
if (count($this->cookies)) { |
1033
|
209 |
|
$cookieHeader = ''; |
1034
|
209 |
|
foreach ($this->cookies as $name => $cookie) { |
1035
|
209 |
|
$cookieHeader .= $name . '=' . $cookie['value'] . '; '; |
1036
|
209 |
|
} |
1037
|
209 |
|
curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2)); |
1038
|
209 |
|
} |
1039
|
|
|
|
1040
|
242 |
|
foreach ($this->extracurlopts as $opt => $val) { |
1041
|
|
|
curl_setopt($curl, $opt, $val); |
1042
|
242 |
|
} |
1043
|
|
|
|
1044
|
242 |
|
$result = curl_exec($curl); |
1045
|
|
|
|
1046
|
242 |
|
if ($this->debug > 1) { |
1047
|
|
|
$message = "---CURL INFO---\n"; |
1048
|
|
|
foreach (curl_getinfo($curl) as $name => $val) { |
1049
|
|
|
if (is_array($val)) { |
1050
|
|
|
$val = implode("\n", $val); |
1051
|
|
|
} |
1052
|
|
|
$message .= $name . ': ' . $val . "\n"; |
1053
|
|
|
} |
1054
|
|
|
$message .= '---END---'; |
1055
|
|
|
Logger::instance()->debugMessage($message); |
1056
|
|
|
} |
1057
|
|
|
|
1058
|
242 |
|
if (!$result) { |
1059
|
|
|
/// @todo we should use a better check here - what if we get back '' or '0'? |
1060
|
|
|
|
1061
|
1 |
|
$this->errstr = 'no response'; |
1062
|
1 |
|
$resp = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl)); |
1063
|
1 |
|
curl_close($curl); |
1064
|
1 |
|
if ($keepAlive) { |
1065
|
1 |
|
$this->xmlrpc_curl_handle = null; |
1066
|
1 |
|
} |
1067
|
1 |
|
} else { |
1068
|
242 |
|
if (!$keepAlive) { |
1069
|
150 |
|
curl_close($curl); |
1070
|
150 |
|
} |
1071
|
242 |
|
$resp = $req->parseResponse($result, true, $this->return_type); |
1072
|
|
|
// if we got back a 302, we can not reuse the curl handle for later calls |
1073
|
242 |
|
if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) { |
1074
|
|
|
curl_close($curl); |
1075
|
|
|
$this->xmlrpc_curl_handle = null; |
1076
|
|
|
} |
1077
|
|
|
} |
1078
|
|
|
|
1079
|
242 |
|
return $resp; |
1080
|
|
|
} |
1081
|
|
|
|
1082
|
|
|
/** |
1083
|
|
|
* Send an array of requests and return an array of responses. |
1084
|
|
|
* |
1085
|
|
|
* Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method |
1086
|
|
|
* system.multicall, and revert to sending many successive calls in case of failure. |
1087
|
|
|
* This failure is also stored in $this->no_multicall for subsequent calls. |
1088
|
|
|
* Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported, |
1089
|
|
|
* so there is no way to reliably distinguish between that and a temporary failure. |
1090
|
|
|
* If you are sure that server supports multicall and do not want to fallback to using many single calls, set the |
1091
|
|
|
* fourth parameter to FALSE. |
1092
|
|
|
* |
1093
|
|
|
* NB: trying to shoehorn extra functionality into existing syntax has resulted |
1094
|
|
|
* in pretty much convoluted code... |
1095
|
|
|
* |
1096
|
|
|
* @param Request[] $reqs an array of Request objects |
1097
|
|
|
* @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method |
1098
|
|
|
* @param string $method the http protocol variant to be used. See the details in the docs for the send() method |
1099
|
|
|
* @param boolean fallback When true, upon receiving an error during multicall, multiple single calls will be |
1100
|
|
|
* attempted |
1101
|
|
|
* |
1102
|
|
|
* @return Response[] |
1103
|
|
|
*/ |
1104
|
58 |
|
public function multicall($reqs, $timeout = 0, $method = '', $fallback = true) |
1105
|
|
|
{ |
1106
|
58 |
|
if ($method == '') { |
1107
|
|
|
$method = $this->method; |
1108
|
|
|
} |
1109
|
58 |
|
if (!$this->no_multicall) { |
1110
|
39 |
|
$results = $this->_try_multicall($reqs, $timeout, $method); |
1111
|
39 |
|
if (is_array($results)) { |
1112
|
|
|
// System.multicall succeeded |
1113
|
39 |
|
return $results; |
1114
|
|
|
} else { |
1115
|
|
|
// either system.multicall is unsupported by server, |
1116
|
|
|
// or call failed for some other reason. |
1117
|
|
View Code Duplication |
if ($fallback) { |
|
|
|
|
1118
|
|
|
// Don't try it next time... |
1119
|
|
|
$this->no_multicall = true; |
1120
|
|
|
} else { |
1121
|
|
|
if (is_a($results, '\PhpXmlRpc\Response')) { |
1122
|
|
|
$result = $results; |
1123
|
|
|
} else { |
1124
|
|
|
$result = new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], PhpXmlRpc::$xmlrpcstr['multicall_error']); |
1125
|
|
|
} |
1126
|
|
|
} |
1127
|
|
|
} |
1128
|
|
|
} else { |
1129
|
|
|
// override fallback, in case careless user tries to do two |
1130
|
|
|
// opposite things at the same time |
1131
|
20 |
|
$fallback = true; |
1132
|
|
|
} |
1133
|
|
|
|
1134
|
20 |
|
$results = array(); |
1135
|
20 |
|
if ($fallback) { |
1136
|
|
|
// system.multicall is (probably) unsupported by server: |
1137
|
|
|
// emulate multicall via multiple requests |
1138
|
20 |
|
foreach ($reqs as $req) { |
1139
|
20 |
|
$results[] = $this->send($req, $timeout, $method); |
1140
|
20 |
|
} |
1141
|
20 |
|
} else { |
1142
|
|
|
// user does NOT want to fallback on many single calls: |
1143
|
|
|
// since we should always return an array of responses, |
1144
|
|
|
// return an array with the same error repeated n times |
1145
|
|
|
foreach ($reqs as $req) { |
1146
|
|
|
$results[] = $result; |
|
|
|
|
1147
|
|
|
} |
1148
|
|
|
} |
1149
|
|
|
|
1150
|
20 |
|
return $results; |
1151
|
|
|
} |
1152
|
|
|
|
1153
|
|
|
/** |
1154
|
|
|
* Attempt to boxcar $reqs via system.multicall. |
1155
|
|
|
* |
1156
|
|
|
* Returns either an array of Response, a single error Response or false (when received response does not respect |
1157
|
|
|
* valid multicall syntax). |
1158
|
|
|
* |
1159
|
|
|
* @param Request[] $reqs |
1160
|
|
|
* @param int $timeout |
1161
|
|
|
* @param string $method |
1162
|
|
|
* @return Response[]|bool|mixed|Response |
1163
|
|
|
*/ |
1164
|
39 |
|
private function _try_multicall($reqs, $timeout, $method) |
1165
|
|
|
{ |
1166
|
|
|
// Construct multicall request |
1167
|
39 |
|
$calls = array(); |
1168
|
39 |
|
foreach ($reqs as $req) { |
1169
|
39 |
|
$call['methodName'] = new Value($req->method(), 'string'); |
|
|
|
|
1170
|
39 |
|
$numParams = $req->getNumParams(); |
1171
|
39 |
|
$params = array(); |
1172
|
39 |
|
for ($i = 0; $i < $numParams; $i++) { |
1173
|
39 |
|
$params[$i] = $req->getParam($i); |
1174
|
39 |
|
} |
1175
|
39 |
|
$call['params'] = new Value($params, 'array'); |
|
|
|
|
1176
|
39 |
|
$calls[] = new Value($call, 'struct'); |
|
|
|
|
1177
|
39 |
|
} |
1178
|
39 |
|
$multiCall = new Request('system.multicall'); |
1179
|
39 |
|
$multiCall->addParam(new Value($calls, 'array')); |
|
|
|
|
1180
|
|
|
|
1181
|
|
|
// Attempt RPC call |
1182
|
39 |
|
$result = $this->send($multiCall, $timeout, $method); |
1183
|
|
|
|
1184
|
39 |
|
if ($result->faultCode() != 0) { |
1185
|
|
|
// call to system.multicall failed |
1186
|
|
|
return $result; |
1187
|
|
|
} |
1188
|
|
|
|
1189
|
|
|
// Unpack responses. |
1190
|
39 |
|
$rets = $result->value(); |
1191
|
|
|
|
1192
|
39 |
|
if ($this->return_type == 'xml') { |
1193
|
|
|
return $rets; |
1194
|
39 |
|
} elseif ($this->return_type == 'phpvals') { |
1195
|
|
|
/// @todo test this code branch... |
1196
|
20 |
|
$rets = $result->value(); |
1197
|
20 |
|
if (!is_array($rets)) { |
1198
|
|
|
return false; // bad return type from system.multicall |
1199
|
|
|
} |
1200
|
20 |
|
$numRets = count($rets); |
1201
|
20 |
|
if ($numRets != count($reqs)) { |
1202
|
|
|
return false; // wrong number of return values. |
1203
|
|
|
} |
1204
|
|
|
|
1205
|
20 |
|
$response = array(); |
1206
|
20 |
|
for ($i = 0; $i < $numRets; $i++) { |
1207
|
20 |
|
$val = $rets[$i]; |
1208
|
20 |
|
if (!is_array($val)) { |
1209
|
|
|
return false; |
1210
|
|
|
} |
1211
|
20 |
|
switch (count($val)) { |
1212
|
20 |
|
case 1: |
1213
|
20 |
|
if (!isset($val[0])) { |
1214
|
|
|
return false; // Bad value |
1215
|
|
|
} |
1216
|
|
|
// Normal return value |
1217
|
20 |
|
$response[$i] = new Response($val[0], 0, '', 'phpvals'); |
1218
|
20 |
|
break; |
1219
|
20 |
|
case 2: |
1220
|
|
|
/// @todo remove usage of @: it is apparently quite slow |
1221
|
20 |
|
$code = @$val['faultCode']; |
1222
|
20 |
|
if (!is_int($code)) { |
1223
|
|
|
return false; |
1224
|
|
|
} |
1225
|
20 |
|
$str = @$val['faultString']; |
1226
|
20 |
|
if (!is_string($str)) { |
1227
|
|
|
return false; |
1228
|
|
|
} |
1229
|
20 |
|
$response[$i] = new Response(0, $code, $str); |
1230
|
20 |
|
break; |
1231
|
|
|
default: |
1232
|
|
|
return false; |
1233
|
20 |
|
} |
1234
|
20 |
|
} |
1235
|
|
|
|
1236
|
20 |
|
return $response; |
1237
|
|
|
} else { |
1238
|
|
|
// return type == 'xmlrpcvals' |
1239
|
|
|
|
1240
|
20 |
|
$rets = $result->value(); |
1241
|
20 |
|
if ($rets->kindOf() != 'array') { |
1242
|
|
|
return false; // bad return type from system.multicall |
1243
|
|
|
} |
1244
|
20 |
|
$numRets = $rets->count(); |
1245
|
20 |
|
if ($numRets != count($reqs)) { |
1246
|
|
|
return false; // wrong number of return values. |
1247
|
|
|
} |
1248
|
|
|
|
1249
|
20 |
|
$response = array(); |
1250
|
20 |
|
foreach($rets as $val) { |
1251
|
20 |
|
switch ($val->kindOf()) { |
1252
|
20 |
|
case 'array': |
1253
|
20 |
|
if ($val->count() != 1) { |
1254
|
|
|
return false; // Bad value |
1255
|
|
|
} |
1256
|
|
|
// Normal return value |
1257
|
20 |
|
$response[] = new Response($val[0]); |
1258
|
20 |
|
break; |
1259
|
20 |
|
case 'struct': |
1260
|
20 |
|
$code = $val['faultCode']; |
1261
|
|
|
/** @var Value $code */ |
1262
|
20 |
|
if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') { |
1263
|
|
|
return false; |
1264
|
|
|
} |
1265
|
20 |
|
$str = $val['faultString']; |
1266
|
|
|
/** @var Value $str */ |
1267
|
20 |
|
if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') { |
1268
|
|
|
return false; |
1269
|
|
|
} |
1270
|
20 |
|
$response[] = new Response(0, $code->scalarval(), $str->scalarval()); |
1271
|
20 |
|
break; |
1272
|
|
|
default: |
1273
|
|
|
return false; |
1274
|
20 |
|
} |
1275
|
20 |
|
} |
1276
|
|
|
|
1277
|
20 |
|
return $response; |
1278
|
|
|
} |
1279
|
|
|
} |
1280
|
|
|
} |
1281
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.