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__ . "/xmlrpc_client.php";
\r
38 require_once __DIR__ . "/xmlrpcresp.php";
\r
39 require_once __DIR__ . "/xmlrpcmsg.php";
\r
40 require_once __DIR__ . "/xmlrpcval.php";
\r
44 public $xmlrpcI4 = "i4";
\r
45 public $xmlrpcInt = "int";
\r
46 public $xmlrpcBoolean = "boolean";
\r
47 public $xmlrpcDouble = "double";
\r
48 public $xmlrpcString = "string";
\r
49 public $xmlrpcDateTime = "dateTime.iso8601";
\r
50 public $xmlrpcBase64 = "base64";
\r
51 public $xmlrpcArray = "array";
\r
52 public $xmlrpcStruct = "struct";
\r
53 public $xmlrpcValue = "undefined";
\r
54 public $xmlrpcNull = "null";
\r
56 public $xmlrpcTypes;
\r
58 public $xmlrpc_valid_parents = array(
\r
59 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
\r
60 'BOOLEAN' => array('VALUE'),
\r
61 'I4' => array('VALUE'),
\r
62 'INT' => array('VALUE'),
\r
63 'STRING' => array('VALUE'),
\r
64 'DOUBLE' => array('VALUE'),
\r
65 'DATETIME.ISO8601' => array('VALUE'),
\r
66 'BASE64' => array('VALUE'),
\r
67 'MEMBER' => array('STRUCT'),
\r
68 'NAME' => array('MEMBER'),
\r
69 'DATA' => array('ARRAY'),
\r
70 'ARRAY' => array('VALUE'),
\r
71 'STRUCT' => array('VALUE'),
\r
72 'PARAM' => array('PARAMS'),
\r
73 'METHODNAME' => array('METHODCALL'),
\r
74 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
\r
75 'FAULT' => array('METHODRESPONSE'),
\r
76 'NIL' => array('VALUE'), // only used when extension activated
\r
77 'EX:NIL' => array('VALUE') // only used when extension activated
\r
80 // tables used for transcoding different charsets into us-ascii xml
\r
81 public $xml_iso88591_Entities = array("in" => array(), "out" => array());
\r
83 /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159?
\r
84 /// These will NOT be present in true ISO-8859-1, but will save the unwary
\r
85 /// windows user from sending junk (though no luck when reciving them...)
\r
87 $GLOBALS['xml_cp1252_Entities']=array();
\r
88 for ($i = 128; $i < 160; $i++)
\r
90 $GLOBALS['xml_cp1252_Entities']['in'][] = chr($i);
\r
92 $GLOBALS['xml_cp1252_Entities']['out'] = array(
\r
93 '€', '?', '‚', 'ƒ',
\r
94 '„', '…', '†', '‡',
\r
95 'ˆ', '‰', 'Š', '‹',
\r
96 'Œ', '?', 'Ž', '?',
\r
97 '?', '‘', '’', '“',
\r
98 '”', '•', '–', '—',
\r
99 '˜', '™', 'š', '›',
\r
100 'œ', '?', 'ž', 'Ÿ'
\r
104 public $xmlrpcerr = array(
\r
105 'unknown_method'=>1,
\r
106 'invalid_return'=>2,
\r
107 'incorrect_params'=>3,
\r
108 'introspect_unknown'=>4,
\r
113 'invalid_request'=>15,
\r
115 'server_error'=>17,
\r
116 'multicall_error'=>18,
\r
117 'multicall_notstruct'=>9,
\r
118 'multicall_nomethod'=>10,
\r
119 'multicall_notstring'=>11,
\r
120 'multicall_recursion'=>12,
\r
121 'multicall_noparams'=>13,
\r
122 'multicall_notarray'=>14,
\r
124 'cannot_decompress'=>103,
\r
125 'decompress_fail'=>104,
\r
126 'dechunk_fail'=>105,
\r
127 'server_cannot_decompress'=>106,
\r
128 'server_decompress_fail'=>107
\r
131 public $xmlrpcstr = array(
\r
132 'unknown_method'=>'Unknown method',
\r
133 'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload',
\r
134 'incorrect_params'=>'Incorrect parameters passed to method',
\r
135 'introspect_unknown'=>"Can't introspect: method unknown",
\r
136 'http_error'=>"Didn't receive 200 OK from remote server.",
\r
137 'no_data'=>'No data received from server.',
\r
138 'no_ssl'=>'No SSL support compiled in.',
\r
139 'curl_fail'=>'CURL error',
\r
140 'invalid_request'=>'Invalid request payload',
\r
141 'no_curl'=>'No CURL support compiled in.',
\r
142 'server_error'=>'Internal server error',
\r
143 'multicall_error'=>'Received from server invalid multicall response',
\r
144 'multicall_notstruct'=>'system.multicall expected struct',
\r
145 'multicall_nomethod'=>'missing methodName',
\r
146 'multicall_notstring'=>'methodName is not a string',
\r
147 'multicall_recursion'=>'recursive system.multicall forbidden',
\r
148 'multicall_noparams'=>'missing params',
\r
149 'multicall_notarray'=>'params is not an array',
\r
151 'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress',
\r
152 'decompress_fail'=>'Received from server invalid compressed HTTP',
\r
153 'dechunk_fail'=>'Received from server invalid chunked HTTP',
\r
154 'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress',
\r
155 'server_decompress_fail'=>'Received from client invalid compressed HTTP request'
\r
158 // The charset encoding used by the server for received messages and
\r
159 // by the client for received responses when received charset cannot be determined
\r
160 // or is not supported
\r
161 public $xmlrpc_defencoding = "UTF-8";
\r
163 // The encoding used internally by PHP.
\r
164 // String values received as xml will be converted to this, and php strings will be converted to xml
\r
165 // as if having been coded with this
\r
166 public $xmlrpc_internalencoding = "ISO-8859-1"; // TODO: maybe this would be better as UTF-8, or atleast configurable?
\r
168 public $xmlrpcName = "XML-RPC for PHP";
\r
169 public $xmlrpcVersion = "3.0.0.beta";
\r
171 // let user errors start at 800
\r
172 public $xmlrpcerruser = 800;
\r
173 // let XML parse errors start at 100
\r
174 public $xmlrpcerrxml = 100;
\r
176 // set to TRUE to enable correct decoding of <NIL/> and <EX:NIL/> values
\r
177 public $xmlrpc_null_extension = false;
\r
179 // set to TRUE to enable encoding of php NULL values to <EX:NIL/> instead of <NIL/>
\r
180 public $xmlrpc_null_apache_encoding = false;
\r
182 public $xmlrpc_null_apache_encoding_ns = "http://ws.apache.org/xmlrpc/namespaces/extensions";
\r
184 // used to store state during parsing
\r
185 // quick explanation of components:
\r
186 // ac - used to accumulate values
\r
187 // isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1)
\r
188 // isf_reason - used for storing xmlrpcresp fault string
\r
189 // lv - used to indicate "looking for a value": implements
\r
190 // the logic to allow values with no types to be strings
\r
191 // params - used to store parameters in method calls
\r
192 // method - used to store method name
\r
193 // stack - array with genealogy of xml elements names:
\r
194 // used to validate nesting of xmlrpc elements
\r
195 public $_xh = null;
\r
197 private static $instance = null;
\r
199 private function __construct() {
\r
200 $this->xmlrpcTypes = array(
\r
201 $this->xmlrpcI4 => 1,
\r
202 $this->xmlrpcInt => 1,
\r
203 $this->xmlrpcBoolean => 1,
\r
204 $this->xmlrpcDouble => 1,
\r
205 $this->xmlrpcString => 1,
\r
206 $this->xmlrpcDateTime => 1,
\r
207 $this->xmlrpcBase64 => 1,
\r
208 $this->xmlrpcArray => 2,
\r
209 $this->xmlrpcStruct => 3,
\r
210 $this->xmlrpcNull => 1
\r
213 for($i = 0; $i < 32; $i++) {
\r
214 $this->xml_iso88591_Entities["in"][] = chr($i);
\r
215 $this->xml_iso88591_Entities["out"][] = "&#{$i};";
\r
218 for($i = 160; $i < 256; $i++) {
\r
219 $this->xml_iso88591_Entities["in"][] = chr($i);
\r
220 $this->xml_iso88591_Entities["out"][] = "&#{$i};";
\r
225 * This class is singleton for performance reasons: this way the ASCII array needs to be done only once.
\r
227 public static function instance() {
\r
228 if(Phpxmlrpc::$instance === null) {
\r
229 Phpxmlrpc::$instance = new Xmlrpc();
\r
232 return Phpxmlrpc::$instance;
\r
238 * Convert a string to the correct XML representation in a target charset
\r
239 * To help correct communication of non-ascii chars inside strings, regardless
\r
240 * of the charset used when sending requests, parsing them, sending responses
\r
241 * and parsing responses, an option is to convert all non-ascii chars present in the message
\r
242 * into their equivalent 'charset entity'. Charset entities enumerated this way
\r
243 * are independent of the charset encoding used to transmit them, and all XML
\r
244 * parsers are bound to understand them.
\r
245 * Note that in the std case we are not sending a charset encoding mime type
\r
246 * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
\r
248 * @todo do a bit of basic benchmarking (strtr vs. str_replace)
\r
249 * @todo make usage of iconv() or recode_string() or mb_string() where available
\r
251 function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')
\r
253 $xmlrpc = Phpxmlrpc::instance();
\r
254 if ($src_encoding == '')
\r
256 // lame, but we know no better...
\r
257 $src_encoding = $xmlrpc->xmlrpc_internalencoding;
\r
260 switch(strtoupper($src_encoding.'_'.$dest_encoding))
\r
262 case 'ISO-8859-1_':
\r
263 case 'ISO-8859-1_US-ASCII':
\r
264 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
265 $escaped_data = str_replace($xmlrpc->xml_iso88591_Entities['in'], $xmlrpc->xml_iso88591_Entities['out'], $escaped_data);
\r
267 case 'ISO-8859-1_UTF-8':
\r
268 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
269 $escaped_data = utf8_encode($escaped_data);
\r
271 case 'ISO-8859-1_ISO-8859-1':
\r
272 case 'US-ASCII_US-ASCII':
\r
273 case 'US-ASCII_UTF-8':
\r
275 case 'US-ASCII_ISO-8859-1':
\r
276 case 'UTF-8_UTF-8':
\r
277 //case 'CP1252_CP1252':
\r
278 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
281 case 'UTF-8_US-ASCII':
\r
282 case 'UTF-8_ISO-8859-1':
\r
283 // NB: this will choke on invalid UTF-8, going most likely beyond EOF
\r
284 $escaped_data = '';
\r
285 // be kind to users creating string xmlrpcvals out of different php types
\r
286 $data = (string) $data;
\r
287 $ns = strlen ($data);
\r
288 for ($nn = 0; $nn < $ns; $nn++)
\r
292 //1 7 0bbbbbbb (127)
\r
295 /// @todo shall we replace this with a (supposedly) faster str_replace?
\r
298 $escaped_data .= '"';
\r
301 $escaped_data .= '&';
\r
304 $escaped_data .= ''';
\r
307 $escaped_data .= '<';
\r
310 $escaped_data .= '>';
\r
313 $escaped_data .= $ch;
\r
316 //2 11 110bbbbb 10bbbbbb (2047)
\r
317 else if ($ii>>5 == 6)
\r
320 $ii = ord($data[$nn+1]);
\r
322 $ii = ($b1 * 64) + $b2;
\r
323 $ent = sprintf ('&#%d;', $ii);
\r
324 $escaped_data .= $ent;
\r
327 //3 16 1110bbbb 10bbbbbb 10bbbbbb
\r
328 else if ($ii>>4 == 14)
\r
331 $ii = ord($data[$nn+1]);
\r
333 $ii = ord($data[$nn+2]);
\r
335 $ii = ((($b1 * 64) + $b2) * 64) + $b3;
\r
336 $ent = sprintf ('&#%d;', $ii);
\r
337 $escaped_data .= $ent;
\r
340 //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
\r
341 else if ($ii>>3 == 30)
\r
344 $ii = ord($data[$nn+1]);
\r
346 $ii = ord($data[$nn+2]);
\r
348 $ii = ord($data[$nn+3]);
\r
350 $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
\r
351 $ent = sprintf ('&#%d;', $ii);
\r
352 $escaped_data .= $ent;
\r
359 case 'CP1252_US-ASCII':
\r
360 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
361 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
\r
362 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
\r
364 case 'CP1252_UTF-8':
\r
365 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
366 /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them)
\r
367 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
\r
368 $escaped_data = utf8_encode($escaped_data);
\r
370 case 'CP1252_ISO-8859-1':
\r
371 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data);
\r
372 // 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
373 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
\r
377 $escaped_data = '';
\r
378 error_log("Converting from $src_encoding to $dest_encoding: not supported...");
\r
380 return $escaped_data;
\r
383 /// xml parser handler function for opening element tags
\r
384 function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false)
\r
386 $xmlrpc = Phpxmlrpc::instance();
\r
387 // if invalid xmlrpc already detected, skip all processing
\r
388 if ($xmlrpc->_xh['isf'] < 2)
\r
390 // check for correct element nesting
\r
391 // top level element can only be of 2 types
\r
392 /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
\r
393 /// there is only a single top level element in xml anyway
\r
394 if (count($xmlrpc->_xh['stack']) == 0)
\r
396 if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (
\r
397 $name != 'VALUE' && !$accept_single_vals))
\r
399 $xmlrpc->_xh['isf'] = 2;
\r
400 $xmlrpc->_xh['isf_reason'] = 'missing top level xmlrpc element';
\r
405 $xmlrpc->_xh['rt'] = strtolower($name);
\r
406 $xmlrpc->_xh['rt'] = strtolower($name);
\r
411 // not top level element: see if parent is OK
\r
412 $parent = end($xmlrpc->_xh['stack']);
\r
413 if (!array_key_exists($name, $xmlrpc->xmlrpc_valid_parents) || !in_array($parent, $xmlrpc->xmlrpc_valid_parents[$name]))
\r
415 $xmlrpc->_xh['isf'] = 2;
\r
416 $xmlrpc->_xh['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
\r
423 // optimize for speed switch cases: most common cases first
\r
425 /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
\r
426 $xmlrpc->_xh['vt']='value'; // indicator: no value found yet
\r
427 $xmlrpc->_xh['ac']='';
\r
428 $xmlrpc->_xh['lv']=1;
\r
429 $xmlrpc->_xh['php_class']=null;
\r
436 case 'DATETIME.ISO8601':
\r
438 if ($xmlrpc->_xh['vt']!='value')
\r
440 //two data elements inside a value: an error occurred!
\r
441 $xmlrpc->_xh['isf'] = 2;
\r
442 $xmlrpc->_xh['isf_reason'] = "$name element following a {$xmlrpc->_xh['vt']} element inside a single value";
\r
445 $xmlrpc->_xh['ac']=''; // reset the accumulator
\r
449 if ($xmlrpc->_xh['vt']!='value')
\r
451 //two data elements inside a value: an error occurred!
\r
452 $xmlrpc->_xh['isf'] = 2;
\r
453 $xmlrpc->_xh['isf_reason'] = "$name element following a {$xmlrpc->_xh['vt']} element inside a single value";
\r
456 // create an empty array to hold child values, and push it onto appropriate stack
\r
457 $cur_val = array();
\r
458 $cur_val['values'] = array();
\r
459 $cur_val['type'] = $name;
\r
460 // check for out-of-band information to rebuild php objs
\r
461 // and in case it is found, save it
\r
462 if (@isset($attrs['PHP_CLASS']))
\r
464 $cur_val['php_class'] = $attrs['PHP_CLASS'];
\r
466 $xmlrpc->_xh['valuestack'][] = $cur_val;
\r
467 $xmlrpc->_xh['vt']='data'; // be prepared for a data element next
\r
470 if ($xmlrpc->_xh['vt']!='data')
\r
472 //two data elements inside a value: an error occurred!
\r
473 $xmlrpc->_xh['isf'] = 2;
\r
474 $xmlrpc->_xh['isf_reason'] = "found two data elements inside an array element";
\r
478 case 'METHODRESPONSE':
\r
480 // valid elements that add little to processing
\r
484 /// @todo we could check for 2 NAME elements inside a MEMBER element
\r
485 $xmlrpc->_xh['ac']='';
\r
488 $xmlrpc->_xh['isf']=1;
\r
491 $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
492 //$xmlrpc->_xh['ac']='';
\r
493 // Drop trough intentionally
\r
495 // clear value type, so we can check later if no value has been passed for this param/member
\r
496 $xmlrpc->_xh['vt']=null;
\r
500 if ($xmlrpc->xmlrpc_null_extension)
\r
502 if ($xmlrpc->_xh['vt']!='value')
\r
504 //two data elements inside a value: an error occurred!
\r
505 $xmlrpc->_xh['isf'] = 2;
\r
506 $xmlrpc->_xh['isf_reason'] = "$name element following a {$xmlrpc->_xh['vt']} element inside a single value";
\r
509 $xmlrpc->_xh['ac']=''; // reset the accumulator
\r
512 // we do not support the <NIL/> extension, so
\r
513 // drop through intentionally
\r
515 /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
\r
516 $xmlrpc->_xh['isf'] = 2;
\r
517 $xmlrpc->_xh['isf_reason'] = "found not-xmlrpc xml element $name";
\r
521 // Save current element name to stack, to validate nesting
\r
522 $xmlrpc->_xh['stack'][] = $name;
\r
524 /// @todo optimization creep: move this inside the big switch() above
\r
527 $xmlrpc->_xh['lv']=0;
\r
532 /// Used in decoding xml chunks that might represent single xmlrpc values
\r
533 function xmlrpc_se_any($parser, $name, $attrs)
\r
535 xmlrpc_se($parser, $name, $attrs, true);
\r
538 /// xml parser handler function for close element tags
\r
539 function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)
\r
541 $xmlrpc = Phpxmlrpc::instance();
\r
543 if ($xmlrpc->_xh['isf'] < 2)
\r
545 // push this element name from stack
\r
546 // NB: if XML validates, correct opening/closing is guaranteed and
\r
547 // we do not have to check for $name == $curr_elem.
\r
548 // we also checked for proper nesting at start of elements...
\r
549 $curr_elem = array_pop($xmlrpc->_xh['stack']);
\r
554 // This if() detects if no scalar was inside <VALUE></VALUE>
\r
555 if ($xmlrpc->_xh['vt']=='value')
\r
557 $xmlrpc->_xh['value']=$xmlrpc->_xh['ac'];
\r
558 $xmlrpc->_xh['vt']=$xmlrpc->xmlrpcString;
\r
561 if ($rebuild_xmlrpcvals)
\r
563 // build the xmlrpc val out of the data received, and substitute it
\r
564 $temp = new xmlrpcval($xmlrpc->_xh['value'], $xmlrpc->_xh['vt']);
\r
565 // in case we got info about underlying php class, save it
\r
566 // in the object we're rebuilding
\r
567 if (isset($xmlrpc->_xh['php_class']))
\r
568 $temp->_php_class = $xmlrpc->_xh['php_class'];
\r
569 // check if we are inside an array or struct:
\r
570 // if value just built is inside an array, let's move it into array on the stack
\r
571 $vscount = count($xmlrpc->_xh['valuestack']);
\r
572 if ($vscount && $xmlrpc->_xh['valuestack'][$vscount-1]['type']=='ARRAY')
\r
574 $xmlrpc->_xh['valuestack'][$vscount-1]['values'][] = $temp;
\r
578 $xmlrpc->_xh['value'] = $temp;
\r
583 /// @todo this needs to treat correctly php-serialized objects,
\r
584 /// since std deserializing is done by php_xmlrpc_decode,
\r
585 /// which we will not be calling...
\r
586 if (isset($xmlrpc->_xh['php_class']))
\r
590 // check if we are inside an array or struct:
\r
591 // if value just built is inside an array, let's move it into array on the stack
\r
592 $vscount = count($xmlrpc->_xh['valuestack']);
\r
593 if ($vscount && $xmlrpc->_xh['valuestack'][$vscount-1]['type']=='ARRAY')
\r
595 $xmlrpc->_xh['valuestack'][$vscount-1]['values'][] = $xmlrpc->_xh['value'];
\r
604 case 'DATETIME.ISO8601':
\r
606 $xmlrpc->_xh['vt']=strtolower($name);
\r
607 /// @todo: optimization creep - remove the if/elseif cycle below
\r
608 /// since the case() in which we are already did that
\r
609 if ($name=='STRING')
\r
611 $xmlrpc->_xh['value']=$xmlrpc->_xh['ac'];
\r
613 elseif ($name=='DATETIME.ISO8601')
\r
615 if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $xmlrpc->_xh['ac']))
\r
617 error_log('XML-RPC: invalid value received in DATETIME: '.$xmlrpc->_xh['ac']);
\r
619 $xmlrpc->_xh['vt']=$xmlrpc->xmlrpcDateTime;
\r
620 $xmlrpc->_xh['value']=$xmlrpc->_xh['ac'];
\r
622 elseif ($name=='BASE64')
\r
624 /// @todo check for failure of base64 decoding / catch warnings
\r
625 $xmlrpc->_xh['value']=base64_decode($xmlrpc->_xh['ac']);
\r
627 elseif ($name=='BOOLEAN')
\r
629 // special case here: we translate boolean 1 or 0 into PHP
\r
630 // constants true or false.
\r
631 // Strings 'true' and 'false' are accepted, even though the
\r
632 // spec never mentions them (see eg. Blogger api docs)
\r
633 // NB: this simple checks helps a lot sanitizing input, ie no
\r
634 // security problems around here
\r
635 if ($xmlrpc->_xh['ac']=='1' || strcasecmp($xmlrpc->_xh['ac'], 'true') == 0)
\r
637 $xmlrpc->_xh['value']=true;
\r
641 // log if receiveing something strange, even though we set the value to false anyway
\r
642 if ($xmlrpc->_xh['ac']!='0' && strcasecmp($xmlrpc->_xh['ac'], 'false') != 0)
\r
643 error_log('XML-RPC: invalid value received in BOOLEAN: '.$xmlrpc->_xh['ac']);
\r
644 $xmlrpc->_xh['value']=false;
\r
647 elseif ($name=='DOUBLE')
\r
649 // we have a DOUBLE
\r
650 // we must check that only 0123456789-.<space> are characters here
\r
651 // NOTE: regexp could be much stricter than this...
\r
652 if (!preg_match('/^[+-eE0123456789 \t.]+$/', $xmlrpc->_xh['ac']))
\r
654 /// @todo: find a better way of throwing an error than this!
\r
655 error_log('XML-RPC: non numeric value received in DOUBLE: '.$xmlrpc->_xh['ac']);
\r
656 $xmlrpc->_xh['value']='ERROR_NON_NUMERIC_FOUND';
\r
660 // it's ok, add it on
\r
661 $xmlrpc->_xh['value']=(double)$xmlrpc->_xh['ac'];
\r
666 // we have an I4/INT
\r
667 // we must check that only 0123456789-<space> are characters here
\r
668 if (!preg_match('/^[+-]?[0123456789 \t]+$/', $xmlrpc->_xh['ac']))
\r
670 /// @todo find a better way of throwing an error than this!
\r
671 error_log('XML-RPC: non numeric value received in INT: '.$xmlrpc->_xh['ac']);
\r
672 $xmlrpc->_xh['value']='ERROR_NON_NUMERIC_FOUND';
\r
676 // it's ok, add it on
\r
677 $xmlrpc->_xh['value']=(int)$xmlrpc->_xh['ac'];
\r
680 //$xmlrpc->_xh['ac']=''; // is this necessary?
\r
681 $xmlrpc->_xh['lv']=3; // indicate we've found a value
\r
684 $xmlrpc->_xh['valuestack'][count($xmlrpc->_xh['valuestack'])-1]['name'] = $xmlrpc->_xh['ac'];
\r
687 //$xmlrpc->_xh['ac']=''; // is this necessary?
\r
688 // add to array in the stack the last element built,
\r
689 // unless no VALUE was found
\r
690 if ($xmlrpc->_xh['vt'])
\r
692 $vscount = count($xmlrpc->_xh['valuestack']);
\r
693 $xmlrpc->_xh['valuestack'][$vscount-1]['values'][$xmlrpc->_xh['valuestack'][$vscount-1]['name']] = $xmlrpc->_xh['value'];
\r
695 error_log('XML-RPC: missing VALUE inside STRUCT in received xml');
\r
698 //$xmlrpc->_xh['ac']=''; // is this necessary?
\r
699 $xmlrpc->_xh['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty
\r
703 // fetch out of stack array of values, and promote it to current value
\r
704 $curr_val = array_pop($xmlrpc->_xh['valuestack']);
\r
705 $xmlrpc->_xh['value'] = $curr_val['values'];
\r
706 $xmlrpc->_xh['vt']=strtolower($name);
\r
707 if (isset($curr_val['php_class']))
\r
709 $xmlrpc->_xh['php_class'] = $curr_val['php_class'];
\r
713 // add to array of params the current value,
\r
714 // unless no VALUE was found
\r
715 if ($xmlrpc->_xh['vt'])
\r
717 $xmlrpc->_xh['params'][]=$xmlrpc->_xh['value'];
\r
718 $xmlrpc->_xh['pt'][]=$xmlrpc->_xh['vt'];
\r
721 error_log('XML-RPC: missing VALUE inside PARAM in received xml');
\r
724 $xmlrpc->_xh['method']=preg_replace('/^[\n\r\t ]+/', '', $xmlrpc->_xh['ac']);
\r
728 if ($xmlrpc->xmlrpc_null_extension)
\r
730 $xmlrpc->_xh['vt']='null';
\r
731 $xmlrpc->_xh['value']=null;
\r
732 $xmlrpc->_xh['lv']=3;
\r
735 // drop through intentionally if nil extension not enabled
\r
739 case 'METHORESPONSE':
\r
742 // End of INVALID ELEMENT!
\r
743 // shall we add an assert here for unreachable code???
\r
749 /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values
\r
750 function xmlrpc_ee_fast($parser, $name)
\r
752 xmlrpc_ee($parser, $name, false);
\r
755 /// xml parser handler function for character data
\r
756 function xmlrpc_cd($parser, $data)
\r
758 $xmlrpc = Phpxmlrpc::instance();
\r
759 // skip processing if xml fault already detected
\r
760 if ($xmlrpc->_xh['isf'] < 2)
\r
762 // "lookforvalue==3" means that we've found an entire value
\r
763 // and should discard any further character data
\r
764 if($xmlrpc->_xh['lv']!=3)
\r
766 // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2
\r
767 //if($xmlrpc->_xh['lv']==1)
\r
769 // if we've found text and we're just in a <value> then
\r
770 // say we've found a value
\r
771 //$xmlrpc->_xh['lv']=2;
\r
773 // we always initialize the accumulator before starting parsing, anyway...
\r
774 //if(!@isset($xmlrpc->_xh['ac']))
\r
776 // $xmlrpc->_xh['ac'] = '';
\r
778 $xmlrpc->_xh['ac'].=$data;
\r
783 /// xml parser handler function for 'other stuff', ie. not char data or
\r
784 /// element start/end tag. In fact it only gets called on unknown entities...
\r
785 function xmlrpc_dh($parser, $data)
\r
787 $xmlrpc = Phpxmlrpc::instance();
\r
788 // skip processing if xml fault already detected
\r
789 if ($xmlrpc->_xh['isf'] < 2)
\r
791 if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
\r
793 // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2
\r
794 //if($xmlrpc->_xh['lv']==1)
\r
796 // $xmlrpc->_xh['lv']=2;
\r
798 $xmlrpc->_xh['ac'].=$data;
\r
807 * Given a timestamp, return the corresponding ISO8601 encoded string.
\r
809 * Really, timezones ought to be supported
\r
810 * but the XML-RPC spec says:
\r
812 * "Don't assume a timezone. It should be specified by the server in its
\r
813 * documentation what assumptions it makes about timezones."
\r
815 * These routines always assume localtime unless
\r
816 * $utc is set to 1, in which case UTC is assumed
\r
817 * and an adjustment for locale is made when encoding
\r
819 * @param int $timet (timestamp)
\r
820 * @param int $utc (0 or 1)
\r
823 function iso8601_encode($timet, $utc=0)
\r
827 $t=strftime("%Y%m%dT%H:%M:%S", $timet);
\r
831 if(function_exists('gmstrftime'))
\r
833 // gmstrftime doesn't exist in some versions
\r
835 $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
\r
839 $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
\r
846 * Given an ISO8601 date string, return a timet in the localtime, or UTC
\r
847 * @param string $idate
\r
848 * @param int $utc either 0 or 1
\r
849 * @return int (datetime)
\r
851 function iso8601_decode($idate, $utc=0)
\r
854 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
858 $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
\r
862 $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
\r
869 * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types.
\r
871 * Works with xmlrpc message objects as input, too.
\r
873 * Given proper options parameter, can rebuild generic php object instances
\r
874 * (provided those have been encoded to xmlrpc format using a corresponding
\r
875 * option in php_xmlrpc_encode())
\r
876 * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
\r
877 * This means that the remote communication end can decide which php code will
\r
878 * get executed on your server, leaving the door possibly open to 'php-injection'
\r
879 * style of attacks (provided you have some classes defined on your server that
\r
880 * might wreak havoc if instances are built outside an appropriate context).
\r
881 * Make sure you trust the remote server/client before eanbling this!
\r
883 * @author Dan Libby (dan@libby.com)
\r
885 * @param xmlrpcval $xmlrpc_val
\r
886 * @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
889 function php_xmlrpc_decode($xmlrpc_val, $options=array())
\r
891 switch($xmlrpc_val->kindOf())
\r
894 if (in_array('extension_api', $options))
\r
896 reset($xmlrpc_val->me);
\r
897 list($typ,$val) = each($xmlrpc_val->me);
\r
900 case 'dateTime.iso8601':
\r
901 $xmlrpc_val->scalar = $val;
\r
902 $xmlrpc_val->xmlrpc_type = 'datetime';
\r
903 $xmlrpc_val->timestamp = iso8601_decode($val);
\r
904 return $xmlrpc_val;
\r
906 $xmlrpc_val->scalar = $val;
\r
907 $xmlrpc_val->type = $typ;
\r
908 return $xmlrpc_val;
\r
910 return $xmlrpc_val->scalarval();
\r
913 if (in_array('dates_as_objects', $options) && $xmlrpc_val->scalartyp() == 'dateTime.iso8601')
\r
915 // we return a Datetime object instead of a string
\r
916 // since now the constructor of xmlrpcval accepts safely strings, ints and datetimes,
\r
917 // we cater to all 3 cases here
\r
918 $out = $xmlrpc_val->scalarval();
\r
919 if (is_string($out))
\r
921 $out = strtotime($out);
\r
925 $result = new Datetime();
\r
926 $result->setTimestamp($out);
\r
929 elseif (is_a($out, 'Datetime'))
\r
934 return $xmlrpc_val->scalarval();
\r
936 $size = $xmlrpc_val->arraysize();
\r
938 for($i = 0; $i < $size; $i++)
\r
940 $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
\r
944 $xmlrpc_val->structreset();
\r
945 // If user said so, try to rebuild php objects for specific struct vals.
\r
946 /// @todo should we raise a warning for class not found?
\r
947 // shall we check for proper subclass of xmlrpcval instead of
\r
948 // presence of _php_class to detect what we can do?
\r
949 if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
\r
950 && class_exists($xmlrpc_val->_php_class))
\r
952 $obj = @new $xmlrpc_val->_php_class;
\r
953 while(list($key,$value)=$xmlrpc_val->structeach())
\r
955 $obj->$key = php_xmlrpc_decode($value, $options);
\r
962 while(list($key,$value)=$xmlrpc_val->structeach())
\r
964 $arr[$key] = php_xmlrpc_decode($value, $options);
\r
969 $paramcount = $xmlrpc_val->getNumParams();
\r
971 for($i = 0; $i < $paramcount; $i++)
\r
973 $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
\r
979 // This constant left here only for historical reasons...
\r
980 // it was used to decide if we have to define xmlrpc_encode on our own, but
\r
981 // we do not do it anymore
\r
982 if(function_exists('xmlrpc_decode'))
\r
984 define('XMLRPC_EPI_ENABLED','1');
\r
988 define('XMLRPC_EPI_ENABLED','0');
\r
992 * Takes native php types and encodes them into xmlrpc PHP object format.
\r
993 * It will not re-encode xmlrpcval objects.
\r
995 * Feature creep -- could support more types via optional type argument
\r
996 * (string => datetime support has been added, ??? => base64 not yet)
\r
998 * If given a proper options parameter, php object instances will be encoded
\r
999 * into 'special' xmlrpc values, that can later be decoded into php objects
\r
1000 * by calling php_xmlrpc_decode() with a corresponding option
\r
1002 * @author Dan Libby (dan@libby.com)
\r
1004 * @param mixed $php_val the value to be converted into an xmlrpcval object
\r
1005 * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
\r
1006 * @return xmlrpcval
\r
1008 function php_xmlrpc_encode($php_val, $options=array())
\r
1010 $xmlrpc = Phpxmlrpc::instance();
\r
1011 $type = gettype($php_val);
\r
1015 if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))
\r
1016 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcDateTime);
\r
1018 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcString);
\r
1021 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcInt);
\r
1024 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcDouble);
\r
1026 // <G_Giunta_2001-02-29>
\r
1027 // Add support for encoding/decoding of booleans, since they are supported in PHP
\r
1029 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcBoolean);
\r
1031 // </G_Giunta_2001-02-29>
\r
1033 // PHP arrays can be encoded to either xmlrpc structs or arrays,
\r
1034 // depending on wheter they are hashes or plain 0..n integer indexed
\r
1035 // A shorter one-liner would be
\r
1036 // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
\r
1037 // but execution time skyrockets!
\r
1041 foreach($php_val as $key => $val)
\r
1043 $arr[$key] = php_xmlrpc_encode($val, $options);
\r
1044 if(!$ko && $key !== $j)
\r
1052 $xmlrpc_val = new xmlrpcval($arr, $xmlrpc->xmlrpcStruct);
\r
1056 $xmlrpc_val = new xmlrpcval($arr, $xmlrpc->xmlrpcArray);
\r
1060 if(is_a($php_val, 'xmlrpcval'))
\r
1062 $xmlrpc_val = $php_val;
\r
1064 else if(is_a($php_val, 'DateTime'))
\r
1066 $xmlrpc_val = new xmlrpcval($php_val->format('Ymd\TH:i:s'), $xmlrpc->xmlrpcStruct);
\r
1072 while(list($k,$v) = each($php_val))
\r
1074 $arr[$k] = php_xmlrpc_encode($v, $options);
\r
1076 $xmlrpc_val = new xmlrpcval($arr, $xmlrpc->xmlrpcStruct);
\r
1077 if (in_array('encode_php_objs', $options))
\r
1079 // let's save original class name into xmlrpcval:
\r
1080 // might be useful later on...
\r
1081 $xmlrpc_val->_php_class = get_class($php_val);
\r
1086 if (in_array('extension_api', $options))
\r
1088 $xmlrpc_val = new xmlrpcval('', $xmlrpc->xmlrpcString);
\r
1090 else if (in_array('null_extension', $options))
\r
1092 $xmlrpc_val = new xmlrpcval('', $xmlrpc->xmlrpcNull);
\r
1096 $xmlrpc_val = new xmlrpcval();
\r
1100 if (in_array('extension_api', $options))
\r
1102 $xmlrpc_val = new xmlrpcval((int)$php_val, $xmlrpc->xmlrpcInt);
\r
1106 $xmlrpc_val = new xmlrpcval();
\r
1108 // catch "user function", "unknown type"
\r
1110 // giancarlo pinerolo <ping@alt.it>
\r
1111 // it has to return
\r
1112 // an empty object in case, not a boolean.
\r
1113 $xmlrpc_val = new xmlrpcval();
\r
1116 return $xmlrpc_val;
\r
1120 * Convert the xml representation of a method response, method request or single
\r
1121 * xmlrpc value into the appropriate object (a.k.a. deserialize)
\r
1122 * @param string $xml_val
\r
1123 * @param array $options
\r
1124 * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp
\r
1126 function php_xmlrpc_decode_xml($xml_val, $options=array())
\r
1128 $xmlrpc = Phpxmlrpc::instance();
\r
1130 $xmlrpc->_xh = array();
\r
1131 $xmlrpc->_xh['ac'] = '';
\r
1132 $xmlrpc->_xh['stack'] = array();
\r
1133 $xmlrpc->_xh['valuestack'] = array();
\r
1134 $xmlrpc->_xh['params'] = array();
\r
1135 $xmlrpc->_xh['pt'] = array();
\r
1136 $xmlrpc->_xh['isf'] = 0;
\r
1137 $xmlrpc->_xh['isf_reason'] = '';
\r
1138 $xmlrpc->_xh['method'] = false;
\r
1139 $xmlrpc->_xh['rt'] = '';
\r
1140 /// @todo 'guestimate' encoding
\r
1141 $parser = xml_parser_create();
\r
1142 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
\r
1143 // What if internal encoding is not in one of the 3 allowed?
\r
1144 // we use the broadest one, ie. utf8!
\r
1145 if (!in_array($xmlrpc->xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
\r
1147 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
\r
1151 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $xmlrpc->xmlrpc_internalencoding);
\r
1153 xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
\r
1154 xml_set_character_data_handler($parser, 'xmlrpc_cd');
\r
1155 xml_set_default_handler($parser, 'xmlrpc_dh');
\r
1156 if(!xml_parse($parser, $xml_val, 1))
\r
1158 $errstr = sprintf('XML error: %s at line %d, column %d',
\r
1159 xml_error_string(xml_get_error_code($parser)),
\r
1160 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
\r
1161 error_log($errstr);
\r
1162 xml_parser_free($parser);
\r
1165 xml_parser_free($parser);
\r
1166 if ($xmlrpc->_xh['isf'] > 1) // test that $xmlrpc->_xh['value'] is an obj, too???
\r
1168 error_log($xmlrpc->_xh['isf_reason']);
\r
1171 switch ($xmlrpc->_xh['rt'])
\r
1173 case 'methodresponse':
\r
1174 $v =& $xmlrpc->_xh['value'];
\r
1175 if ($xmlrpc->_xh['isf'] == 1)
\r
1177 $vc = $v->structmem('faultCode');
\r
1178 $vs = $v->structmem('faultString');
\r
1179 $r = new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval());
\r
1183 $r = new xmlrpcresp($v);
\r
1186 case 'methodcall':
\r
1187 $m = new xmlrpcmsg($xmlrpc->_xh['method']);
\r
1188 for($i=0; $i < count($xmlrpc->_xh['params']); $i++)
\r
1190 $m->addParam($xmlrpc->_xh['params'][$i]);
\r
1194 return $xmlrpc->_xh['value'];
\r
1201 * decode a string that is encoded w/ "chunked" transfer encoding
\r
1202 * as defined in rfc2068 par. 19.4.6
\r
1203 * code shamelessly stolen from nusoap library by Dietrich Ayala
\r
1205 * @param string $buffer the string to be decoded
\r
1208 function decode_chunked($buffer)
\r
1214 // read chunk-size, chunk-extension (if any) and crlf
\r
1215 // get the position of the linebreak
\r
1216 $chunkend = strpos($buffer,"\r\n") + 2;
\r
1217 $temp = substr($buffer,0,$chunkend);
\r
1218 $chunk_size = hexdec( trim($temp) );
\r
1219 $chunkstart = $chunkend;
\r
1220 while($chunk_size > 0)
\r
1222 $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
\r
1224 // just in case we got a broken connection
\r
1225 if($chunkend == false)
\r
1227 $chunk = substr($buffer,$chunkstart);
\r
1228 // append chunk-data to entity-body
\r
1230 $length += strlen($chunk);
\r
1234 // read chunk-data and crlf
\r
1235 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
\r
1236 // append chunk-data to entity-body
\r
1238 // length := length + chunk-size
\r
1239 $length += strlen($chunk);
\r
1240 // read chunk-size and crlf
\r
1241 $chunkstart = $chunkend + 2;
\r
1243 $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
\r
1244 if($chunkend == false)
\r
1246 break; //just in case we got a broken connection
\r
1248 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
\r
1249 $chunk_size = hexdec( trim($temp) );
\r
1250 $chunkstart = $chunkend;
\r
1256 * xml charset encoding guessing helper function.
\r
1257 * Tries to determine the charset encoding of an XML chunk received over HTTP.
\r
1258 * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
\r
1259 * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
\r
1260 * which will be most probably using UTF-8 anyway...
\r
1262 * @param string $httpheader the http Content-type header
\r
1263 * @param string $xmlchunk xml content buffer
\r
1264 * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
\r
1267 * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
\r
1269 function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
\r
1271 $xmlrpc = Phpxmlrpc::instance();
\r
1273 // discussion: see http://www.yale.edu/pclt/encoding/
\r
1274 // 1 - test if encoding is specified in HTTP HEADERS
\r
1277 // LWS: (\13\10)?( |\t)+
\r
1278 // token: (any char but excluded stuff)+
\r
1279 // quoted string: " (any char but double quotes and cointrol chars)* "
\r
1280 // header: Content-type = ...; charset=value(; ...)*
\r
1281 // where value is of type token, no LWS allowed between 'charset' and value
\r
1282 // Note: we do not check for invalid chars in VALUE:
\r
1283 // this had better be done using pure ereg as below
\r
1284 // Note 2: we might be removing whitespace/tabs that ought to be left in if
\r
1285 // the received charset is a quoted string. But nobody uses such charset names...
\r
1287 /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
\r
1288 $matches = array();
\r
1289 if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches))
\r
1291 return strtoupper(trim($matches[1], " \t\""));
\r
1294 // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
\r
1295 // (source: http://www.w3.org/TR/2000/REC-xml-20001006)
\r
1296 // NOTE: actually, according to the spec, even if we find the BOM and determine
\r
1297 // an encoding, we should check if there is an encoding specified
\r
1298 // in the xml declaration, and verify if they match.
\r
1299 /// @todo implement check as described above?
\r
1300 /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
\r
1301 if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))
\r
1305 elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))
\r
1309 elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))
\r
1314 // 3 - test if encoding is specified in the xml declaration
\r
1316 // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
\r
1317 // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
\r
1318 if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".
\r
1319 '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
\r
1320 $xmlchunk, $matches))
\r
1322 return strtoupper(substr($matches[2], 1, -1));
\r
1325 // 4 - if mbstring is available, let it do the guesswork
\r
1326 // NB: we favour finding an encoding that is compatible with what we can process
\r
1327 if(extension_loaded('mbstring'))
\r
1329 if($encoding_prefs)
\r
1331 $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
\r
1335 $enc = mb_detect_encoding($xmlchunk);
\r
1337 // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
\r
1338 // IANA also likes better US-ASCII, so go with it
\r
1339 if($enc == 'ASCII')
\r
1341 $enc = 'US-'.$enc;
\r
1347 // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
\r
1348 // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
\r
1349 // this should be the standard. And we should be getting text/xml as request and response.
\r
1350 // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
\r
1351 return $xmlrpc->xmlrpc_defencoding;
\r
1356 * Checks if a given charset encoding is present in a list of encodings or
\r
1357 * if it is a valid subset of any encoding in the list
\r
1358 * @param string $encoding charset to be tested
\r
1359 * @param mixed $validlist comma separated list of valid charsets (or array of charsets)
\r
1362 function is_valid_charset($encoding, $validlist)
\r
1364 $charset_supersets = array(
\r
1365 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
\r
1366 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
\r
1367 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
\r
1368 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
\r
1369 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
\r
1371 if (is_string($validlist))
\r
1372 $validlist = explode(',', $validlist);
\r
1373 if (@in_array(strtoupper($encoding), $validlist))
\r
1377 if (array_key_exists($encoding, $charset_supersets))
\r
1378 foreach ($validlist as $allowed)
\r
1379 if (in_array($allowed, $charset_supersets[$encoding]))
\r