Completed
Push — master ( eccdf7...f1de52 )
by Mario
02:37
created

xmlrpcval   C

Complexity

Total Complexity 76

Size/Duplication

Total Lines 493
Duplicated Lines 8.11 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 40
loc 493
rs 5.488
c 0
b 0
f 0
wmc 76
lcom 1
cbo 0

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like xmlrpcval often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use xmlrpcval, and based on these observations, apply Extract Interface, too.

1
<?php
2
// by Edd Dumbill (C) 1999-2002
3
// <[email protected]>
4
// $Id: xmlrpc.inc,v 1.174 2009/03/16 19:36:38 ggiunta Exp $
5
6
// Copyright (c) 1999,2000,2002 Edd Dumbill.
7
// All rights reserved.
8
//
9
// Redistribution and use in source and binary forms, with or without
10
// modification, are permitted provided that the following conditions
11
// are met:
12
//
13
//    * Redistributions of source code must retain the above copyright
14
//      notice, this list of conditions and the following disclaimer.
15
//
16
//    * Redistributions in binary form must reproduce the above
17
//      copyright notice, this list of conditions and the following
18
//      disclaimer in the documentation and/or other materials provided
19
//      with the distribution.
20
//
21
//    * Neither the name of the "XML-RPC for PHP" nor the names of its
22
//      contributors may be used to endorse or promote products derived
23
//      from this software without specific prior written permission.
24
//
25
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29
// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36
// OF THE POSSIBILITY OF SUCH DAMAGE.
37
38
	if(!function_exists('xml_parser_create'))
39
	{
40
		// For PHP 4 onward, XML functionality is always compiled-in on windows:
41
		// no more need to dl-open it. It might have been compiled out on *nix...
42
		if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN'))
43
		{
44
			dl('xml.so');
45
		}
46
	}
47
48
	// G. Giunta 2005/01/29: declare global these variables,
49
	// so that xmlrpc.inc will work even if included from within a function
50
	// Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used.
51
	$GLOBALS['xmlrpcI4']='i4';
52
	$GLOBALS['xmlrpcInt']='int';
53
	$GLOBALS['xmlrpcBoolean']='boolean';
54
	$GLOBALS['xmlrpcDouble']='double';
55
	$GLOBALS['xmlrpcString']='string';
56
	$GLOBALS['xmlrpcDateTime']='dateTime.iso8601';
57
	$GLOBALS['xmlrpcBase64']='base64';
58
	$GLOBALS['xmlrpcArray']='array';
59
	$GLOBALS['xmlrpcStruct']='struct';
60
	$GLOBALS['xmlrpcValue']='undefined';
61
62
	$GLOBALS['xmlrpcTypes']=array(
63
		$GLOBALS['xmlrpcI4']       => 1,
64
		$GLOBALS['xmlrpcInt']      => 1,
65
		$GLOBALS['xmlrpcBoolean']  => 1,
66
		$GLOBALS['xmlrpcString']   => 1,
67
		$GLOBALS['xmlrpcDouble']   => 1,
68
		$GLOBALS['xmlrpcDateTime'] => 1,
69
		$GLOBALS['xmlrpcBase64']   => 1,
70
		$GLOBALS['xmlrpcArray']    => 2,
71
		$GLOBALS['xmlrpcStruct']   => 3
72
	);
73
74
	$GLOBALS['xmlrpc_valid_parents'] = array(
75
		'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
76
		'BOOLEAN' => array('VALUE'),
77
		'I4' => array('VALUE'),
78
		'INT' => array('VALUE'),
79
		'STRING' => array('VALUE'),
80
		'DOUBLE' => array('VALUE'),
81
		'DATETIME.ISO8601' => array('VALUE'),
82
		'BASE64' => array('VALUE'),
83
		'MEMBER' => array('STRUCT'),
84
		'NAME' => array('MEMBER'),
85
		'DATA' => array('ARRAY'),
86
		'ARRAY' => array('VALUE'),
87
		'STRUCT' => array('VALUE'),
88
		'PARAM' => array('PARAMS'),
89
		'METHODNAME' => array('METHODCALL'),
90
		'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
91
		'FAULT' => array('METHODRESPONSE'),
92
		'NIL' => array('VALUE'), // only used when extension activated
93
		'EX:NIL' => array('VALUE') // only used when extension activated
94
	);
95
96
	// define extra types for supporting NULL (useful for json or <NIL/>)
97
	$GLOBALS['xmlrpcNull']='null';
98
	$GLOBALS['xmlrpcTypes']['null']=1;
99
100
	// Not in use anymore since 2.0. Shall we remove it?
101
	/// @deprecated
102
	$GLOBALS['xmlEntities']=array(
103
		'amp'  => '&',
104
		'quot' => '"',
105
		'lt'   => '<',
106
		'gt'   => '>',
107
		'apos' => "'"
108
	);
109
110
	// tables used for transcoding different charsets into us-ascii xml
111
112
	$GLOBALS['xml_iso88591_Entities']=array();
113
	$GLOBALS['xml_iso88591_Entities']['in'] = array();
114
	$GLOBALS['xml_iso88591_Entities']['out'] = array();
115
	for ($i = 0; $i < 32; $i++)
116
	{
117
		$GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
118
		$GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
119
	}
120
	for ($i = 160; $i < 256; $i++)
121
	{
122
		$GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
123
		$GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
124
	}
125
126
	/// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159?
127
	/// These will NOT be present in true ISO-8859-1, but will save the unwary
128
	/// windows user from sending junk (though no luck when reciving them...)
129
  /*
130
	$GLOBALS['xml_cp1252_Entities']=array();
131
	for ($i = 128; $i < 160; $i++)
132
	{
133
		$GLOBALS['xml_cp1252_Entities']['in'][] = chr($i);
134
	}
135
	$GLOBALS['xml_cp1252_Entities']['out'] = array(
136
		'&#x20AC;', '?',        '&#x201A;', '&#x0192;',
137
		'&#x201E;', '&#x2026;', '&#x2020;', '&#x2021;',
138
		'&#x02C6;', '&#x2030;', '&#x0160;', '&#x2039;',
139
		'&#x0152;', '?',        '&#x017D;', '?',
140
		'?',        '&#x2018;', '&#x2019;', '&#x201C;',
141
		'&#x201D;', '&#x2022;', '&#x2013;', '&#x2014;',
142
		'&#x02DC;', '&#x2122;', '&#x0161;', '&#x203A;',
143
		'&#x0153;', '?',        '&#x017E;', '&#x0178;'
144
	);
145
  */
146
147
	$GLOBALS['xmlrpcerr'] = array(
148
	'unknown_method'=>1,
149
	'invalid_return'=>2,
150
	'incorrect_params'=>3,
151
	'introspect_unknown'=>4,
152
	'http_error'=>5,
153
	'no_data'=>6,
154
	'no_ssl'=>7,
155
	'curl_fail'=>8,
156
	'invalid_request'=>15,
157
	'no_curl'=>16,
158
	'server_error'=>17,
159
	'multicall_error'=>18,
160
	'multicall_notstruct'=>9,
161
	'multicall_nomethod'=>10,
162
	'multicall_notstring'=>11,
163
	'multicall_recursion'=>12,
164
	'multicall_noparams'=>13,
165
	'multicall_notarray'=>14,
166
167
	'cannot_decompress'=>103,
168
	'decompress_fail'=>104,
169
	'dechunk_fail'=>105,
170
	'server_cannot_decompress'=>106,
171
	'server_decompress_fail'=>107
172
	);
173
174
	$GLOBALS['xmlrpcstr'] = array(
175
	'unknown_method'=>'Unknown method',
176
	'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload',
177
	'incorrect_params'=>'Incorrect parameters passed to method',
178
	'introspect_unknown'=>"Can't introspect: method unknown",
179
	'http_error'=>"Didn't receive 200 OK from remote server.",
180
	'no_data'=>'No data received from server.',
181
	'no_ssl'=>'No SSL support compiled in.',
182
	'curl_fail'=>'CURL error',
183
	'invalid_request'=>'Invalid request payload',
184
	'no_curl'=>'No CURL support compiled in.',
185
	'server_error'=>'Internal server error',
186
	'multicall_error'=>'Received from server invalid multicall response',
187
	'multicall_notstruct'=>'system.multicall expected struct',
188
	'multicall_nomethod'=>'missing methodName',
189
	'multicall_notstring'=>'methodName is not a string',
190
	'multicall_recursion'=>'recursive system.multicall forbidden',
191
	'multicall_noparams'=>'missing params',
192
	'multicall_notarray'=>'params is not an array',
193
194
	'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress',
195
	'decompress_fail'=>'Received from server invalid compressed HTTP',
196
	'dechunk_fail'=>'Received from server invalid chunked HTTP',
197
	'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress',
198
	'server_decompress_fail'=>'Received from client invalid compressed HTTP request'
199
	);
200
201
	// The charset encoding used by the server for received messages and
202
	// by the client for received responses when received charset cannot be determined
203
	// or is not supported
204
	$GLOBALS['xmlrpc_defencoding']='UTF-8';
205
206
	// The encoding used internally by PHP.
207
	// String values received as xml will be converted to this, and php strings will be converted to xml
208
	// as if having been coded with this
209
	$GLOBALS['xmlrpc_internalencoding']='ISO-8859-1';
210
211
	$GLOBALS['xmlrpcName']='XML-RPC for PHP';
212
	$GLOBALS['xmlrpcVersion']='3.0.0.beta';
213
214
	// let user errors start at 800
215
	$GLOBALS['xmlrpcerruser']=800;
216
	// let XML parse errors start at 100
217
	$GLOBALS['xmlrpcerrxml']=100;
218
219
	// formulate backslashes for escaping regexp
220
	// Not in use anymore since 2.0. Shall we remove it?
221
	/// @deprecated
222
	$GLOBALS['xmlrpc_backslash']=chr(92).chr(92);
223
224
	// set to TRUE to enable correct decoding of <NIL/> and <EX:NIL/> values
225
	$GLOBALS['xmlrpc_null_extension']=false;
226
227
	// set to TRUE to enable encoding of php NULL values to <EX:NIL/> instead of <NIL/>
228
	$GLOBALS['xmlrpc_null_apache_encoding']=false;
229
230
	// used to store state during parsing
231
	// quick explanation of components:
232
	//   ac - used to accumulate values
233
	//   isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1)
234
	//   isf_reason - used for storing xmlrpcresp fault string
235
	//   lv - used to indicate "looking for a value": implements
236
	//        the logic to allow values with no types to be strings
237
	//   params - used to store parameters in method calls
238
	//   method - used to store method name
239
	//   stack - array with genealogy of xml elements names:
240
	//           used to validate nesting of xmlrpc elements
241
	$GLOBALS['_xh']=null;
242
243
	/**
244
	* Convert a string to the correct XML representation in a target charset
245
	* To help correct communication of non-ascii chars inside strings, regardless
246
	* of the charset used when sending requests, parsing them, sending responses
247
	* and parsing responses, an option is to convert all non-ascii chars present in the message
248
	* into their equivalent 'charset entity'. Charset entities enumerated this way
249
	* are independent of the charset encoding used to transmit them, and all XML
250
	* parsers are bound to understand them.
251
	* Note that in the std case we are not sending a charset encoding mime type
252
	* along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
253
	*
254
	* @todo do a bit of basic benchmarking (strtr vs. str_replace)
255
	* @todo	make usage of iconv() or recode_string() or mb_string() where available
256
	*/
257
	function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')
258
	{
259
		if ($src_encoding == '')
260
		{
261
			// lame, but we know no better...
262
			$src_encoding = $GLOBALS['xmlrpc_internalencoding'];
263
		}
264
265
		switch(strtoupper($src_encoding.'_'.$dest_encoding))
266
		{
267
			case 'ISO-8859-1_':
268
			case 'ISO-8859-1_US-ASCII':
269
				$escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
270
				$escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
271
				break;
272
			case 'ISO-8859-1_UTF-8':
273
				$escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
274
				$escaped_data = utf8_encode($escaped_data);
275
				break;
276
			case 'ISO-8859-1_ISO-8859-1':
277
			case 'US-ASCII_US-ASCII':
278
			case 'US-ASCII_UTF-8':
279
			case 'US-ASCII_':
280
			case 'US-ASCII_ISO-8859-1':
281
			case 'UTF-8_UTF-8':
282
			//case 'CP1252_CP1252':
283
				$escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
284
				break;
285
			case 'UTF-8_':
286
			case 'UTF-8_US-ASCII':
287
			case 'UTF-8_ISO-8859-1':
288
	// NB: this will choke on invalid UTF-8, going most likely beyond EOF
289
	$escaped_data = '';
290
	// be kind to users creating string xmlrpcvals out of different php types
291
	$data = (string) $data;
292
	$ns = strlen ($data);
293
	for ($nn = 0; $nn < $ns; $nn++)
294
	{
295
		$ch = $data[$nn];
296
		$ii = ord($ch);
297
		//1 7 0bbbbbbb (127)
298
		if ($ii < 128)
299
		{
300
			/// @todo shall we replace this with a (supposedly) faster str_replace?
301
			switch($ii){
302
				case 34:
303
					$escaped_data .= '&quot;';
304
					break;
305
				case 38:
306
					$escaped_data .= '&amp;';
307
					break;
308
				case 39:
309
					$escaped_data .= '&apos;';
310
					break;
311
				case 60:
312
					$escaped_data .= '&lt;';
313
					break;
314
				case 62:
315
					$escaped_data .= '&gt;';
316
					break;
317
				default:
318
					$escaped_data .= $ch;
319
			} // switch
320
		}
321
		//2 11 110bbbbb 10bbbbbb (2047)
322
		else if ($ii>>5 == 6)
323
		{
324
			$b1 = ($ii & 31);
325
			$ii = ord($data[$nn+1]);
326
			$b2 = ($ii & 63);
327
			$ii = ($b1 * 64) + $b2;
328
			$ent = sprintf ('&#%d;', $ii);
329
			$escaped_data .= $ent;
330
			$nn += 1;
331
		}
332
		//3 16 1110bbbb 10bbbbbb 10bbbbbb
333
		else if ($ii>>4 == 14)
334
		{
335
			$b1 = ($ii & 15);
336
			$ii = ord($data[$nn+1]);
337
			$b2 = ($ii & 63);
338
			$ii = ord($data[$nn+2]);
339
			$b3 = ($ii & 63);
340
			$ii = ((($b1 * 64) + $b2) * 64) + $b3;
341
			$ent = sprintf ('&#%d;', $ii);
342
			$escaped_data .= $ent;
343
			$nn += 2;
344
		}
345
		//4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
346
		else if ($ii>>3 == 30)
347
		{
348
			$b1 = ($ii & 7);
349
			$ii = ord($data[$nn+1]);
350
			$b2 = ($ii & 63);
351
			$ii = ord($data[$nn+2]);
352
			$b3 = ($ii & 63);
353
			$ii = ord($data[$nn+3]);
354
			$b4 = ($ii & 63);
355
			$ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
356
			$ent = sprintf ('&#%d;', $ii);
357
			$escaped_data .= $ent;
358
			$nn += 3;
359
		}
360
	}
361
				break;
362
/*
363
			case 'CP1252_':
364
			case 'CP1252_US-ASCII':
365
				$escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
366
				$escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
367
				$escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
368
				break;
369
			case 'CP1252_UTF-8':
370
				$escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
371
				/// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them)
372
				$escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
373
				$escaped_data = utf8_encode($escaped_data);
374
				break;
375
			case 'CP1252_ISO-8859-1':
376
				$escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
377
				// we might as well replave all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities...
378
				$escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
379
				break;
380
*/
381
			default:
382
				$escaped_data = '';
383
				error_log("Converting from $src_encoding to $dest_encoding: not supported...");
384
		}
385
		return $escaped_data;
386
	}
387
388
	/// xml parser handler function for opening element tags
389
	function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false)
390
	{
391
		// if invalid xmlrpc already detected, skip all processing
392
		if ($GLOBALS['_xh']['isf'] < 2)
393
		{
394
			// check for correct element nesting
395
			// top level element can only be of 2 types
396
			/// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
397
			///       there is only a single top level element in xml anyway
398
			if (count($GLOBALS['_xh']['stack']) == 0)
399
			{
400
				if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (
401
					$name != 'VALUE' && !$accept_single_vals))
402
				{
403
					$GLOBALS['_xh']['isf'] = 2;
404
					$GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element';
405
					return;
406
				}
407
				else
408
				{
409
					$GLOBALS['_xh']['rt'] = strtolower($name);
410
					$GLOBALS['_xh']['rt'] = strtolower($name);
411
				}
412
			}
413
			else
414
			{
415
				// not top level element: see if parent is OK
416
				$parent = end($GLOBALS['_xh']['stack']);
417
				if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name]))
418
				{
419
					$GLOBALS['_xh']['isf'] = 2;
420
					$GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
421
					return;
422
				}
423
			}
424
425
			switch($name)
426
			{
427
				// optimize for speed switch cases: most common cases first
428
				case 'VALUE':
429
					/// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
430
					$GLOBALS['_xh']['vt']='value'; // indicator: no value found yet
431
					$GLOBALS['_xh']['ac']='';
432
					$GLOBALS['_xh']['lv']=1;
433
					$GLOBALS['_xh']['php_class']=null;
434
					break;
435
				case 'I4':
436
				case 'INT':
437
				case 'STRING':
438
				case 'BOOLEAN':
439
				case 'DOUBLE':
440
				case 'DATETIME.ISO8601':
441
				case 'BASE64':
442
					if ($GLOBALS['_xh']['vt']!='value')
443
					{
444
						//two data elements inside a value: an error occurred!
445
						$GLOBALS['_xh']['isf'] = 2;
446
						$GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
447
						return;
448
					}
449
					$GLOBALS['_xh']['ac']=''; // reset the accumulator
450
					break;
451
				case 'STRUCT':
452
				case 'ARRAY':
453
					if ($GLOBALS['_xh']['vt']!='value')
454
					{
455
						//two data elements inside a value: an error occurred!
456
						$GLOBALS['_xh']['isf'] = 2;
457
						$GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
458
						return;
459
					}
460
					// create an empty array to hold child values, and push it onto appropriate stack
461
					$cur_val = array();
462
					$cur_val['values'] = array();
463
					$cur_val['type'] = $name;
464
					// check for out-of-band information to rebuild php objs
465
					// and in case it is found, save it
466
					if (@isset($attrs['PHP_CLASS']))
467
					{
468
						$cur_val['php_class'] = $attrs['PHP_CLASS'];
469
					}
470
					$GLOBALS['_xh']['valuestack'][] = $cur_val;
471
					$GLOBALS['_xh']['vt']='data'; // be prepared for a data element next
472
					break;
473
				case 'DATA':
474
					if ($GLOBALS['_xh']['vt']!='data')
475
					{
476
						//two data elements inside a value: an error occurred!
477
						$GLOBALS['_xh']['isf'] = 2;
478
						$GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element";
479
						return;
480
					}
481
				case 'METHODCALL':
482
				case 'METHODRESPONSE':
483
				case 'PARAMS':
484
					// valid elements that add little to processing
485
					break;
486
				case 'METHODNAME':
487
				case 'NAME':
488
					/// @todo we could check for 2 NAME elements inside a MEMBER element
489
					$GLOBALS['_xh']['ac']='';
490
					break;
491
				case 'FAULT':
492
					$GLOBALS['_xh']['isf']=1;
493
					break;
494
				case 'MEMBER':
495
					$GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on
496
					//$GLOBALS['_xh']['ac']='';
497
					// Drop trough intentionally
498
				case 'PARAM':
499
					// clear value type, so we can check later if no value has been passed for this param/member
500
					$GLOBALS['_xh']['vt']=null;
501
					break;
502
				case 'NIL':
503
				case 'EX:NIL':
504
					if ($GLOBALS['xmlrpc_null_extension'])
505
					{
506
						if ($GLOBALS['_xh']['vt']!='value')
507
						{
508
							//two data elements inside a value: an error occurred!
509
							$GLOBALS['_xh']['isf'] = 2;
510
							$GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
511
							return;
512
						}
513
						$GLOBALS['_xh']['ac']=''; // reset the accumulator
514
						break;
515
					}
516
					// we do not support the <NIL/> extension, so
517
					// drop through intentionally
518
				default:
519
					/// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
520
					$GLOBALS['_xh']['isf'] = 2;
521
					$GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name";
522
					break;
523
			}
524
525
			// Save current element name to stack, to validate nesting
526
			$GLOBALS['_xh']['stack'][] = $name;
527
528
			/// @todo optimization creep: move this inside the big switch() above
529
			if($name!='VALUE')
530
			{
531
				$GLOBALS['_xh']['lv']=0;
532
			}
533
		}
534
	}
535
536
	/// Used in decoding xml chunks that might represent single xmlrpc values
537
	function xmlrpc_se_any($parser, $name, $attrs)
538
	{
539
		xmlrpc_se($parser, $name, $attrs, true);
540
	}
541
542
	/// xml parser handler function for close element tags
543
	function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)
544
	{
545
		if ($GLOBALS['_xh']['isf'] < 2)
546
		{
547
			// push this element name from stack
548
			// NB: if XML validates, correct opening/closing is guaranteed and
549
			// we do not have to check for $name == $curr_elem.
550
			// we also checked for proper nesting at start of elements...
551
			$curr_elem = array_pop($GLOBALS['_xh']['stack']);
552
553
			switch($name)
554
			{
555
				case 'VALUE':
556
					// This if() detects if no scalar was inside <VALUE></VALUE>
557
					if ($GLOBALS['_xh']['vt']=='value')
558
					{
559
						$GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
560
						$GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString'];
561
					}
562
563
					if ($rebuild_xmlrpcvals)
564
					{
565
						// build the xmlrpc val out of the data received, and substitute it
566
						$temp = new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']);
567
						// in case we got info about underlying php class, save it
568
						// in the object we're rebuilding
569
						if (isset($GLOBALS['_xh']['php_class']))
570
							$temp->_php_class = $GLOBALS['_xh']['php_class'];
571
						// check if we are inside an array or struct:
572
						// if value just built is inside an array, let's move it into array on the stack
573
						$vscount = count($GLOBALS['_xh']['valuestack']);
574
						if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
575
						{
576
							$GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp;
577
						}
578
						else
579
						{
580
							$GLOBALS['_xh']['value'] = $temp;
581
						}
582
					}
583
					else
584
					{
585
						/// @todo this needs to treat correctly php-serialized objects,
586
						/// since std deserializing is done by php_xmlrpc_decode,
587
						/// which we will not be calling...
588
						if (isset($GLOBALS['_xh']['php_class']))
589
						{
590
						}
591
592
						// check if we are inside an array or struct:
593
						// if value just built is inside an array, let's move it into array on the stack
594
						$vscount = count($GLOBALS['_xh']['valuestack']);
595
						if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
596
						{
597
							$GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value'];
598
						}
599
					}
600
					break;
601
				case 'BOOLEAN':
602
				case 'I4':
603
				case 'INT':
604
				case 'STRING':
605
				case 'DOUBLE':
606
				case 'DATETIME.ISO8601':
607
				case 'BASE64':
608
					$GLOBALS['_xh']['vt']=strtolower($name);
609
					/// @todo: optimization creep - remove the if/elseif cycle below
610
					/// since the case() in which we are already did that
611
					if ($name=='STRING')
612
					{
613
						$GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
614
					}
615
					elseif ($name=='DATETIME.ISO8601')
616
					{
617
						if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $GLOBALS['_xh']['ac']))
618
						{
619
							error_log('XML-RPC: invalid value received in DATETIME: '.$GLOBALS['_xh']['ac']);
620
						}
621
						$GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime'];
622
						$GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
623
					}
624
					elseif ($name=='BASE64')
625
					{
626
						/// @todo check for failure of base64 decoding / catch warnings
627
						$GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']);
628
					}
629
					elseif ($name=='BOOLEAN')
630
					{
631
						// special case here: we translate boolean 1 or 0 into PHP
632
						// constants true or false.
633
						// Strings 'true' and 'false' are accepted, even though the
634
						// spec never mentions them (see eg. Blogger api docs)
635
						// NB: this simple checks helps a lot sanitizing input, ie no
636
						// security problems around here
637
						if ($GLOBALS['_xh']['ac']=='1' || strcasecmp($GLOBALS['_xh']['ac'], 'true') == 0)
638
						{
639
							$GLOBALS['_xh']['value']=true;
640
						}
641
						else
642
						{
643
							// log if receiveing something strange, even though we set the value to false anyway
644
							if ($GLOBALS['_xh']['ac']!='0' && strcasecmp($GLOBALS['_xh']['ac'], 'false') != 0)
645
								error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']);
646
							$GLOBALS['_xh']['value']=false;
647
						}
648
					}
649
					elseif ($name=='DOUBLE')
650
					{
651
						// we have a DOUBLE
652
						// we must check that only 0123456789-.<space> are characters here
653
						// NOTE: regexp could be much stricter than this...
654
						if (!preg_match('/^[+-eE0123456789 \t.]+$/', $GLOBALS['_xh']['ac']))
655
						{
656
							/// @todo: find a better way of throwing an error than this!
657
							error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']);
658
							$GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
659
						}
660
						else
661
						{
662
							// it's ok, add it on
663
							$GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac'];
664
						}
665
					}
666
					else
667
					{
668
						// we have an I4/INT
669
						// we must check that only 0123456789-<space> are characters here
670
						if (!preg_match('/^[+-]?[0123456789 \t]+$/', $GLOBALS['_xh']['ac']))
671
						{
672
							/// @todo find a better way of throwing an error than this!
673
							error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']);
674
							$GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
675
						}
676
						else
677
						{
678
							// it's ok, add it on
679
							$GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac'];
680
						}
681
					}
682
					//$GLOBALS['_xh']['ac']=''; // is this necessary?
683
					$GLOBALS['_xh']['lv']=3; // indicate we've found a value
684
					break;
685
				case 'NAME':
686
					$GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac'];
687
					break;
688
				case 'MEMBER':
689
					//$GLOBALS['_xh']['ac']=''; // is this necessary?
690
					// add to array in the stack the last element built,
691
					// unless no VALUE was found
692
					if ($GLOBALS['_xh']['vt'])
693
					{
694
						$vscount = count($GLOBALS['_xh']['valuestack']);
695
						$GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value'];
696
					} else
697
						error_log('XML-RPC: missing VALUE inside STRUCT in received xml');
698
					break;
699
				case 'DATA':
700
					//$GLOBALS['_xh']['ac']=''; // is this necessary?
701
					$GLOBALS['_xh']['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty
702
					break;
703
				case 'STRUCT':
704
				case 'ARRAY':
705
					// fetch out of stack array of values, and promote it to current value
706
					$curr_val = array_pop($GLOBALS['_xh']['valuestack']);
707
					$GLOBALS['_xh']['value'] = $curr_val['values'];
708
					$GLOBALS['_xh']['vt']=strtolower($name);
709
					if (isset($curr_val['php_class']))
710
					{
711
						$GLOBALS['_xh']['php_class'] = $curr_val['php_class'];
712
					}
713
					break;
714
				case 'PARAM':
715
					// add to array of params the current value,
716
					// unless no VALUE was found
717
					if ($GLOBALS['_xh']['vt'])
718
					{
719
						$GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value'];
720
						$GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt'];
721
					}
722
					else
723
						error_log('XML-RPC: missing VALUE inside PARAM in received xml');
724
					break;
725
				case 'METHODNAME':
726
					$GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', $GLOBALS['_xh']['ac']);
727
					break;
728
				case 'NIL':
729
				case 'EX:NIL':
730
					if ($GLOBALS['xmlrpc_null_extension'])
731
					{
732
						$GLOBALS['_xh']['vt']='null';
733
						$GLOBALS['_xh']['value']=null;
734
						$GLOBALS['_xh']['lv']=3;
735
						break;
736
					}
737
					// drop through intentionally if nil extension not enabled
738
				case 'PARAMS':
739
				case 'FAULT':
740
				case 'METHODCALL':
741
				case 'METHORESPONSE':
742
					break;
743
				default:
744
					// End of INVALID ELEMENT!
745
					// shall we add an assert here for unreachable code???
746
					break;
747
			}
748
		}
749
	}
750
751
	/// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values
752
	function xmlrpc_ee_fast($parser, $name)
753
	{
754
		xmlrpc_ee($parser, $name, false);
755
	}
756
757
	/// xml parser handler function for character data
758
	function xmlrpc_cd($parser, $data)
759
	{
760
		// skip processing if xml fault already detected
761
		if ($GLOBALS['_xh']['isf'] < 2)
762
		{
763
			// "lookforvalue==3" means that we've found an entire value
764
			// and should discard any further character data
765
			if($GLOBALS['_xh']['lv']!=3)
766
			{
767
				// G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2
768
				//if($GLOBALS['_xh']['lv']==1)
769
				//{
770
					// if we've found text and we're just in a <value> then
771
					// say we've found a value
772
					//$GLOBALS['_xh']['lv']=2;
773
				//}
774
				// we always initialize the accumulator before starting parsing, anyway...
775
				//if(!@isset($GLOBALS['_xh']['ac']))
776
				//{
777
				//	$GLOBALS['_xh']['ac'] = '';
778
				//}
779
				$GLOBALS['_xh']['ac'].=$data;
780
			}
781
		}
782
	}
783
784
	/// xml parser handler function for 'other stuff', ie. not char data or
785
	/// element start/end tag. In fact it only gets called on unknown entities...
786
	function xmlrpc_dh($parser, $data)
787
	{
788
		// skip processing if xml fault already detected
789
		if ($GLOBALS['_xh']['isf'] < 2)
790
		{
791
			if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
792
			{
793
				// G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2
794
				//if($GLOBALS['_xh']['lv']==1)
795
				//{
796
				//	$GLOBALS['_xh']['lv']=2;
797
				//}
798
				$GLOBALS['_xh']['ac'].=$data;
799
			}
800
		}
801
		return true;
802
	}
803
804
	class xmlrpc_client
805
	{
806
		var $path;
807
		var $server;
808
		var $port=0;
809
		var $method='http';
810
		var $errno;
811
		var $errstr;
812
		var $debug=0;
813
		var $username='';
814
		var $password='';
815
		var $authtype=1;
816
		var $cert='';
817
		var $certpass='';
818
		var $cacert='';
819
		var $cacertdir='';
820
		var $key='';
821
		var $keypass='';
822
		var $verifypeer=true;
823
		var $verifyhost=1;
824
		var $no_multicall=false;
825
		var $proxy='';
826
		var $proxyport=0;
827
		var $proxy_user='';
828
		var $proxy_pass='';
829
		var $proxy_authtype=1;
830
		var $cookies=array();
831
		var $extracurlopts=array();
832
833
		/**
834
		* List of http compression methods accepted by the client for responses.
835
		* NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
836
		*
837
		* NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
838
		* in those cases it will be up to CURL to decide the compression methods
839
		* it supports. You might check for the presence of 'zlib' in the output of
840
		* curl_version() to determine wheter compression is supported or not
841
		*/
842
		var $accepted_compression = array();
843
		/**
844
		* Name of compression scheme to be used for sending requests.
845
		* Either null, gzip or deflate
846
		*/
847
		var $request_compression = '';
848
		/**
849
		* CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
850
		* http://curl.haxx.se/docs/faq.html#7.3)
851
		*/
852
		var $xmlrpc_curl_handle = null;
853
		/// Wheter to use persistent connections for http 1.1 and https
854
		var $keepalive = false;
855
		/// Charset encodings that can be decoded without problems by the client
856
		var $accepted_charset_encodings = array();
857
		/// Charset encoding to be used in serializing request. NULL = use ASCII
858
		var $request_charset_encoding = '';
859
		/**
860
		* Decides the content of xmlrpcresp objects returned by calls to send()
861
		* valid strings are 'xmlrpcvals', 'phpvals' or 'xml'
862
		*/
863
		var $return_type = 'xmlrpcvals';
864
		/**
865
		* Sent to servers in http headers
866
		*/
867
		var $user_agent;
868
869
		/**
870
		* @param string $path either the complete server URL or the PATH part of the xmlrc server URL, e.g. /xmlrpc/server.php
871
		* @param string $server the server name / ip address
872
		* @param integer $port the port the server is listening on, defaults to 80 or 443 depending on protocol used
873
		* @param string $method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed
874
		*/
875
		function xmlrpc_client($path, $server='', $port='', $method='')
876
		{
877
			// allow user to specify all params in $path
878
			if($server == '' and $port == '' and $method == '')
879
			{
880
				$parts = parse_url($path);
881
				$server = $parts['host'];
882
				$path = isset($parts['path']) ? $parts['path'] : '';
883
				if(isset($parts['query']))
884
				{
885
					$path .= '?'.$parts['query'];
886
				}
887
				if(isset($parts['fragment']))
888
				{
889
					$path .= '#'.$parts['fragment'];
890
				}
891
				if(isset($parts['port']))
892
				{
893
					$port = $parts['port'];
894
				}
895
				if(isset($parts['scheme']))
896
				{
897
					$method = $parts['scheme'];
898
				}
899
				if(isset($parts['user']))
900
				{
901
					$this->username = $parts['user'];
902
				}
903
				if(isset($parts['pass']))
904
				{
905
					$this->password = $parts['pass'];
906
				}
907
			}
908
			if($path == '' || $path[0] != '/')
909
			{
910
				$this->path='/'.$path;
911
			}
912
			else
913
			{
914
				$this->path=$path;
915
			}
916
			$this->server=$server;
917
			if($port != '')
918
			{
919
				$this->port=$port;
920
			}
921
			if($method != '')
922
			{
923
				$this->method=$method;
924
			}
925
926
			// if ZLIB is enabled, let the client by default accept compressed responses
927
			if(function_exists('gzinflate') || (
928
				function_exists('curl_init') && (($info = curl_version()) &&
929
				((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
930
			))
931
			{
932
				$this->accepted_compression = array('gzip', 'deflate');
933
			}
934
935
			// keepalives: enabled by default
936
			$this->keepalive = true;
937
938
			// by default the xml parser can support these 3 charset encodings
939
			$this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
940
941
			// initialize user_agent string
942
			$this->user_agent = $GLOBALS['xmlrpcName'] . ' ' . $GLOBALS['xmlrpcVersion'];
943
		}
944
945
		/**
946
		* Enables/disables the echoing to screen of the xmlrpc responses received
947
		* @param integer $debug values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
948
		* @access public
949
		*/
950
		function setDebug($in)
951
		{
952
			$this->debug=$in;
953
		}
954
955
		/**
956
		* Add some http BASIC AUTH credentials, used by the client to authenticate
957
		* @param string $u username
958
		* @param string $p password
959
		* @param integer $t auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC (basic auth)
960
		* @access public
961
		*/
962
		function setCredentials($u, $p, $t=1)
963
		{
964
			$this->username=$u;
965
			$this->password=$p;
966
			$this->authtype=$t;
967
		}
968
969
		/**
970
		* Add a client-side https certificate
971
		* @param string $cert
972
		* @param string $certpass
973
		* @access public
974
		*/
975
		function setCertificate($cert, $certpass)
976
		{
977
			$this->cert = $cert;
978
			$this->certpass = $certpass;
979
		}
980
981
		/**
982
		* Add a CA certificate to verify server with (see man page about
983
		* CURLOPT_CAINFO for more details
984
		* @param string $cacert certificate file name (or dir holding certificates)
985
		* @param bool $is_dir set to true to indicate cacert is a dir. defaults to false
986
		* @access public
987
		*/
988
		function setCaCertificate($cacert, $is_dir=false)
989
		{
990
			if ($is_dir)
991
			{
992
				$this->cacertdir = $cacert;
993
			}
994
			else
995
			{
996
				$this->cacert = $cacert;
997
			}
998
		}
999
1000
		/**
1001
		* Set attributes for SSL communication: private SSL key
1002
		* NB: does not work in older php/curl installs
1003
		* Thanks to Daniel Convissor
1004
		* @param string $key The name of a file containing a private SSL key
1005
		* @param string $keypass The secret password needed to use the private SSL key
1006
		* @access public
1007
		*/
1008
		function setKey($key, $keypass)
1009
		{
1010
			$this->key = $key;
1011
			$this->keypass = $keypass;
1012
		}
1013
1014
		/**
1015
		* Set attributes for SSL communication: verify server certificate
1016
		* @param bool $i enable/disable verification of peer certificate
1017
		* @access public
1018
		*/
1019
		function setSSLVerifyPeer($i)
1020
		{
1021
			$this->verifypeer = $i;
1022
		}
1023
1024
		/**
1025
		* Set attributes for SSL communication: verify match of server cert w. hostname
1026
		* @param int $i
1027
		* @access public
1028
		*/
1029
		function setSSLVerifyHost($i)
1030
		{
1031
			$this->verifyhost = $i;
1032
		}
1033
1034
		/**
1035
		* Set proxy info
1036
		* @param string $proxyhost
1037
		* @param string $proxyport Defaults to 8080 for HTTP and 443 for HTTPS
1038
		* @param string $proxyusername Leave blank if proxy has public access
1039
		* @param string $proxypassword Leave blank if proxy has public access
1040
		* @param int $proxyauthtype set to constant CURLAUTH_NTLM to use NTLM auth with proxy
1041
		* @access public
1042
		*/
1043
		function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 1)
1044
		{
1045
			$this->proxy = $proxyhost;
1046
			$this->proxyport = $proxyport;
1047
			$this->proxy_user = $proxyusername;
1048
			$this->proxy_pass = $proxypassword;
1049
			$this->proxy_authtype = $proxyauthtype;
1050
		}
1051
1052
		/**
1053
		* Enables/disables reception of compressed xmlrpc responses.
1054
		* Note that enabling reception of compressed responses merely adds some standard
1055
		* http headers to xmlrpc requests. It is up to the xmlrpc server to return
1056
		* compressed responses when receiving such requests.
1057
		* @param string $compmethod either 'gzip', 'deflate', 'any' or ''
1058
		* @access public
1059
		*/
1060
		function setAcceptedCompression($compmethod)
1061
		{
1062
			if ($compmethod == 'any')
1063
				$this->accepted_compression = array('gzip', 'deflate');
1064
			else
1065
				$this->accepted_compression = array($compmethod);
1066
		}
1067
1068
		/**
1069
		* Enables/disables http compression of xmlrpc request.
1070
		* Take care when sending compressed requests: servers might not support them
1071
		* (and automatic fallback to uncompressed requests is not yet implemented)
1072
		* @param string $compmethod either 'gzip', 'deflate' or ''
1073
		* @access public
1074
		*/
1075
		function setRequestCompression($compmethod)
1076
		{
1077
			$this->request_compression = $compmethod;
1078
		}
1079
1080
		/**
1081
		* Adds a cookie to list of cookies that will be sent to server.
1082
		* NB: setting any param but name and value will turn the cookie into a 'version 1' cookie:
1083
		* do not do it unless you know what you are doing
1084
		* @param string $name
1085
		* @param string $value
1086
		* @param string $path
1087
		* @param string $domain
1088
		* @param int $port
1089
		* @access public
1090
		*
1091
		* @todo check correctness of urlencoding cookie value (copied from php way of doing it...)
1092
		*/
1093
		function setCookie($name, $value='', $path='', $domain='', $port=null)
1094
		{
1095
			$this->cookies[$name]['value'] = urlencode($value);
1096
			if ($path || $domain || $port)
1097
			{
1098
				$this->cookies[$name]['path'] = $path;
1099
				$this->cookies[$name]['domain'] = $domain;
1100
				$this->cookies[$name]['port'] = $port;
1101
				$this->cookies[$name]['version'] = 1;
1102
			}
1103
			else
1104
			{
1105
				$this->cookies[$name]['version'] = 0;
1106
			}
1107
		}
1108
1109
		/**
1110
		* Directly set cURL options, for extra flexibility
1111
		* It allows eg. to bind client to a specific IP interface / address
1112
		* @param $options array
1113
		*/
1114
		function SetCurlOptions( $options )
1115
		{
1116
			$this->extracurlopts = $options;
1117
		}
1118
1119
		/**
1120
		* Set user-agent string that will be used by this client instance
1121
		* in http headers sent to the server
1122
		*/
1123
		function SetUserAgent( $agentstring )
1124
		{
1125
			$this->user_agent = $agentstring;
1126
		}
1127
1128
		/**
1129
		* Send an xmlrpc request
1130
		* @param mixed $msg The message object, or an array of messages for using multicall, or the complete xml representation of a request
1131
		* @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply
1132
		* @param string $method if left unspecified, the http protocol chosen during creation of the object will be used
1133
		* @return xmlrpcresp
1134
		* @access public
1135
		*/
1136
		function& send($msg, $timeout=0, $method='')
1137
		{
1138
			// if user deos not specify http protocol, use native method of this client
1139
			// (i.e. method set during call to constructor)
1140
			if($method == '')
1141
			{
1142
				$method = $this->method;
1143
			}
1144
1145
			if(is_array($msg))
1146
			{
1147
				// $msg is an array of xmlrpcmsg's
1148
				$r = $this->multicall($msg, $timeout, $method);
1149
				return $r;
1150
			}
1151
			elseif(is_string($msg))
1152
			{
1153
				$n = new xmlrpcmsg('');
1154
				$n->payload = $msg;
1155
				$msg = $n;
1156
			}
1157
1158
			// where msg is an xmlrpcmsg
1159
			$msg->debug=$this->debug;
1160
1161
			if($method == 'https')
1162
			{
1163
				$r =& $this->sendPayloadHTTPS(
1164
					$msg,
1165
					$this->server,
1166
					$this->port,
1167
					$timeout,
1168
					$this->username,
1169
					$this->password,
1170
					$this->authtype,
1171
					$this->cert,
1172
					$this->certpass,
1173
					$this->cacert,
1174
					$this->cacertdir,
1175
					$this->proxy,
1176
					$this->proxyport,
1177
					$this->proxy_user,
1178
					$this->proxy_pass,
1179
					$this->proxy_authtype,
1180
					$this->keepalive,
1181
					$this->key,
1182
					$this->keypass
1183
				);
1184
			}
1185
			elseif($method == 'http11')
1186
			{
1187
				$r =& $this->sendPayloadCURL(
1188
					$msg,
1189
					$this->server,
1190
					$this->port,
1191
					$timeout,
1192
					$this->username,
1193
					$this->password,
1194
					$this->authtype,
1195
					null,
1196
					null,
1197
					null,
1198
					null,
1199
					$this->proxy,
1200
					$this->proxyport,
1201
					$this->proxy_user,
1202
					$this->proxy_pass,
1203
					$this->proxy_authtype,
1204
					'http',
1205
					$this->keepalive
1206
				);
1207
			}
1208
			else
1209
			{
1210
				$r =& $this->sendPayloadHTTP10(
1211
					$msg,
1212
					$this->server,
1213
					$this->port,
1214
					$timeout,
1215
					$this->username,
1216
					$this->password,
1217
					$this->authtype,
1218
					$this->proxy,
1219
					$this->proxyport,
1220
					$this->proxy_user,
1221
					$this->proxy_pass,
1222
					$this->proxy_authtype
1223
				);
1224
			}
1225
1226
			return $r;
1227
		}
1228
1229
		/**
1230
		* @access private
1231
		*/
1232
		function &sendPayloadHTTP10($msg, $server, $port, $timeout=0,
1233
			$username='', $password='', $authtype=1, $proxyhost='',
1234
			$proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1)
1235
		{
1236
			if($port==0)
1237
			{
1238
				$port=80;
1239
			}
1240
1241
			// Only create the payload if it was not created previously
1242
			if(empty($msg->payload))
1243
			{
1244
				$msg->createPayload($this->request_charset_encoding);
1245
			}
1246
1247
			$payload = $msg->payload;
1248
			// Deflate request body and set appropriate request headers
1249
			if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1250
			{
1251
				if($this->request_compression == 'gzip')
1252
				{
1253
					$a = @gzencode($payload);
1254
					if($a)
1255
					{
1256
						$payload = $a;
1257
						$encoding_hdr = "Content-Encoding: gzip\r\n";
1258
					}
1259
				}
1260
				else
1261
				{
1262
					$a = @gzcompress($payload);
1263
					if($a)
1264
					{
1265
						$payload = $a;
1266
						$encoding_hdr = "Content-Encoding: deflate\r\n";
1267
					}
1268
				}
1269
			}
1270
			else
1271
			{
1272
				$encoding_hdr = '';
1273
			}
1274
1275
			// thanks to Grant Rauscher <[email protected]> for this
1276
			$credentials='';
1277
			if($username!='')
1278
			{
1279
				$credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
1280
				if ($authtype != 1)
1281
				{
1282
					error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported with HTTP 1.0');
1283
				}
1284
			}
1285
1286
			$accepted_encoding = '';
1287
			if(is_array($this->accepted_compression) && count($this->accepted_compression))
1288
			{
1289
				$accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
1290
			}
1291
1292
			$proxy_credentials = '';
1293
			if($proxyhost)
1294
			{
1295
				if($proxyport == 0)
1296
				{
1297
					$proxyport = 8080;
1298
				}
1299
				$connectserver = $proxyhost;
1300
				$connectport = $proxyport;
1301
				$uri = 'http://'.$server.':'.$port.$this->path;
1302
				if($proxyusername != '')
1303
				{
1304
					if ($proxyauthtype != 1)
1305
					{
1306
						error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported with HTTP 1.0');
1307
					}
1308
					$proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n";
1309
				}
1310
			}
1311
			else
1312
			{
1313
				$connectserver = $server;
1314
				$connectport = $port;
1315
				$uri = $this->path;
1316
			}
1317
1318
			// Cookie generation, as per rfc2965 (version 1 cookies) or
1319
			// netscape's rules (version 0 cookies)
1320
			$cookieheader='';
1321
			if (count($this->cookies))
1322
			{
1323
				$version = '';
1324
				foreach ($this->cookies as $name => $cookie)
1325
				{
1326
					if ($cookie['version'])
1327
					{
1328
						$version = ' $Version="' . $cookie['version'] . '";';
1329
						$cookieheader .= ' ' . $name . '="' . $cookie['value'] . '";';
1330
						if ($cookie['path'])
1331
							$cookieheader .= ' $Path="' . $cookie['path'] . '";';
1332
						if ($cookie['domain'])
1333
							$cookieheader .= ' $Domain="' . $cookie['domain'] . '";';
1334
						if ($cookie['port'])
1335
							$cookieheader .= ' $Port="' . $cookie['port'] . '";';
1336
					}
1337
					else
1338
					{
1339
						$cookieheader .= ' ' . $name . '=' . $cookie['value'] . ";";
1340
					}
1341
				}
1342
				$cookieheader = 'Cookie:' . $version . substr($cookieheader, 0, -1) . "\r\n";
1343
			}
1344
1345
			$op= 'POST ' . $uri. " HTTP/1.0\r\n" .
1346
				'User-Agent: ' . $this->user_agent . "\r\n" .
1347
				'Host: '. $server . ':' . $port . "\r\n" .
1348
				$credentials .
1349
				$proxy_credentials .
1350
				$accepted_encoding .
1351
				$encoding_hdr .
1352
				'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
1353
				$cookieheader .
1354
				'Content-Type: ' . $msg->content_type . "\r\nContent-Length: " .
1355
				strlen($payload) . "\r\n\r\n" .
1356
				$payload;
1357
1358
			if($this->debug > 1)
1359
			{
1360
				print "<PRE>\n---SENDING---\n" . htmlentities($op) . "\n---END---\n</PRE>";
1361
				// let the client see this now in case http times out...
1362
				flush();
1363
			}
1364
1365
			if($timeout>0)
1366
			{
1367
				$fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout);
1368
			}
1369
			else
1370
			{
1371
				$fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr);
1372
			}
1373
			if($fp)
1374
			{
1375
				if($timeout>0 && function_exists('stream_set_timeout'))
1376
				{
1377
					stream_set_timeout($fp, $timeout);
1378
				}
1379
			}
1380
			else
1381
			{
1382
				$this->errstr='Connect error: '.$this->errstr;
1383
				$r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')');
1384
				return $r;
1385
			}
1386
1387
			if(!fputs($fp, $op, strlen($op)))
1388
			{
1389
				fclose($fp);
1390
				$this->errstr='Write error';
1391
				$r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr);
1392
				return $r;
1393
			}
1394
			else
1395
			{
1396
				// reset errno and errstr on succesful socket connection
1397
				$this->errstr = '';
1398
			}
1399
			// G. Giunta 2005/10/24: close socket before parsing.
1400
			// should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1401
			$ipd='';
1402
			do
1403
			{
1404
				// shall we check for $data === FALSE?
1405
				// as per the manual, it signals an error
1406
				$ipd.=fread($fp, 32768);
1407
			} while(!feof($fp));
1408
			fclose($fp);
1409
			$r =& $msg->parseResponse($ipd, false, $this->return_type);
1410
			return $r;
1411
1412
		}
1413
1414
		/**
1415
		* @access private
1416
		*/
1417
		function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='',
1418
			$password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='',
1419
			$proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1,
1420
			$keepalive=false, $key='', $keypass='')
1421
		{
1422
			$r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username,
1423
				$password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport,
1424
				$proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass);
1425
			return $r;
1426
		}
1427
1428
		/**
1429
		* Contributed by Justin Miller <[email protected]>
1430
		* Requires curl to be built into PHP
1431
		* NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1432
		* @access private
1433
		*/
1434
		function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='',
1435
			$password='', $authtype=1, $cert='', $certpass='', $cacert='', $cacertdir='',
1436
			$proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $method='https',
1437
			$keepalive=false, $key='', $keypass='')
1438
		{
1439
			if(!function_exists('curl_init'))
1440
			{
1441
				$this->errstr='CURL unavailable on this install';
1442
				$r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']);
1443
				return $r;
1444
			}
1445
			if($method == 'https')
1446
			{
1447
				if(($info = curl_version()) &&
1448
					((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version']))))
1449
				{
1450
					$this->errstr='SSL unavailable on this install';
1451
					$r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']);
1452
					return $r;
1453
				}
1454
			}
1455
1456
			if($port == 0)
1457
			{
1458
				if($method == 'http')
1459
				{
1460
					$port = 80;
1461
				}
1462
				else
1463
				{
1464
					$port = 443;
1465
				}
1466
			}
1467
1468
			// Only create the payload if it was not created previously
1469
			if(empty($msg->payload))
1470
			{
1471
				$msg->createPayload($this->request_charset_encoding);
1472
			}
1473
1474
			// Deflate request body and set appropriate request headers
1475
			$payload = $msg->payload;
1476
			if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1477
			{
1478
				if($this->request_compression == 'gzip')
1479
				{
1480
					$a = @gzencode($payload);
1481
					if($a)
1482
					{
1483
						$payload = $a;
1484
						$encoding_hdr = 'Content-Encoding: gzip';
1485
					}
1486
				}
1487
				else
1488
				{
1489
					$a = @gzcompress($payload);
1490
					if($a)
1491
					{
1492
						$payload = $a;
1493
						$encoding_hdr = 'Content-Encoding: deflate';
1494
					}
1495
				}
1496
			}
1497
			else
1498
			{
1499
				$encoding_hdr = '';
1500
			}
1501
1502
			if($this->debug > 1)
1503
			{
1504
				print "<PRE>\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n</PRE>";
1505
				// let the client see this now in case http times out...
1506
				flush();
1507
			}
1508
1509
			if(!$keepalive || !$this->xmlrpc_curl_handle)
1510
			{
1511
				$curl = curl_init($method . '://' . $server . ':' . $port . $this->path);
1512
				if($keepalive)
1513
				{
1514
					$this->xmlrpc_curl_handle = $curl;
1515
				}
1516
			}
1517
			else
1518
			{
1519
				$curl = $this->xmlrpc_curl_handle;
1520
			}
1521
1522
			// results into variable
1523
			curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
1524
1525
			if($this->debug)
1526
			{
1527
				curl_setopt($curl, CURLOPT_VERBOSE, 1);
1528
			}
1529
			curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
1530
			// required for XMLRPC: post the data
1531
			curl_setopt($curl, CURLOPT_POST, 1);
1532
			// the data
1533
			curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1534
1535
			// return the header too
1536
			curl_setopt($curl, CURLOPT_HEADER, 1);
1537
1538
			// will only work with PHP >= 5.0
1539
			// NB: if we set an empty string, CURL will add http header indicating
1540
			// ALL methods it is supporting. This is possibly a better option than
1541
			// letting the user tell what curl can / cannot do...
1542
			if(is_array($this->accepted_compression) && count($this->accepted_compression))
1543
			{
1544
				//curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
1545
				// empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1546
				if (count($this->accepted_compression) == 1)
1547
				{
1548
					curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
1549
				}
1550
				else
1551
					curl_setopt($curl, CURLOPT_ENCODING, '');
1552
			}
1553
			// extra headers
1554
			$headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
1555
			// if no keepalive is wanted, let the server know it in advance
1556
			if(!$keepalive)
1557
			{
1558
				$headers[] = 'Connection: close';
1559
			}
1560
			// request compression header
1561
			if($encoding_hdr)
1562
			{
1563
				$headers[] = $encoding_hdr;
1564
			}
1565
1566
			curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1567
			// timeout is borked
1568
			if($timeout)
1569
			{
1570
				curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
1571
			}
1572
1573
			if($username && $password)
1574
			{
1575
				curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password);
1576
				if (defined('CURLOPT_HTTPAUTH'))
1577
				{
1578
					curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype);
1579
				}
1580
				else if ($authtype != 1)
1581
				{
1582
					error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported by the current PHP/curl install');
1583
				}
1584
			}
1585
1586
			if($method == 'https')
1587
			{
1588
				// set cert file
1589
				if($cert)
1590
				{
1591
					curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1592
				}
1593
				// set cert password
1594
				if($certpass)
1595
				{
1596
					curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass);
1597
				}
1598
				// whether to verify remote host's cert
1599
				curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1600
				// set ca certificates file/dir
1601
				if($cacert)
1602
				{
1603
					curl_setopt($curl, CURLOPT_CAINFO, $cacert);
1604
				}
1605
				if($cacertdir)
1606
				{
1607
					curl_setopt($curl, CURLOPT_CAPATH, $cacertdir);
1608
				}
1609
				// set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1610
				if($key)
1611
				{
1612
					curl_setopt($curl, CURLOPT_SSLKEY, $key);
1613
				}
1614
				// set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1615
				if($keypass)
1616
				{
1617
					curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass);
1618
				}
1619
				// 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
1620
				curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1621
			}
1622
1623
			// proxy info
1624
			if($proxyhost)
1625
			{
1626
				if($proxyport == 0)
1627
				{
1628
					$proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080
1629
				}
1630
				curl_setopt($curl, CURLOPT_PROXY, $proxyhost.':'.$proxyport);
1631
				//curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport);
1632
				if($proxyusername)
1633
				{
1634
					curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword);
1635
					if (defined('CURLOPT_PROXYAUTH'))
1636
					{
1637
						curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype);
1638
					}
1639
					else if ($proxyauthtype != 1)
1640
					{
1641
						error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1642
					}
1643
				}
1644
			}
1645
1646
			// NB: should we build cookie http headers by hand rather than let CURL do it?
1647
			// the following code does not honour 'expires', 'path' and 'domain' cookie attributes
1648
			// set to client obj the the user...
1649
			if (count($this->cookies))
1650
			{
1651
				$cookieheader = '';
1652
				foreach ($this->cookies as $name => $cookie)
1653
				{
1654
					$cookieheader .= $name . '=' . $cookie['value'] . '; ';
1655
				}
1656
				curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2));
1657
			}
1658
1659
			foreach ($this->extracurlopts as $opt => $val)
1660
			{
1661
				curl_setopt($curl, $opt, $val);
1662
			}
1663
1664
			$result = curl_exec($curl);
1665
1666
			if ($this->debug > 1)
1667
			{
1668
				print "<PRE>\n---CURL INFO---\n";
1669
				foreach(curl_getinfo($curl) as $name => $val)
1670
					 print $name . ': ' . htmlentities($val). "\n";
1671
				print "---END---\n</PRE>";
1672
			}
1673
1674
			if(!$result) /// @todo we should use a better check here - what if we get back '' or '0'?
1675
			{
1676
				$this->errstr='no response';
1677
				$resp=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl));
1678
				curl_close($curl);
1679
				if($keepalive)
1680
				{
1681
					$this->xmlrpc_curl_handle = null;
1682
				}
1683
			}
1684
			else
1685
			{
1686
				if(!$keepalive)
1687
				{
1688
					curl_close($curl);
1689
				}
1690
				$resp =& $msg->parseResponse($result, true, $this->return_type);
1691
			}
1692
			return $resp;
1693
		}
1694
1695
		/**
1696
		* Send an array of request messages and return an array of responses.
1697
		* Unless $this->no_multicall has been set to true, it will try first
1698
		* to use one single xmlrpc call to server method system.multicall, and
1699
		* revert to sending many successive calls in case of failure.
1700
		* This failure is also stored in $this->no_multicall for subsequent calls.
1701
		* Unfortunately, there is no server error code universally used to denote
1702
		* the fact that multicall is unsupported, so there is no way to reliably
1703
		* distinguish between that and a temporary failure.
1704
		* If you are sure that server supports multicall and do not want to
1705
		* fallback to using many single calls, set the fourth parameter to FALSE.
1706
		*
1707
		* NB: trying to shoehorn extra functionality into existing syntax has resulted
1708
		* in pretty much convoluted code...
1709
		*
1710
		* @param array $msgs an array of xmlrpcmsg objects
1711
		* @param integer $timeout connection timeout (in seconds)
1712
		* @param string $method the http protocol variant to be used
1713
		* @param boolean fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted
1714
		* @return array
1715
		* @access public
1716
		*/
1717
		function multicall($msgs, $timeout=0, $method='', $fallback=true)
1718
		{
1719
			if ($method == '')
1720
			{
1721
				$method = $this->method;
1722
			}
1723
			if(!$this->no_multicall)
1724
			{
1725
				$results = $this->_try_multicall($msgs, $timeout, $method);
1726
				if(is_array($results))
1727
				{
1728
					// System.multicall succeeded
1729
					return $results;
1730
				}
1731
				else
1732
				{
1733
					// either system.multicall is unsupported by server,
1734
					// or call failed for some other reason.
1735
					if ($fallback)
1736
					{
1737
						// Don't try it next time...
1738
						$this->no_multicall = true;
1739
					}
1740
					else
1741
					{
1742
						if (is_a($results, 'xmlrpcresp'))
1743
						{
1744
							$result = $results;
1745
						}
1746
						else
1747
						{
1748
							$result = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']);
1749
						}
1750
					}
1751
				}
1752
			}
1753
			else
1754
			{
1755
				// override fallback, in case careless user tries to do two
1756
				// opposite things at the same time
1757
				$fallback = true;
1758
			}
1759
1760
			$results = array();
1761
			if ($fallback)
1762
			{
1763
				// system.multicall is (probably) unsupported by server:
1764
				// emulate multicall via multiple requests
1765
				foreach($msgs as $msg)
1766
				{
1767
					$results[] =& $this->send($msg, $timeout, $method);
1768
				}
1769
			}
1770
			else
1771
			{
1772
				// user does NOT want to fallback on many single calls:
1773
				// since we should always return an array of responses,
1774
				// return an array with the same error repeated n times
1775
				foreach($msgs as $msg)
1776
				{
1777
					$results[] = $result;
1778
				}
1779
			}
1780
			return $results;
1781
		}
1782
1783
		/**
1784
		* Attempt to boxcar $msgs via system.multicall.
1785
		* Returns either an array of xmlrpcreponses, an xmlrpc error response
1786
		* or false (when received response does not respect valid multicall syntax)
1787
		* @access private
1788
		*/
1789
		function _try_multicall($msgs, $timeout, $method)
1790
		{
1791
			// Construct multicall message
1792
			$calls = array();
1793
			foreach($msgs as $msg)
1794
			{
1795
				$call['methodName'] = new xmlrpcval($msg->method(),'string');
1796
				$numParams = $msg->getNumParams();
1797
				$params = array();
1798
				for($i = 0; $i < $numParams; $i++)
1799
				{
1800
					$params[$i] = $msg->getParam($i);
1801
				}
1802
				$call['params'] = new xmlrpcval($params, 'array');
1803
				$calls[] = new xmlrpcval($call, 'struct');
1804
			}
1805
			$multicall = new xmlrpcmsg('system.multicall');
1806
			$multicall->addParam(new xmlrpcval($calls, 'array'));
1807
1808
			// Attempt RPC call
1809
			$result =& $this->send($multicall, $timeout, $method);
1810
1811
			if($result->faultCode() != 0)
1812
			{
1813
				// call to system.multicall failed
1814
				return $result;
1815
			}
1816
1817
			// Unpack responses.
1818
			$rets = $result->value();
1819
1820
			if ($this->return_type == 'xml')
1821
			{
1822
					return $rets;
1823
			}
1824
			else if ($this->return_type == 'phpvals')
1825
			{
1826
				///@todo test this code branch...
1827
				$rets = $result->value();
1828
				if(!is_array($rets))
1829
				{
1830
					return false;		// bad return type from system.multicall
1831
				}
1832
				$numRets = count($rets);
1833
				if($numRets != count($msgs))
1834
				{
1835
					return false;		// wrong number of return values.
1836
				}
1837
1838
				$response = array();
1839
				for($i = 0; $i < $numRets; $i++)
1840
				{
1841
					$val = $rets[$i];
1842
					if (!is_array($val)) {
1843
						return false;
1844
					}
1845
					switch(count($val))
1846
					{
1847
						case 1:
1848
							if(!isset($val[0]))
1849
							{
1850
								return false;		// Bad value
1851
							}
1852
							// Normal return value
1853
							$response[$i] = new xmlrpcresp($val[0], 0, '', 'phpvals');
1854
							break;
1855
						case 2:
1856
							///	@todo remove usage of @: it is apparently quite slow
1857
							$code = @$val['faultCode'];
1858
							if(!is_int($code))
1859
							{
1860
								return false;
1861
							}
1862
							$str = @$val['faultString'];
1863
							if(!is_string($str))
1864
							{
1865
								return false;
1866
							}
1867
							$response[$i] = new xmlrpcresp(0, $code, $str);
1868
							break;
1869
						default:
1870
							return false;
1871
					}
1872
				}
1873
				return $response;
1874
			}
1875
			else // return type == 'xmlrpcvals'
1876
			{
1877
				$rets = $result->value();
1878
				if($rets->kindOf() != 'array')
1879
				{
1880
					return false;		// bad return type from system.multicall
1881
				}
1882
				$numRets = $rets->arraysize();
1883
				if($numRets != count($msgs))
1884
				{
1885
					return false;		// wrong number of return values.
1886
				}
1887
1888
				$response = array();
1889
				for($i = 0; $i < $numRets; $i++)
1890
				{
1891
					$val = $rets->arraymem($i);
1892
					switch($val->kindOf())
1893
					{
1894
						case 'array':
1895
							if($val->arraysize() != 1)
1896
							{
1897
								return false;		// Bad value
1898
							}
1899
							// Normal return value
1900
							$response[$i] = new xmlrpcresp($val->arraymem(0));
1901
							break;
1902
						case 'struct':
1903
							$code = $val->structmem('faultCode');
1904
							if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int')
1905
							{
1906
								return false;
1907
							}
1908
							$str = $val->structmem('faultString');
1909
							if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string')
1910
							{
1911
								return false;
1912
							}
1913
							$response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval());
1914
							break;
1915
						default:
1916
							return false;
1917
					}
1918
				}
1919
				return $response;
1920
			}
1921
		}
1922
	} // end class xmlrpc_client
1923
1924
	class xmlrpcresp
1925
	{
1926
		var $val = 0;
1927
		var $valtyp;
1928
		var $errno = 0;
1929
		var $errstr = '';
1930
		var $payload;
1931
		var $hdrs = array();
1932
		var $_cookies = array();
1933
		var $content_type = 'text/xml';
1934
		var $raw_data = '';
1935
1936
		/**
1937
		* @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string)
1938
		* @param integer $fcode set it to anything but 0 to create an error response
1939
		* @param string $fstr the error string, in case of an error response
1940
		* @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml'
1941
		*
1942
		* @todo add check that $val / $fcode / $fstr is of correct type???
1943
		* NB: as of now we do not do it, since it might be either an xmlrpcval or a plain
1944
		* php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called...
1945
		*/
1946
		function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='')
1947
		{
1948
			if($fcode != 0)
1949
			{
1950
				// error response
1951
				$this->errno = $fcode;
1952
				$this->errstr = $fstr;
1953
				//$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later.
1954
			}
1955
			else
1956
			{
1957
				// successful response
1958
				$this->val = $val;
1959
				if ($valtyp == '')
1960
				{
1961
					// user did not declare type of response value: try to guess it
1962
					if (is_object($this->val) && is_a($this->val, 'xmlrpcval'))
1963
					{
1964
						$this->valtyp = 'xmlrpcvals';
1965
					}
1966
					else if (is_string($this->val))
1967
					{
1968
						$this->valtyp = 'xml';
1969
1970
					}
1971
					else
1972
					{
1973
						$this->valtyp = 'phpvals';
1974
					}
1975
				}
1976
				else
1977
				{
1978
					// user declares type of resp value: believe him
1979
					$this->valtyp = $valtyp;
1980
				}
1981
			}
1982
		}
1983
1984
		/**
1985
		* Returns the error code of the response.
1986
		* @return integer the error code of this response (0 for not-error responses)
1987
		* @access public
1988
		*/
1989
		function faultCode()
1990
		{
1991
			return $this->errno;
1992
		}
1993
1994
		/**
1995
		* Returns the error code of the response.
1996
		* @return string the error string of this response ('' for not-error responses)
1997
		* @access public
1998
		*/
1999
		function faultString()
2000
		{
2001
			return $this->errstr;
2002
		}
2003
2004
		/**
2005
		* Returns the value received by the server.
2006
		* @return mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects
2007
		* @access public
2008
		*/
2009
		function value()
2010
		{
2011
			return $this->val;
2012
		}
2013
2014
		/**
2015
		* Returns an array with the cookies received from the server.
2016
		* Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...)
2017
		* with attributes being e.g. 'expires', 'path', domain'.
2018
		* NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past)
2019
		* are still present in the array. It is up to the user-defined code to decide
2020
		* how to use the received cookies, and wheter they have to be sent back with the next
2021
		* request to the server (using xmlrpc_client::setCookie) or not
2022
		* @return array array of cookies received from the server
2023
		* @access public
2024
		*/
2025
		function cookies()
2026
		{
2027
			return $this->_cookies;
2028
		}
2029
2030
		/**
2031
		* Returns xml representation of the response. XML prologue not included
2032
		* @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
2033
		* @return string the xml representation of the response
2034
		* @access public
2035
		*/
2036
		function serialize($charset_encoding='')
2037
		{
2038
			if ($charset_encoding != '')
2039
				$this->content_type = 'text/xml; charset=' . $charset_encoding;
2040
			else
2041
				$this->content_type = 'text/xml';
2042
			$result = "<methodResponse>\n";
2043
			if($this->errno)
2044
			{
2045
				// G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients
2046
				// by xml-encoding non ascii chars
2047
				$result .= "<fault>\n" .
2048
"<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
2049
"</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
2050
xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "</string></value>\n</member>\n" .
2051
"</struct>\n</value>\n</fault>";
2052
			}
2053
			else
2054
			{
2055
				if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval'))
2056
				{
2057
					if (is_string($this->val) && $this->valtyp == 'xml')
2058
					{
2059
						$result .= "<params>\n<param>\n" .
2060
							$this->val .
2061
							"</param>\n</params>";
2062
					}
2063
					else
2064
					{
2065
						/// @todo try to build something serializable?
2066
						die('cannot serialize xmlrpcresp objects whose content is native php values');
2067
					}
2068
				}
2069
				else
2070
				{
2071
					$result .= "<params>\n<param>\n" .
2072
						$this->val->serialize($charset_encoding) .
2073
						"</param>\n</params>";
2074
				}
2075
			}
2076
			$result .= "\n</methodResponse>";
2077
			$this->payload = $result;
2078
			return $result;
2079
		}
2080
	}
2081
2082
	class xmlrpcmsg
2083
	{
2084
		var $payload;
2085
		var $methodname;
2086
		var $params=array();
2087
		var $debug=0;
2088
		var $content_type = 'text/xml';
2089
2090
		/**
2091
		* @param string $meth the name of the method to invoke
2092
		* @param array $pars array of parameters to be paased to the method (xmlrpcval objects)
2093
		*/
2094
		function xmlrpcmsg($meth, $pars=0)
2095
		{
2096
			$this->methodname=$meth;
2097
			if(is_array($pars) && count($pars)>0)
2098
			{
2099
				for($i=0; $i<count($pars); $i++)
2100
				{
2101
					$this->addParam($pars[$i]);
2102
				}
2103
			}
2104
		}
2105
2106
		/**
2107
		* @access private
2108
		*/
2109
		function xml_header($charset_encoding='')
2110
		{
2111
			if ($charset_encoding != '')
2112
			{
2113
				return "<?xml version=\"1.0\" encoding=\"$charset_encoding\" ?" . ">\n<methodCall>\n";
2114
			}
2115
			else
2116
			{
2117
				return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
2118
			}
2119
		}
2120
2121
		/**
2122
		* @access private
2123
		*/
2124
		function xml_footer()
2125
		{
2126
			return '</methodCall>';
2127
		}
2128
2129
		/**
2130
		* @access private
2131
		*/
2132
		function kindOf()
2133
		{
2134
			return 'msg';
2135
		}
2136
2137
		/**
2138
		* @access private
2139
		*/
2140
		function createPayload($charset_encoding='')
2141
		{
2142
			if ($charset_encoding != '')
2143
				$this->content_type = 'text/xml; charset=' . $charset_encoding;
2144
			else
2145
				$this->content_type = 'text/xml';
2146
			$this->payload=$this->xml_header($charset_encoding);
2147
			$this->payload.='<methodName>' . $this->methodname . "</methodName>\n";
2148
			$this->payload.="<params>\n";
2149
			for($i=0; $i<count($this->params); $i++)
2150
			{
2151
				$p=$this->params[$i];
2152
				$this->payload.="<param>\n" . $p->serialize($charset_encoding) .
2153
				"</param>\n";
2154
			}
2155
			$this->payload.="</params>\n";
2156
			$this->payload.=$this->xml_footer();
2157
		}
2158
2159
		/**
2160
		* Gets/sets the xmlrpc method to be invoked
2161
		* @param string $meth the method to be set (leave empty not to set it)
2162
		* @return string the method that will be invoked
2163
		* @access public
2164
		*/
2165
		function method($meth='')
2166
		{
2167
			if($meth!='')
2168
			{
2169
				$this->methodname=$meth;
2170
			}
2171
			return $this->methodname;
2172
		}
2173
2174
		/**
2175
		* Returns xml representation of the message. XML prologue included
2176
		* @return string the xml representation of the message, xml prologue included
2177
		* @access public
2178
		*/
2179
		function serialize($charset_encoding='')
2180
		{
2181
			$this->createPayload($charset_encoding);
2182
			return $this->payload;
2183
		}
2184
2185
		/**
2186
		* Add a parameter to the list of parameters to be used upon method invocation
2187
		* @param xmlrpcval $par
2188
		* @return boolean false on failure
2189
		* @access public
2190
		*/
2191
		function addParam($par)
2192
		{
2193
			// add check: do not add to self params which are not xmlrpcvals
2194
			if(is_object($par) && is_a($par, 'xmlrpcval'))
2195
			{
2196
				$this->params[]=$par;
2197
				return true;
2198
			}
2199
			else
2200
			{
2201
				return false;
2202
			}
2203
		}
2204
2205
		/**
2206
		* Returns the nth parameter in the message. The index zero-based.
2207
		* @param integer $i the index of the parameter to fetch (zero based)
2208
		* @return xmlrpcval the i-th parameter
2209
		* @access public
2210
		*/
2211
		function getParam($i) { return $this->params[$i]; }
2212
2213
		/**
2214
		* Returns the number of parameters in the messge.
2215
		* @return integer the number of parameters currently set
2216
		* @access public
2217
		*/
2218
		function getNumParams() { return count($this->params); }
2219
2220
		/**
2221
		* Given an open file handle, read all data available and parse it as axmlrpc response.
2222
		* NB: the file handle is not closed by this function.
2223
		* NNB: might have trouble in rare cases to work on network streams, as we
2224
		*      check for a read of 0 bytes instead of feof($fp).
2225
		*      But since checking for feof(null) returns false, we would risk an
2226
		*      infinite loop in that case, because we cannot trust the caller
2227
		*      to give us a valid pointer to an open file...
2228
		* @access public
2229
		* @return xmlrpcresp
2230
		* @todo add 2nd & 3rd param to be passed to ParseResponse() ???
2231
		*/
2232
		function &parseResponseFile($fp)
2233
		{
2234
			$ipd='';
2235
			while($data=fread($fp, 32768))
2236
			{
2237
				$ipd.=$data;
2238
			}
2239
			//fclose($fp);
2240
			$r =& $this->parseResponse($ipd);
2241
			return $r;
2242
		}
2243
2244
		/**
2245
		* Parses HTTP headers and separates them from data.
2246
		* @access private
2247
		*/
2248
		function &parseResponseHeaders(&$data, $headers_processed=false)
2249
		{
2250
				// Support "web-proxy-tunelling" connections for https through proxies
2251
				if(preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data))
2252
				{
2253
					// Look for CR/LF or simple LF as line separator,
2254
					// (even though it is not valid http)
2255
					$pos = strpos($data,"\r\n\r\n");
2256
					if($pos || is_int($pos))
2257
					{
2258
						$bd = $pos+4;
2259
					}
2260
					else
2261
					{
2262
						$pos = strpos($data,"\n\n");
2263
						if($pos || is_int($pos))
2264
						{
2265
							$bd = $pos+2;
2266
						}
2267
						else
2268
						{
2269
							// No separation between response headers and body: fault?
2270
							$bd = 0;
2271
						}
2272
					}
2273
					if ($bd)
2274
					{
2275
						// this filters out all http headers from proxy.
2276
						// maybe we could take them into account, too?
2277
						$data = substr($data, $bd);
2278
					}
2279
					else
2280
					{
2281
						error_log('XML-RPC: '.__METHOD__.': HTTPS via proxy error, tunnel connection possibly failed');
2282
						$r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)');
2283
						return $r;
2284
					}
2285
				}
2286
2287
				// Strip HTTP 1.1 100 Continue header if present
2288
				while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data))
2289
				{
2290
					$pos = strpos($data, 'HTTP', 12);
2291
					// server sent a Continue header without any (valid) content following...
2292
					// give the client a chance to know it
2293
					if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
2294
					{
2295
						break;
2296
					}
2297
					$data = substr($data, $pos);
2298
				}
2299
				if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data))
2300
				{
2301
					$errstr= substr($data, 0, strpos($data, "\n")-1);
2302
					error_log('XML-RPC: '.__METHOD__.': HTTP error, got response: ' .$errstr);
2303
					$r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
2304
					return $r;
2305
				}
2306
2307
				$GLOBALS['_xh']['headers'] = array();
2308
				$GLOBALS['_xh']['cookies'] = array();
2309
2310
				// be tolerant to usage of \n instead of \r\n to separate headers and data
2311
				// (even though it is not valid http)
2312
				$pos = strpos($data,"\r\n\r\n");
2313
				if($pos || is_int($pos))
2314
				{
2315
					$bd = $pos+4;
2316
				}
2317
				else
2318
				{
2319
					$pos = strpos($data,"\n\n");
2320
					if($pos || is_int($pos))
2321
					{
2322
						$bd = $pos+2;
2323
					}
2324
					else
2325
					{
2326
						// No separation between response headers and body: fault?
2327
						// we could take some action here instead of going on...
2328
						$bd = 0;
2329
					}
2330
				}
2331
				// be tolerant to line endings, and extra empty lines
2332
				$ar = preg_split("/\r?\n/", trim(substr($data, 0, $pos)));
2333
				while(list(,$line) = @each($ar))
2334
				{
2335
					// take care of multi-line headers and cookies
2336
					$arr = explode(':',$line,2);
2337
					if(count($arr) > 1)
2338
					{
2339
						$header_name = strtolower(trim($arr[0]));
2340
						/// @todo some other headers (the ones that allow a CSV list of values)
2341
						/// do allow many values to be passed using multiple header lines.
2342
						/// We should add content to $GLOBALS['_xh']['headers'][$header_name]
2343
						/// instead of replacing it for those...
2344
						if ($header_name == 'set-cookie' || $header_name == 'set-cookie2')
2345
						{
2346
							if ($header_name == 'set-cookie2')
2347
							{
2348
								// version 2 cookies:
2349
								// there could be many cookies on one line, comma separated
2350
								$cookies = explode(',', $arr[1]);
2351
							}
2352
							else
2353
							{
2354
								$cookies = array($arr[1]);
2355
							}
2356
							foreach ($cookies as $cookie)
2357
							{
2358
								// glue together all received cookies, using a comma to separate them
2359
								// (same as php does with getallheaders())
2360
								if (isset($GLOBALS['_xh']['headers'][$header_name]))
2361
									$GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie);
2362
								else
2363
									$GLOBALS['_xh']['headers'][$header_name] = trim($cookie);
2364
								// parse cookie attributes, in case user wants to correctly honour them
2365
								// feature creep: only allow rfc-compliant cookie attributes?
2366
								// @todo support for server sending multiple time cookie with same name, but using different PATHs
2367
								$cookie = explode(';', $cookie);
2368
								foreach ($cookie as $pos => $val)
2369
								{
2370
									$val = explode('=', $val, 2);
2371
									$tag = trim($val[0]);
2372
									$val = trim(@$val[1]);
2373
									/// @todo with version 1 cookies, we should strip leading and trailing " chars
2374
									if ($pos == 0)
2375
									{
2376
										$cookiename = $tag;
2377
										$GLOBALS['_xh']['cookies'][$tag] = array();
2378
										$GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val);
2379
									}
2380
									else
2381
									{
2382
										if ($tag != 'value')
2383
										{
2384
										  $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val;
2385
										}
2386
									}
2387
								}
2388
							}
2389
						}
2390
						else
2391
						{
2392
							$GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]);
2393
						}
2394
					}
2395
					elseif(isset($header_name))
2396
					{
2397
						///	@todo version1 cookies might span multiple lines, thus breaking the parsing above
2398
						$GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line);
2399
					}
2400
				}
2401
2402
				$data = substr($data, $bd);
2403
2404
				if($this->debug && count($GLOBALS['_xh']['headers']))
2405
				{
2406
					print '<PRE>';
2407
					foreach($GLOBALS['_xh']['headers'] as $header => $value)
2408
					{
2409
						print htmlentities("HEADER: $header: $value\n");
2410
					}
2411
					foreach($GLOBALS['_xh']['cookies'] as $header => $value)
2412
					{
2413
						print htmlentities("COOKIE: $header={$value['value']}\n");
2414
					}
2415
					print "</PRE>\n";
2416
				}
2417
2418
				// if CURL was used for the call, http headers have been processed,
2419
				// and dechunking + reinflating have been carried out
2420
				if(!$headers_processed)
2421
				{
2422
					// Decode chunked encoding sent by http 1.1 servers
2423
					if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
2424
					{
2425
						if(!$data = decode_chunked($data))
2426
						{
2427
							error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to rebuild the chunked data received from server');
2428
							$r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
2429
							return $r;
2430
						}
2431
					}
2432
2433
					// Decode gzip-compressed stuff
2434
					// code shamelessly inspired from nusoap library by Dietrich Ayala
2435
					if(isset($GLOBALS['_xh']['headers']['content-encoding']))
2436
					{
2437
						$GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']);
2438
						if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
2439
						{
2440
							// if decoding works, use it. else assume data wasn't gzencoded
2441
							if(function_exists('gzinflate'))
2442
							{
2443
								if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data))
2444
								{
2445
									$data = $degzdata;
2446
									if($this->debug)
2447
									print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2448
								}
2449
								elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
2450
								{
2451
									$data = $degzdata;
2452
									if($this->debug)
2453
									print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2454
								}
2455
								else
2456
								{
2457
									error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to decode the deflated data received from server');
2458
									$r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
2459
									return $r;
2460
								}
2461
							}
2462
							else
2463
							{
2464
								error_log('XML-RPC: '.__METHOD__.': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
2465
								$r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
2466
								return $r;
2467
							}
2468
						}
2469
					}
2470
				} // end of 'if needed, de-chunk, re-inflate response'
2471
2472
				// real stupid hack to avoid PHP complaining about returning NULL by ref
2473
				$r = null;
2474
				$r =& $r;
2475
				return $r;
2476
		}
2477
2478
		/**
2479
		* Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object.
2480
		* @param string $data the xmlrpc response, eventually including http headers
2481
		* @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding
2482
		* @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
2483
		* @return xmlrpcresp
2484
		* @access public
2485
		*/
2486
		function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals')
2487
		{
2488
			if($this->debug)
2489
			{
2490
				//by maHo, replaced htmlspecialchars with htmlentities
2491
				print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
2492
			}
2493
2494
			if($data == '')
2495
			{
2496
				error_log('XML-RPC: '.__METHOD__.': no response received from server.');
2497
				$r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
2498
				return $r;
2499
			}
2500
2501
			$GLOBALS['_xh']=array();
2502
2503
			$raw_data = $data;
2504
			// parse the HTTP headers of the response, if present, and separate them from data
2505
			if(substr($data, 0, 4) == 'HTTP')
2506
			{
2507
				$r =& $this->parseResponseHeaders($data, $headers_processed);
2508
				if ($r)
2509
				{
2510
					// failed processing of HTTP response headers
2511
					// save into response obj the full payload received, for debugging
2512
					$r->raw_data = $data;
2513
					return $r;
2514
				}
2515
			}
2516
			else
2517
			{
2518
				$GLOBALS['_xh']['headers'] = array();
2519
				$GLOBALS['_xh']['cookies'] = array();
2520
			}
2521
2522
			if($this->debug)
2523
			{
2524
				$start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2525
				if ($start)
2526
				{
2527
					$start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2528
					$end = strpos($data, '-->', $start);
2529
					$comments = substr($data, $start, $end-$start);
2530
					print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n</PRE>";
2531
				}
2532
			}
2533
2534
			// be tolerant of extra whitespace in response body
2535
			$data = trim($data);
2536
2537
			/// @todo return an error msg if $data=='' ?
2538
2539
			// be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
2540
			// idea from Luca Mariano <[email protected]> originally in PEARified version of the lib
2541
			$pos = strrpos($data, '</methodResponse>');
2542
			if($pos !== false)
2543
			{
2544
				$data = substr($data, 0, $pos+17);
2545
			}
2546
2547
			// if user wants back raw xml, give it to him
2548
			if ($return_type == 'xml')
2549
			{
2550
				$r = new xmlrpcresp($data, 0, '', 'xml');
2551
				$r->hdrs = $GLOBALS['_xh']['headers'];
2552
				$r->_cookies = $GLOBALS['_xh']['cookies'];
2553
				$r->raw_data = $raw_data;
2554
				return $r;
2555
			}
2556
2557
			// try to 'guestimate' the character encoding of the received response
2558
			$resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data);
2559
2560
			$GLOBALS['_xh']['ac']='';
2561
			//$GLOBALS['_xh']['qt']=''; //unused...
2562
			$GLOBALS['_xh']['stack'] = array();
2563
			$GLOBALS['_xh']['valuestack'] = array();
2564
			$GLOBALS['_xh']['isf']=0; // 0 = OK, 1 for xmlrpc fault responses, 2 = invalid xmlrpc
2565
			$GLOBALS['_xh']['isf_reason']='';
2566
			$GLOBALS['_xh']['rt']=''; // 'methodcall or 'methodresponse'
2567
2568
			// if response charset encoding is not known / supported, try to use
2569
			// the default encoding and parse the xml anyway, but log a warning...
2570
			if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2571
			// the following code might be better for mb_string enabled installs, but
2572
			// makes the lib about 200% slower...
2573
			//if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2574
			{
2575
				error_log('XML-RPC: '.__METHOD__.': invalid charset encoding of received response: '.$resp_encoding);
2576
				$resp_encoding = $GLOBALS['xmlrpc_defencoding'];
2577
			}
2578
			$parser = xml_parser_create($resp_encoding);
2579
			xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
2580
			// G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
2581
			// the xml parser to give us back data in the expected charset.
2582
			// What if internal encoding is not in one of the 3 allowed?
2583
			// we use the broadest one, ie. utf8
2584
			// This allows to send data which is native in various charset,
2585
			// by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding
2586
			if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2587
			{
2588
				xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
2589
			}
2590
			else
2591
			{
2592
				xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
2593
			}
2594
2595
			if ($return_type == 'phpvals')
2596
			{
2597
				xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
2598
			}
2599
			else
2600
			{
2601
				xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
2602
			}
2603
2604
			xml_set_character_data_handler($parser, 'xmlrpc_cd');
2605
			xml_set_default_handler($parser, 'xmlrpc_dh');
2606
2607
			// first error check: xml not well formed
2608
			if(!xml_parse($parser, $data, count($data)))
2609
			{
2610
				// thanks to Peter Kocks <[email protected]>
2611
				if((xml_get_current_line_number($parser)) == 1)
2612
				{
2613
					$errstr = 'XML error at line 1, check URL';
2614
				}
2615
				else
2616
				{
2617
					$errstr = sprintf('XML error: %s at line %d, column %d',
2618
						xml_error_string(xml_get_error_code($parser)),
2619
						xml_get_current_line_number($parser), xml_get_current_column_number($parser));
2620
				}
2621
				error_log($errstr);
2622
				$r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
2623
				xml_parser_free($parser);
2624
				if($this->debug)
2625
				{
2626
					print $errstr;
2627
				}
2628
				$r->hdrs = $GLOBALS['_xh']['headers'];
2629
				$r->_cookies = $GLOBALS['_xh']['cookies'];
2630
				$r->raw_data = $raw_data;
2631
				return $r;
2632
			}
2633
			xml_parser_free($parser);
2634
			// second error check: xml well formed but not xml-rpc compliant
2635
			if ($GLOBALS['_xh']['isf'] > 1)
2636
			{
2637
				if ($this->debug)
2638
				{
2639
					/// @todo echo something for user?
2640
				}
2641
2642
				$r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2643
				$GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
2644
			}
2645
			// third error check: parsing of the response has somehow gone boink.
2646
			// NB: shall we omit this check, since we trust the parsing code?
2647
			elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value']))
2648
			{
2649
				// something odd has happened
2650
				// and it's time to generate a client side error
2651
				// indicating something odd went on
2652
				$r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2653
					$GLOBALS['xmlrpcstr']['invalid_return']);
2654
			}
2655
			else
2656
			{
2657
				if ($this->debug)
2658
				{
2659
					print "<PRE>---PARSED---\n";
2660
					// somehow htmlentities chokes on var_export, and some full html string...
2661
					//print htmlentitites(var_export($GLOBALS['_xh']['value'], true));
2662
					print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true));
2663
					print "\n---END---</PRE>";
2664
				}
2665
2666
				// note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object.
2667
				$v =& $GLOBALS['_xh']['value'];
2668
2669
				if($GLOBALS['_xh']['isf'])
2670
				{
2671
					/// @todo we should test here if server sent an int and a string,
2672
					/// and/or coerce them into such...
2673
					if ($return_type == 'xmlrpcvals')
2674
					{
2675
						$errno_v = $v->structmem('faultCode');
2676
						$errstr_v = $v->structmem('faultString');
2677
						$errno = $errno_v->scalarval();
2678
						$errstr = $errstr_v->scalarval();
2679
					}
2680
					else
2681
					{
2682
						$errno = $v['faultCode'];
2683
						$errstr = $v['faultString'];
2684
					}
2685
2686
					if($errno == 0)
2687
					{
2688
						// FAULT returned, errno needs to reflect that
2689
						$errno = -1;
2690
					}
2691
2692
					$r = new xmlrpcresp(0, $errno, $errstr);
2693
				}
2694
				else
2695
				{
2696
					$r=new xmlrpcresp($v, 0, '', $return_type);
2697
				}
2698
			}
2699
2700
			$r->hdrs = $GLOBALS['_xh']['headers'];
2701
			$r->_cookies = $GLOBALS['_xh']['cookies'];
2702
			$r->raw_data = $raw_data;
2703
			return $r;
2704
		}
2705
	}
2706
2707
	class xmlrpcval
2708
	{
2709
		var $me=array();
2710
		var $mytype=0;
2711
		var $_php_class=null;
2712
2713
		/**
2714
		* @param mixed $val
2715
		* @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed
2716
		*/
2717
		function xmlrpcval($val=-1, $type='')
2718
		{
2719
			/// @todo: optimization creep - do not call addXX, do it all inline.
2720
			/// downside: booleans will not be coerced anymore
2721
			if($val!==-1 || $type!='')
2722
			{
2723
				// optimization creep: inlined all work done by constructor
2724
				switch($type)
2725
				{
2726
					case '':
2727
						$this->mytype=1;
2728
						$this->me['string']=$val;
2729
						break;
2730
					case 'i4':
2731
					case 'int':
2732
					case 'double':
2733
					case 'string':
2734
					case 'boolean':
2735
					case 'dateTime.iso8601':
2736
					case 'base64':
2737
					case 'null':
2738
						$this->mytype=1;
2739
						$this->me[$type]=$val;
2740
						break;
2741
					case 'array':
2742
						$this->mytype=2;
2743
						$this->me['array']=$val;
2744
						break;
2745
					case 'struct':
2746
						$this->mytype=3;
2747
						$this->me['struct']=$val;
2748
						break;
2749
					default:
2750
						error_log("XML-RPC: ".__METHOD__.": not a known type ($type)");
2751
				}
2752
				/*if($type=='')
2753
				{
2754
					$type='string';
2755
				}
2756
				if($GLOBALS['xmlrpcTypes'][$type]==1)
2757
				{
2758
					$this->addScalar($val,$type);
2759
				}
2760
				elseif($GLOBALS['xmlrpcTypes'][$type]==2)
2761
				{
2762
					$this->addArray($val);
2763
				}
2764
				elseif($GLOBALS['xmlrpcTypes'][$type]==3)
2765
				{
2766
					$this->addStruct($val);
2767
				}*/
2768
			}
2769
		}
2770
2771
		/**
2772
		* Add a single php value to an (unitialized) xmlrpcval
2773
		* @param mixed $val
2774
		* @param string $type
2775
		* @return int 1 or 0 on failure
2776
		*/
2777
		function addScalar($val, $type='string')
2778
		{
2779
			$typeof=@$GLOBALS['xmlrpcTypes'][$type];
2780
			if($typeof!=1)
2781
			{
2782
				error_log("XML-RPC: ".__METHOD__.": not a scalar type ($type)");
2783
				return 0;
2784
			}
2785
2786
			// coerce booleans into correct values
2787
			// NB: we should either do it for datetimes, integers and doubles, too,
2788
			// or just plain remove this check, implemented on booleans only...
2789
			if($type==$GLOBALS['xmlrpcBoolean'])
2790
			{
2791
				if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
2792
				{
2793
					$val=true;
2794
				}
2795
				else
2796
				{
2797
					$val=false;
2798
				}
2799
			}
2800
2801
			switch($this->mytype)
2802
			{
2803
				case 1:
2804
					error_log('XML-RPC: '.__METHOD__.': scalar xmlrpcval can have only one value');
2805
					return 0;
2806
				case 3:
2807
					error_log('XML-RPC: '.__METHOD__.': cannot add anonymous scalar to struct xmlrpcval');
2808
					return 0;
2809
				case 2:
2810
					// we're adding a scalar value to an array here
2811
					//$ar=$this->me['array'];
2812
					//$ar[]=new xmlrpcval($val, $type);
2813
					//$this->me['array']=$ar;
2814
					// Faster (?) avoid all the costly array-copy-by-val done here...
2815
					$this->me['array'][]=new xmlrpcval($val, $type);
2816
					return 1;
2817
				default:
2818
					// a scalar, so set the value and remember we're scalar
2819
					$this->me[$type]=$val;
2820
					$this->mytype=$typeof;
2821
					return 1;
2822
			}
2823
		}
2824
2825
		/**
2826
		* Add an array of xmlrpcval objects to an xmlrpcval
2827
		* @param array $vals
2828
		* @return int 1 or 0 on failure
2829
		* @access public
2830
		*
2831
		* @todo add some checking for $vals to be an array of xmlrpcvals?
2832
		*/
2833
		function addArray($vals)
2834
		{
2835
			if($this->mytype==0)
2836
			{
2837
				$this->mytype=$GLOBALS['xmlrpcTypes']['array'];
2838
				$this->me['array']=$vals;
2839
				return 1;
2840
			}
2841
			elseif($this->mytype==2)
2842
			{
2843
				// we're adding to an array here
2844
				$this->me['array'] = array_merge($this->me['array'], $vals);
2845
				return 1;
2846
			}
2847
			else
2848
			{
2849
				error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
2850
				return 0;
2851
			}
2852
		}
2853
2854
		/**
2855
		* Add an array of named xmlrpcval objects to an xmlrpcval
2856
		* @param array $vals
2857
		* @return int 1 or 0 on failure
2858
		* @access public
2859
		*
2860
		* @todo add some checking for $vals to be an array?
2861
		*/
2862
		function addStruct($vals)
2863
		{
2864
			if($this->mytype==0)
2865
			{
2866
				$this->mytype=$GLOBALS['xmlrpcTypes']['struct'];
2867
				$this->me['struct']=$vals;
2868
				return 1;
2869
			}
2870
			elseif($this->mytype==3)
2871
			{
2872
				// we're adding to a struct here
2873
				$this->me['struct'] = array_merge($this->me['struct'], $vals);
2874
				return 1;
2875
			}
2876
			else
2877
			{
2878
				error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
2879
				return 0;
2880
			}
2881
		}
2882
2883
		// poor man's version of print_r ???
2884
		// DEPRECATED!
2885
		function dump($ar)
2886
		{
2887
			foreach($ar as $key => $val)
2888
			{
2889
				echo "$key => $val<br />";
2890
				if($key == 'array')
2891
				{
2892
					while(list($key2, $val2) = each($val))
2893
					{
2894
						echo "-- $key2 => $val2<br />";
2895
					}
2896
				}
2897
			}
2898
		}
2899
2900
		/**
2901
		* Returns a string containing "struct", "array" or "scalar" describing the base type of the value
2902
		* @return string
2903
		* @access public
2904
		*/
2905
		function kindOf()
2906
		{
2907
			switch($this->mytype)
2908
			{
2909
				case 3:
2910
					return 'struct';
2911
					break;
2912
				case 2:
2913
					return 'array';
2914
					break;
2915
				case 1:
2916
					return 'scalar';
2917
					break;
2918
				default:
2919
					return 'undef';
2920
			}
2921
		}
2922
2923
		/**
2924
		* @access private
2925
		*/
2926
		function serializedata($typ, $val, $charset_encoding='')
2927
		{
2928
			$rs='';
2929
			switch(@$GLOBALS['xmlrpcTypes'][$typ])
2930
			{
2931
				case 1:
2932
					switch($typ)
2933
					{
2934
						case $GLOBALS['xmlrpcBase64']:
2935
							$rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
2936
							break;
2937
						case $GLOBALS['xmlrpcBoolean']:
2938
							$rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
2939
							break;
2940
						case $GLOBALS['xmlrpcString']:
2941
							// G. Giunta 2005/2/13: do NOT use htmlentities, since
2942
							// it will produce named html entities, which are invalid xml
2943
							$rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "</${typ}>";
2944
							break;
2945
						case $GLOBALS['xmlrpcInt']:
2946
						case $GLOBALS['xmlrpcI4']:
2947
							$rs.="<${typ}>".(int)$val."</${typ}>";
2948
							break;
2949
						case $GLOBALS['xmlrpcDouble']:
2950
							// avoid using standard conversion of float to string because it is locale-dependent,
2951
							// and also because the xmlrpc spec forbids exponential notation.
2952
							// sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
2953
							// The code below tries its best at keeping max precision while avoiding exp notation,
2954
							// but there is of course no limit in the number of decimal places to be used...
2955
							$rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', ''))."</${typ}>";
2956
							break;
2957
						case $GLOBALS['xmlrpcDateTime']:
2958
							if (is_string($val))
2959
							{
2960
								$rs.="<${typ}>${val}</${typ}>";
2961
							}
2962
							else if(is_a($val, 'DateTime'))
2963
							{
2964
								$rs.="<${typ}>".$val->format('Ymd\TH:i:s')."</${typ}>";
2965
							}
2966
							else if(is_int($val))
2967
							{
2968
								$rs.="<${typ}>".strftime("%Y%m%dT%H:%M:%S", $val)."</${typ}>";
2969
							}
2970
							else
2971
							{
2972
								// not really a good idea here: but what shall we output anyway? left for backward compat...
2973
								$rs.="<${typ}>${val}</${typ}>";
2974
							}
2975
							break;
2976
						case $GLOBALS['xmlrpcNull']:
2977
							if ($GLOBALS['xmlrpc_null_apache_encoding'])
2978
							{
2979
								$rs.="<ex:nil/>";
2980
							}
2981
							else
2982
							{
2983
								$rs.="<nil/>";
2984
							}
2985
							break;
2986
						default:
2987
							// no standard type value should arrive here, but provide a possibility
2988
							// for xmlrpcvals of unknown type...
2989
							$rs.="<${typ}>${val}</${typ}>";
2990
					}
2991
					break;
2992
				case 3:
2993
					// struct
2994
					if ($this->_php_class)
2995
					{
2996
						$rs.='<struct php_class="' . $this->_php_class . "\">\n";
2997
					}
2998
					else
2999
					{
3000
						$rs.="<struct>\n";
3001
					}
3002
					foreach($val as $key2 => $val2)
3003
					{
3004
						$rs.='<member><name>'.xmlrpc_encode_entitites($key2, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding)."</name>\n";
3005
						//$rs.=$this->serializeval($val2);
3006
						$rs.=$val2->serialize($charset_encoding);
3007
						$rs.="</member>\n";
3008
					}
3009
					$rs.='</struct>';
3010
					break;
3011
				case 2:
3012
					// array
3013
					$rs.="<array>\n<data>\n";
3014
					for($i=0; $i<count($val); $i++)
3015
					{
3016
						//$rs.=$this->serializeval($val[$i]);
3017
						$rs.=$val[$i]->serialize($charset_encoding);
3018
					}
3019
					$rs.="</data>\n</array>";
3020
					break;
3021
				default:
3022
					break;
3023
			}
3024
			return $rs;
3025
		}
3026
3027
		/**
3028
		* Returns xml representation of the value. XML prologue not included
3029
		* @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
3030
		* @return string
3031
		* @access public
3032
		*/
3033
		function serialize($charset_encoding='')
3034
		{
3035
			// add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
3036
			//if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
3037
			//{
3038
				reset($this->me);
3039
				list($typ, $val) = each($this->me);
3040
				return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n";
3041
			//}
3042
		}
3043
3044
		// DEPRECATED
3045
		function serializeval($o)
3046
		{
3047
			// add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
3048
			//if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
3049
			//{
3050
				$ar=$o->me;
3051
				reset($ar);
3052
				list($typ, $val) = each($ar);
3053
				return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
3054
			//}
3055
		}
3056
3057
		/**
3058
		* Checks wheter a struct member with a given name is present.
3059
		* Works only on xmlrpcvals of type struct.
3060
		* @param string $m the name of the struct member to be looked up
3061
		* @return boolean
3062
		* @access public
3063
		*/
3064
		function structmemexists($m)
3065
		{
3066
			return array_key_exists($m, $this->me['struct']);
3067
		}
3068
3069
		/**
3070
		* Returns the value of a given struct member (an xmlrpcval object in itself).
3071
		* Will raise a php warning if struct member of given name does not exist
3072
		* @param string $m the name of the struct member to be looked up
3073
		* @return xmlrpcval
3074
		* @access public
3075
		*/
3076
		function structmem($m)
3077
		{
3078
			return $this->me['struct'][$m];
3079
		}
3080
3081
		/**
3082
		* Reset internal pointer for xmlrpcvals of type struct.
3083
		* @access public
3084
		*/
3085
		function structreset()
3086
		{
3087
			reset($this->me['struct']);
3088
		}
3089
3090
		/**
3091
		* Return next member element for xmlrpcvals of type struct.
3092
		* @return xmlrpcval
3093
		* @access public
3094
		*/
3095
		function structeach()
3096
		{
3097
			return each($this->me['struct']);
3098
		}
3099
3100
		// DEPRECATED! this code looks like it is very fragile and has not been fixed
3101
		// for a long long time. Shall we remove it for 2.0?
3102
		function getval()
3103
		{
3104
			// UNSTABLE
3105
			reset($this->me);
3106
			list($a,$b)=each($this->me);
3107
			// contributed by I Sofer, 2001-03-24
3108
			// add support for nested arrays to scalarval
3109
			// i've created a new method here, so as to
3110
			// preserve back compatibility
3111
3112
			if(is_array($b))
3113
			{
3114
				@reset($b);
3115
				while(list($id,$cont) = @each($b))
3116
				{
3117
					$b[$id] = $cont->scalarval();
3118
				}
3119
			}
3120
3121
			// add support for structures directly encoding php objects
3122
			if(is_object($b))
3123
			{
3124
				$t = get_object_vars($b);
3125
				@reset($t);
3126
				while(list($id,$cont) = @each($t))
3127
				{
3128
					$t[$id] = $cont->scalarval();
3129
				}
3130
				@reset($t);
3131
				while(list($id,$cont) = @each($t))
3132
				{
3133
					@$b->$id = $cont;
3134
				}
3135
			}
3136
			// end contrib
3137
			return $b;
3138
		}
3139
3140
		/**
3141
		* Returns the value of a scalar xmlrpcval
3142
		* @return mixed
3143
		* @access public
3144
		*/
3145
		function scalarval()
3146
		{
3147
			reset($this->me);
3148
			list(,$b)=each($this->me);
3149
			return $b;
3150
		}
3151
3152
		/**
3153
		* Returns the type of the xmlrpcval.
3154
		* For integers, 'int' is always returned in place of 'i4'
3155
		* @return string
3156
		* @access public
3157
		*/
3158
		function scalartyp()
3159
		{
3160
			reset($this->me);
3161
			list($a,)=each($this->me);
3162
			if($a==$GLOBALS['xmlrpcI4'])
3163
			{
3164
				$a=$GLOBALS['xmlrpcInt'];
3165
			}
3166
			return $a;
3167
		}
3168
3169
		/**
3170
		* Returns the m-th member of an xmlrpcval of struct type
3171
		* @param integer $m the index of the value to be retrieved (zero based)
3172
		* @return xmlrpcval
3173
		* @access public
3174
		*/
3175
		function arraymem($m)
3176
		{
3177
			return $this->me['array'][$m];
3178
		}
3179
3180
		/**
3181
		* Returns the number of members in an xmlrpcval of array type
3182
		* @return integer
3183
		* @access public
3184
		*/
3185
		function arraysize()
3186
		{
3187
			return count($this->me['array']);
3188
		}
3189
3190
		/**
3191
		* Returns the number of members in an xmlrpcval of struct type
3192
		* @return integer
3193
		* @access public
3194
		*/
3195
		function structsize()
3196
		{
3197
			return count($this->me['struct']);
3198
		}
3199
	}
3200
3201
3202
	// date helpers
3203
3204
	/**
3205
	* Given a timestamp, return the corresponding ISO8601 encoded string.
3206
	*
3207
	* Really, timezones ought to be supported
3208
	* but the XML-RPC spec says:
3209
	*
3210
	* "Don't assume a timezone. It should be specified by the server in its
3211
	* documentation what assumptions it makes about timezones."
3212
	*
3213
	* These routines always assume localtime unless
3214
	* $utc is set to 1, in which case UTC is assumed
3215
	* and an adjustment for locale is made when encoding
3216
	*
3217
	* @param int $timet (timestamp)
3218
	* @param int $utc (0 or 1)
3219
	* @return string
3220
	*/
3221
	function iso8601_encode($timet, $utc=0)
3222
	{
3223
		if(!$utc)
3224
		{
3225
			$t=strftime("%Y%m%dT%H:%M:%S", $timet);
3226
		}
3227
		else
3228
		{
3229
			if(function_exists('gmstrftime'))
3230
			{
3231
				// gmstrftime doesn't exist in some versions
3232
				// of PHP
3233
				$t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
3234
			}
3235
			else
3236
			{
3237
				$t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
3238
			}
3239
		}
3240
		return $t;
3241
	}
3242
3243
	/**
3244
	* Given an ISO8601 date string, return a timet in the localtime, or UTC
3245
	* @param string $idate
3246
	* @param int $utc either 0 or 1
3247
	* @return int (datetime)
3248
	*/
3249
	function iso8601_decode($idate, $utc=0)
3250
	{
3251
		$t=0;
3252
		if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs))
3253
		{
3254
			if($utc)
3255
			{
3256
				$t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3257
			}
3258
			else
3259
			{
3260
				$t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3261
			}
3262
		}
3263
		return $t;
3264
	}
3265
3266
	/**
3267
	* Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types.
3268
	*
3269
	* Works with xmlrpc message objects as input, too.
3270
	*
3271
	* Given proper options parameter, can rebuild generic php object instances
3272
	* (provided those have been encoded to xmlrpc format using a corresponding
3273
	* option in php_xmlrpc_encode())
3274
	* PLEASE NOTE that rebuilding php objects involves calling their constructor function.
3275
	* This means that the remote communication end can decide which php code will
3276
	* get executed on your server, leaving the door possibly open to 'php-injection'
3277
	* style of attacks (provided you have some classes defined on your server that
3278
	* might wreak havoc if instances are built outside an appropriate context).
3279
	* Make sure you trust the remote server/client before eanbling this!
3280
	*
3281
	* @author Dan Libby ([email protected])
3282
	*
3283
	* @param xmlrpcval $xmlrpc_val
3284
	* @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects (standard is
3285
	* @return mixed
3286
	*/
3287
	function php_xmlrpc_decode($xmlrpc_val, $options=array())
3288
	{
3289
		switch($xmlrpc_val->kindOf())
3290
		{
3291
			case 'scalar':
3292
				if (in_array('extension_api', $options))
3293
				{
3294
					reset($xmlrpc_val->me);
3295
					list($typ,$val) = each($xmlrpc_val->me);
3296
					switch ($typ)
3297
					{
3298
						case 'dateTime.iso8601':
3299
							$xmlrpc_val->scalar = $val;
3300
							$xmlrpc_val->xmlrpc_type = 'datetime';
3301
							$xmlrpc_val->timestamp = iso8601_decode($val);
3302
							return $xmlrpc_val;
3303
						case 'base64':
3304
							$xmlrpc_val->scalar = $val;
3305
							$xmlrpc_val->type = $typ;
3306
							return $xmlrpc_val;
3307
						default:
3308
							return $xmlrpc_val->scalarval();
3309
					}
3310
				}
3311
				if (in_array('dates_as_objects', $options) && $xmlrpc_val->scalartyp() == 'dateTime.iso8601')
3312
				{
3313
					// we return a Datetime object instead of a string
3314
					// since now the constructor of xmlrpcval accepts safely strings, ints and datetimes,
3315
					// we cater to all 3 cases here
3316
					$out = $xmlrpc_val->scalarval();
3317
					if (is_string($out))
3318
					{
3319
						$out = strtotime($out);
3320
					}
3321
					if (is_int($out))
3322
					{
3323
						$result = new Datetime();
3324
						$result->setTimestamp($out);
3325
						return $result;
3326
					}
3327
					elseif (is_a($out, 'Datetime'))
3328
					{
3329
						return $out;
3330
					}
3331
				}
3332
				return $xmlrpc_val->scalarval();
3333
			case 'array':
3334
				$size = $xmlrpc_val->arraysize();
3335
				$arr = array();
3336
				for($i = 0; $i < $size; $i++)
3337
				{
3338
					$arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
3339
				}
3340
				return $arr;
3341
			case 'struct':
3342
				$xmlrpc_val->structreset();
3343
				// If user said so, try to rebuild php objects for specific struct vals.
3344
				/// @todo should we raise a warning for class not found?
3345
				// shall we check for proper subclass of xmlrpcval instead of
3346
				// presence of _php_class to detect what we can do?
3347
				if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
3348
					&& class_exists($xmlrpc_val->_php_class))
3349
				{
3350
					$obj = @new $xmlrpc_val->_php_class;
3351
					while(list($key,$value)=$xmlrpc_val->structeach())
3352
					{
3353
						$obj->$key = php_xmlrpc_decode($value, $options);
3354
					}
3355
					return $obj;
3356
				}
3357
				else
3358
				{
3359
					$arr = array();
3360
					while(list($key,$value)=$xmlrpc_val->structeach())
3361
					{
3362
						$arr[$key] = php_xmlrpc_decode($value, $options);
3363
					}
3364
					return $arr;
3365
				}
3366
			case 'msg':
3367
				$paramcount = $xmlrpc_val->getNumParams();
3368
				$arr = array();
3369
				for($i = 0; $i < $paramcount; $i++)
3370
				{
3371
					$arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
3372
				}
3373
				return $arr;
3374
			}
3375
	}
3376
3377
	// This constant left here only for historical reasons...
3378
	// it was used to decide if we have to define xmlrpc_encode on our own, but
3379
	// we do not do it anymore
3380
	if(function_exists('xmlrpc_decode'))
3381
	{
3382
		define('XMLRPC_EPI_ENABLED','1');
3383
	}
3384
	else
3385
	{
3386
		define('XMLRPC_EPI_ENABLED','0');
3387
	}
3388
3389
	/**
3390
	* Takes native php types and encodes them into xmlrpc PHP object format.
3391
	* It will not re-encode xmlrpcval objects.
3392
	*
3393
	* Feature creep -- could support more types via optional type argument
3394
	* (string => datetime support has been added, ??? => base64 not yet)
3395
	*
3396
	* If given a proper options parameter, php object instances will be encoded
3397
	* into 'special' xmlrpc values, that can later be decoded into php objects
3398
	* by calling php_xmlrpc_decode() with a corresponding option
3399
	*
3400
	* @author Dan Libby ([email protected])
3401
	*
3402
	* @param mixed $php_val the value to be converted into an xmlrpcval object
3403
	* @param array $options	can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
3404
	* @return xmlrpcval
3405
	*/
3406
	function php_xmlrpc_encode($php_val, $options=array())
3407
	{
3408
		$type = gettype($php_val);
3409
		switch($type)
3410
		{
3411
			case 'string':
3412
				if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))
3413
					$xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDateTime']);
3414
				else
3415
					$xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcString']);
3416
				break;
3417
			case 'integer':
3418
				$xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcInt']);
3419
				break;
3420
			case 'double':
3421
				$xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDouble']);
3422
				break;
3423
				// <G_Giunta_2001-02-29>
3424
				// Add support for encoding/decoding of booleans, since they are supported in PHP
3425
			case 'boolean':
3426
				$xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcBoolean']);
3427
				break;
3428
				// </G_Giunta_2001-02-29>
3429
			case 'array':
3430
				// PHP arrays can be encoded to either xmlrpc structs or arrays,
3431
				// depending on wheter they are hashes or plain 0..n integer indexed
3432
				// A shorter one-liner would be
3433
				// $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
3434
				// but execution time skyrockets!
3435
				$j = 0;
3436
				$arr = array();
3437
				$ko = false;
3438
				foreach($php_val as $key => $val)
3439
				{
3440
					$arr[$key] = php_xmlrpc_encode($val, $options);
3441
					if(!$ko && $key !== $j)
3442
					{
3443
						$ko = true;
3444
					}
3445
					$j++;
3446
				}
3447
				if($ko)
3448
				{
3449
					$xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3450
				}
3451
				else
3452
				{
3453
					$xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcArray']);
3454
				}
3455
				break;
3456
			case 'object':
3457
				if(is_a($php_val, 'xmlrpcval'))
3458
				{
3459
					$xmlrpc_val = $php_val;
3460
				}
3461
				else if(is_a($php_val, 'DateTime'))
3462
				{
3463
					$xmlrpc_val = new xmlrpcval($php_val->format('Ymd\TH:i:s'), $GLOBALS['xmlrpcStruct']);
3464
				}
3465
				else
3466
				{
3467
					$arr = array();
3468
					reset($php_val);
3469
					while(list($k,$v) = each($php_val))
3470
					{
3471
						$arr[$k] = php_xmlrpc_encode($v, $options);
3472
					}
3473
					$xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3474
					if (in_array('encode_php_objs', $options))
3475
					{
3476
						// let's save original class name into xmlrpcval:
3477
						// might be useful later on...
3478
						$xmlrpc_val->_php_class = get_class($php_val);
3479
					}
3480
				}
3481
				break;
3482
			case 'NULL':
3483
				if (in_array('extension_api', $options))
3484
				{
3485
					$xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcString']);
3486
				}
3487
				else if (in_array('null_extension', $options))
3488
				{
3489
					$xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcNull']);
3490
				}
3491
				else
3492
				{
3493
					$xmlrpc_val = new xmlrpcval();
3494
				}
3495
				break;
3496
			case 'resource':
3497
				if (in_array('extension_api', $options))
3498
				{
3499
					$xmlrpc_val = new xmlrpcval((int)$php_val, $GLOBALS['xmlrpcInt']);
3500
				}
3501
				else
3502
				{
3503
					$xmlrpc_val = new xmlrpcval();
3504
				}
3505
			// catch "user function", "unknown type"
3506
			default:
3507
				// giancarlo pinerolo <[email protected]>
3508
				// it has to return
3509
				// an empty object in case, not a boolean.
3510
				$xmlrpc_val = new xmlrpcval();
3511
				break;
3512
			}
3513
			return $xmlrpc_val;
3514
	}
3515
3516
	/**
3517
	* Convert the xml representation of a method response, method request or single
3518
	* xmlrpc value into the appropriate object (a.k.a. deserialize)
3519
	* @param string $xml_val
3520
	* @param array $options
3521
	* @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp
3522
	*/
3523
	function php_xmlrpc_decode_xml($xml_val, $options=array())
3524
	{
3525
		$GLOBALS['_xh'] = array();
3526
		$GLOBALS['_xh']['ac'] = '';
3527
		$GLOBALS['_xh']['stack'] = array();
3528
		$GLOBALS['_xh']['valuestack'] = array();
3529
		$GLOBALS['_xh']['params'] = array();
3530
		$GLOBALS['_xh']['pt'] = array();
3531
		$GLOBALS['_xh']['isf'] = 0;
3532
		$GLOBALS['_xh']['isf_reason'] = '';
3533
		$GLOBALS['_xh']['method'] = false;
3534
		$GLOBALS['_xh']['rt'] = '';
3535
		/// @todo 'guestimate' encoding
3536
		$parser = xml_parser_create();
3537
		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
3538
		// What if internal encoding is not in one of the 3 allowed?
3539
		// we use the broadest one, ie. utf8!
3540
		if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
3541
		{
3542
			xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
3543
		}
3544
		else
3545
		{
3546
			xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
3547
		}
3548
		xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
3549
		xml_set_character_data_handler($parser, 'xmlrpc_cd');
3550
		xml_set_default_handler($parser, 'xmlrpc_dh');
3551
		if(!xml_parse($parser, $xml_val, 1))
3552
		{
3553
			$errstr = sprintf('XML error: %s at line %d, column %d',
3554
						xml_error_string(xml_get_error_code($parser)),
3555
						xml_get_current_line_number($parser), xml_get_current_column_number($parser));
3556
			error_log($errstr);
3557
			xml_parser_free($parser);
3558
			return false;
3559
		}
3560
		xml_parser_free($parser);
3561
		if ($GLOBALS['_xh']['isf'] > 1) // test that $GLOBALS['_xh']['value'] is an obj, too???
3562
		{
3563
			error_log($GLOBALS['_xh']['isf_reason']);
3564
			return false;
3565
		}
3566
		switch ($GLOBALS['_xh']['rt'])
3567
		{
3568
			case 'methodresponse':
3569
				$v =& $GLOBALS['_xh']['value'];
3570
				if ($GLOBALS['_xh']['isf'] == 1)
3571
				{
3572
					$vc = $v->structmem('faultCode');
3573
					$vs = $v->structmem('faultString');
3574
					$r = new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval());
3575
				}
3576
				else
3577
				{
3578
					$r = new xmlrpcresp($v);
3579
				}
3580
				return $r;
3581
			case 'methodcall':
3582
				$m = new xmlrpcmsg($GLOBALS['_xh']['method']);
3583
				for($i=0; $i < count($GLOBALS['_xh']['params']); $i++)
3584
				{
3585
					$m->addParam($GLOBALS['_xh']['params'][$i]);
3586
				}
3587
				return $m;
3588
			case 'value':
3589
				return $GLOBALS['_xh']['value'];
3590
			default:
3591
				return false;
3592
		}
3593
	}
3594
3595
	/**
3596
	* decode a string that is encoded w/ "chunked" transfer encoding
3597
	* as defined in rfc2068 par. 19.4.6
3598
	* code shamelessly stolen from nusoap library by Dietrich Ayala
3599
	*
3600
	* @param string $buffer the string to be decoded
3601
	* @return string
3602
	*/
3603
	function decode_chunked($buffer)
3604
	{
3605
		// length := 0
3606
		$length = 0;
3607
		$new = '';
3608
3609
		// read chunk-size, chunk-extension (if any) and crlf
3610
		// get the position of the linebreak
3611
		$chunkend = strpos($buffer,"\r\n") + 2;
3612
		$temp = substr($buffer,0,$chunkend);
3613
		$chunk_size = hexdec( trim($temp) );
3614
		$chunkstart = $chunkend;
3615
		while($chunk_size > 0)
3616
		{
3617
			$chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
3618
3619
			// just in case we got a broken connection
3620
			if($chunkend == false)
3621
			{
3622
				$chunk = substr($buffer,$chunkstart);
3623
				// append chunk-data to entity-body
3624
				$new .= $chunk;
3625
				$length += strlen($chunk);
3626
				break;
3627
			}
3628
3629
			// read chunk-data and crlf
3630
			$chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3631
			// append chunk-data to entity-body
3632
			$new .= $chunk;
3633
			// length := length + chunk-size
3634
			$length += strlen($chunk);
3635
			// read chunk-size and crlf
3636
			$chunkstart = $chunkend + 2;
3637
3638
			$chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
3639
			if($chunkend == false)
3640
			{
3641
				break; //just in case we got a broken connection
3642
			}
3643
			$temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3644
			$chunk_size = hexdec( trim($temp) );
3645
			$chunkstart = $chunkend;
3646
		}
3647
		return $new;
3648
	}
3649
3650
	/**
3651
	* xml charset encoding guessing helper function.
3652
	* Tries to determine the charset encoding of an XML chunk received over HTTP.
3653
	* NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
3654
	* we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
3655
	* which will be most probably using UTF-8 anyway...
3656
	*
3657
	* @param string $httpheaders the http Content-type header
3658
	* @param string $xmlchunk xml content buffer
3659
	* @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
3660
	*
3661
	* @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
3662
	*/
3663
	function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
3664
	{
3665
		// discussion: see http://www.yale.edu/pclt/encoding/
3666
		// 1 - test if encoding is specified in HTTP HEADERS
3667
3668
		//Details:
3669
		// LWS:           (\13\10)?( |\t)+
3670
		// token:         (any char but excluded stuff)+
3671
		// quoted string: " (any char but double quotes and cointrol chars)* "
3672
		// header:        Content-type = ...; charset=value(; ...)*
3673
		//   where value is of type token, no LWS allowed between 'charset' and value
3674
		// Note: we do not check for invalid chars in VALUE:
3675
		//   this had better be done using pure ereg as below
3676
		// Note 2: we might be removing whitespace/tabs that ought to be left in if
3677
		//   the received charset is a quoted string. But nobody uses such charset names...
3678
3679
		/// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
3680
		$matches = array();
3681
		if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches))
3682
		{
3683
			return strtoupper(trim($matches[1], " \t\""));
3684
		}
3685
3686
		// 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
3687
		//     (source: http://www.w3.org/TR/2000/REC-xml-20001006)
3688
		//     NOTE: actually, according to the spec, even if we find the BOM and determine
3689
		//     an encoding, we should check if there is an encoding specified
3690
		//     in the xml declaration, and verify if they match.
3691
		/// @todo implement check as described above?
3692
		/// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
3693
		if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))
3694
		{
3695
			return 'UCS-4';
3696
		}
3697
		elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))
3698
		{
3699
			return 'UTF-16';
3700
		}
3701
		elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))
3702
		{
3703
			return 'UTF-8';
3704
		}
3705
3706
		// 3 - test if encoding is specified in the xml declaration
3707
		// Details:
3708
		// SPACE:         (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
3709
		// EQ:            SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
3710
		if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".
3711
			'\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
3712
			$xmlchunk, $matches))
3713
		{
3714
			return strtoupper(substr($matches[2], 1, -1));
3715
		}
3716
3717
		// 4 - if mbstring is available, let it do the guesswork
3718
		// NB: we favour finding an encoding that is compatible with what we can process
3719
		if(extension_loaded('mbstring'))
3720
		{
3721
			if($encoding_prefs)
3722
			{
3723
				$enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
3724
			}
3725
			else
3726
			{
3727
				$enc = mb_detect_encoding($xmlchunk);
3728
			}
3729
			// NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
3730
			// IANA also likes better US-ASCII, so go with it
3731
			if($enc == 'ASCII')
3732
			{
3733
				$enc = 'US-'.$enc;
3734
			}
3735
			return $enc;
3736
		}
3737
		else
3738
		{
3739
			// no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
3740
			// Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
3741
			// this should be the standard. And we should be getting text/xml as request and response.
3742
			// BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
3743
			return $GLOBALS['xmlrpc_defencoding'];
3744
		}
3745
	}
3746
3747
	/**
3748
	* Checks if a given charset encoding is present in a list of encodings or
3749
	* if it is a valid subset of any encoding in the list
3750
	* @param string $encoding charset to be tested
3751
	* @param mixed $validlist comma separated list of valid charsets (or array of charsets)
3752
	*/
3753
	function is_valid_charset($encoding, $validlist)
3754
	{
3755
		$charset_supersets = array(
3756
			'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
3757
				'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
3758
				'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
3759
				'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
3760
				'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
3761
		);
3762
		if (is_string($validlist))
3763
			$validlist = explode(',', $validlist);
3764
		if (@in_array(strtoupper($encoding), $validlist))
3765
			return true;
3766
		else
3767
		{
3768
			if (array_key_exists($encoding, $charset_supersets))
3769
				foreach ($validlist as $allowed)
3770
					if (in_array($allowed, $charset_supersets[$encoding]))
3771
						return true;
3772
				return false;
3773
		}
3774
	}
3775
3776
?>