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