2 // by Edd Dumbill (C) 1999-2002
\r
3 // <edd@usefulinc.com>
\r
5 // Copyright (c) 1999,2000,2002 Edd Dumbill.
\r
6 // All rights reserved.
\r
8 // Redistribution and use in source and binary forms, with or without
\r
9 // modification, are permitted provided that the following conditions
\r
12 // * Redistributions of source code must retain the above copyright
\r
13 // notice, this list of conditions and the following disclaimer.
\r
15 // * Redistributions in binary form must reproduce the above
\r
16 // copyright notice, this list of conditions and the following
\r
17 // disclaimer in the documentation and/or other materials provided
\r
18 // with the distribution.
\r
20 // * Neither the name of the "XML-RPC for PHP" nor the names of its
\r
21 // contributors may be used to endorse or promote products derived
\r
22 // from this software without specific prior written permission.
\r
24 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
\r
25 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
\r
26 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
\r
27 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
\r
28 // REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
\r
29 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
\r
30 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
\r
31 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
\r
32 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
\r
33 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
\r
34 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
\r
35 // OF THE POSSIBILITY OF SUCH DAMAGE.
\r
37 require_once __DIR__ . "/phpxmlrpc.php";
\r
38 require_once __DIR__ . "/xmlrpc_client.php";
\r
39 require_once __DIR__ . "/xmlrpcresp.php";
\r
40 require_once __DIR__ . "/xmlrpcmsg.php";
\r
41 require_once __DIR__ . "/xmlrpcval.php";
\r
44 * Convert a string to the correct XML representation in a target charset
\r
45 * To help correct communication of non-ascii chars inside strings, regardless
\r
46 * of the charset used when sending requests, parsing them, sending responses
\r
47 * and parsing responses, an option is to convert all non-ascii chars present in the message
\r
48 * into their equivalent 'charset entity'. Charset entities enumerated this way
\r
49 * are independent of the charset encoding used to transmit them, and all XML
\r
50 * parsers are bound to understand them.
\r
51 * Note that in the std case we are not sending a charset encoding mime type
\r
52 * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
\r
54 * @todo do a bit of basic benchmarking (strtr vs. str_replace)
\r
55 * @todo make usage of iconv() or recode_string() or mb_string() where available
\r
57 function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')
\r
59 $xmlrpc = Phpxmlrpc::instance();
\r
60 if ($src_encoding == '')
\r
62 // lame, but we know no better...
\r
63 $src_encoding = $xmlrpc->xmlrpc_internalencoding;
\r
66 switch(strtoupper($src_encoding.'_'.$dest_encoding))
\r
69 case 'ISO-8859-1_US-ASCII':
\r
70 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
71 $escaped_data = str_replace($xmlrpc->xml_iso88591_Entities['in'], $xmlrpc->xml_iso88591_Entities['out'], $escaped_data);
\r
73 case 'ISO-8859-1_UTF-8':
\r
74 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
75 $escaped_data = utf8_encode($escaped_data);
\r
77 case 'ISO-8859-1_ISO-8859-1':
\r
78 case 'US-ASCII_US-ASCII':
\r
79 case 'US-ASCII_UTF-8':
\r
81 case 'US-ASCII_ISO-8859-1':
\r
83 //case 'CP1252_CP1252':
\r
84 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
87 case 'UTF-8_US-ASCII':
\r
88 case 'UTF-8_ISO-8859-1':
\r
89 // NB: this will choke on invalid UTF-8, going most likely beyond EOF
\r
91 // be kind to users creating string xmlrpcvals out of different php types
\r
92 $data = (string) $data;
\r
93 $ns = strlen ($data);
\r
94 for ($nn = 0; $nn < $ns; $nn++)
\r
98 //1 7 0bbbbbbb (127)
\r
101 /// @todo shall we replace this with a (supposedly) faster str_replace?
\r
104 $escaped_data .= '"';
\r
107 $escaped_data .= '&';
\r
110 $escaped_data .= ''';
\r
113 $escaped_data .= '<';
\r
116 $escaped_data .= '>';
\r
119 $escaped_data .= $ch;
\r
122 //2 11 110bbbbb 10bbbbbb (2047)
\r
123 else if ($ii>>5 == 6)
\r
126 $ii = ord($data[$nn+1]);
\r
128 $ii = ($b1 * 64) + $b2;
\r
129 $ent = sprintf ('&#%d;', $ii);
\r
130 $escaped_data .= $ent;
\r
133 //3 16 1110bbbb 10bbbbbb 10bbbbbb
\r
134 else if ($ii>>4 == 14)
\r
137 $ii = ord($data[$nn+1]);
\r
139 $ii = ord($data[$nn+2]);
\r
141 $ii = ((($b1 * 64) + $b2) * 64) + $b3;
\r
142 $ent = sprintf ('&#%d;', $ii);
\r
143 $escaped_data .= $ent;
\r
146 //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
\r
147 else if ($ii>>3 == 30)
\r
150 $ii = ord($data[$nn+1]);
\r
152 $ii = ord($data[$nn+2]);
\r
154 $ii = ord($data[$nn+3]);
\r
156 $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
\r
157 $ent = sprintf ('&#%d;', $ii);
\r
158 $escaped_data .= $ent;
\r
165 case 'CP1252_US-ASCII':
\r
166 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
167 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
\r
168 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
\r
170 case 'CP1252_UTF-8':
\r
171 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
172 /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them)
\r
173 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
\r
174 $escaped_data = utf8_encode($escaped_data);
\r
176 case 'CP1252_ISO-8859-1':
\r
177 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
178 // we might as well replave all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities...
\r
179 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
\r
183 $escaped_data = '';
\r
184 error_log("Converting from $src_encoding to $dest_encoding: not supported...");
\r
186 return $escaped_data;
\r
189 /// xml parser handler function for opening element tags
\r
190 function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false)
\r
192 $xmlrpc = Phpxmlrpc::instance();
\r
193 // if invalid xmlrpc already detected, skip all processing
\r
194 if ($xmlrpc->_xh['isf'] < 2)
\r
196 // check for correct element nesting
\r
197 // top level element can only be of 2 types
\r
198 /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
\r
199 /// there is only a single top level element in xml anyway
\r
200 if (count($xmlrpc->_xh['stack']) == 0)
\r
202 if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (
\r
203 $name != 'VALUE' && !$accept_single_vals))
\r
205 $xmlrpc->_xh['isf'] = 2;
\r
206 $xmlrpc->_xh['isf_reason'] = 'missing top level xmlrpc element';
\r
211 $xmlrpc->_xh['rt'] = strtolower($name);
\r
212 $xmlrpc->_xh['rt'] = strtolower($name);
\r
217 // not top level element: see if parent is OK
\r
218 $parent = end($xmlrpc->_xh['stack']);
\r
219 if (!array_key_exists($name, $xmlrpc->xmlrpc_valid_parents) || !in_array($parent, $xmlrpc->xmlrpc_valid_parents[$name]))
\r
221 $xmlrpc->_xh['isf'] = 2;
\r
222 $xmlrpc->_xh['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
\r
229 // optimize for speed switch cases: most common cases first
\r
231 /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
\r
232 $xmlrpc->_xh['vt']='value'; // indicator: no value found yet
\r
233 $xmlrpc->_xh['ac']='';
\r
234 $xmlrpc->_xh['lv']=1;
\r
235 $xmlrpc->_xh['php_class']=null;
\r
242 case 'DATETIME.ISO8601':
\r
244 if ($xmlrpc->_xh['vt']!='value')
\r
246 //two data elements inside a value: an error occurred!
\r
247 $xmlrpc->_xh['isf'] = 2;
\r
248 $xmlrpc->_xh['isf_reason'] = "$name element following a {$xmlrpc->_xh['vt']} element inside a single value";
\r
251 $xmlrpc->_xh['ac']=''; // reset the accumulator
\r
255 if ($xmlrpc->_xh['vt']!='value')
\r
257 //two data elements inside a value: an error occurred!
\r
258 $xmlrpc->_xh['isf'] = 2;
\r
259 $xmlrpc->_xh['isf_reason'] = "$name element following a {$xmlrpc->_xh['vt']} element inside a single value";
\r
262 // create an empty array to hold child values, and push it onto appropriate stack
\r
263 $cur_val = array();
\r
264 $cur_val['values'] = array();
\r
265 $cur_val['type'] = $name;
\r
266 // check for out-of-band information to rebuild php objs
\r
267 // and in case it is found, save it
\r
268 if (@isset($attrs['PHP_CLASS']))
\r
270 $cur_val['php_class'] = $attrs['PHP_CLASS'];
\r
272 $xmlrpc->_xh['valuestack'][] = $cur_val;
\r
273 $xmlrpc->_xh['vt']='data'; // be prepared for a data element next
\r
276 if ($xmlrpc->_xh['vt']!='data')
\r
278 //two data elements inside a value: an error occurred!
\r
279 $xmlrpc->_xh['isf'] = 2;
\r
280 $xmlrpc->_xh['isf_reason'] = "found two data elements inside an array element";
\r
284 case 'METHODRESPONSE':
\r
286 // valid elements that add little to processing
\r
290 /// @todo we could check for 2 NAME elements inside a MEMBER element
\r
291 $xmlrpc->_xh['ac']='';
\r
294 $xmlrpc->_xh['isf']=1;
\r
297 $xmlrpc->_xh['valuestack'][count($xmlrpc->_xh['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on
\r
298 //$xmlrpc->_xh['ac']='';
\r
299 // Drop trough intentionally
\r
301 // clear value type, so we can check later if no value has been passed for this param/member
\r
302 $xmlrpc->_xh['vt']=null;
\r
306 if ($xmlrpc->xmlrpc_null_extension)
\r
308 if ($xmlrpc->_xh['vt']!='value')
\r
310 //two data elements inside a value: an error occurred!
\r
311 $xmlrpc->_xh['isf'] = 2;
\r
312 $xmlrpc->_xh['isf_reason'] = "$name element following a {$xmlrpc->_xh['vt']} element inside a single value";
\r
315 $xmlrpc->_xh['ac']=''; // reset the accumulator
\r
318 // we do not support the <NIL/> extension, so
\r
319 // drop through intentionally
\r
321 /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
\r
322 $xmlrpc->_xh['isf'] = 2;
\r
323 $xmlrpc->_xh['isf_reason'] = "found not-xmlrpc xml element $name";
\r
327 // Save current element name to stack, to validate nesting
\r
328 $xmlrpc->_xh['stack'][] = $name;
\r
330 /// @todo optimization creep: move this inside the big switch() above
\r
333 $xmlrpc->_xh['lv']=0;
\r
338 /// Used in decoding xml chunks that might represent single xmlrpc values
\r
339 function xmlrpc_se_any($parser, $name, $attrs)
\r
341 xmlrpc_se($parser, $name, $attrs, true);
\r
344 /// xml parser handler function for close element tags
\r
345 function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)
\r
347 $xmlrpc = Phpxmlrpc::instance();
\r
349 if ($xmlrpc->_xh['isf'] < 2)
\r
351 // push this element name from stack
\r
352 // NB: if XML validates, correct opening/closing is guaranteed and
\r
353 // we do not have to check for $name == $curr_elem.
\r
354 // we also checked for proper nesting at start of elements...
\r
355 $curr_elem = array_pop($xmlrpc->_xh['stack']);
\r
360 // This if() detects if no scalar was inside <VALUE></VALUE>
\r
361 if ($xmlrpc->_xh['vt']=='value')
\r
363 $xmlrpc->_xh['value']=$xmlrpc->_xh['ac'];
\r
364 $xmlrpc->_xh['vt']=$xmlrpc->xmlrpcString;
\r
367 if ($rebuild_xmlrpcvals)
\r
369 // build the xmlrpc val out of the data received, and substitute it
\r
370 $temp = new xmlrpcval($xmlrpc->_xh['value'], $xmlrpc->_xh['vt']);
\r
371 // in case we got info about underlying php class, save it
\r
372 // in the object we're rebuilding
\r
373 if (isset($xmlrpc->_xh['php_class']))
\r
374 $temp->_php_class = $xmlrpc->_xh['php_class'];
\r
375 // check if we are inside an array or struct:
\r
376 // if value just built is inside an array, let's move it into array on the stack
\r
377 $vscount = count($xmlrpc->_xh['valuestack']);
\r
378 if ($vscount && $xmlrpc->_xh['valuestack'][$vscount-1]['type']=='ARRAY')
\r
380 $xmlrpc->_xh['valuestack'][$vscount-1]['values'][] = $temp;
\r
384 $xmlrpc->_xh['value'] = $temp;
\r
389 /// @todo this needs to treat correctly php-serialized objects,
\r
390 /// since std deserializing is done by php_xmlrpc_decode,
\r
391 /// which we will not be calling...
\r
392 if (isset($xmlrpc->_xh['php_class']))
\r
396 // check if we are inside an array or struct:
\r
397 // if value just built is inside an array, let's move it into array on the stack
\r
398 $vscount = count($xmlrpc->_xh['valuestack']);
\r
399 if ($vscount && $xmlrpc->_xh['valuestack'][$vscount-1]['type']=='ARRAY')
\r
401 $xmlrpc->_xh['valuestack'][$vscount-1]['values'][] = $xmlrpc->_xh['value'];
\r
410 case 'DATETIME.ISO8601':
\r
412 $xmlrpc->_xh['vt']=strtolower($name);
\r
413 /// @todo: optimization creep - remove the if/elseif cycle below
\r
414 /// since the case() in which we are already did that
\r
415 if ($name=='STRING')
\r
417 $xmlrpc->_xh['value']=$xmlrpc->_xh['ac'];
\r
419 elseif ($name=='DATETIME.ISO8601')
\r
421 if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $xmlrpc->_xh['ac']))
\r
423 error_log('XML-RPC: invalid value received in DATETIME: '.$xmlrpc->_xh['ac']);
\r
425 $xmlrpc->_xh['vt']=$xmlrpc->xmlrpcDateTime;
\r
426 $xmlrpc->_xh['value']=$xmlrpc->_xh['ac'];
\r
428 elseif ($name=='BASE64')
\r
430 /// @todo check for failure of base64 decoding / catch warnings
\r
431 $xmlrpc->_xh['value']=base64_decode($xmlrpc->_xh['ac']);
\r
433 elseif ($name=='BOOLEAN')
\r
435 // special case here: we translate boolean 1 or 0 into PHP
\r
436 // constants true or false.
\r
437 // Strings 'true' and 'false' are accepted, even though the
\r
438 // spec never mentions them (see eg. Blogger api docs)
\r
439 // NB: this simple checks helps a lot sanitizing input, ie no
\r
440 // security problems around here
\r
441 if ($xmlrpc->_xh['ac']=='1' || strcasecmp($xmlrpc->_xh['ac'], 'true') == 0)
\r
443 $xmlrpc->_xh['value']=true;
\r
447 // log if receiveing something strange, even though we set the value to false anyway
\r
448 if ($xmlrpc->_xh['ac']!='0' && strcasecmp($xmlrpc->_xh['ac'], 'false') != 0)
\r
449 error_log('XML-RPC: invalid value received in BOOLEAN: '.$xmlrpc->_xh['ac']);
\r
450 $xmlrpc->_xh['value']=false;
\r
453 elseif ($name=='DOUBLE')
\r
455 // we have a DOUBLE
\r
456 // we must check that only 0123456789-.<space> are characters here
\r
457 // NOTE: regexp could be much stricter than this...
\r
458 if (!preg_match('/^[+-eE0123456789 \t.]+$/', $xmlrpc->_xh['ac']))
\r
460 /// @todo: find a better way of throwing an error than this!
\r
461 error_log('XML-RPC: non numeric value received in DOUBLE: '.$xmlrpc->_xh['ac']);
\r
462 $xmlrpc->_xh['value']='ERROR_NON_NUMERIC_FOUND';
\r
466 // it's ok, add it on
\r
467 $xmlrpc->_xh['value']=(double)$xmlrpc->_xh['ac'];
\r
472 // we have an I4/INT
\r
473 // we must check that only 0123456789-<space> are characters here
\r
474 if (!preg_match('/^[+-]?[0123456789 \t]+$/', $xmlrpc->_xh['ac']))
\r
476 /// @todo find a better way of throwing an error than this!
\r
477 error_log('XML-RPC: non numeric value received in INT: '.$xmlrpc->_xh['ac']);
\r
478 $xmlrpc->_xh['value']='ERROR_NON_NUMERIC_FOUND';
\r
482 // it's ok, add it on
\r
483 $xmlrpc->_xh['value']=(int)$xmlrpc->_xh['ac'];
\r
486 //$xmlrpc->_xh['ac']=''; // is this necessary?
\r
487 $xmlrpc->_xh['lv']=3; // indicate we've found a value
\r
490 $xmlrpc->_xh['valuestack'][count($xmlrpc->_xh['valuestack'])-1]['name'] = $xmlrpc->_xh['ac'];
\r
493 //$xmlrpc->_xh['ac']=''; // is this necessary?
\r
494 // add to array in the stack the last element built,
\r
495 // unless no VALUE was found
\r
496 if ($xmlrpc->_xh['vt'])
\r
498 $vscount = count($xmlrpc->_xh['valuestack']);
\r
499 $xmlrpc->_xh['valuestack'][$vscount-1]['values'][$xmlrpc->_xh['valuestack'][$vscount-1]['name']] = $xmlrpc->_xh['value'];
\r
501 error_log('XML-RPC: missing VALUE inside STRUCT in received xml');
\r
504 //$xmlrpc->_xh['ac']=''; // is this necessary?
\r
505 $xmlrpc->_xh['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty
\r
509 // fetch out of stack array of values, and promote it to current value
\r
510 $curr_val = array_pop($xmlrpc->_xh['valuestack']);
\r
511 $xmlrpc->_xh['value'] = $curr_val['values'];
\r
512 $xmlrpc->_xh['vt']=strtolower($name);
\r
513 if (isset($curr_val['php_class']))
\r
515 $xmlrpc->_xh['php_class'] = $curr_val['php_class'];
\r
519 // add to array of params the current value,
\r
520 // unless no VALUE was found
\r
521 if ($xmlrpc->_xh['vt'])
\r
523 $xmlrpc->_xh['params'][]=$xmlrpc->_xh['value'];
\r
524 $xmlrpc->_xh['pt'][]=$xmlrpc->_xh['vt'];
\r
527 error_log('XML-RPC: missing VALUE inside PARAM in received xml');
\r
530 $xmlrpc->_xh['method']=preg_replace('/^[\n\r\t ]+/', '', $xmlrpc->_xh['ac']);
\r
534 if ($xmlrpc->xmlrpc_null_extension)
\r
536 $xmlrpc->_xh['vt']='null';
\r
537 $xmlrpc->_xh['value']=null;
\r
538 $xmlrpc->_xh['lv']=3;
\r
541 // drop through intentionally if nil extension not enabled
\r
545 case 'METHORESPONSE':
\r
548 // End of INVALID ELEMENT!
\r
549 // shall we add an assert here for unreachable code???
\r
555 /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values
\r
556 function xmlrpc_ee_fast($parser, $name)
\r
558 xmlrpc_ee($parser, $name, false);
\r
561 /// xml parser handler function for character data
\r
562 function xmlrpc_cd($parser, $data)
\r
564 $xmlrpc = Phpxmlrpc::instance();
\r
565 // skip processing if xml fault already detected
\r
566 if ($xmlrpc->_xh['isf'] < 2)
\r
568 // "lookforvalue==3" means that we've found an entire value
\r
569 // and should discard any further character data
\r
570 if($xmlrpc->_xh['lv']!=3)
\r
572 // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2
\r
573 //if($xmlrpc->_xh['lv']==1)
\r
575 // if we've found text and we're just in a <value> then
\r
576 // say we've found a value
\r
577 //$xmlrpc->_xh['lv']=2;
\r
579 // we always initialize the accumulator before starting parsing, anyway...
\r
580 //if(!@isset($xmlrpc->_xh['ac']))
\r
582 // $xmlrpc->_xh['ac'] = '';
\r
584 $xmlrpc->_xh['ac'].=$data;
\r
589 /// xml parser handler function for 'other stuff', ie. not char data or
\r
590 /// element start/end tag. In fact it only gets called on unknown entities...
\r
591 function xmlrpc_dh($parser, $data)
\r
593 $xmlrpc = Phpxmlrpc::instance();
\r
594 // skip processing if xml fault already detected
\r
595 if ($xmlrpc->_xh['isf'] < 2)
\r
597 if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
\r
599 // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2
\r
600 //if($xmlrpc->_xh['lv']==1)
\r
602 // $xmlrpc->_xh['lv']=2;
\r
604 $xmlrpc->_xh['ac'].=$data;
\r
613 * Given a timestamp, return the corresponding ISO8601 encoded string.
\r
615 * Really, timezones ought to be supported
\r
616 * but the XML-RPC spec says:
\r
618 * "Don't assume a timezone. It should be specified by the server in its
\r
619 * documentation what assumptions it makes about timezones."
\r
621 * These routines always assume localtime unless
\r
622 * $utc is set to 1, in which case UTC is assumed
\r
623 * and an adjustment for locale is made when encoding
\r
625 * @param int $timet (timestamp)
\r
626 * @param int $utc (0 or 1)
\r
629 function iso8601_encode($timet, $utc=0)
\r
633 $t=strftime("%Y%m%dT%H:%M:%S", $timet);
\r
637 if(function_exists('gmstrftime'))
\r
639 // gmstrftime doesn't exist in some versions
\r
641 $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
\r
645 $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
\r
652 * Given an ISO8601 date string, return a timet in the localtime, or UTC
\r
653 * @param string $idate
\r
654 * @param int $utc either 0 or 1
\r
655 * @return int (datetime)
\r
657 function iso8601_decode($idate, $utc=0)
\r
660 if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs))
\r
664 $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
\r
668 $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
\r
675 * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types.
\r
677 * Works with xmlrpc message objects as input, too.
\r
679 * Given proper options parameter, can rebuild generic php object instances
\r
680 * (provided those have been encoded to xmlrpc format using a corresponding
\r
681 * option in php_xmlrpc_encode())
\r
682 * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
\r
683 * This means that the remote communication end can decide which php code will
\r
684 * get executed on your server, leaving the door possibly open to 'php-injection'
\r
685 * style of attacks (provided you have some classes defined on your server that
\r
686 * might wreak havoc if instances are built outside an appropriate context).
\r
687 * Make sure you trust the remote server/client before eanbling this!
\r
689 * @author Dan Libby (dan@libby.com)
\r
691 * @param xmlrpcval $xmlrpc_val
\r
692 * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects (standard is
\r
695 function php_xmlrpc_decode($xmlrpc_val, $options=array())
\r
697 switch($xmlrpc_val->kindOf())
\r
700 if (in_array('extension_api', $options))
\r
702 reset($xmlrpc_val->me);
\r
703 list($typ,$val) = each($xmlrpc_val->me);
\r
706 case 'dateTime.iso8601':
\r
707 $xmlrpc_val->scalar = $val;
\r
708 $xmlrpc_val->xmlrpc_type = 'datetime';
\r
709 $xmlrpc_val->timestamp = iso8601_decode($val);
\r
710 return $xmlrpc_val;
\r
712 $xmlrpc_val->scalar = $val;
\r
713 $xmlrpc_val->type = $typ;
\r
714 return $xmlrpc_val;
\r
716 return $xmlrpc_val->scalarval();
\r
719 if (in_array('dates_as_objects', $options) && $xmlrpc_val->scalartyp() == 'dateTime.iso8601')
\r
721 // we return a Datetime object instead of a string
\r
722 // since now the constructor of xmlrpcval accepts safely strings, ints and datetimes,
\r
723 // we cater to all 3 cases here
\r
724 $out = $xmlrpc_val->scalarval();
\r
725 if (is_string($out))
\r
727 $out = strtotime($out);
\r
731 $result = new Datetime();
\r
732 $result->setTimestamp($out);
\r
735 elseif (is_a($out, 'Datetime'))
\r
740 return $xmlrpc_val->scalarval();
\r
742 $size = $xmlrpc_val->arraysize();
\r
744 for($i = 0; $i < $size; $i++)
\r
746 $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
\r
750 $xmlrpc_val->structreset();
\r
751 // If user said so, try to rebuild php objects for specific struct vals.
\r
752 /// @todo should we raise a warning for class not found?
\r
753 // shall we check for proper subclass of xmlrpcval instead of
\r
754 // presence of _php_class to detect what we can do?
\r
755 if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
\r
756 && class_exists($xmlrpc_val->_php_class))
\r
758 $obj = @new $xmlrpc_val->_php_class;
\r
759 while(list($key,$value)=$xmlrpc_val->structeach())
\r
761 $obj->$key = php_xmlrpc_decode($value, $options);
\r
768 while(list($key,$value)=$xmlrpc_val->structeach())
\r
770 $arr[$key] = php_xmlrpc_decode($value, $options);
\r
775 $paramcount = $xmlrpc_val->getNumParams();
\r
777 for($i = 0; $i < $paramcount; $i++)
\r
779 $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
\r
785 // This constant left here only for historical reasons...
\r
786 // it was used to decide if we have to define xmlrpc_encode on our own, but
\r
787 // we do not do it anymore
\r
788 if(function_exists('xmlrpc_decode'))
\r
790 define('XMLRPC_EPI_ENABLED','1');
\r
794 define('XMLRPC_EPI_ENABLED','0');
\r
798 * Takes native php types and encodes them into xmlrpc PHP object format.
\r
799 * It will not re-encode xmlrpcval objects.
\r
801 * Feature creep -- could support more types via optional type argument
\r
802 * (string => datetime support has been added, ??? => base64 not yet)
\r
804 * If given a proper options parameter, php object instances will be encoded
\r
805 * into 'special' xmlrpc values, that can later be decoded into php objects
\r
806 * by calling php_xmlrpc_decode() with a corresponding option
\r
808 * @author Dan Libby (dan@libby.com)
\r
810 * @param mixed $php_val the value to be converted into an xmlrpcval object
\r
811 * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
\r
812 * @return xmlrpcval
\r
814 function php_xmlrpc_encode($php_val, $options=array())
\r
816 $xmlrpc = Phpxmlrpc::instance();
\r
817 $type = gettype($php_val);
\r
821 if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))
\r
822 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcDateTime);
\r
824 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcString);
\r
827 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcInt);
\r
830 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcDouble);
\r
832 // <G_Giunta_2001-02-29>
\r
833 // Add support for encoding/decoding of booleans, since they are supported in PHP
\r
835 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcBoolean);
\r
837 // </G_Giunta_2001-02-29>
\r
839 // PHP arrays can be encoded to either xmlrpc structs or arrays,
\r
840 // depending on wheter they are hashes or plain 0..n integer indexed
\r
841 // A shorter one-liner would be
\r
842 // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
\r
843 // but execution time skyrockets!
\r
847 foreach($php_val as $key => $val)
\r
849 $arr[$key] = php_xmlrpc_encode($val, $options);
\r
850 if(!$ko && $key !== $j)
\r
858 $xmlrpc_val = new xmlrpcval($arr, $xmlrpc->xmlrpcStruct);
\r
862 $xmlrpc_val = new xmlrpcval($arr, $xmlrpc->xmlrpcArray);
\r
866 if(is_a($php_val, 'xmlrpcval'))
\r
868 $xmlrpc_val = $php_val;
\r
870 else if(is_a($php_val, 'DateTime'))
\r
872 $xmlrpc_val = new xmlrpcval($php_val->format('Ymd\TH:i:s'), $xmlrpc->xmlrpcStruct);
\r
878 while(list($k,$v) = each($php_val))
\r
880 $arr[$k] = php_xmlrpc_encode($v, $options);
\r
882 $xmlrpc_val = new xmlrpcval($arr, $xmlrpc->xmlrpcStruct);
\r
883 if (in_array('encode_php_objs', $options))
\r
885 // let's save original class name into xmlrpcval:
\r
886 // might be useful later on...
\r
887 $xmlrpc_val->_php_class = get_class($php_val);
\r
892 if (in_array('extension_api', $options))
\r
894 $xmlrpc_val = new xmlrpcval('', $xmlrpc->xmlrpcString);
\r
896 else if (in_array('null_extension', $options))
\r
898 $xmlrpc_val = new xmlrpcval('', $xmlrpc->xmlrpcNull);
\r
902 $xmlrpc_val = new xmlrpcval();
\r
906 if (in_array('extension_api', $options))
\r
908 $xmlrpc_val = new xmlrpcval((int)$php_val, $xmlrpc->xmlrpcInt);
\r
912 $xmlrpc_val = new xmlrpcval();
\r
914 // catch "user function", "unknown type"
\r
916 // giancarlo pinerolo <ping@alt.it>
\r
917 // it has to return
\r
918 // an empty object in case, not a boolean.
\r
919 $xmlrpc_val = new xmlrpcval();
\r
922 return $xmlrpc_val;
\r
926 * Convert the xml representation of a method response, method request or single
\r
927 * xmlrpc value into the appropriate object (a.k.a. deserialize)
\r
928 * @param string $xml_val
\r
929 * @param array $options
\r
930 * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp
\r
932 function php_xmlrpc_decode_xml($xml_val, $options=array())
\r
934 $xmlrpc = Phpxmlrpc::instance();
\r
936 $xmlrpc->_xh = array();
\r
937 $xmlrpc->_xh['ac'] = '';
\r
938 $xmlrpc->_xh['stack'] = array();
\r
939 $xmlrpc->_xh['valuestack'] = array();
\r
940 $xmlrpc->_xh['params'] = array();
\r
941 $xmlrpc->_xh['pt'] = array();
\r
942 $xmlrpc->_xh['isf'] = 0;
\r
943 $xmlrpc->_xh['isf_reason'] = '';
\r
944 $xmlrpc->_xh['method'] = false;
\r
945 $xmlrpc->_xh['rt'] = '';
\r
946 /// @todo 'guestimate' encoding
\r
947 $parser = xml_parser_create();
\r
948 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
\r
949 // What if internal encoding is not in one of the 3 allowed?
\r
950 // we use the broadest one, ie. utf8!
\r
951 if (!in_array($xmlrpc->xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
\r
953 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
\r
957 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $xmlrpc->xmlrpc_internalencoding);
\r
959 xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
\r
960 xml_set_character_data_handler($parser, 'xmlrpc_cd');
\r
961 xml_set_default_handler($parser, 'xmlrpc_dh');
\r
962 if(!xml_parse($parser, $xml_val, 1))
\r
964 $errstr = sprintf('XML error: %s at line %d, column %d',
\r
965 xml_error_string(xml_get_error_code($parser)),
\r
966 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
\r
967 error_log($errstr);
\r
968 xml_parser_free($parser);
\r
971 xml_parser_free($parser);
\r
972 if ($xmlrpc->_xh['isf'] > 1) // test that $xmlrpc->_xh['value'] is an obj, too???
\r
974 error_log($xmlrpc->_xh['isf_reason']);
\r
977 switch ($xmlrpc->_xh['rt'])
\r
979 case 'methodresponse':
\r
980 $v =& $xmlrpc->_xh['value'];
\r
981 if ($xmlrpc->_xh['isf'] == 1)
\r
983 $vc = $v->structmem('faultCode');
\r
984 $vs = $v->structmem('faultString');
\r
985 $r = new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval());
\r
989 $r = new xmlrpcresp($v);
\r
993 $m = new xmlrpcmsg($xmlrpc->_xh['method']);
\r
994 for($i=0; $i < count($xmlrpc->_xh['params']); $i++)
\r
996 $m->addParam($xmlrpc->_xh['params'][$i]);
\r
1000 return $xmlrpc->_xh['value'];
\r
1007 * decode a string that is encoded w/ "chunked" transfer encoding
\r
1008 * as defined in rfc2068 par. 19.4.6
\r
1009 * code shamelessly stolen from nusoap library by Dietrich Ayala
\r
1011 * @param string $buffer the string to be decoded
\r
1014 function decode_chunked($buffer)
\r
1020 // read chunk-size, chunk-extension (if any) and crlf
\r
1021 // get the position of the linebreak
\r
1022 $chunkend = strpos($buffer,"\r\n") + 2;
\r
1023 $temp = substr($buffer,0,$chunkend);
\r
1024 $chunk_size = hexdec( trim($temp) );
\r
1025 $chunkstart = $chunkend;
\r
1026 while($chunk_size > 0)
\r
1028 $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
\r
1030 // just in case we got a broken connection
\r
1031 if($chunkend == false)
\r
1033 $chunk = substr($buffer,$chunkstart);
\r
1034 // append chunk-data to entity-body
\r
1036 $length += strlen($chunk);
\r
1040 // read chunk-data and crlf
\r
1041 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
\r
1042 // append chunk-data to entity-body
\r
1044 // length := length + chunk-size
\r
1045 $length += strlen($chunk);
\r
1046 // read chunk-size and crlf
\r
1047 $chunkstart = $chunkend + 2;
\r
1049 $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
\r
1050 if($chunkend == false)
\r
1052 break; //just in case we got a broken connection
\r
1054 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
\r
1055 $chunk_size = hexdec( trim($temp) );
\r
1056 $chunkstart = $chunkend;
\r
1062 * xml charset encoding guessing helper function.
\r
1063 * Tries to determine the charset encoding of an XML chunk received over HTTP.
\r
1064 * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
\r
1065 * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
\r
1066 * which will be most probably using UTF-8 anyway...
\r
1068 * @param string $httpheader the http Content-type header
\r
1069 * @param string $xmlchunk xml content buffer
\r
1070 * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
\r
1073 * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
\r
1075 function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
\r
1077 $xmlrpc = Phpxmlrpc::instance();
\r
1079 // discussion: see http://www.yale.edu/pclt/encoding/
\r
1080 // 1 - test if encoding is specified in HTTP HEADERS
\r
1083 // LWS: (\13\10)?( |\t)+
\r
1084 // token: (any char but excluded stuff)+
\r
1085 // quoted string: " (any char but double quotes and cointrol chars)* "
\r
1086 // header: Content-type = ...; charset=value(; ...)*
\r
1087 // where value is of type token, no LWS allowed between 'charset' and value
\r
1088 // Note: we do not check for invalid chars in VALUE:
\r
1089 // this had better be done using pure ereg as below
\r
1090 // Note 2: we might be removing whitespace/tabs that ought to be left in if
\r
1091 // the received charset is a quoted string. But nobody uses such charset names...
\r
1093 /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
\r
1094 $matches = array();
\r
1095 if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches))
\r
1097 return strtoupper(trim($matches[1], " \t\""));
\r
1100 // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
\r
1101 // (source: http://www.w3.org/TR/2000/REC-xml-20001006)
\r
1102 // NOTE: actually, according to the spec, even if we find the BOM and determine
\r
1103 // an encoding, we should check if there is an encoding specified
\r
1104 // in the xml declaration, and verify if they match.
\r
1105 /// @todo implement check as described above?
\r
1106 /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
\r
1107 if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))
\r
1111 elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))
\r
1115 elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))
\r
1120 // 3 - test if encoding is specified in the xml declaration
\r
1122 // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
\r
1123 // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
\r
1124 if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".
\r
1125 '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
\r
1126 $xmlchunk, $matches))
\r
1128 return strtoupper(substr($matches[2], 1, -1));
\r
1131 // 4 - if mbstring is available, let it do the guesswork
\r
1132 // NB: we favour finding an encoding that is compatible with what we can process
\r
1133 if(extension_loaded('mbstring'))
\r
1135 if($encoding_prefs)
\r
1137 $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
\r
1141 $enc = mb_detect_encoding($xmlchunk);
\r
1143 // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
\r
1144 // IANA also likes better US-ASCII, so go with it
\r
1145 if($enc == 'ASCII')
\r
1147 $enc = 'US-'.$enc;
\r
1153 // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
\r
1154 // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
\r
1155 // this should be the standard. And we should be getting text/xml as request and response.
\r
1156 // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
\r
1157 return $xmlrpc->xmlrpc_defencoding;
\r
1162 * Checks if a given charset encoding is present in a list of encodings or
\r
1163 * if it is a valid subset of any encoding in the list
\r
1164 * @param string $encoding charset to be tested
\r
1165 * @param mixed $validlist comma separated list of valid charsets (or array of charsets)
\r
1168 function is_valid_charset($encoding, $validlist)
\r
1170 $charset_supersets = array(
\r
1171 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
\r
1172 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
\r
1173 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
\r
1174 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
\r
1175 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
\r
1177 if (is_string($validlist))
\r
1178 $validlist = explode(',', $validlist);
\r
1179 if (@in_array(strtoupper($encoding), $validlist))
\r
1183 if (array_key_exists($encoding, $charset_supersets))
\r
1184 foreach ($validlist as $allowed)
\r
1185 if (in_array($allowed, $charset_supersets[$encoding]))
\r