2 // by Edd Dumbill (C) 1999-2002
4 // $Id: xmlrpc.inc,v 1.113 2006/01/22 23:55:57 ggiunta Exp $
7 // Copyright (c) 1999,2000,2002 Edd Dumbill.
8 // All rights reserved.
10 // Redistribution and use in source and binary forms, with or without
11 // modification, are permitted provided that the following conditions
14 // * Redistributions of source code must retain the above copyright
15 // notice, this list of conditions and the following disclaimer.
17 // * Redistributions in binary form must reproduce the above
18 // copyright notice, this list of conditions and the following
19 // disclaimer in the documentation and/or other materials provided
20 // with the distribution.
22 // * Neither the name of the "XML-RPC for PHP" nor the names of its
23 // contributors may be used to endorse or promote products derived
24 // from this software without specific prior written permission.
26 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
29 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
30 // REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
32 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
37 // OF THE POSSIBILITY OF SUCH DAMAGE.
39 if(!function_exists('xml_parser_create'))
41 // For PHP 4 onward, XML functionality is always compiled-in on windows:
42 // no more need to dl-open it. It might have been compiled out on *nix...
43 if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN'))
49 // Try to be backward compat with php < 4.2 (are we not being nice ?)
50 if(substr(phpversion(), 0, 3) == '4.0' || @version_compare(substr(phpversion(), 0, 3) == '4.1'))
52 // give an opportunity to user to specify where to include other files from
53 if(!defined('PHP_XMLRPC_COMPAT_DIR'))
55 define('PHP_XMLRPC_COMPAT_DIR',dirname(__FILE__).'/compat/');
57 if(substr(phpversion(), 0, 3) == '4.0')
59 include(PHP_XMLRPC_COMPAT_DIR."is_scalar.php");
60 include(PHP_XMLRPC_COMPAT_DIR."array_key_exists.php");
61 include(PHP_XMLRPC_COMPAT_DIR."version_compare.php");
63 include(PHP_XMLRPC_COMPAT_DIR."var_export.php");
64 include(PHP_XMLRPC_COMPAT_DIR."is_a.php");
67 // G. Giunta 2005/01/29: declare global these variables,
68 // so that xmlrpc.inc will work even if included from within a function
69 // Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used.
70 $GLOBALS['xmlrpcI4']='i4';
71 $GLOBALS['xmlrpcInt']='int';
72 $GLOBALS['xmlrpcBoolean']='boolean';
73 $GLOBALS['xmlrpcDouble']='double';
74 $GLOBALS['xmlrpcString']='string';
75 $GLOBALS['xmlrpcDateTime']='dateTime.iso8601';
76 $GLOBALS['xmlrpcBase64']='base64';
77 $GLOBALS['xmlrpcArray']='array';
78 $GLOBALS['xmlrpcStruct']='struct';
79 $GLOBALS['xmlrpcValue']='undefined';
81 $GLOBALS['xmlrpcTypes']=array(
82 $GLOBALS['xmlrpcI4'] => 1,
83 $GLOBALS['xmlrpcInt'] => 1,
84 $GLOBALS['xmlrpcBoolean'] => 1,
85 $GLOBALS['xmlrpcString'] => 1,
86 $GLOBALS['xmlrpcDouble'] => 1,
87 $GLOBALS['xmlrpcDateTime'] => 1,
88 $GLOBALS['xmlrpcBase64'] => 1,
89 $GLOBALS['xmlrpcArray'] => 2,
90 $GLOBALS['xmlrpcStruct'] => 3
93 $GLOBALS['xmlrpc_valid_parents'] = array(
94 'BOOLEAN' => array('VALUE'),
95 'I4' => array('VALUE'),
96 'INT' => array('VALUE'),
97 'STRING' => array('VALUE'),
98 'DOUBLE' => array('VALUE'),
99 'DATETIME.ISO8601' => array('VALUE'),
100 'BASE64' => array('VALUE'),
101 'ARRAY' => array('VALUE'),
102 'STRUCT' => array('VALUE'),
103 'PARAM' => array('PARAMS'),
104 'METHODNAME' => array('METHODCALL'),
105 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
106 'MEMBER' => array('STRUCT'),
107 'NAME' => array('MEMBER'),
108 'DATA' => array('ARRAY'),
109 'FAULT' => array('METHODRESPONSE'),
110 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
113 $GLOBALS['xmlEntities']=array(
121 // tables used for transcoding different charsets into us-ascii xml
123 $GLOBALS['xml_iso88591_Entities']=array();
124 $GLOBALS['xml_iso88591_Entities']['in'] = array();
125 $GLOBALS['xml_iso88591_Entities']['out'] = array();
126 for ($i = 0; $i < 32; $i++)
129 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
130 $GLOBALS['xml_iso88591_Entities']['out'][] = "&#$i;";
132 for ($i = 160; $i < 256; $i++)
134 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
135 $GLOBALS['xml_iso88591_Entities']['out'][] = "&#$i;";
138 /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159.
139 /// These will NOT be present in true ISO-8859-1, but will save the unwary
140 /// windows user from sending junk.
144 '\x80'=>'€', '\x81'=>'?', '\x82'=>'‚', '\x83'=>'ƒ',
145 '\x84'=>'„', '\x85'=>'…', '\x86'=>'†', \x87'=>'‡',
146 '\x88'=>'ˆ', '\x89'=>'‰', '\x8A'=>'Š', '\x8B'=>'‹',
147 '\x8C'=>'Œ', '\x8D'=>'?', '\x8E'=>'Ž', '\x8F'=>'?',
148 '\x90'=>'?', '\x91'=>'‘', '\x92'=>'’', '\x93'=>'“',
149 '\x94'=>'”', '\x95'=>'•', '\x96'=>'–', '\x97'=>'—',
150 '\x98'=>'˜', '\x99'=>'™', '\x9A'=>'š', '\x9B'=>'›',
151 '\x9C'=>'œ', '\x9D'=>'?', '\x9E'=>'ž', '\x9F'=>'Ÿ'
155 $GLOBALS['xmlrpcerr']['unknown_method']=1;
156 $GLOBALS['xmlrpcstr']['unknown_method']='Unknown method';
157 $GLOBALS['xmlrpcerr']['invalid_return']=2;
158 $GLOBALS['xmlrpcstr']['invalid_return']='Invalid return payload: enable debugging to examine incoming payload';
159 $GLOBALS['xmlrpcerr']['incorrect_params']=3;
160 $GLOBALS['xmlrpcstr']['incorrect_params']='Incorrect parameters passed to method';
161 $GLOBALS['xmlrpcerr']['introspect_unknown']=4;
162 $GLOBALS['xmlrpcstr']['introspect_unknown']="Can't introspect: method unknown";
163 $GLOBALS['xmlrpcerr']['http_error']=5;
164 $GLOBALS['xmlrpcstr']['http_error']="Didn't receive 200 OK from remote server.";
165 $GLOBALS['xmlrpcerr']['no_data']=6;
166 $GLOBALS['xmlrpcstr']['no_data']='No data received from server.';
167 $GLOBALS['xmlrpcerr']['no_ssl']=7;
168 $GLOBALS['xmlrpcstr']['no_ssl']='No SSL support compiled in.';
169 $GLOBALS['xmlrpcerr']['curl_fail']=8;
170 $GLOBALS['xmlrpcstr']['curl_fail']='CURL error';
171 $GLOBALS['xmlrpcerr']['invalid_request']=15;
172 $GLOBALS['xmlrpcstr']['invalid_request']='Invalid request payload';
173 $GLOBALS['xmlrpcerr']['no_curl']=16;
174 $GLOBALS['xmlrpcstr']['no_curl']='No CURL support compiled in.';
175 $GLOBALS['xmlrpcerr']['server_error']=17;
176 $GLOBALS['xmlrpcstr']['server_error']='Internal server error';
177 $GLOBALS['xmlrpcerr']['multicall_error']=18;
178 $GLOBALS['xmlrpcstr']['multicall_error']='Received from server invalid multicall response';
180 $GLOBALS['xmlrpcerr']['multicall_notstruct'] = 9;
181 $GLOBALS['xmlrpcstr']['multicall_notstruct'] = 'system.multicall expected struct';
182 $GLOBALS['xmlrpcerr']['multicall_nomethod'] = 10;
183 $GLOBALS['xmlrpcstr']['multicall_nomethod'] = 'missing methodName';
184 $GLOBALS['xmlrpcerr']['multicall_notstring'] = 11;
185 $GLOBALS['xmlrpcstr']['multicall_notstring'] = 'methodName is not a string';
186 $GLOBALS['xmlrpcerr']['multicall_recursion'] = 12;
187 $GLOBALS['xmlrpcstr']['multicall_recursion'] = 'recursive system.multicall forbidden';
188 $GLOBALS['xmlrpcerr']['multicall_noparams'] = 13;
189 $GLOBALS['xmlrpcstr']['multicall_noparams'] = 'missing params';
190 $GLOBALS['xmlrpcerr']['multicall_notarray'] = 14;
191 $GLOBALS['xmlrpcstr']['multicall_notarray'] = 'params is not an array';
193 $GLOBALS['xmlrpcerr']['cannot_decompress']=103;
194 $GLOBALS['xmlrpcstr']['cannot_decompress']='Received from server compressed HTTP and cannot decompress';
195 $GLOBALS['xmlrpcerr']['decompress_fail']=104;
196 $GLOBALS['xmlrpcstr']['decompress_fail']='Received from server invalid compressed HTTP';
197 $GLOBALS['xmlrpcerr']['dechunk_fail']=105;
198 $GLOBALS['xmlrpcstr']['dechunk_fail']='Received from server invalid chunked HTTP';
199 $GLOBALS['xmlrpcerr']['server_cannot_decompress']=106;
200 $GLOBALS['xmlrpcstr']['server_cannot_decompress']='Received from client compressed HTTP request and cannot decompress';
201 $GLOBALS['xmlrpcerr']['server_decompress_fail']=107;
202 $GLOBALS['xmlrpcstr']['server_decompress_fail']='Received from client invalid compressed HTTP request';
204 // The charset encoding used by the server for received messages and
205 // by the client for received responses when received charset cannot be determined
206 // or is not supported
207 $GLOBALS['xmlrpc_defencoding']='UTF-8';
208 // The encoding used internally by PHP.
209 // String values received as xml will be converted to this, and php strings will be converted to xml
211 // as if having been coded with this
212 $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1';
214 $GLOBALS['xmlrpcName']='XML-RPC for PHP';
215 $GLOBALS['xmlrpcVersion']='2.0RC3';
217 // let user errors start at 800
218 $GLOBALS['xmlrpcerruser']=800;
219 // let XML parse errors start at 100
220 $GLOBALS['xmlrpcerrxml']=100;
222 // formulate backslashes for escaping regexp
223 $GLOBALS['xmlrpc_backslash']=chr(92).chr(92);
225 // used to store state during parsing
226 // quick explanation of components:
227 // ac - used to accumulate values
228 // isf - used to indicate a fault
229 // lv - used to indicate "looking for a value": implements
230 // the logic to allow values with no types to be strings
231 // params - used to store parameters in method calls
232 // method - used to store method name
233 // stack - array with genealogy of xml elements names:
234 // used to validate nesting of xmlrpc elements
236 $GLOBALS['_xh']=null;
239 * Convert a string to the correct XML representation in a target charset
240 * To help correct communication of non-ascii chars inside strings, regardless
241 * of the charset used when sending requests, parsing them, sending responses
242 * and parsing responses, an option is to convert all non-ascii chars present in the message
243 * into their equivalent 'charset entity'. Charset entities enumerated this way
244 * are independent of the charset encoding used to transmit them, and all XML
245 * parsers are bound to understand them.
246 * Note that in the std case we are not sending a charset encoding mime type
247 * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
249 * @todo do a bit of basic benchmarking (strtr vs. str_replace)
250 * @todo make usage of iconv() or recode_string() or mb_string() where available
252 function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')
254 if ($src_encoding == '')
256 // lame, but we know no better...
257 $src_encoding = $GLOBALS['xmlrpc_internalencoding'];
259 //if ($dest_encoding == '')
261 // // lame, but we know no better...
262 // $dest_encoding = 'US-ASCII';
264 switch(strtoupper($src_encoding.'_'.$dest_encoding))
267 case 'ISO-8859-1_US-ASCII':
268 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
269 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
271 case 'ISO-8859-1_UTF-8':
272 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
273 $escaped_data = utf8_encode($escaped_data);
275 case 'ISO-8859-1_ISO-8859-1':
276 case 'US-ASCII_US-ASCII':
277 case 'US-ASCII_UTF-8':
279 case 'US-ASCII_ISO-8859-1':
281 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
284 case 'UTF-8_US-ASCII':
285 case 'UTF-8_ISO-8859-1':
286 // NB: this will choke on invalid UTF-8, going most likely beyond EOF
288 // be kind to users creating string xmlrpcvals out of different php types
289 $data = (string) $data;
290 $ns = strlen ($data);
291 for ($nn = 0; $nn < $ns; $nn++)
298 /// @todo shall we replace this with a (suuposedly) faster str_replace?
301 $escaped_data .= '"';
304 $escaped_data .= '&';
306 $escaped_data .= ''';
309 $escaped_data .= '<';
312 $escaped_data .= '>';
315 $escaped_data .= $ch;
318 //2 11 110bbbbb 10bbbbbb (2047)
319 else if ($ii>>5 == 6)
322 $ii = ord($data[$nn+1]);
324 $ii = ($b1 * 64) + $b2;
325 $ent = sprintf ("&#%d;", $ii);
326 $escaped_data .= $ent;
328 //3 16 1110bbbb 10bbbbbb 10bbbbbb
329 else if ($ii>>4 == 14)
332 $ii = ord($data[$nn+1]);
334 $ii = ord($data[$nn+2]);
336 $ii = ((($b1 * 64) + $b2) * 64) + $b3;
337 $ent = sprintf ("&#%d;", $ii);
338 $escaped_data .= $ent;
340 //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
341 else if ($ii>>3 == 30)
344 $ii = ord($data[$nn+1]);
346 $ii = ord($data[$nn+2]);
348 $ii = ord($data[$nn+3]);
350 $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
351 $ent = sprintf ("&#%d;", $ii);
352 $escaped_data .= $ent;
358 error_log("Converting from $src_encoding to $dest_encoding: not supported...");
361 return $escaped_data;
364 function xmlrpc_se($parser, $name, $attrs)
366 // if invalid xmlrpc already detected, skip all processing
367 if ($GLOBALS['_xh']['isf'] < 2)
369 // check for correct element nesting
370 // top level element can only be of 2 types
371 if (count($GLOBALS['_xh']['stack']) == 0)
373 if ($name != 'METHODRESPONSE' && $name != 'METHODCALL')
375 $GLOBALS['_xh']['isf'] = 2;
376 $GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element';
382 // not top level element: see if parent is OK
383 $parent = end($GLOBALS['_xh']['stack']);
384 if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name]))
386 $GLOBALS['_xh']['isf'] = 2;
387 $GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
396 // create an empty array to hold child values, and push it onto appropriate stack
398 $cur_val['values'] = array();
399 $cur_val['type'] = $name;
400 // check for out-of-band information to rebuild php objs
401 // and in case it is found, save it
402 if (@isset($attrs['PHP_CLASS']))
404 $cur_val['php_class'] = $attrs['PHP_CLASS'];
406 $GLOBALS['_xh']['valuestack'][] = $cur_val;
410 case 'METHODRESPONSE':
412 // valid elements that add little to processing
416 $GLOBALS['_xh']['ac']='';
419 $GLOBALS['_xh']['isf']=1;
422 $GLOBALS['_xh']['vt']='value'; // indicator: no value found yet
423 $GLOBALS['_xh']['ac']='';
424 $GLOBALS['_xh']['lv']=1;
425 $GLOBALS['_xh']['php_class']=null;
432 case 'DATETIME.ISO8601':
434 if ($GLOBALS['_xh']['vt']!='value')
436 //two data elements inside a value: an error occurred!
437 $GLOBALS['_xh']['isf'] = 2;
438 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
442 $GLOBALS['_xh']['ac']=''; // reset the accumulator
445 $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
446 //$GLOBALS['_xh']['ac']='';
447 // Drop trough intentionally
449 // clear value type, so we can check later if no value has been passed for this param/member
450 $GLOBALS['_xh']['vt']=null;
453 /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
454 $GLOBALS['_xh']['isf'] = 2;
455 $GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name";
459 // Save current element name to stack, to validate nesting
460 $GLOBALS['_xh']['stack'][] = $name;
464 $GLOBALS['_xh']['lv']=0;
469 function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)
471 if ($GLOBALS['_xh']['isf'] < 2)
473 // push this element name from stack
474 // NB: if XML validates, correct opening/closing is guaranteed and
475 // we do not have to check for $name == $curr_elem.
476 // we also checked for proper nesting at start of elements...
477 $curr_elem = array_pop($GLOBALS['_xh']['stack']);
483 // fetch out of stack array of values, and promote it to current value
484 $curr_val = array_pop($GLOBALS['_xh']['valuestack']);
485 $GLOBALS['_xh']['value'] = $curr_val['values'];
486 $GLOBALS['_xh']['vt']=strtolower($name);
487 if (isset($curr_val['php_class']))
489 $GLOBALS['_xh']['php_class'] = $curr_val['php_class'];
493 $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac'];
500 case 'DATETIME.ISO8601':
502 $GLOBALS['_xh']['vt']=strtolower($name);
505 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
507 elseif ($name=='DATETIME.ISO8601')
509 /// @todo validate datetime values with a correct format mask?
510 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime'];
511 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
513 elseif ($name=='BASE64')
515 /// @todo check for failure of base64 decoding / catch warnings
516 $GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']);
518 elseif ($name=='BOOLEAN')
520 // special case here: we translate boolean 1 or 0 into PHP
521 // constants true or false
522 // NB: this simple checks helps a lot sanitizing input, ie no
523 // security problems around here
524 if ($GLOBALS['_xh']['ac']=='1')
526 $GLOBALS['_xh']['value']=true;
530 // log if receiveing something strange, even though we set the value to false anyway
531 if ($GLOBALS['_xh']['ac']!='0')
532 error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']);
533 $GLOBALS['_xh']['value']=false;
536 elseif ($name=='DOUBLE')
539 // we must check that only 0123456789-.<space> are characters here
540 if (!ereg("^[+-]?[eE0123456789 \\t.]+$", $GLOBALS['_xh']['ac']))
542 /// @todo: find a better way of throwing an error
544 error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']);
545 $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
549 // it's ok, add it on
550 $GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac'];
556 // we must check that only 0123456789-<space> are characters here
557 if (!ereg("^[+-]?[0123456789 \\t]+$", $GLOBALS['_xh']['ac']))
559 /// @todo find a better way of throwing an error
561 error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']);
562 $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
566 // it's ok, add it on
567 $GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac'];
570 $GLOBALS['_xh']['ac']=''; // is this necessary?
571 $GLOBALS['_xh']['lv']=3; // indicate we've found a value
574 // This if() detects if no scalar was inside <VALUE></VALUE>
575 if ($GLOBALS['_xh']['vt']=='value')
577 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
578 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString'];
581 if ($rebuild_xmlrpcvals)
583 // build the xmlrpc val out of the data received, and substitute it
584 $temp =& new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']);
585 // in case we got info about underlying php class, save it
586 // in the object we're rebuilding
587 if (isset($GLOBALS['_xh']['php_class']))
588 $temp->_php_class = $GLOBALS['_xh']['php_class'];
589 // check if we are inside an array or struct:
590 // if value just built is inside an array, let's move it into array on the stack
591 $vscount = count($GLOBALS['_xh']['valuestack']);
592 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
594 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp;
598 $GLOBALS['_xh']['value'] = $temp;
603 /// @todo this needs to treat correctly php-serialized objects,
604 /// since std deserializing is done by php_xmlrpc_decode,
605 /// which we will not be calling...
606 if (isset($GLOBALS['_xh']['php_class']))
610 // check if we are inside an array or struct:
611 // if value just built is inside an array, let's move it into array on the stack
612 $vscount = count($GLOBALS['_xh']['valuestack']);
613 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
615 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value'];
620 $GLOBALS['_xh']['ac']=''; // is this necessary?
621 // add to array in the stack the last element built,
622 // unless no VALUE was found
623 if ($GLOBALS['_xh']['vt'])
625 $vscount = count($GLOBALS['_xh']['valuestack']);
626 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value'];
628 error_log('XML-RPC: missing VALUE inside STRUCT in received xml');
631 $GLOBALS['_xh']['ac']=''; // is this necessary?
634 // add to array of params the current value,
635 // unless no VALUE was found
636 if ($GLOBALS['_xh']['vt'])
638 $GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value'];
639 $GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt'];
642 error_log('XML-RPC: missing VALUE inside PARAM in received xml');
645 $GLOBALS['_xh']['method']=ereg_replace("^[\n\r\t ]+", '', $GLOBALS['_xh']['ac']);
650 case 'METHORESPONSE':
653 // End of INVALID ELEMENT!
654 // shall we add an assert here for unreachable code???
660 function xmlrpc_ee_fast($parser, $name)
662 xmlrpc_ee($parser, $name, false);
665 function xmlrpc_cd($parser, $data)
667 //if(ereg("^[\n\r \t]+$", $data)) return;
668 // print "adding [${data}]\n";
670 // skip processing if xml fault already detected
671 if ($GLOBALS['_xh']['isf'] < 2)
673 if($GLOBALS['_xh']['lv']!=3)
675 // "lookforvalue==3" means that we've found an entire value
676 // and should discard any further character data
677 if($GLOBALS['_xh']['lv']==1)
679 // if we've found text and we're just in a <value> then
680 // say we've found a value
681 $GLOBALS['_xh']['lv']=2;
683 if(!@isset($GLOBALS['_xh']['ac']))
685 $GLOBALS['_xh']['ac'] = '';
687 $GLOBALS['_xh']['ac'].=$data;
692 function xmlrpc_dh($parser, $data)
694 // skip processing if xml fault already detected
695 if ($GLOBALS['_xh']['isf'] < 2)
697 if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
699 if($GLOBALS['_xh']['lv']==1)
701 $GLOBALS['_xh']['lv']=2;
703 $GLOBALS['_xh']['ac'].=$data;
725 var $no_multicall=false;
728 var $proxy_user = '';
729 var $proxy_pass = '';
730 var $cookies=array();
732 * List of http compression methods accepted by the client for responses.
733 * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
735 * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
736 * in those cases it will be up to CURL to decide the compression methods
737 * it supports. You might check for the presence of 'zlib' in the output of
738 * curl_version() to determine wheter compression is supported or not
740 var $accepted_compression = array();
742 * Name of compression scheme to be used for sending requests.
743 * Either null, gzip or deflate
745 var $request_compression = '';
747 * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
748 * http://curl.haxx.se/docs/faq.html#7.3)
750 var $xmlrpc_curl_handle = null;
751 /// Wheter to use persistent connections for http 1.1 and https
752 var $keepalive = false;
753 /// Charset encodings that can be decoded without problems by the client
754 var $accepted_charset_encodings = array();
755 /// Charset encoding to be used in serializing request. NULL = use ASCII
756 var $request_charset_encoding = '';
758 * Decides the content of xmlrpcresp objects returned by calls to send()
759 * valid strings are 'xmlrpcvals', 'phpvals' or 'xml'
761 var $return_type = 'xmlrpcvals';
764 * @param string $path either the complete server URL or the PATH part of the xmlrc server URL, e.g. /xmlrpc/server.php
765 * @param string $server the server name / ip address
766 * @param integer $port the port the server is listening on, defaults to 80 or 443 depending on protocol used
767 * @param string $method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed
769 function xmlrpc_client($path, $server='', $port='', $method='')
771 // allow user to specify all params in $path
772 if($server == '' and $port == '' and $method == '')
774 $parts = parse_url($path);
775 $server = $parts['host'];
776 $path = $parts['path'];
777 if(isset($parts['query']))
779 $path .= '?'.$parts['query'];
781 if(isset($parts['fragment']))
783 $path .= '#'.$parts['fragment'];
785 if(isset($parts['port']))
787 $port = $parts['port'];
789 if(isset($parts['scheme']))
791 $method = $parts['scheme'];
793 if(isset($parts['user']))
795 $this->username = $parts['user'];
797 if(isset($parts['pass']))
799 $this->password = $parts['pass'];
802 if($path == '' || $path[0] != '/')
804 $this->path='/'.$path;
810 $this->server=$server;
817 $this->method=$method;
820 // if ZLIB is enabled, let the client by default accept compressed responses
821 if(function_exists('gzinflate') || (
822 function_exists('curl_init') && (($info = curl_version()) &&
823 ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
826 $this->accepted_compression = array('gzip', 'deflate');
829 // keepalives: enabled by default ONLY for PHP >= 4.3.8
830 // (see http://curl.haxx.se/docs/faq.html#7.3)
831 if(version_compare(phpversion(), '4.3.8') >= 0)
833 $this->keepalive = true;
836 // by default the xml parser can support these 3 charset encodings
837 $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
841 * Enables/disables the echoing to screen of the xmlrpc responses received
842 * @param integer $debug values 0, 1 and 2 are supported (2 = echo sent msg too, beside received response)
845 function setDebug($in)
851 * Add some http BASIC AUTH credentials, used by the client to authenticate
852 * @param string $u username
853 * @param string $p password
856 function setCredentials($u, $p)
863 * Add a client-side https certificate
864 * @param string $cert
865 * @param string $certpass
868 function setCertificate($cert, $certpass)
871 $this->certpass = $certpass;
875 * @param string $key The name of a file containing a private SSL key
876 * @param string $keypass The secret password needed to use the private SSL key
878 * NB: does not work in older php/curl installs
879 * Thanks to Daniel Convissor
881 function setKey($key, $keypass)
884 $this->keypass = $keypass;
890 function setSSLVerifyPeer($i)
892 $this->verifypeer = $i;
898 function setSSLVerifyHost($i)
900 $this->verifyhost = $i;
906 * @param string $proxyhost
907 * @param string $proxyport Defaults to 8080 for HTTP and 443 for HTTPS
908 * @param string $proxyusername Leave blank if proxy has public access
909 * @param string $proxypassword Leave blank if proxy has public access
912 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '')
914 $this->proxy = $proxyhost;
915 $this->proxyport = $proxyport;
916 $this->proxy_user = $proxyusername;
917 $this->proxy_pass = $proxypassword;
921 * Enables/disables reception of compressed xmlrpc responses.
922 * Note that enabling reception of compressed responses merely adds some standard
923 * http headers to xmlrpc requests. It is up to the xmlrpc server to return
924 * compressed responses when receiving such requests.
925 * @param string $compmethod either 'gzip', 'deflate', 'any' or ''
928 function setAcceptedCompression($compmethod)
930 if ($compmethod == 'any')
931 $this->accepted_compression = array('gzip', 'deflate');
933 $this->accepted_compression = array($compmethod);
937 * Enables/disables http compression of xmlrpc request.
938 * Take care when sending compressed requests: servers might not support them
939 * (and automatic fallback to uncompressed requests is not yet implemented)
940 * @param string $compmethod either 'gzip', 'deflate' or ''
943 function setRequestCompression($compmethod)
945 $this->request_compression = $compmethod;
949 * Adds a cookie to list of cookies that will be sent to server.
950 * NB: setting any param but name and value will turn the cookie into a 'version 1' cookie:
951 * do not do it unless you know what you are doing
952 * @param string $name
953 * @param string $value
954 * @param string $path
955 * @param string $domain
956 * @param string $port
959 * @todo check correctness of urlencoding cookie value (copied from php way of doing it...)
961 function setCookie($name, $value='', $path='', $domain='', $port=null)
963 $this->cookies[$name]['value'] = urlencode($value);
964 if ($path || $domain || $port)
966 $this->cookies[$name]['path'] = $path;
967 $this->cookies[$name]['domain'] = $domain;
968 $this->cookies[$name]['port'] = $port;
969 $this->cookies[$name]['version'] = 1;
973 $this->cookies[$name]['version'] = 0;
978 * Send an xmlrpc request
979 * @param mixed $msg The message object, or an array of messages for using multicall, or the complete xml representation of a request
980 * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply
981 * @param string $method if left unspecified, the http protocol chosen during creation of the object will be used
983 function& send($msg, $timeout=0, $method='')
985 // if user deos not specify http protocol, use native method of this client
986 // (i.e. method set during call to constructor)
989 $method = $this->method;
994 // $msg is an array of xmlrpcmsg's
995 $r = $this->multicall($msg, $timeout, $method);
998 elseif(is_string($msg))
1000 $n =& new xmlrpcmsg('');
1005 // where msg is an xmlrpcmsg
1006 $msg->debug=$this->debug;
1008 if($method == 'https')
1010 $r =& $this->sendPayloadHTTPS(
1028 elseif($method == 'http11')
1030 $r =& $this->sendPayloadCURL(
1049 $r =& $this->sendPayloadHTTP10(
1069 function &sendPayloadHTTP10($msg, $server, $port, $timeout=0,$username='', $password='',
1070 $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='')
1077 // Only create the payload if it was not created previously
1078 if(empty($msg->payload))
1080 $msg->createPayload($this->request_charset_encoding);
1083 $payload = $msg->payload;
1084 // Deflate request body and set appropriate request headers
1085 if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1087 if($this->request_compression == 'gzip')
1089 $a = @gzencode($msg->payload);
1093 $encoding_hdr = "Content-Encoding: gzip\r\n";
1098 $a = @gzdeflate($msg->payload);
1102 $encoding_hdr = "Content-Encoding: deflate\r\n";
1111 // thanks to Grant Rauscher <grant7@firstworld.net>
1116 $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
1119 $accepted_encoding = '';
1120 if(is_array($this->accepted_compression) && count($this->accepted_compression))
1122 $accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
1125 $proxy_credentials = '';
1132 $connectserver = $proxyhost;
1133 $connectport = $proxyport;
1134 $uri = 'http://'.$server.':'.$port.$this->path;
1135 if($proxyusername != '')
1137 $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n";
1142 $connectserver = $server;
1143 $connectport = $port;
1147 // Cookie generation, as per rfc2965 (version 1 cookies) or
1148 // netscape's rules (version 0 cookies)
1150 foreach ($this->cookies as $name => $cookie)
1152 if ($cookie['version'])
1154 $cookieheader .= 'Cookie: $Version="' . $cookie['version'] . '"; ';
1155 $cookieheader .= $name . '="' . $cookie['value'] . '";';
1156 if ($cookie['path'])
1157 $cookieheader .= ' $Path="' . $cookie['path'] . '";';
1158 if ($cookie['domain'])
1159 $cookieheader .= ' $Domain="' . $cookie['domain'] . '";';
1160 if ($cookie['port'])
1161 $cookieheader .= ' $Port="' . $cookie['domain'] . '";';
1162 $cookieheader = substr($cookieheader, 0, -1) . "\r\n";
1166 $cookieheader .= 'Cookie: ' . $name . '=' . $cookie['value'] . "\r\n";
1170 $op= "POST " . $uri. " HTTP/1.0\r\n" .
1171 "User-Agent: " . $GLOBALS['xmlrpcName'] . " " . $GLOBALS['xmlrpcVersion'] . "\r\n" .
1172 "Host: ". $server . "\r\n" .
1174 $proxy_credentials .
1175 $accepted_encoding .
1177 "Accept-Charset: " . implode(',', $this->accepted_charset_encodings) . "\r\n" .
1179 "Content-Type: " . $msg->content_type . "\r\nContent-Length: " .
1180 strlen($payload) . "\r\n\r\n" .
1184 if($this->debug > 1)
1186 print "<PRE>\n---SENDING---\n" . htmlentities($op) . "\n---END---\n</PRE>";
1187 // let the client see this now in case http times out...
1193 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout);
1197 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr);
1201 if($timeout>0 && function_exists('stream_set_timeout'))
1203 stream_set_timeout($fp, $timeout);
1208 $this->errstr='Connect error: '.$this->errstr;
1209 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')');
1213 if(!fputs($fp, $op, strlen($op)))
1215 $this->errstr='Write error';
1216 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr);
1221 // reset errno and errstr on succesful socket connection
1224 // G. Giunta 2005/10/24: close socket before parsing.
1225 // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1226 //$resp=&$msg->parseResponseFile($fp);
1228 while($data=fread($fp, 32768))
1230 // shall we check for $data === FALSE?
1231 // as per the manual, it signals an error
1235 $r =& $msg->parseResponse($ipd, false, $this->return_type);
1243 function &sendPayloadHTTPS($msg, $server, $port, $timeout=0,$username='', $password='', $cert='',$certpass='',
1244 $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $keepalive=false, $key='', $keypass='')
1246 $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username, $password, $cert, $certpass,
1247 $proxyhost, $proxyport, $proxyusername, $proxypassword, 'https', $keepalive, $key, $keypass);
1252 * Contributed by Justin Miller <justin@voxel.net>
1253 * Requires curl to be built into PHP
1254 * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1257 function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='', $password='', $cert='', $certpass='',
1258 $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $method='https', $keepalive=false,
1259 $key='', $keypass='')
1261 if(!function_exists('curl_init'))
1263 $this->errstr='CURL unavailable on this install';
1264 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']);
1267 if($method == 'https')
1269 if(($info = curl_version()) &&
1270 ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version']))))
1272 $this->errstr='SSL unavailable on this install';
1273 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']);
1280 if($method == 'http')
1290 // Only create the payload if it was not created previously
1291 if(empty($msg->payload))
1293 $msg->createPayload($this->request_charset_encoding);
1296 // Deflate request body and set appropriate request headers
1297 $payload = $msg->payload;
1298 if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1300 if($this->request_compression == 'gzip')
1302 $a = @gzencode($msg->payload);
1306 $encoding_hdr = "Content-Encoding: gzip";
1311 $a = @gzdeflate($msg->payload);
1315 $encoding_hdr = "Content-Encoding: deflate";
1324 if($this->debug > 1)
1326 print "<PRE>\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n</PRE>";
1327 // let the client see this now in case http times out...
1331 if(!$keepalive || !$this->xmlrpc_curl_handle)
1333 $curl = curl_init($method . '://' . $server . ':' . $port . $this->path);
1336 $this->xmlrpc_curl_handle = $curl;
1341 $curl = $this->xmlrpc_curl_handle;
1344 // results into variable
1345 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
1349 curl_setopt($curl, CURLOPT_VERBOSE, 1);
1351 curl_setopt($curl, CURLOPT_USERAGENT, $GLOBALS['xmlrpcName'].' '.$GLOBALS['xmlrpcVersion']);
1352 // required for XMLRPC: post the data
1353 curl_setopt($curl, CURLOPT_POST, 1);
1355 curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1357 // return the header too
1358 curl_setopt($curl, CURLOPT_HEADER, 1);
1360 // will only work with PHP >= 5.0
1361 // NB: if we set an empty string, CURL will add http header indicating
1362 // ALL methods it is supporting. This is possibly a better option than
1363 // letting the user tell what curl can / cannot do...
1364 if(is_array($this->accepted_compression) && count($this->accepted_compression))
1366 //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
1367 // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1368 curl_setopt($curl, CURLOPT_ENCODING, '');
1371 $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
1372 // if no keepalive is wanted, let the server know it in advance
1375 $headers[] = 'Connection: close';
1377 // request compression header
1380 $headers[] = $encoding_hdr;
1383 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1384 // timeout is borked
1387 curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
1390 if($username && $password)
1392 curl_setopt($curl, CURLOPT_USERPWD,"$username:$password");
1395 if($method == 'https')
1400 curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1402 // set cert password
1405 curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass);
1407 // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1410 curl_setopt($curl, CURLOPT_SSLKEY, $key);
1412 // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1415 curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass);
1417 // whether to verify remote host's cert
1418 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1419 // 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
1420 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1428 $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080
1430 curl_setopt($curl, CURLOPT_PROXY,$proxyhost.':'.$proxyport);
1431 //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport);
1434 curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword);
1438 // NB: should we build cookie http headers by hand rather than let CURL do it?
1439 // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
1440 // set to clint obj the the user...
1441 if (count($this->cookies))
1444 foreach ($this->cookies as $name => $cookie)
1446 $cookieheader .= $name . '=' . $cookie['value'] . ', ';
1448 curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2));
1451 $result = curl_exec($curl);
1455 $this->errstr='no response';
1456 $resp=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl));
1468 $resp =& $msg->parseResponse($result, true, $this->return_type);
1474 * Send an array of request messages and return an array of responses.
1475 * Unless $this->no_multicall has been set to true, it will try first
1476 * to use one single xmlrpc call to server method system.multicall, and
1477 * revert to sending many successive calls in case of failure.
1478 * This failure is also stored in $this->no_multicall for subsequent calls.
1479 * Unfortunately, there is no server error code universally used to denote
1480 * the fact that multicall is unsupported, so there is no way to reliably
1481 * distinguish between that and a temporary failure.
1482 * If you are sure that server supports multicall and do not want to
1483 * fallback to using many single calls, set the fourth parameter to FALSE.
1485 * NB: trying to shoehorn extra functionality into existing syntax has resulted
1486 * in pretty much convoluted code...
1489 * @param array $msgs an array of xmlrpcmsg objects
1490 * @param integer $timeout connection timeout (in seconds)
1491 * @param string $method the http protocol variant to be used
1492 * @param boolen fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted
1494 function multicall($msgs, $timeout=0, $method='http', $fallback=true)
1496 if(!$this->no_multicall)
1498 $results = $this->_try_multicall($msgs, $timeout, $method);
1499 if(is_array($results))
1501 // System.multicall succeeded
1506 // either system.multicall is unsupported by server,
1507 // or call failed for some other reason.
1510 // Don't try it next time...
1511 $this->no_multicall = true;
1515 if (is_a($result, 'xmlrpcresp'))
1521 $result =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']);
1528 // override fallback, in case careless user tries to do two
1529 // opposite things at the same time
1536 // system.multicall is (probably) unsupported by server:
1537 // emulate multicall via multiple requests
1538 foreach($msgs as $msg)
1540 $results[] =& $this->send($msg, $timeout, $method);
1545 // user does NOT want to fallback on many single calls:
1546 // since we should always return an array of responses,
1547 // return an array with the same error repeated n times
1548 foreach($msgs as $msg)
1550 $results[] = $result;
1557 * Attempt to boxcar $msgs via system.multicall.
1558 * Returns either an array of xmlrpcreponses, an xmlrpc error response
1559 * or false (when recived response does not respect valid multiccall syntax)
1562 function _try_multicall($msgs, $timeout, $method)
1564 // Construct multicall message
1566 foreach($msgs as $msg)
1568 $call['methodName'] =& new xmlrpcval($msg->method(),'string');
1569 $numParams = $msg->getNumParams();
1571 for($i = 0; $i < $numParams; $i++)
1573 $params[$i] = $msg->getParam($i);
1575 $call['params'] =& new xmlrpcval($params, 'array');
1576 $calls[] =& new xmlrpcval($call, 'struct');
1578 $multicall =& new xmlrpcmsg('system.multicall');
1579 $multicall->addParam(new xmlrpcval($calls, 'array'));
1582 $result =& $this->send($multicall, $timeout, $method);
1583 //if(!is_object($result))
1585 // return ($result || 0); // transport failed
1588 if($result->faultCode() != 0)
1590 // call to system.multicall failed
1594 // Unpack responses.
1595 $rets = $result->value();
1597 if ($this->return_type == 'xml')
1601 else if ($this->return_type == 'phpvals')
1603 ///@todo test this code branch...
1604 $rets = $result->value();
1605 if(!is_array($rets))
1607 return false; // bad return type from system.multicall
1609 $numRets = count($rets);
1610 if($numRets != count($msgs))
1612 return false; // wrong number of return values.
1615 $response = array();
1616 for($i = 0; $i < $numRets; $i++)
1619 if (!is_array($val)) {
1627 return false; // Bad value
1629 // Normal return value
1630 $response[$i] =& new xmlrpcresp($val[0], 0, '', 'phpvals');
1633 /// @todo remove usage of @: it is apparently quite slow
1634 $code = @$val['faultCode'];
1639 $str = @$val['faultString'];
1640 if(!is_string($str))
1644 $response[$i] =& new xmlrpcresp(0, $code, $str);
1652 else // return type == 'xmlrpcvals'
1654 $rets = $result->value();
1655 if($rets->kindOf() != 'array')
1657 return false; // bad return type from system.multicall
1659 $numRets = $rets->arraysize();
1660 if($numRets != count($msgs))
1662 return false; // wrong number of return values.
1665 $response = array();
1666 for($i = 0; $i < $numRets; $i++)
1668 $val = $rets->arraymem($i);
1669 switch($val->kindOf())
1672 if($val->arraysize() != 1)
1674 return false; // Bad value
1676 // Normal return value
1677 $response[$i] =& new xmlrpcresp($val->arraymem(0));
1680 $code = $val->structmem('faultCode');
1681 if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int')
1685 $str = $val->structmem('faultString');
1686 if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string')
1690 $response[$i] =& new xmlrpcresp(0, $code->scalarval(), $str->scalarval());
1699 } // end class xmlrpc_client
1707 var $hdrs = array();
1708 var $_cookies = array();
1709 var $content_type = 'text/xml';
1712 * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string)
1713 * @param integer $fcode set it to anything but 0 to create an error response
1714 * @param string $fstr the error string, in case of an error response
1715 * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml'
1717 * @todo add check that $val is of correct type???
1718 * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain
1719 * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called...
1721 function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='')
1726 $this->errno = $fcode;
1727 $this->errstr = $fstr;
1728 //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later.
1730 /*elseif(!is_object($val) || !is_a($val, 'xmlrpcval'))
1733 error_log("Invalid type '" . gettype($val) . "' (value: $val) passed to xmlrpcresp. Defaulting to empty value.");
1734 $this->val =& new xmlrpcval();
1738 // successful response
1742 // user did not declare type of response value: try to guess it
1743 if (is_object($this->val) && is_a($this->val, 'xmlrpcval'))
1745 $this->valtyp = 'xmlrpcvals';
1747 else if (is_string($this->val))
1749 $this->valtyp = 'xml';
1754 $this->valtyp = 'phpvals';
1759 // user declares type of resp value: believe him
1760 $this->valtyp = $valtyp;
1766 * @return integer the error code of this response (0 for not-error responses)
1768 function faultCode()
1770 return $this->errno;
1774 * @return string the error string of this response ('' for not-error responses)
1776 function faultString()
1778 return $this->errstr;
1782 * @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
1790 * Returns an array with the cookies received from the server.
1791 * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...)
1792 * with attributes being e.g. 'expires', 'path', domain'.
1793 * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past)
1794 * are still present in the array. It is up to the user-defined code to decide
1795 * how to use the received cookies, and wheter they have to be sent back with the next
1796 * request to the server (using xmlrpc_client::setCookie) or not
1797 * @return array array of cookies received from the server
1802 return $this->_cookies;
1806 * Return xml representation of the response
1807 * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
1808 * @return string the xml representation of the response
1810 function serialize($charset_encoding='')
1812 if ($charset_encoding != '')
1813 $this->content_type = 'text/xml; charset=' . $charset_encoding;
1815 $this->content_type = 'text/xml';
1816 $result = "<methodResponse>\n";
1819 // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients
1820 // by xml-encoding non ascii chars
1821 $result .= "<fault>\n" .
1822 "<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
1823 "</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
1824 xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "</string></value>\n</member>\n" .
1825 "</struct>\n</value>\n</fault>";
1829 if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval'))
1831 if (is_string($this->val) && $this->valtyp == 'xml')
1833 $result .= "<params>\n<param>\n" .
1835 "</param>\n</params>";
1839 /// @todo try to build something serializable?
1840 die('cannot serialize xmlrpcresp objects whose content is native php values');
1845 $result .= "<params>\n<param>\n" .
1846 $this->val->serialize($charset_encoding) .
1847 "</param>\n</params>";
1850 $result .= "\n</methodResponse>";
1859 var $params=array();
1861 var $content_type = 'text/xml';
1864 * @param string $meth the name of the method to invoke
1865 * @param array $pars array of parameters to be paased to the method (xmlrpcval objects)
1867 function xmlrpcmsg($meth, $pars=0)
1869 $this->methodname=$meth;
1870 if(is_array($pars) && sizeof($pars)>0)
1872 for($i=0; $i<sizeof($pars); $i++)
1874 $this->addParam($pars[$i]);
1879 function xml_header()
1881 return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
1884 function xml_footer()
1886 return "</methodCall>";
1894 function createPayload($charset_encoding='')
1896 if ($charset_encoding != '')
1897 $this->content_type = 'text/xml; charset=' . $charset_encoding;
1899 $this->content_type = 'text/xml';
1900 $this->payload=$this->xml_header();
1901 $this->payload.='<methodName>' . $this->methodname . "</methodName>\n";
1902 // if(sizeof($this->params)) {
1903 $this->payload.="<params>\n";
1904 for($i=0; $i<sizeof($this->params); $i++)
1906 $p=$this->params[$i];
1907 $this->payload.="<param>\n" . $p->serialize($charset_encoding) .
1910 $this->payload.="</params>\n";
1912 $this->payload.=$this->xml_footer();
1913 //$this->payload=str_replace("\n", "\r\n", $this->payload);
1917 * Gets/sets the xmlrpc method to be invoked
1918 * @param string $meth the method to be set (leave empty not to set it)
1919 * @return string the method that will be invoked
1922 function method($meth='')
1926 $this->methodname=$meth;
1928 return $this->methodname;
1932 * @return string the xml representation of the message
1934 function serialize($charset_encoding='')
1936 $this->createPayload($charset_encoding);
1937 return $this->payload;
1941 * Add a parameter to the list of parameters to be used upon method invocation
1942 * @param xmlrpcval $par
1943 * @return boolean false on failure
1945 function addParam($par)
1947 // add check: do not add to self params which are not xmlrpcvals
1948 if(is_object($par) && is_a($par, 'xmlrpcval'))
1950 $this->params[]=$par;
1960 * @param integer $i the index of the parameter to fetch (zero based)
1961 * @return xmlrpcval the i-th parameter
1963 function getParam($i) { return $this->params[$i]; }
1966 * @return integer the number of parameters currently set
1968 function getNumParams() { return sizeof($this->params); }
1972 * @todo add 2nd & 3rd param to be passed to ParseResponse() ???
1974 function &parseResponseFile($fp)
1977 while($data=fread($fp, 32768))
1982 $r =& $this->parseResponse($ipd);
1987 * Parses HTTP headers and separates them from data.
1990 function &parseResponseHeaders(&$data, $headers_processed=false)
1992 // Strip HTTP 1.1 100 Continue header if present
1993 while(ereg('^HTTP/1\.1 1[0-9]{2} ', $data))
1995 $pos = strpos($data, 'HTTP', 12);
1996 // server sent a Continue header without any (valid) content following...
1997 // give the client a chance to know it
1998 if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
2002 $data = substr($data, $pos);
2004 if(!ereg('^HTTP/[0-9.]+ 200 ', $data))
2006 $errstr= substr($data, 0, strpos($data, "\n")-1);
2007 error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .$errstr);
2008 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
2012 $GLOBALS['_xh']['headers'] = array();
2013 $GLOBALS['_xh']['cookies'] = array();
2015 // be tolerant to usage of \n instead of \r\n to separate headers and data
2016 // (even though it is not valid http)
2017 $pos = strpos($data,"\r\n\r\n");
2018 if($pos || is_int($pos))
2024 $pos = strpos($data,"\n\n");
2025 if($pos || is_int($pos))
2031 // No separation between response headers and body: fault?
2035 // be tolerant to line endings, and extra empty lines
2036 $ar = split("\r?\n", trim(substr($data, 0, $pos)));
2037 while(list(,$line) = @each($ar))
2039 // take care of multi-line headers and cookies
2040 $arr = explode(':',$line,2);
2043 $header_name = strtolower(trim($arr[0]));
2044 /// @todo some other headers (the ones that allow a CSV list of values)
2045 /// do allow many values to be passed using multiple header lines.
2046 /// We should add content to $GLOBALS['_xh']['headers'][$header_name]
2047 /// instead of replacing it for those...
2048 if ($header_name == 'set-cookie' || $header_name == 'set-cookie2')
2050 if ($header_name == 'set-cookie2')
2052 // version 2 cookies:
2053 // there could be many cookies on one line, comma separated
2054 $cookies = explode(',', $arr[1]);
2058 $cookies = array($arr[1]);
2060 foreach ($cookies as $cookie)
2062 // glue together all received cookies, using a comma to separate them
2063 // (same as php does with getallheaders())
2064 if (isset($GLOBALS['_xh']['headers'][$header_name]))
2065 $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie);
2067 $GLOBALS['_xh']['headers'][$header_name] = trim($cookie);
2068 // parse cookie attributes, in case user wants to coorectly honour then
2069 // feature creep: only allow rfc-compliant cookie attributes?
2070 $cookie = explode(';', $cookie);
2071 foreach ($cookie as $pos => $val)
2073 $val = explode('=', $val, 2);
2074 $tag = trim($val[0]);
2075 $val = trim(@$val[1]);
2076 /// @todo with version 1 cookies, we should strip leading and trailing " chars
2080 $GLOBALS['_xh']['cookies'][$tag] = array();
2081 $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val);
2085 $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val;
2092 $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]);
2095 elseif(isset($header_name))
2097 /// @todo version1 cookies might span multiple lines, thus breaking the parsing above
2098 $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line);
2101 // rebuild full cookie set
2102 /*if (isset($GLOBALS['_xh']['headers']['set-cookie']))
2105 $received = explode(';', $GLOBALS['_xh']['headers']['set-cookie']);
2106 foreach($received as $cookie)
2108 list($name, $value) = explode('=', $cookie);
2109 $name = trim($name);
2110 $value = trim($value);
2111 // these values are in fact attributes
2112 if ($name != 'Comment' && $name != 'Comment' && $name != 'Comment' && $name != 'Comment' && $name != 'Comment' && $name != 'Comment')
2114 $cookies[$name] = $value;
2119 $data = substr($data, $bd);
2121 if($this->debug && count($GLOBALS['_xh']['headers']))
2124 foreach($GLOBALS['_xh']['headers'] as $header => $value)
2126 print "HEADER: $header: $value\n";
2128 foreach($GLOBALS['_xh']['cookies'] as $header => $value)
2130 print "COOKIE: $header={$value['value']}\n";
2135 // if CURL was used for the call, http headers have been processed,
2136 // and dechunking + reinflating have been carried out
2137 if(!$headers_processed)
2139 // Decode chunked encoding sent by http 1.1 servers
2140 if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
2142 if(!$data = decode_chunked($data))
2144 error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server');
2145 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
2150 // Decode gzip-compressed stuff
2151 // code shamelessly inspired from nusoap library by Dietrich Ayala
2152 if(isset($GLOBALS['_xh']['headers']['content-encoding']))
2154 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
2156 // if decoding works, use it. else assume data wasn't gzencoded
2157 if(function_exists('gzinflate'))
2159 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzinflate($data))
2163 print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2165 elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
2169 print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2173 error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server');
2174 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
2180 error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
2181 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
2186 } // end of 'if needed, de-chunk, re-inflate response'
2188 // real stupid hack to avoid PHP 4 complaining about returning NULL by ref
2195 * @param string $data the xmlrpc response, eventually including http headers
2196 * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and conseuqent decoding
2197 * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
2200 function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals')
2205 //by maHo, replaced htmlspecialchars with htmlentities
2206 print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
2207 $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2210 $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2211 $end = strpos($data, '-->', $start);
2212 $comments = substr($data, $start, $end-$start);
2213 print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n</PRE>";
2219 error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.');
2220 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
2224 $GLOBALS['_xh']=array();
2226 // parse the HTTP headers of the response, if present, and separate them from data
2227 if(ereg("^HTTP",$data))
2229 $r =& $this->parseResponseHeaders($data, $headers_processed);
2237 $GLOBALS['_xh']['headers'] = array();
2238 $GLOBALS['_xh']['cookies'] = array();
2242 // be tolerant of extra whitespace in response body
2243 $data = trim($data);
2245 /// @todo return an error msg if $data=='' ?
2247 // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
2248 // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib
2250 // Poor man's version of strrpos for php 4...
2251 $pos = strpos($data, '</methodResponse>');
2252 while($pos || is_int($pos))
2255 $pos = strpos($data, '</methodResponse>', $bd);
2259 $data = substr($data, 0, $bd);
2262 // if user wants back raw xml, give it to him
2263 if ($return_type == 'xml')
2265 $r =& new xmlrpcresp($data, 0, '', 'xml');
2266 $r->hdrs = $GLOBALS['_xh']['headers'];
2267 $r->_cookies = $GLOBALS['_xh']['cookies'];
2271 // try to 'guestimate' the character encoding of the received response
2272 $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data);
2274 $GLOBALS['_xh']['stack'] = array();
2275 $GLOBALS['_xh']['valuestack'] = array();
2276 $GLOBALS['_xh']['isf']=0;
2277 $GLOBALS['_xh']['isf_reason']='';
2278 $GLOBALS['_xh']['ac']='';
2279 $GLOBALS['_xh']['qt']='';
2281 // if response charset encoding is not known / supported, try to use
2282 // the default encoding and parse the xml anyway, but log a warning...
2283 if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2284 // the following code might be better for mb_string enabled installs, but
2285 // makes the lib about 200% slower...
2286 //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2288 error_log('XML-RPC: xmlrpcmsg::parseResponse: invalid charset encoding of received response: '.$resp_encoding);
2289 $resp_encoding = $GLOBALS['xmlrpc_defencoding'];
2291 $parser = xml_parser_create($resp_encoding);
2292 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
2293 // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
2294 // the xml parser to give us back data in the expected charset
2295 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
2297 if ($return_type == 'phpvals')
2299 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
2303 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
2306 xml_set_character_data_handler($parser, 'xmlrpc_cd');
2307 xml_set_default_handler($parser, 'xmlrpc_dh');
2309 if(!xml_parse($parser, $data, sizeof($data)))
2311 // thanks to Peter Kocks <peter.kocks@baygate.com>
2312 if((xml_get_current_line_number($parser)) == 1)
2314 $errstr = 'XML error at line 1, check URL';
2318 $errstr = sprintf('XML error: %s at line %d',
2319 xml_error_string(xml_get_error_code($parser)),
2320 xml_get_current_line_number($parser));
2323 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
2324 xml_parser_free($parser);
2329 $r->hdrs = $GLOBALS['_xh']['headers'];
2330 $r->_cookies = $GLOBALS['_xh']['cookies'];
2333 xml_parser_free($parser);
2334 if ($GLOBALS['_xh']['isf'] > 1)
2338 /// @todo echo something for user?
2341 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2342 $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
2344 elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value']))
2346 // then something odd has happened
2347 // and it's time to generate a client side error
2348 // indicating something odd went on
2349 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2350 $GLOBALS['xmlrpcstr']['invalid_return']);
2356 print "<PRE>---PARSED---\n" ;
2357 var_export($GLOBALS['_xh']['value']);
2358 print "\n---END---</PRE>";
2359 } // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object.
2361 $v =& $GLOBALS['_xh']['value'];
2363 if($GLOBALS['_xh']['isf'])
2365 if ($return_type == 'xmlrpcvals')
2367 $errno_v = $v->structmem('faultCode');
2368 $errstr_v = $v->structmem('faultString');
2369 $errno = $errno_v->scalarval();
2370 $errstr = $errstr_v->scalarval();
2374 $errno = $v['faultCode'];
2375 $errstr = $v['faultString'];
2380 // FAULT returned, errno needs to reflect that
2384 $r =& new xmlrpcresp($v, $errno, $errstr);
2388 $r=&new xmlrpcresp($v, 0, '', 'phpvals');
2392 $r->hdrs = $GLOBALS['_xh']['headers'];
2393 $r->_cookies = $GLOBALS['_xh']['cookies'];
2402 var $_php_class=null;
2404 function xmlrpcval($val=-1, $type='')
2406 //$this->me=array();
2408 if($val!==-1 || $type!='')
2414 if($GLOBALS['xmlrpcTypes'][$type]==1)
2416 $this->addScalar($val,$type);
2418 elseif($GLOBALS['xmlrpcTypes'][$type]==2)
2420 $this->addArray($val);
2422 elseif($GLOBALS['xmlrpcTypes'][$type]==3)
2424 $this->addStruct($val);
2429 function addScalar($val, $type='string')
2431 $typeof=@$GLOBALS['xmlrpcTypes'][$type];
2434 error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($typeof)");
2438 // coerce booleans into correct values
2439 // NB: shall we do it for datetimes, integers and doubles, too?
2440 if($type==$GLOBALS['xmlrpcBoolean'])
2442 if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
2452 switch($this->mytype)
2455 error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value');
2458 error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval');
2461 // we're adding a scalar value to an array here
2462 //$ar=$this->me['array'];
2463 //$ar[]=&new xmlrpcval($val, $type);
2464 //$this->me['array']=$ar;
2465 // Faster (?) avoid all the costly array-copy-by-val done here...
2466 $this->me['array'][]=&new xmlrpcval($val, $type);
2469 // a scalar, so set the value and remember we're scalar
2470 $this->me[$type]=$val;
2471 $this->mytype=$typeof;
2476 /// @todo add some checking for $vals to be an array of xmlrpcvals?
2477 function addArray($vals)
2479 if($this->mytype==0)
2481 $this->mytype=$GLOBALS['xmlrpcTypes']['array'];
2482 $this->me['array']=$vals;
2485 elseif($this->mytype==2)
2487 // we're adding to an array here
2488 $this->me['array'] = array_merge($this->me['array'], $vals);
2492 error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']');
2497 /// @todo add some checking for $vals to be an array?
2498 function addStruct($vals)
2500 if($this->mytype==0)
2502 $this->mytype=$GLOBALS['xmlrpcTypes']['struct'];
2503 $this->me['struct']=$vals;
2506 elseif($this->mytype==3)
2508 // we're adding to a struct here
2509 $this->me['struct'] = array_merge($this->me['struct'], $vals);
2513 error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']');
2518 // poor man's version of print_r ???
2522 foreach($ar as $key => $val)
2524 echo "$key => $val<br />";
2527 while(list($key2, $val2) = each($val))
2529 echo "-- $key2 => $val2<br />";
2537 switch($this->mytype)
2553 function serializedata($typ, $val, $charset_encoding='')
2556 switch(@$GLOBALS['xmlrpcTypes'][$typ])
2560 if ($this->_php_class)
2562 $rs.='<struct php_class="' . $this->_php_class . "\">\n";
2568 foreach($val as $key2 => $val2)
2570 $rs.="<member><name>${key2}</name>\n";
2571 //$rs.=$this->serializeval($val2);
2572 $rs.=$val2->serialize();
2579 $rs.="<array>\n<data>\n";
2580 for($i=0; $i<sizeof($val); $i++)
2582 //$rs.=$this->serializeval($val[$i]);
2583 $rs.=$val[$i]->serialize();
2585 $rs.="</data>\n</array>";
2590 case $GLOBALS['xmlrpcBase64']:
2591 $rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
2593 case $GLOBALS['xmlrpcBoolean']:
2594 $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
2596 case $GLOBALS['xmlrpcString']:
2597 // G. Giunta 2005/2/13: do NOT use htmlentities, since
2598 // it will produce named html entities, which are invalid xml
2599 $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "</${typ}>";
2600 // $rs.="<${typ}>" . htmlentities($val). "</${typ}>";
2602 case $GLOBALS['xmlrpcInt']:
2603 case $GLOBALS['xmlrpcI4']:
2604 $rs.="<${typ}>".(int)$val."</${typ}>";
2606 case $GLOBALS['xmlrpcDouble']:
2607 $rs.="<${typ}>".(double)$val."</${typ}>";
2610 // no standard type value should arrive here, but provide a possibility
2611 // for xmlrpcvals of unknown type...
2612 $rs.="<${typ}>${val}</${typ}>";
2622 * Return xml representation of the value
2623 * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
2625 function serialize($charset_encoding='')
2627 // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
2628 //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
2631 list($typ, $val) = each($this->me);
2632 return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n";
2637 function serializeval($o)
2639 // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
2640 //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
2644 list($typ, $val) = each($ar);
2645 return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
2650 * Checks wheter a struct member with a given name is present.
2651 * Works only on xmlrpcvals of type struct.
2652 * @param string $m the name of the struct member to be looked up
2655 function structmemexists($m)
2657 return array_key_exists($this->me['struct'][$m]);
2661 * Returns the value of a given struct member (an xmlrpcval object in itself).
2662 * Will raise a php warning if struct member of given name does not exist
2663 * @param string $m the name of the struct member to be looked up
2666 function structmem($m)
2668 return $this->me['struct'][$m];
2671 function structreset()
2673 reset($this->me['struct']);
2676 function structeach()
2678 return each($this->me['struct']);
2681 // DEPRECATED! this code looks like it is very fragile and has not been fixed
2682 // for a long long time. Shall we remove it for 2.0?
2687 list($a,$b)=each($this->me);
2688 // contributed by I Sofer, 2001-03-24
2689 // add support for nested arrays to scalarval
2690 // i've created a new method here, so as to
2691 // preserve back compatibility
2696 while(list($id,$cont) = @each($b))
2698 $b[$id] = $cont->scalarval();
2702 // add support for structures directly encoding php objects
2705 $t = get_object_vars($b);
2707 while(list($id,$cont) = @each($t))
2709 $t[$id] = $cont->scalarval();
2712 while(list($id,$cont) = @each($t))
2714 //@eval('$b->'.$id.' = $cont;');
2723 * Returns the value of a scalar xmlrpcval
2726 function scalarval()
2729 list(,$b)=each($this->me);
2734 * Returns the type of the xmlrpcval.
2735 * For integers, 'int' is always returned in place of 'i4'
2738 function scalartyp()
2741 list($a,$b)=each($this->me);
2742 if($a==$GLOBALS['xmlrpcI4'])
2744 $a=$GLOBALS['xmlrpcInt'];
2750 * Returns the m-th member of an xmlrpcval of struct type
2751 * @param integer $m the index of the value to be retrieved (zero based)
2754 function arraymem($m)
2756 return $this->me['array'][$m];
2760 * Returns the number of members in an xmlrpcval of array type
2763 function arraysize()
2765 return count($this->me['array']);
2769 * Returns the number of members in an xmlrpcval of struct type
2772 function structsize()
2774 return count($this->me['struct']);
2780 function iso8601_encode($timet, $utc=0)
2782 // return an ISO8601 encoded string
2783 // really, timezones ought to be supported
2784 // but the XML-RPC spec says:
2786 // "Don't assume a timezone. It should be specified by the server in its
2787 // documentation what assumptions it makes about timezones."
2789 // these routines always assume localtime unless
2790 // $utc is set to 1, in which case UTC is assumed
2791 // and an adjustment for locale is made when encoding
2794 $t=strftime("%Y%m%dT%H:%M:%S", $timet);
2798 if(function_exists('gmstrftime'))
2800 // gmstrftime doesn't exist in some versions
2802 $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
2806 $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
2812 function iso8601_decode($idate, $utc=0)
2814 // return a timet in the localtime, or UTC
2816 if(ereg("([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})", $idate, $regs))
2820 $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
2824 $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
2831 * Takes an xmlrpc value in PHP xmlrpcval object format
2832 * and translates it into native PHP types.
2833 * Works with xmlrpc message objects as input, too.
2835 * @author Dan Libby (dan@libby.com)
2837 * @param xmlrpcval $xmlrpc_val
2838 * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects
2841 function php_xmlrpc_decode($xmlrpc_val, $options=array())
2843 switch($xmlrpc_val->kindOf())
2846 return $xmlrpc_val->scalarval();
2848 $size = $xmlrpc_val->arraysize();
2850 for($i = 0; $i < $size; $i++)
2852 $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
2856 $xmlrpc_val->structreset();
2857 // If user said so, try to rebuild php objects for specific struct vals.
2858 /// @todo should we raise a warning for class not found?
2859 // shall we check for proper subclass of xmlrpcval instead of
2860 // presence of _php_class to detect what we can do?
2861 if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
2862 && class_exists($xmlrpc_val->_php_class))
2864 $obj = @new $xmlrpc_val->_php_class;
2865 while(list($key,$value)=$xmlrpc_val->structeach())
2867 $obj->$key = php_xmlrpc_decode($value, $options);
2874 while(list($key,$value)=$xmlrpc_val->structeach())
2876 $arr[$key] = php_xmlrpc_decode($value, $options);
2881 $paramcount = $xmlrpc_val->getNumParams();
2883 for($i = 0; $i < $paramcount; $i++)
2885 $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
2891 if(function_exists('xmlrpc_decode'))
2893 define('XMLRPC_EPI_ENABLED','1');
2897 define('XMLRPC_EPI_ENABLED','0');
2901 * Takes native php types and encodes them into xmlrpc PHP object format.
2902 * It will not re-encode xmlrpcval objects.
2903 * Feature creep -- could support more types via optional type argument
2904 * (string => datetime support has been added, ??? => base64 not yet)
2906 * @author Dan Libby (dan@libby.com)
2908 * @param mixed $php_val the value to be converted into an xmlrpcval object
2909 * @param array $options can include 'encode_php_objs' and 'auto_dates'
2912 function &php_xmlrpc_encode($php_val, $options=array())
2914 $type = gettype($php_val);
2915 $xmlrpc_val =& new xmlrpcval;
2920 // PHP arrays can be encoded to either xmlrpc structs or arrays,
2921 // depending on wheter they are hashes or plain 0..n integer indexed
2922 // A shorter one-liner would be
2923 // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
2924 // but execution time skyrockets!
2928 foreach($php_val as $key => $val)
2930 $arr[$key] =& php_xmlrpc_encode($val, $options);
2931 if(!$ko && $key !== $j)
2939 $xmlrpc_val->addStruct($arr);
2943 $xmlrpc_val->addArray($arr);
2947 if(is_a($php_val, 'xmlrpcval'))
2949 $xmlrpc_val = $php_val;
2954 while(list($k,$v) = each($php_val))
2956 $arr[$k] = php_xmlrpc_encode($v, $options);
2958 $xmlrpc_val->addStruct($arr);
2959 if (in_array('encode_php_objs', $options))
2961 // let's save original class name into xmlrpcval:
2962 // might be useful later on...
2963 $xmlrpc_val->_php_class = get_class($php_val);
2968 $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcInt']);
2971 $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcDouble']);
2974 if (in_array('auto_dates', $options) && ereg("^[0-9]{8}\T{1}[0-9]{2}\:[0-9]{2}\:[0-9]{2}$", $php_val))
2975 $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcDateTime']);
2977 $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcString']);
2979 // <G_Giunta_2001-02-29>
2980 // Add support for encoding/decoding of booleans, since they are supported in PHP
2982 $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcBoolean']);
2984 // </G_Giunta_2001-02-29>
2985 // catch "resource", "NULL", "user function", "unknown type"
2986 //case 'unknown type':
2988 // giancarlo pinerolo <ping@alt.it>
2990 // an empty object in case (which is already
2991 // at this point), not a boolean.
2998 * decode a string that is encoded w/ "chunked" transfer encoding
2999 * as defined in rfc2068 par. 19.4.6
3000 * code shamelessly stolen from nusoap library by Dietrich Ayala
3002 * @param string $buffer the string to be decoded
3005 function decode_chunked($buffer)
3011 // read chunk-size, chunk-extension (if any) and crlf
3012 // get the position of the linebreak
3013 $chunkend = strpos($buffer,"\r\n") + 2;
3014 $temp = substr($buffer,0,$chunkend);
3015 $chunk_size = hexdec( trim($temp) );
3016 $chunkstart = $chunkend;
3017 // while(chunk-size > 0) {
3018 while($chunk_size > 0)
3020 $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
3022 // just in case we got a broken connection
3023 if($chunkend == false)
3025 $chunk = substr($buffer,$chunkstart);
3026 // append chunk-data to entity-body
3028 $length += strlen($chunk);
3032 // read chunk-data and crlf
3033 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3034 // append chunk-data to entity-body
3036 // length := length + chunk-size
3037 $length += strlen($chunk);
3038 // read chunk-size and crlf
3039 $chunkstart = $chunkend + 2;
3041 $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
3042 if($chunkend == false)
3044 break; //just in case we got a broken connection
3046 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3047 $chunk_size = hexdec( trim($temp) );
3048 $chunkstart = $chunkend;
3054 * Given a string defining a php type or phpxmlrpc type (loosely defined: strings
3055 * accepted come from javadoc blocks), return corresponding phpxmlrpc type.
3056 * NB: for php 'resource' types returns empty string, since resources cannot be serialized;
3057 * for php class names returns 'struct', since php objects can be serialized as xmlrpc structs
3058 * @param string $phptype
3061 function php_2_xmlrpc_type($phptype)
3063 switch(strtolower($phptype))
3066 return $GLOBALS['xmlrpcString'];
3068 case $GLOBALS['xmlrpcInt']: // 'int'
3069 case $GLOBALS['xmlrpcI4']:
3070 return $GLOBALS['xmlrpcInt'];
3072 return $GLOBALS['xmlrpcDouble'];
3074 return $GLOBALS['xmlrpcBoolean'];
3076 return $GLOBALS['xmlrpcArray'];
3078 return $GLOBALS['xmlrpcStruct'];
3079 case $GLOBALS['xmlrpcBase64']:
3080 case $GLOBALS['xmlrpcStruct']:
3081 return strtolower($phptype);
3085 if(class_exists($phptype))
3087 return $GLOBALS['xmlrpcStruct'];
3091 // unknown: might be any xmlrpc type
3092 return $GLOBALS['xmlrpcValue'];
3098 * Given a user-defined PHP function, create a PHP 'wrapper' function that can
3099 * be exposed as xmlrpc method from an xmlrpc_server object and called from remote
3102 * Since php is a typeless language, to infer types of input and output parameters,
3103 * it relies on parsing the javadoc-style comment block associated with the given
3104 * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
3105 * in the @param tag is also allowed, if you need the php function to receive/send
3106 * data in that particular format (note that base64 enncoding/decoding is transparently
3107 * carried out by the lib, while datetime vals are passed around as strings)
3109 * Known limitations:
3110 * - requires PHP 5.0.3 +
3111 * - only works for user-defined functions, not for PHP internal functions
3112 * (reflection does not support retrieving number/type of params for those)
3113 * - functions returning php objects will generate special xmlrpc responses:
3114 * when the xmlrpc decoding of those responses is carried out by this same lib, using
3115 * the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt.
3116 * In short: php objects can be serialized, too (except for their resource members),
3117 * using this function.
3118 * Other libs might choke on the very same xml that will be generated in this case
3119 * (i.e. it has a nonstandard attribute on struct element tags)
3120 * - usage of javadoc @param tags using param names in a different order from the
3121 * function prototype is not considered valid (to be fixed?)
3123 * Note that since rel. 2.0RC3 the preferred method to have the server call 'standard'
3124 * php functions (ie. functions not expecting a single xmlrpcmsg obj as parameter)
3125 * is by making use of the functions_parameters_type class member.
3127 * @param string $funcname the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') might be ok too, in the future...
3128 * @return false on error, or an array containing the name of the new php function,
3129 * its signature and docs, to be used in the server dispatch map
3131 * @todo decide how to deal with params passed by ref: bomb out or allow?
3132 * @todo finish using javadoc info to build method sig if all params are named but out of order
3133 * @done switch to some automagic object encoding scheme
3134 * @todo add a check for params of 'resource' type
3135 * @todo add some trigger_errors when returning false?
3136 * @todo what to do when the PHP function returns NULL? we are currently returning bogus responses!!!
3138 function wrap_php_function($funcname, $newfuncname='')
3140 if(version_compare(phpversion(), '5.0.3') == -1)
3142 // up to php 5.0.3 some useful reflection methods were missing
3145 if((is_array($funcname) && !method_exists($funcname[0], $funcname[1])) || !function_exists($funcname))
3151 // determine name of new php function
3152 if($newfuncname == '')
3154 if(is_array($funcname))
3156 $xmlrpcfuncname = "xmlrpc_".implode('_', $funcname);
3160 $xmlrpcfuncname = "xmlrpc_$funcname";
3165 $xmlrpcfuncname = $newfuncname;
3167 while(function_exists($xmlrpcfuncname))
3169 $xmlrpcfuncname .= 'x';
3171 $code = "function $xmlrpcfuncname(\$msg) {\n";
3173 // start to introspect PHP code
3174 $func =& new ReflectionFunction($funcname);
3175 if($func->isInternal())
3177 // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
3178 // instead of getparameters to fully reflect internal php functions ?
3182 // retrieve parameter names, types and description from javadoc comments
3184 // function description
3186 // type of return val: by default 'any'
3187 $returns = $GLOBALS['xmlrpcValue'];
3188 // type + name of function parameters
3189 $paramDocs = array();
3191 $docs = $func->getDocComment();
3194 $docs = explode("\n", $docs);
3196 foreach($docs as $doc)
3198 $doc = trim($doc, " \r\t/*");
3199 if(strlen($doc) && strpos($doc, '@') !== 0 && !$i)
3207 elseif(strpos($doc, '@param') === 0)
3209 // syntax: @param type [$name] desc
3210 if(preg_match('/@param\s+(\S+)(\s+\$\S+)?\s+(.+)/', $doc, $matches))
3212 if(strpos($matches[1], '|'))
3214 //$paramDocs[$i]['type'] = explode('|', $matches[1]);
3215 $paramDocs[$i]['type'] = 'mixed';
3219 $paramDocs[$i]['type'] = $matches[1];
3221 $paramDocs[$i]['name'] = trim($matches[2]);
3222 $paramDocs[$i]['doc'] = $matches[3];
3226 elseif(strpos($doc, '@return') === 0)
3228 $returns = preg_split("/\s+/", $doc);
3229 if(isset($returns[1]))
3231 $returns = php_2_xmlrpc_type($returns[1]);
3237 // start introspection of actual function prototype and building of PHP code
3239 $params = $func->getParameters();
3243 $parsvariations = array();
3245 $pnum = count($params);
3246 foreach($params as $param)
3248 if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != '$'.strtolower($param->getName()))
3250 // param name from phpdoc info does not match param definition!
3251 $paramDocs[$i]['type'] = 'mixed';
3254 if($param->isOptional())
3256 // this particular parameter is optional. save as valid previous list of parameters
3257 $innercode .= "if (\$paramcount > $i) {\n";
3258 $parsvariations[] = $pars;
3260 $innercode .= "\$p$i = \$msg->getParam($i);\n";
3261 $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_xmlrpc_decode(\$p$i);\n";
3264 if($param->isOptional())
3266 $innercode .= "}\n";
3270 // last allowed parameters combination
3271 $parsvariations[] = $pars;
3276 if(count($parsvariations) == 0)
3278 // only known good synopsis = no parameters
3279 $parsvariations[] = array();
3284 $minpars = count($parsvariations[0]);
3289 // add to code the check for min params number
3290 // NB: this check needs to be done BEFORE decoding param values
3291 $innercode = "\$paramcount = \$msg->getNumParams();\n" .
3292 "if (\$paramcount < $minpars) return new xmlrpcresp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode;
3296 $innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode;
3299 $innercode .= "\$np = false;";
3300 foreach($parsvariations as $pars)
3302 $innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = $funcname(" . implode(',', $pars) . "); else\n";
3303 // build a 'generic' signature (only use an appropriate return type)
3304 $sig = array($returns);
3305 for($i=0; $i < count($pars); $i++)
3307 if (isset($paramDocs[$i]['type']))
3309 $sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']);
3313 $sig[] = $GLOBALS['xmlrpcValue'];
3318 $innercode .= "\$np = true;\n";
3319 $innercode .= "if (\$np) return new xmlrpcresp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else\n";
3320 //$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n";
3321 if($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64'])
3323 $innercode .= "return new xmlrpcresp(new xmlrpcval(\$retval, '$returns'));";
3327 $innercode .= "return new xmlrpcresp(php_xmlrpc_encode(\$retval, array('encode_php_objs')));";
3329 // shall we exclude functions returning by ref?
3330 // if($func->returnsReference())
3332 $code = $code . $innercode . "\n}\n \$allOK=1;";
3337 //$xmlrpcfuncname = create_function('$m', $innercode);
3344 /// @todo examine if $paramDocs matches $parsvariations and build array for
3345 /// usage as method signature, plus put together a nice string for docs
3347 $ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc);
3353 * Given an xmlrpc client and a method name, register a php wrapper function
3354 * that will call it and return results using native php types for both
3355 * params and results. The generated php function will return an xmlrpcresp
3356 * oject for failed xmlrpc calls
3358 * Known limitations:
3359 * - server must support system.methodsignature for the wanted xmlrpc method
3360 * - for methods that expose many signatures, only one can be picked (we
3361 * could in priciple check if signatures differ only by number of params
3362 * and not by type, but it would be more complication than we can spare time)
3363 * - nested xmlrpc params: the caller of the generated php function has to
3364 * encode on its own the params passed to the php function if these are structs
3365 * or arrays whose (sub)members include values of type datetime or base64
3367 * Notes: the connection properties of the given client will be copied
3368 * and reused for the connection used during the call to the generated
3370 * Calling the generated php function 'might' be slow: a new xmlrpc client
3371 * is created on every invocation and an xmlrpc-connection opened+closed.
3372 * An extra 'debug' param is appended to param list of xmlrpc method, useful
3373 * for debugging purposes.
3375 * @param xmlrpc_client $client an xmlrpc client set up correctly to communicate with target server
3376 * @param string $methodname the xmlrpc method to be mapped to a php function
3377 * @param integer $signum the index of the method signature to use in mapping (if method exposes many sigs)
3378 * @return string the name of the generated php function (or false)
3380 function wrap_xmlrpc_method($client, $methodname, $signum=0, $timeout=0, $protocol='', $newfuncname='')
3382 $msg =& new xmlrpcmsg('system.methodSignature');
3383 $msg->addparam(new xmlrpcval($methodname));
3384 $response =& $client->send($msg, $timeout, $protocol);
3385 if(!$response || $response->faultCode())
3391 $desc = $response->value();
3392 if($desc->kindOf() != 'array' || $desc->arraysize() <= $signum)
3398 if($newfuncname != '')
3400 $xmlrpcfuncname = $newfuncname;
3404 $xmlrpcfuncname = 'xmlrpc_'.str_replace('.', '_', $methodname);
3406 while(function_exists($xmlrpcfuncname))
3408 $xmlrpcfuncname .= 'x';
3410 $desc = $desc->arraymem($signum);
3411 $code = "function $xmlrpcfuncname (";
3412 $innercode = "\$client =& new xmlrpc_client('$client->path', '$client->server');\n";
3413 // copy all client fields to the client that will be generated runtime
3414 // (this provides for future expansion of client obj)
3415 foreach($client as $fld => $val)
3417 if($fld != 'debug' && $fld != 'return_type')
3419 $val = var_export($val, true);
3420 $innercode .= "\$client->$fld = $val;\n";
3423 $innercode .= "\$client->setDebug(\$debug);\n";
3424 $innercode .= "\$client->return_type = 'xmlrpcvals';\n";
3425 $innercode .= "\$msg =& new xmlrpcmsg('$methodname');\n";
3429 $pcount = $desc->arraysize();
3430 for($i = 1; $i < $pcount; $i++)
3433 $ptype = $desc->arraymem($i);
3434 $ptype = $ptype->scalarval();
3435 if($ptype == 'dateTime.iso8601' || $ptype == 'base64')
3437 $innercode .= "\$p$i =& new xmlrpcval(\$p$i, '$ptype');\n";
3441 $innercode .= "\$p$i =& php_xmlrpc_encode(\$p$i);\n";
3443 $innercode .= "\$msg->addparam(\$p$i);\n";
3445 $plist[] = '$debug = 0';
3446 $plist = implode(',', $plist);
3448 $innercode .= "\$res =& \$client->send(\$msg, $timeout, '$protocol');\n";
3449 $innercode .= "if (\$res->faultcode()) return \$res; else return php_xmlrpc_decode(\$res->value(), array('decode_php_objs'));";
3451 $code = $code . $plist. ") {\n" . $innercode . "\n}\n\$allOK=1;";
3456 //$xmlrpcfuncname = create_function('$m', $innercode);
3459 return $xmlrpcfuncname;
3470 * xml charset encoding guessing helper function.
3471 * Tries to determine the charset encoding of an XML chunk
3472 * received over HTTP.
3474 * NB: according to the spec (RFC 3023, if text/xml content-type is received over HTTP without a content-type,
3476 * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
3478 * which will be most probably using UTF-8 anyway...
3480 * @param string $httpheaders the http Content-type header
3481 * @param string $xmlchunk xml content buffer
3482 * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
3484 * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
3486 function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
3488 // discussion: see http://www.yale.edu/pclt/encoding/
3489 // 1 - test if encoding is specified in HTTP HEADERS
3492 // LWS: (\13\10)?( |\t)+
3493 // token: (any char but excluded stuff)+
3494 // header: Content-type = ...; charset=value(; ...)*
3495 // where value is of type token, no LWS allowed between 'charset' and value
3496 // Note: we do not check for invalid chars in VALUE:
3497 // this had better be done using pure ereg as below
3499 /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
3500 if(eregi(";((\\xD\\xA)?[ \\x9]+)*charset=", $httpheader))
3502 /// @BUG if charset is received uppercase, this line will fail!
3503 $in = strpos($httpheader, 'charset=')+8;
3504 $out = strpos($httpheader, ';', $in) ? strpos($httpheader, ';', $in) : strlen($httpheader);
3505 return strtoupper(trim(substr($httpheader, $in, $out-$in)));
3508 // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
3509 // (source: http://www.w3.org/TR/2000/REC-xml-20001006)
3510 // NOTE: actually, according to the spec, even if we find the BOM and determine
3511 // an encoding, we should check if there is an encoding specified
3512 // in the xml declaration, and verify if they match.
3513 /// @todo implement check as described above?
3514 /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
3515 if(@ereg("^(\\x00\\x00\\xFE\\xFF|\\xFF\\xFE\\x00\\x00|\\x00\\x00\\xFF\\xFE|\\xFE\\xFF\\x00\\x00)", $xmlchunk))
3516 // if (preg_match("/^(\\x00\\x00\\xFE\\xFF|\\xFF\\xFE\\x00\\x00|\\x00\\x00\\xFF\\xFE|\\xFE\\xFF\\x00\\x00)/", $xmlchunk))
3520 elseif(ereg("^(\\xFE\\xFF|\\xFF\\xFE)", $xmlchunk))
3524 elseif(ereg("^(\\xEF\\xBB\\xBF)", $xmlchunk))
3529 // 3 - test if encoding is specified in the xml declaration
3531 // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
3532 // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
3534 "[ \\x9\\xD\\xA]+" . "version" . "[ \\x9\\xD\\xA]*=[ \\x9\\xD\\xA]*" . "((\"[a-zA-Z0-9_.:-]+\")|('[a-zA-Z0-9_.:-]+'))".
3535 "[ \\x9\\xD\\xA]+" . "encoding" . "[ \\x9\\xD\\xA]*=[ \\x9\\xD\\xA]*" . "((\"[A-Za-z][A-Za-z0-9._-]*\")|('[A-Za-z][A-Za-z0-9._-]*'))",
3538 return strtoupper(substr($regs[4], 1, strlen($regs[4])-2));
3541 // 4 - if mbstring is available, let it do the guesswork
3542 // NB: we favour finding an encoding that is compatible with what we can process
3543 if(extension_loaded('mbstring'))
3547 $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
3551 $enc = mb_detect_encoding($xmlchunk);
3553 // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
3554 // IANA also likes better US-ASCII, so go with it
3563 // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
3564 // Both RFC 2616 (HTTP 1.1) and 1945(http 1.0) clearly state that for text/xxx content types
3565 // this should be the standard. And we should be getting text/xml as request and response.
3566 // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
3567 return $GLOBALS['xmlrpc_defencoding'];
3572 * Checks if a given charset encoding is present in a list of encodings or
3573 * if it is a valid subset of any encoding in the list
3574 * @param string $encoding charset to be tested
3575 * @param mixed $validlist comma separated list of valid charsets (or array of charsets)
3577 function is_valid_charset($encoding, $validlist)
3579 $charset_supersets = array(
3580 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
3581 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
3582 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
3583 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
3584 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
3586 if (is_string($validlist))
3587 $validlist = split(',', $validlist);
3588 if (@in_array(strtoupper($encoding), $validlist))
3592 if (array_key_exists($encoding, $charset_supersets))
3593 foreach ($validlist as $allowed)
3594 if (in_array($allowed, $charset_supersets[$encoding]))