00001 <?php 00002 // by Edd Dumbill (C) 1999-2002 00003 // <edd@usefulinc.com> 00004 // $Id: xmlrpc.inc,v 1.174 2009/03/16 19:36:38 ggiunta Exp $ 00005 00006 // Copyright (c) 1999,2000,2002 Edd Dumbill. 00007 // All rights reserved. 00008 // 00009 // Redistribution and use in source and binary forms, with or without 00010 // modification, are permitted provided that the following conditions 00011 // are met: 00012 // 00013 // * Redistributions of source code must retain the above copyright 00014 // notice, this list of conditions and the following disclaimer. 00015 // 00016 // * Redistributions in binary form must reproduce the above 00017 // copyright notice, this list of conditions and the following 00018 // disclaimer in the documentation and/or other materials provided 00019 // with the distribution. 00020 // 00021 // * Neither the name of the "XML-RPC for PHP" nor the names of its 00022 // contributors may be used to endorse or promote products derived 00023 // from this software without specific prior written permission. 00024 // 00025 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00026 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00027 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00028 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00029 // REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00030 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 00031 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 00032 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 00033 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 00034 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 00035 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 00036 // OF THE POSSIBILITY OF SUCH DAMAGE. 00037 00038 if(!function_exists('xml_parser_create')) 00039 { 00040 // For PHP 4 onward, XML functionality is always compiled-in on windows: 00041 // no more need to dl-open it. It might have been compiled out on *nix... 00042 if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN')) 00043 { 00044 dl('xml.so'); 00045 } 00046 } 00047 00048 // Try to be backward compat with php < 4.2 (are we not being nice ?) 00049 $phpversion = phpversion(); 00050 if($phpversion[0] == '4' && $phpversion[2] < 2) 00051 { 00052 // give an opportunity to user to specify where to include other files from 00053 if(!defined('PHP_XMLRPC_COMPAT_DIR')) 00054 { 00055 define('PHP_XMLRPC_COMPAT_DIR',dirname(__FILE__).'/compat/'); 00056 } 00057 if($phpversion[2] == '0') 00058 { 00059 if($phpversion[4] < 6) 00060 { 00061 include(PHP_XMLRPC_COMPAT_DIR.'is_callable.php'); 00062 } 00063 include(PHP_XMLRPC_COMPAT_DIR.'is_scalar.php'); 00064 include(PHP_XMLRPC_COMPAT_DIR.'array_key_exists.php'); 00065 include(PHP_XMLRPC_COMPAT_DIR.'version_compare.php'); 00066 } 00067 include(PHP_XMLRPC_COMPAT_DIR.'var_export.php'); 00068 include(PHP_XMLRPC_COMPAT_DIR.'is_a.php'); 00069 } 00070 00071 // G. Giunta 2005/01/29: declare global these variables, 00072 // so that xmlrpc.inc will work even if included from within a function 00073 // Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used. 00074 $GLOBALS['xmlrpcI4']='i4'; 00075 $GLOBALS['xmlrpcInt']='int'; 00076 $GLOBALS['xmlrpcBoolean']='boolean'; 00077 $GLOBALS['xmlrpcDouble']='double'; 00078 $GLOBALS['xmlrpcString']='string'; 00079 $GLOBALS['xmlrpcDateTime']='dateTime.iso8601'; 00080 $GLOBALS['xmlrpcBase64']='base64'; 00081 $GLOBALS['xmlrpcArray']='array'; 00082 $GLOBALS['xmlrpcStruct']='struct'; 00083 $GLOBALS['xmlrpcValue']='undefined'; 00084 00085 $GLOBALS['xmlrpcTypes']=array( 00086 $GLOBALS['xmlrpcI4'] => 1, 00087 $GLOBALS['xmlrpcInt'] => 1, 00088 $GLOBALS['xmlrpcBoolean'] => 1, 00089 $GLOBALS['xmlrpcString'] => 1, 00090 $GLOBALS['xmlrpcDouble'] => 1, 00091 $GLOBALS['xmlrpcDateTime'] => 1, 00092 $GLOBALS['xmlrpcBase64'] => 1, 00093 $GLOBALS['xmlrpcArray'] => 2, 00094 $GLOBALS['xmlrpcStruct'] => 3 00095 ); 00096 00097 $GLOBALS['xmlrpc_valid_parents'] = array( 00098 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'), 00099 'BOOLEAN' => array('VALUE'), 00100 'I4' => array('VALUE'), 00101 'INT' => array('VALUE'), 00102 'STRING' => array('VALUE'), 00103 'DOUBLE' => array('VALUE'), 00104 'DATETIME.ISO8601' => array('VALUE'), 00105 'BASE64' => array('VALUE'), 00106 'MEMBER' => array('STRUCT'), 00107 'NAME' => array('MEMBER'), 00108 'DATA' => array('ARRAY'), 00109 'ARRAY' => array('VALUE'), 00110 'STRUCT' => array('VALUE'), 00111 'PARAM' => array('PARAMS'), 00112 'METHODNAME' => array('METHODCALL'), 00113 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'), 00114 'FAULT' => array('METHODRESPONSE'), 00115 'NIL' => array('VALUE') // only used when extension activated 00116 ); 00117 00118 // define extra types for supporting NULL (useful for json or <NIL/>) 00119 $GLOBALS['xmlrpcNull']='null'; 00120 $GLOBALS['xmlrpcTypes']['null']=1; 00121 00122 // Not in use anymore since 2.0. Shall we remove it? 00124 $GLOBALS['xmlEntities']=array( 00125 'amp' => '&', 00126 'quot' => '"', 00127 'lt' => '<', 00128 'gt' => '>', 00129 'apos' => "'" 00130 ); 00131 00132 // tables used for transcoding different charsets into us-ascii xml 00133 00134 $GLOBALS['xml_iso88591_Entities']=array(); 00135 $GLOBALS['xml_iso88591_Entities']['in'] = array(); 00136 $GLOBALS['xml_iso88591_Entities']['out'] = array(); 00137 for ($i = 0; $i < 32; $i++) 00138 { 00139 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i); 00140 $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';'; 00141 } 00142 for ($i = 160; $i < 256; $i++) 00143 { 00144 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i); 00145 $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';'; 00146 } 00147 00151 /* 00152 $GLOBALS['xml_cp1252_Entities']=array(); 00153 for ($i = 128; $i < 160; $i++) 00154 { 00155 $GLOBALS['xml_cp1252_Entities']['in'][] = chr($i); 00156 } 00157 $GLOBALS['xml_cp1252_Entities']['out'] = array( 00158 '€', '?', '‚', 'ƒ', 00159 '„', '…', '†', '‡', 00160 'ˆ', '‰', 'Š', '‹', 00161 'Œ', '?', 'Ž', '?', 00162 '?', '‘', '’', '“', 00163 '”', '•', '–', '—', 00164 '˜', '™', 'š', '›', 00165 'œ', '?', 'ž', 'Ÿ' 00166 ); 00167 */ 00168 00169 $GLOBALS['xmlrpcerr'] = array( 00170 'unknown_method'=>1, 00171 'invalid_return'=>2, 00172 'incorrect_params'=>3, 00173 'introspect_unknown'=>4, 00174 'http_error'=>5, 00175 'no_data'=>6, 00176 'no_ssl'=>7, 00177 'curl_fail'=>8, 00178 'invalid_request'=>15, 00179 'no_curl'=>16, 00180 'server_error'=>17, 00181 'multicall_error'=>18, 00182 'multicall_notstruct'=>9, 00183 'multicall_nomethod'=>10, 00184 'multicall_notstring'=>11, 00185 'multicall_recursion'=>12, 00186 'multicall_noparams'=>13, 00187 'multicall_notarray'=>14, 00188 00189 'cannot_decompress'=>103, 00190 'decompress_fail'=>104, 00191 'dechunk_fail'=>105, 00192 'server_cannot_decompress'=>106, 00193 'server_decompress_fail'=>107 00194 ); 00195 00196 $GLOBALS['xmlrpcstr'] = array( 00197 'unknown_method'=>'Unknown method', 00198 'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload', 00199 'incorrect_params'=>'Incorrect parameters passed to method', 00200 'introspect_unknown'=>"Can't introspect: method unknown", 00201 'http_error'=>"Didn't receive 200 OK from remote server.", 00202 'no_data'=>'No data received from server.', 00203 'no_ssl'=>'No SSL support compiled in.', 00204 'curl_fail'=>'CURL error', 00205 'invalid_request'=>'Invalid request payload', 00206 'no_curl'=>'No CURL support compiled in.', 00207 'server_error'=>'Internal server error', 00208 'multicall_error'=>'Received from server invalid multicall response', 00209 'multicall_notstruct'=>'system.multicall expected struct', 00210 'multicall_nomethod'=>'missing methodName', 00211 'multicall_notstring'=>'methodName is not a string', 00212 'multicall_recursion'=>'recursive system.multicall forbidden', 00213 'multicall_noparams'=>'missing params', 00214 'multicall_notarray'=>'params is not an array', 00215 00216 'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress', 00217 'decompress_fail'=>'Received from server invalid compressed HTTP', 00218 'dechunk_fail'=>'Received from server invalid chunked HTTP', 00219 'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress', 00220 'server_decompress_fail'=>'Received from client invalid compressed HTTP request' 00221 ); 00222 00223 // The charset encoding used by the server for received messages and 00224 // by the client for received responses when received charset cannot be determined 00225 // or is not supported 00226 $GLOBALS['xmlrpc_defencoding']='UTF-8'; 00227 00228 // The encoding used internally by PHP. 00229 // String values received as xml will be converted to this, and php strings will be converted to xml 00230 // as if having been coded with this 00231 $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1'; 00232 00233 $GLOBALS['xmlrpcName']='XML-RPC for PHP'; 00234 $GLOBALS['xmlrpcVersion']='2.2.2'; 00235 00236 // let user errors start at 800 00237 $GLOBALS['xmlrpcerruser']=800; 00238 // let XML parse errors start at 100 00239 $GLOBALS['xmlrpcerrxml']=100; 00240 00241 // formulate backslashes for escaping regexp 00242 // Not in use anymore since 2.0. Shall we remove it? 00244 $GLOBALS['xmlrpc_backslash']=chr(92).chr(92); 00245 00246 // set to TRUE to enable correct decoding of <NIL/> values 00247 $GLOBALS['xmlrpc_null_extension']=false; 00248 00249 // used to store state during parsing 00250 // quick explanation of components: 00251 // ac - used to accumulate values 00252 // isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1) 00253 // isf_reason - used for storing xmlrpcresp fault string 00254 // lv - used to indicate "looking for a value": implements 00255 // the logic to allow values with no types to be strings 00256 // params - used to store parameters in method calls 00257 // method - used to store method name 00258 // stack - array with genealogy of xml elements names: 00259 // used to validate nesting of xmlrpc elements 00260 $GLOBALS['_xh']=null; 00261 00276 function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='') 00277 { 00278 if ($src_encoding == '') 00279 { 00280 // lame, but we know no better... 00281 $src_encoding = $GLOBALS['xmlrpc_internalencoding']; 00282 } 00283 00284 switch(strtoupper($src_encoding.'_'.$dest_encoding)) 00285 { 00286 case 'ISO-8859-1_': 00287 case 'ISO-8859-1_US-ASCII': 00288 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); 00289 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data); 00290 break; 00291 case 'ISO-8859-1_UTF-8': 00292 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); 00293 $escaped_data = utf8_encode($escaped_data); 00294 break; 00295 case 'ISO-8859-1_ISO-8859-1': 00296 case 'US-ASCII_US-ASCII': 00297 case 'US-ASCII_UTF-8': 00298 case 'US-ASCII_': 00299 case 'US-ASCII_ISO-8859-1': 00300 case 'UTF-8_UTF-8': 00301 //case 'CP1252_CP1252': 00302 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); 00303 break; 00304 case 'UTF-8_': 00305 case 'UTF-8_US-ASCII': 00306 case 'UTF-8_ISO-8859-1': 00307 // NB: this will choke on invalid UTF-8, going most likely beyond EOF 00308 $escaped_data = ''; 00309 // be kind to users creating string xmlrpcvals out of different php types 00310 $data = (string) $data; 00311 $ns = strlen ($data); 00312 for ($nn = 0; $nn < $ns; $nn++) 00313 { 00314 $ch = $data[$nn]; 00315 $ii = ord($ch); 00316 //1 7 0bbbbbbb (127) 00317 if ($ii < 128) 00318 { 00320 switch($ii){ 00321 case 34: 00322 $escaped_data .= '"'; 00323 break; 00324 case 38: 00325 $escaped_data .= '&'; 00326 break; 00327 case 39: 00328 $escaped_data .= '''; 00329 break; 00330 case 60: 00331 $escaped_data .= '<'; 00332 break; 00333 case 62: 00334 $escaped_data .= '>'; 00335 break; 00336 default: 00337 $escaped_data .= $ch; 00338 } // switch 00339 } 00340 //2 11 110bbbbb 10bbbbbb (2047) 00341 else if ($ii>>5 == 6) 00342 { 00343 $b1 = ($ii & 31); 00344 $ii = ord($data[$nn+1]); 00345 $b2 = ($ii & 63); 00346 $ii = ($b1 * 64) + $b2; 00347 $ent = sprintf ('&#%d;', $ii); 00348 $escaped_data .= $ent; 00349 $nn += 1; 00350 } 00351 //3 16 1110bbbb 10bbbbbb 10bbbbbb 00352 else if ($ii>>4 == 14) 00353 { 00354 $b1 = ($ii & 15); 00355 $ii = ord($data[$nn+1]); 00356 $b2 = ($ii & 63); 00357 $ii = ord($data[$nn+2]); 00358 $b3 = ($ii & 63); 00359 $ii = ((($b1 * 64) + $b2) * 64) + $b3; 00360 $ent = sprintf ('&#%d;', $ii); 00361 $escaped_data .= $ent; 00362 $nn += 2; 00363 } 00364 //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb 00365 else if ($ii>>3 == 30) 00366 { 00367 $b1 = ($ii & 7); 00368 $ii = ord($data[$nn+1]); 00369 $b2 = ($ii & 63); 00370 $ii = ord($data[$nn+2]); 00371 $b3 = ($ii & 63); 00372 $ii = ord($data[$nn+3]); 00373 $b4 = ($ii & 63); 00374 $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4; 00375 $ent = sprintf ('&#%d;', $ii); 00376 $escaped_data .= $ent; 00377 $nn += 3; 00378 } 00379 } 00380 break; 00381 /* 00382 case 'CP1252_': 00383 case 'CP1252_US-ASCII': 00384 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); 00385 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data); 00386 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data); 00387 break; 00388 case 'CP1252_UTF-8': 00389 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); 00391 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data); 00392 $escaped_data = utf8_encode($escaped_data); 00393 break; 00394 case 'CP1252_ISO-8859-1': 00395 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); 00396 // we might as well replave all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities... 00397 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data); 00398 break; 00399 */ 00400 default: 00401 $escaped_data = ''; 00402 error_log("Converting from $src_encoding to $dest_encoding: not supported..."); 00403 } 00404 return $escaped_data; 00405 } 00406 00408 function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false) 00409 { 00410 // if invalid xmlrpc already detected, skip all processing 00411 if ($GLOBALS['_xh']['isf'] < 2) 00412 { 00413 // check for correct element nesting 00414 // top level element can only be of 2 types 00417 if (count($GLOBALS['_xh']['stack']) == 0) 00418 { 00419 if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && ( 00420 $name != 'VALUE' && !$accept_single_vals)) 00421 { 00422 $GLOBALS['_xh']['isf'] = 2; 00423 $GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element'; 00424 return; 00425 } 00426 else 00427 { 00428 $GLOBALS['_xh']['rt'] = strtolower($name); 00429 } 00430 } 00431 else 00432 { 00433 // not top level element: see if parent is OK 00434 $parent = end($GLOBALS['_xh']['stack']); 00435 if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name])) 00436 { 00437 $GLOBALS['_xh']['isf'] = 2; 00438 $GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent"; 00439 return; 00440 } 00441 } 00442 00443 switch($name) 00444 { 00445 // optimize for speed switch cases: most common cases first 00446 case 'VALUE': 00448 $GLOBALS['_xh']['vt']='value'; // indicator: no value found yet 00449 $GLOBALS['_xh']['ac']=''; 00450 $GLOBALS['_xh']['lv']=1; 00451 $GLOBALS['_xh']['php_class']=null; 00452 break; 00453 case 'I4': 00454 case 'INT': 00455 case 'STRING': 00456 case 'BOOLEAN': 00457 case 'DOUBLE': 00458 case 'DATETIME.ISO8601': 00459 case 'BASE64': 00460 if ($GLOBALS['_xh']['vt']!='value') 00461 { 00462 //two data elements inside a value: an error occurred! 00463 $GLOBALS['_xh']['isf'] = 2; 00464 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value"; 00465 return; 00466 } 00467 $GLOBALS['_xh']['ac']=''; // reset the accumulator 00468 break; 00469 case 'STRUCT': 00470 case 'ARRAY': 00471 if ($GLOBALS['_xh']['vt']!='value') 00472 { 00473 //two data elements inside a value: an error occurred! 00474 $GLOBALS['_xh']['isf'] = 2; 00475 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value"; 00476 return; 00477 } 00478 // create an empty array to hold child values, and push it onto appropriate stack 00479 $cur_val = array(); 00480 $cur_val['values'] = array(); 00481 $cur_val['type'] = $name; 00482 // check for out-of-band information to rebuild php objs 00483 // and in case it is found, save it 00484 if (@isset($attrs['PHP_CLASS'])) 00485 { 00486 $cur_val['php_class'] = $attrs['PHP_CLASS']; 00487 } 00488 $GLOBALS['_xh']['valuestack'][] = $cur_val; 00489 $GLOBALS['_xh']['vt']='data'; // be prepared for a data element next 00490 break; 00491 case 'DATA': 00492 if ($GLOBALS['_xh']['vt']!='data') 00493 { 00494 //two data elements inside a value: an error occurred! 00495 $GLOBALS['_xh']['isf'] = 2; 00496 $GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element"; 00497 return; 00498 } 00499 case 'METHODCALL': 00500 case 'METHODRESPONSE': 00501 case 'PARAMS': 00502 // valid elements that add little to processing 00503 break; 00504 case 'METHODNAME': 00505 case 'NAME': 00507 $GLOBALS['_xh']['ac']=''; 00508 break; 00509 case 'FAULT': 00510 $GLOBALS['_xh']['isf']=1; 00511 break; 00512 case 'MEMBER': 00513 $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 00514 //$GLOBALS['_xh']['ac']=''; 00515 // Drop trough intentionally 00516 case 'PARAM': 00517 // clear value type, so we can check later if no value has been passed for this param/member 00518 $GLOBALS['_xh']['vt']=null; 00519 break; 00520 case 'NIL': 00521 if ($GLOBALS['xmlrpc_null_extension']) 00522 { 00523 if ($GLOBALS['_xh']['vt']!='value') 00524 { 00525 //two data elements inside a value: an error occurred! 00526 $GLOBALS['_xh']['isf'] = 2; 00527 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value"; 00528 return; 00529 } 00530 $GLOBALS['_xh']['ac']=''; // reset the accumulator 00531 break; 00532 } 00533 // we do not support the <NIL/> extension, so 00534 // drop through intentionally 00535 default: 00537 $GLOBALS['_xh']['isf'] = 2; 00538 $GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name"; 00539 break; 00540 } 00541 00542 // Save current element name to stack, to validate nesting 00543 $GLOBALS['_xh']['stack'][] = $name; 00544 00546 if($name!='VALUE') 00547 { 00548 $GLOBALS['_xh']['lv']=0; 00549 } 00550 } 00551 } 00552 00554 function xmlrpc_se_any($parser, $name, $attrs) 00555 { 00556 xmlrpc_se($parser, $name, $attrs, true); 00557 } 00558 00560 function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true) 00561 { 00562 if ($GLOBALS['_xh']['isf'] < 2) 00563 { 00564 // push this element name from stack 00565 // NB: if XML validates, correct opening/closing is guaranteed and 00566 // we do not have to check for $name == $curr_elem. 00567 // we also checked for proper nesting at start of elements... 00568 $curr_elem = array_pop($GLOBALS['_xh']['stack']); 00569 00570 switch($name) 00571 { 00572 case 'VALUE': 00573 // This if() detects if no scalar was inside <VALUE></VALUE> 00574 if ($GLOBALS['_xh']['vt']=='value') 00575 { 00576 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac']; 00577 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString']; 00578 } 00579 00580 if ($rebuild_xmlrpcvals) 00581 { 00582 // build the xmlrpc val out of the data received, and substitute it 00583 $temp =& new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']); 00584 // in case we got info about underlying php class, save it 00585 // in the object we're rebuilding 00586 if (isset($GLOBALS['_xh']['php_class'])) 00587 $temp->_php_class = $GLOBALS['_xh']['php_class']; 00588 // check if we are inside an array or struct: 00589 // if value just built is inside an array, let's move it into array on the stack 00590 $vscount = count($GLOBALS['_xh']['valuestack']); 00591 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY') 00592 { 00593 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp; 00594 } 00595 else 00596 { 00597 $GLOBALS['_xh']['value'] = $temp; 00598 } 00599 } 00600 else 00601 { 00605 if (isset($GLOBALS['_xh']['php_class'])) 00606 { 00607 } 00608 00609 // check if we are inside an array or struct: 00610 // if value just built is inside an array, let's move it into array on the stack 00611 $vscount = count($GLOBALS['_xh']['valuestack']); 00612 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY') 00613 { 00614 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value']; 00615 } 00616 } 00617 break; 00618 case 'BOOLEAN': 00619 case 'I4': 00620 case 'INT': 00621 case 'STRING': 00622 case 'DOUBLE': 00623 case 'DATETIME.ISO8601': 00624 case 'BASE64': 00625 $GLOBALS['_xh']['vt']=strtolower($name); 00628 if ($name=='STRING') 00629 { 00630 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac']; 00631 } 00632 elseif ($name=='DATETIME.ISO8601') 00633 { 00634 if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $GLOBALS['_xh']['ac'])) 00635 { 00636 error_log('XML-RPC: invalid value received in DATETIME: '.$GLOBALS['_xh']['ac']); 00637 } 00638 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime']; 00639 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac']; 00640 } 00641 elseif ($name=='BASE64') 00642 { 00644 $GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']); 00645 } 00646 elseif ($name=='BOOLEAN') 00647 { 00648 // special case here: we translate boolean 1 or 0 into PHP 00649 // constants true or false. 00650 // Strings 'true' and 'false' are accepted, even though the 00651 // spec never mentions them (see eg. Blogger api docs) 00652 // NB: this simple checks helps a lot sanitizing input, ie no 00653 // security problems around here 00654 if ($GLOBALS['_xh']['ac']=='1' || strcasecmp($GLOBALS['_xh']['ac'], 'true') == 0) 00655 { 00656 $GLOBALS['_xh']['value']=true; 00657 } 00658 else 00659 { 00660 // log if receiveing something strange, even though we set the value to false anyway 00661 if ($GLOBALS['_xh']['ac']!='0' && strcasecmp($GLOBALS['_xh']['ac'], 'false') != 0) 00662 error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']); 00663 $GLOBALS['_xh']['value']=false; 00664 } 00665 } 00666 elseif ($name=='DOUBLE') 00667 { 00668 // we have a DOUBLE 00669 // we must check that only 0123456789-.<space> are characters here 00670 // NOTE: regexp could be much stricter than this... 00671 if (!preg_match('/^[+-eE0123456789 \t.]+$/', $GLOBALS['_xh']['ac'])) 00672 { 00674 error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']); 00675 $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND'; 00676 } 00677 else 00678 { 00679 // it's ok, add it on 00680 $GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac']; 00681 } 00682 } 00683 else 00684 { 00685 // we have an I4/INT 00686 // we must check that only 0123456789-<space> are characters here 00687 if (!preg_match('/^[+-]?[0123456789 \t]+$/', $GLOBALS['_xh']['ac'])) 00688 { 00690 error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']); 00691 $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND'; 00692 } 00693 else 00694 { 00695 // it's ok, add it on 00696 $GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac']; 00697 } 00698 } 00699 //$GLOBALS['_xh']['ac']=''; // is this necessary? 00700 $GLOBALS['_xh']['lv']=3; // indicate we've found a value 00701 break; 00702 case 'NAME': 00703 $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac']; 00704 break; 00705 case 'MEMBER': 00706 //$GLOBALS['_xh']['ac']=''; // is this necessary? 00707 // add to array in the stack the last element built, 00708 // unless no VALUE was found 00709 if ($GLOBALS['_xh']['vt']) 00710 { 00711 $vscount = count($GLOBALS['_xh']['valuestack']); 00712 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value']; 00713 } else 00714 error_log('XML-RPC: missing VALUE inside STRUCT in received xml'); 00715 break; 00716 case 'DATA': 00717 //$GLOBALS['_xh']['ac']=''; // is this necessary? 00718 $GLOBALS['_xh']['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty 00719 break; 00720 case 'STRUCT': 00721 case 'ARRAY': 00722 // fetch out of stack array of values, and promote it to current value 00723 $curr_val = array_pop($GLOBALS['_xh']['valuestack']); 00724 $GLOBALS['_xh']['value'] = $curr_val['values']; 00725 $GLOBALS['_xh']['vt']=strtolower($name); 00726 if (isset($curr_val['php_class'])) 00727 { 00728 $GLOBALS['_xh']['php_class'] = $curr_val['php_class']; 00729 } 00730 break; 00731 case 'PARAM': 00732 // add to array of params the current value, 00733 // unless no VALUE was found 00734 if ($GLOBALS['_xh']['vt']) 00735 { 00736 $GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value']; 00737 $GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt']; 00738 } 00739 else 00740 error_log('XML-RPC: missing VALUE inside PARAM in received xml'); 00741 break; 00742 case 'METHODNAME': 00743 $GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', $GLOBALS['_xh']['ac']); 00744 break; 00745 case 'NIL': 00746 if ($GLOBALS['xmlrpc_null_extension']) 00747 { 00748 $GLOBALS['_xh']['vt']='null'; 00749 $GLOBALS['_xh']['value']=null; 00750 $GLOBALS['_xh']['lv']=3; 00751 break; 00752 } 00753 // drop through intentionally if nil extension not enabled 00754 case 'PARAMS': 00755 case 'FAULT': 00756 case 'METHODCALL': 00757 case 'METHORESPONSE': 00758 break; 00759 default: 00760 // End of INVALID ELEMENT! 00761 // shall we add an assert here for unreachable code??? 00762 break; 00763 } 00764 } 00765 } 00766 00768 function xmlrpc_ee_fast($parser, $name) 00769 { 00770 xmlrpc_ee($parser, $name, false); 00771 } 00772 00774 function xmlrpc_cd($parser, $data) 00775 { 00776 // skip processing if xml fault already detected 00777 if ($GLOBALS['_xh']['isf'] < 2) 00778 { 00779 // "lookforvalue==3" means that we've found an entire value 00780 // and should discard any further character data 00781 if($GLOBALS['_xh']['lv']!=3) 00782 { 00783 // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2 00784 //if($GLOBALS['_xh']['lv']==1) 00785 //{ 00786 // if we've found text and we're just in a <value> then 00787 // say we've found a value 00788 //$GLOBALS['_xh']['lv']=2; 00789 //} 00790 // we always initialize the accumulator before starting parsing, anyway... 00791 //if(!@isset($GLOBALS['_xh']['ac'])) 00792 //{ 00793 // $GLOBALS['_xh']['ac'] = ''; 00794 //} 00795 $GLOBALS['_xh']['ac'].=$data; 00796 } 00797 } 00798 } 00799 00802 function xmlrpc_dh($parser, $data) 00803 { 00804 // skip processing if xml fault already detected 00805 if ($GLOBALS['_xh']['isf'] < 2) 00806 { 00807 if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') 00808 { 00809 // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2 00810 //if($GLOBALS['_xh']['lv']==1) 00811 //{ 00812 // $GLOBALS['_xh']['lv']=2; 00813 //} 00814 $GLOBALS['_xh']['ac'].=$data; 00815 } 00816 } 00817 return true; 00818 } 00819 00820 class xmlrpc_client 00821 { 00822 var $path; 00823 var $server; 00824 var $port=0; 00825 var $method='http'; 00826 var $errno; 00827 var $errstr; 00828 var $debug=0; 00829 var $username=''; 00830 var $password=''; 00831 var $authtype=1; 00832 var $cert=''; 00833 var $certpass=''; 00834 var $cacert=''; 00835 var $cacertdir=''; 00836 var $key=''; 00837 var $keypass=''; 00838 var $verifypeer=true; 00839 var $verifyhost=1; 00840 var $no_multicall=false; 00841 var $proxy=''; 00842 var $proxyport=0; 00843 var $proxy_user=''; 00844 var $proxy_pass=''; 00845 var $proxy_authtype=1; 00846 var $cookies=array(); 00856 var $accepted_compression = array(); 00861 var $request_compression = ''; 00866 var $xmlrpc_curl_handle = null; 00868 var $keepalive = false; 00870 var $accepted_charset_encodings = array(); 00872 var $request_charset_encoding = ''; 00877 var $return_type = 'xmlrpcvals'; 00878 00885 function xmlrpc_client($path, $server='', $port='', $method='') 00886 { 00887 // allow user to specify all params in $path 00888 if($server == '' and $port == '' and $method == '') 00889 { 00890 $parts = parse_url($path); 00891 $server = $parts['host']; 00892 $path = isset($parts['path']) ? $parts['path'] : ''; 00893 if(isset($parts['query'])) 00894 { 00895 $path .= '?'.$parts['query']; 00896 } 00897 if(isset($parts['fragment'])) 00898 { 00899 $path .= '#'.$parts['fragment']; 00900 } 00901 if(isset($parts['port'])) 00902 { 00903 $port = $parts['port']; 00904 } 00905 if(isset($parts['scheme'])) 00906 { 00907 $method = $parts['scheme']; 00908 } 00909 if(isset($parts['user'])) 00910 { 00911 $this->username = $parts['user']; 00912 } 00913 if(isset($parts['pass'])) 00914 { 00915 $this->password = $parts['pass']; 00916 } 00917 } 00918 if($path == '' || $path[0] != '/') 00919 { 00920 $this->path='/'.$path; 00921 } 00922 else 00923 { 00924 $this->path=$path; 00925 } 00926 $this->server=$server; 00927 if($port != '') 00928 { 00929 $this->port=$port; 00930 } 00931 if($method != '') 00932 { 00933 $this->method=$method; 00934 } 00935 00936 // if ZLIB is enabled, let the client by default accept compressed responses 00937 if(function_exists('gzinflate') || ( 00938 function_exists('curl_init') && (($info = curl_version()) && 00939 ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version']))) 00940 )) 00941 { 00942 $this->accepted_compression = array('gzip', 'deflate'); 00943 } 00944 00945 // keepalives: enabled by default ONLY for PHP >= 4.3.8 00946 // (see http://curl.haxx.se/docs/faq.html#7.3) 00947 if(version_compare(phpversion(), '4.3.8') >= 0) 00948 { 00949 $this->keepalive = true; 00950 } 00951 00952 // by default the xml parser can support these 3 charset encodings 00953 $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII'); 00954 } 00955 00961 function setDebug($in) 00962 { 00963 $this->debug=$in; 00964 } 00965 00973 function setCredentials($u, $p, $t=1) 00974 { 00975 $this->username=$u; 00976 $this->password=$p; 00977 $this->authtype=$t; 00978 } 00979 00986 function setCertificate($cert, $certpass) 00987 { 00988 $this->cert = $cert; 00989 $this->certpass = $certpass; 00990 } 00991 00999 function setCaCertificate($cacert, $is_dir=false) 01000 { 01001 if ($is_dir) 01002 { 01003 $this->cacertdir = $cacert; 01004 } 01005 else 01006 { 01007 $this->cacert = $cacert; 01008 } 01009 } 01010 01019 function setKey($key, $keypass) 01020 { 01021 $this->key = $key; 01022 $this->keypass = $keypass; 01023 } 01024 01030 function setSSLVerifyPeer($i) 01031 { 01032 $this->verifypeer = $i; 01033 } 01034 01040 function setSSLVerifyHost($i) 01041 { 01042 $this->verifyhost = $i; 01043 } 01044 01054 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 1) 01055 { 01056 $this->proxy = $proxyhost; 01057 $this->proxyport = $proxyport; 01058 $this->proxy_user = $proxyusername; 01059 $this->proxy_pass = $proxypassword; 01060 $this->proxy_authtype = $proxyauthtype; 01061 } 01062 01071 function setAcceptedCompression($compmethod) 01072 { 01073 if ($compmethod == 'any') 01074 $this->accepted_compression = array('gzip', 'deflate'); 01075 else 01076 $this->accepted_compression = array($compmethod); 01077 } 01078 01086 function setRequestCompression($compmethod) 01087 { 01088 $this->request_compression = $compmethod; 01089 } 01090 01104 function setCookie($name, $value='', $path='', $domain='', $port=null) 01105 { 01106 $this->cookies[$name]['value'] = urlencode($value); 01107 if ($path || $domain || $port) 01108 { 01109 $this->cookies[$name]['path'] = $path; 01110 $this->cookies[$name]['domain'] = $domain; 01111 $this->cookies[$name]['port'] = $port; 01112 $this->cookies[$name]['version'] = 1; 01113 } 01114 else 01115 { 01116 $this->cookies[$name]['version'] = 0; 01117 } 01118 } 01119 01128 function& send($msg, $timeout=0, $method='') 01129 { 01130 // if user deos not specify http protocol, use native method of this client 01131 // (i.e. method set during call to constructor) 01132 if($method == '') 01133 { 01134 $method = $this->method; 01135 } 01136 01137 if(is_array($msg)) 01138 { 01139 // $msg is an array of xmlrpcmsg's 01140 $r = $this->multicall($msg, $timeout, $method); 01141 return $r; 01142 } 01143 elseif(is_string($msg)) 01144 { 01145 $n =& new xmlrpcmsg(''); 01146 $n->payload = $msg; 01147 $msg = $n; 01148 } 01149 01150 // where msg is an xmlrpcmsg 01151 $msg->debug=$this->debug; 01152 01153 if($method == 'https') 01154 { 01155 $r =& $this->sendPayloadHTTPS( 01156 $msg, 01157 $this->server, 01158 $this->port, 01159 $timeout, 01160 $this->username, 01161 $this->password, 01162 $this->authtype, 01163 $this->cert, 01164 $this->certpass, 01165 $this->cacert, 01166 $this->cacertdir, 01167 $this->proxy, 01168 $this->proxyport, 01169 $this->proxy_user, 01170 $this->proxy_pass, 01171 $this->proxy_authtype, 01172 $this->keepalive, 01173 $this->key, 01174 $this->keypass 01175 ); 01176 } 01177 elseif($method == 'http11') 01178 { 01179 $r =& $this->sendPayloadCURL( 01180 $msg, 01181 $this->server, 01182 $this->port, 01183 $timeout, 01184 $this->username, 01185 $this->password, 01186 $this->authtype, 01187 null, 01188 null, 01189 null, 01190 null, 01191 $this->proxy, 01192 $this->proxyport, 01193 $this->proxy_user, 01194 $this->proxy_pass, 01195 $this->proxy_authtype, 01196 'http', 01197 $this->keepalive 01198 ); 01199 } 01200 else 01201 { 01202 $r =& $this->sendPayloadHTTP10( 01203 $msg, 01204 $this->server, 01205 $this->port, 01206 $timeout, 01207 $this->username, 01208 $this->password, 01209 $this->authtype, 01210 $this->proxy, 01211 $this->proxyport, 01212 $this->proxy_user, 01213 $this->proxy_pass, 01214 $this->proxy_authtype 01215 ); 01216 } 01217 01218 return $r; 01219 } 01220 01224 function &sendPayloadHTTP10($msg, $server, $port, $timeout=0, 01225 $username='', $password='', $authtype=1, $proxyhost='', 01226 $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1) 01227 { 01228 if($port==0) 01229 { 01230 $port=80; 01231 } 01232 01233 // Only create the payload if it was not created previously 01234 if(empty($msg->payload)) 01235 { 01236 $msg->createPayload($this->request_charset_encoding); 01237 } 01238 01239 $payload = $msg->payload; 01240 // Deflate request body and set appropriate request headers 01241 if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) 01242 { 01243 if($this->request_compression == 'gzip') 01244 { 01245 $a = @gzencode($payload); 01246 if($a) 01247 { 01248 $payload = $a; 01249 $encoding_hdr = "Content-Encoding: gzip\r\n"; 01250 } 01251 } 01252 else 01253 { 01254 $a = @gzcompress($payload); 01255 if($a) 01256 { 01257 $payload = $a; 01258 $encoding_hdr = "Content-Encoding: deflate\r\n"; 01259 } 01260 } 01261 } 01262 else 01263 { 01264 $encoding_hdr = ''; 01265 } 01266 01267 // thanks to Grant Rauscher <grant7@firstworld.net> for this 01268 $credentials=''; 01269 if($username!='') 01270 { 01271 $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n"; 01272 if ($authtype != 1) 01273 { 01274 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported with HTTP 1.0'); 01275 } 01276 } 01277 01278 $accepted_encoding = ''; 01279 if(is_array($this->accepted_compression) && count($this->accepted_compression)) 01280 { 01281 $accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n"; 01282 } 01283 01284 $proxy_credentials = ''; 01285 if($proxyhost) 01286 { 01287 if($proxyport == 0) 01288 { 01289 $proxyport = 8080; 01290 } 01291 $connectserver = $proxyhost; 01292 $connectport = $proxyport; 01293 $uri = 'http://'.$server.':'.$port.$this->path; 01294 if($proxyusername != '') 01295 { 01296 if ($proxyauthtype != 1) 01297 { 01298 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported with HTTP 1.0'); 01299 } 01300 $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n"; 01301 } 01302 } 01303 else 01304 { 01305 $connectserver = $server; 01306 $connectport = $port; 01307 $uri = $this->path; 01308 } 01309 01310 // Cookie generation, as per rfc2965 (version 1 cookies) or 01311 // netscape's rules (version 0 cookies) 01312 $cookieheader=''; 01313 if (count($this->cookies)) 01314 { 01315 $version = ''; 01316 foreach ($this->cookies as $name => $cookie) 01317 { 01318 if ($cookie['version']) 01319 { 01320 $version = ' $Version="' . $cookie['version'] . '";'; 01321 $cookieheader .= ' ' . $name . '="' . $cookie['value'] . '";'; 01322 if ($cookie['path']) 01323 $cookieheader .= ' $Path="' . $cookie['path'] . '";'; 01324 if ($cookie['domain']) 01325 $cookieheader .= ' $Domain="' . $cookie['domain'] . '";'; 01326 if ($cookie['port']) 01327 $cookieheader .= ' $Port="' . $cookie['port'] . '";'; 01328 } 01329 else 01330 { 01331 $cookieheader .= ' ' . $name . '=' . $cookie['value'] . ";"; 01332 } 01333 } 01334 $cookieheader = 'Cookie:' . $version . substr($cookieheader, 0, -1) . "\r\n"; 01335 } 01336 01337 $op= 'POST ' . $uri. " HTTP/1.0\r\n" . 01338 'User-Agent: ' . $GLOBALS['xmlrpcName'] . ' ' . $GLOBALS['xmlrpcVersion'] . "\r\n" . 01339 'Host: '. $server . ':' . $port . "\r\n" . 01340 $credentials . 01341 $proxy_credentials . 01342 $accepted_encoding . 01343 $encoding_hdr . 01344 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" . 01345 $cookieheader . 01346 'Content-Type: ' . $msg->content_type . "\r\nContent-Length: " . 01347 strlen($payload) . "\r\n\r\n" . 01348 $payload; 01349 01350 if($this->debug > 1) 01351 { 01352 print "<PRE>\n---SENDING---\n" . htmlentities($op) . "\n---END---\n</PRE>"; 01353 // let the client see this now in case http times out... 01354 flush(); 01355 } 01356 01357 if($timeout>0) 01358 { 01359 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout); 01360 } 01361 else 01362 { 01363 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr); 01364 } 01365 if($fp) 01366 { 01367 if($timeout>0 && function_exists('stream_set_timeout')) 01368 { 01369 stream_set_timeout($fp, $timeout); 01370 } 01371 } 01372 else 01373 { 01374 $this->errstr='Connect error: '.$this->errstr; 01375 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')'); 01376 return $r; 01377 } 01378 01379 if(!fputs($fp, $op, strlen($op))) 01380 { 01381 fclose($fp); 01382 $this->errstr='Write error'; 01383 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr); 01384 return $r; 01385 } 01386 else 01387 { 01388 // reset errno and errstr on succesful socket connection 01389 $this->errstr = ''; 01390 } 01391 // G. Giunta 2005/10/24: close socket before parsing. 01392 // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects) 01393 $ipd=''; 01394 do 01395 { 01396 // shall we check for $data === FALSE? 01397 // as per the manual, it signals an error 01398 $ipd.=fread($fp, 32768); 01399 } while(!feof($fp)); 01400 fclose($fp); 01401 $r =& $msg->parseResponse($ipd, false, $this->return_type); 01402 return $r; 01403 01404 } 01405 01409 function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='', 01410 $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='', 01411 $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, 01412 $keepalive=false, $key='', $keypass='') 01413 { 01414 $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username, 01415 $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport, 01416 $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass); 01417 return $r; 01418 } 01419 01426 function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='', 01427 $password='', $authtype=1, $cert='', $certpass='', $cacert='', $cacertdir='', 01428 $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $method='https', 01429 $keepalive=false, $key='', $keypass='') 01430 { 01431 if(!function_exists('curl_init')) 01432 { 01433 $this->errstr='CURL unavailable on this install'; 01434 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']); 01435 return $r; 01436 } 01437 if($method == 'https') 01438 { 01439 if(($info = curl_version()) && 01440 ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))) 01441 { 01442 $this->errstr='SSL unavailable on this install'; 01443 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']); 01444 return $r; 01445 } 01446 } 01447 01448 if($port == 0) 01449 { 01450 if($method == 'http') 01451 { 01452 $port = 80; 01453 } 01454 else 01455 { 01456 $port = 443; 01457 } 01458 } 01459 01460 // Only create the payload if it was not created previously 01461 if(empty($msg->payload)) 01462 { 01463 $msg->createPayload($this->request_charset_encoding); 01464 } 01465 01466 // Deflate request body and set appropriate request headers 01467 $payload = $msg->payload; 01468 if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) 01469 { 01470 if($this->request_compression == 'gzip') 01471 { 01472 $a = @gzencode($payload); 01473 if($a) 01474 { 01475 $payload = $a; 01476 $encoding_hdr = 'Content-Encoding: gzip'; 01477 } 01478 } 01479 else 01480 { 01481 $a = @gzcompress($payload); 01482 if($a) 01483 { 01484 $payload = $a; 01485 $encoding_hdr = 'Content-Encoding: deflate'; 01486 } 01487 } 01488 } 01489 else 01490 { 01491 $encoding_hdr = ''; 01492 } 01493 01494 if($this->debug > 1) 01495 { 01496 print "<PRE>\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n</PRE>"; 01497 // let the client see this now in case http times out... 01498 flush(); 01499 } 01500 01501 if(!$keepalive || !$this->xmlrpc_curl_handle) 01502 { 01503 $curl = curl_init($method . '://' . $server . ':' . $port . $this->path); 01504 if($keepalive) 01505 { 01506 $this->xmlrpc_curl_handle = $curl; 01507 } 01508 } 01509 else 01510 { 01511 $curl = $this->xmlrpc_curl_handle; 01512 } 01513 01514 // results into variable 01515 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 01516 01517 if($this->debug) 01518 { 01519 curl_setopt($curl, CURLOPT_VERBOSE, 1); 01520 } 01521 curl_setopt($curl, CURLOPT_USERAGENT, $GLOBALS['xmlrpcName'].' '.$GLOBALS['xmlrpcVersion']); 01522 // required for XMLRPC: post the data 01523 curl_setopt($curl, CURLOPT_POST, 1); 01524 // the data 01525 curl_setopt($curl, CURLOPT_POSTFIELDS, $payload); 01526 01527 // return the header too 01528 curl_setopt($curl, CURLOPT_HEADER, 1); 01529 01530 // will only work with PHP >= 5.0 01531 // NB: if we set an empty string, CURL will add http header indicating 01532 // ALL methods it is supporting. This is possibly a better option than 01533 // letting the user tell what curl can / cannot do... 01534 if(is_array($this->accepted_compression) && count($this->accepted_compression)) 01535 { 01536 //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression)); 01537 // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?) 01538 if (count($this->accepted_compression) == 1) 01539 { 01540 curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]); 01541 } 01542 else 01543 curl_setopt($curl, CURLOPT_ENCODING, ''); 01544 } 01545 // extra headers 01546 $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings)); 01547 // if no keepalive is wanted, let the server know it in advance 01548 if(!$keepalive) 01549 { 01550 $headers[] = 'Connection: close'; 01551 } 01552 // request compression header 01553 if($encoding_hdr) 01554 { 01555 $headers[] = $encoding_hdr; 01556 } 01557 01558 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 01559 // timeout is borked 01560 if($timeout) 01561 { 01562 curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1); 01563 } 01564 01565 if($username && $password) 01566 { 01567 curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password); 01568 if (defined('CURLOPT_HTTPAUTH')) 01569 { 01570 curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype); 01571 } 01572 else if ($authtype != 1) 01573 { 01574 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported by the current PHP/curl install'); 01575 } 01576 } 01577 01578 if($method == 'https') 01579 { 01580 // set cert file 01581 if($cert) 01582 { 01583 curl_setopt($curl, CURLOPT_SSLCERT, $cert); 01584 } 01585 // set cert password 01586 if($certpass) 01587 { 01588 curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass); 01589 } 01590 // whether to verify remote host's cert 01591 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer); 01592 // set ca certificates file/dir 01593 if($cacert) 01594 { 01595 curl_setopt($curl, CURLOPT_CAINFO, $cacert); 01596 } 01597 if($cacertdir) 01598 { 01599 curl_setopt($curl, CURLOPT_CAPATH, $cacertdir); 01600 } 01601 // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?) 01602 if($key) 01603 { 01604 curl_setopt($curl, CURLOPT_SSLKEY, $key); 01605 } 01606 // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?) 01607 if($keypass) 01608 { 01609 curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass); 01610 } 01611 // 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 01612 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost); 01613 } 01614 01615 // proxy info 01616 if($proxyhost) 01617 { 01618 if($proxyport == 0) 01619 { 01620 $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080 01621 } 01622 curl_setopt($curl, CURLOPT_PROXY, $proxyhost.':'.$proxyport); 01623 //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport); 01624 if($proxyusername) 01625 { 01626 curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword); 01627 if (defined('CURLOPT_PROXYAUTH')) 01628 { 01629 curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype); 01630 } 01631 else if ($proxyauthtype != 1) 01632 { 01633 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported by the current PHP/curl install'); 01634 } 01635 } 01636 } 01637 01638 // NB: should we build cookie http headers by hand rather than let CURL do it? 01639 // the following code does not honour 'expires', 'path' and 'domain' cookie attributes 01640 // set to client obj the the user... 01641 if (count($this->cookies)) 01642 { 01643 $cookieheader = ''; 01644 foreach ($this->cookies as $name => $cookie) 01645 { 01646 $cookieheader .= $name . '=' . $cookie['value'] . '; '; 01647 } 01648 curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2)); 01649 } 01650 01651 $result = curl_exec($curl); 01652 01653 if ($this->debug > 1) 01654 { 01655 print "<PRE>\n---CURL INFO---\n"; 01656 foreach(curl_getinfo($curl) as $name => $val) 01657 print $name . ': ' . htmlentities($val). "\n"; 01658 print "---END---\n</PRE>"; 01659 } 01660 01661 if(!$result) 01662 { 01663 $this->errstr='no response'; 01664 $resp=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl)); 01665 curl_close($curl); 01666 if($keepalive) 01667 { 01668 $this->xmlrpc_curl_handle = null; 01669 } 01670 } 01671 else 01672 { 01673 if(!$keepalive) 01674 { 01675 curl_close($curl); 01676 } 01677 $resp =& $msg->parseResponse($result, true, $this->return_type); 01678 } 01679 return $resp; 01680 } 01681 01704 function multicall($msgs, $timeout=0, $method='', $fallback=true) 01705 { 01706 if ($method == '') 01707 { 01708 $method = $this->method; 01709 } 01710 if(!$this->no_multicall) 01711 { 01712 $results = $this->_try_multicall($msgs, $timeout, $method); 01713 if(is_array($results)) 01714 { 01715 // System.multicall succeeded 01716 return $results; 01717 } 01718 else 01719 { 01720 // either system.multicall is unsupported by server, 01721 // or call failed for some other reason. 01722 if ($fallback) 01723 { 01724 // Don't try it next time... 01725 $this->no_multicall = true; 01726 } 01727 else 01728 { 01729 if (is_a($results, 'xmlrpcresp')) 01730 { 01731 $result = $results; 01732 } 01733 else 01734 { 01735 $result =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']); 01736 } 01737 } 01738 } 01739 } 01740 else 01741 { 01742 // override fallback, in case careless user tries to do two 01743 // opposite things at the same time 01744 $fallback = true; 01745 } 01746 01747 $results = array(); 01748 if ($fallback) 01749 { 01750 // system.multicall is (probably) unsupported by server: 01751 // emulate multicall via multiple requests 01752 foreach($msgs as $msg) 01753 { 01754 $results[] =& $this->send($msg, $timeout, $method); 01755 } 01756 } 01757 else 01758 { 01759 // user does NOT want to fallback on many single calls: 01760 // since we should always return an array of responses, 01761 // return an array with the same error repeated n times 01762 foreach($msgs as $msg) 01763 { 01764 $results[] = $result; 01765 } 01766 } 01767 return $results; 01768 } 01769 01776 function _try_multicall($msgs, $timeout, $method) 01777 { 01778 // Construct multicall message 01779 $calls = array(); 01780 foreach($msgs as $msg) 01781 { 01782 $call['methodName'] =& new xmlrpcval($msg->method(),'string'); 01783 $numParams = $msg->getNumParams(); 01784 $params = array(); 01785 for($i = 0; $i < $numParams; $i++) 01786 { 01787 $params[$i] = $msg->getParam($i); 01788 } 01789 $call['params'] =& new xmlrpcval($params, 'array'); 01790 $calls[] =& new xmlrpcval($call, 'struct'); 01791 } 01792 $multicall =& new xmlrpcmsg('system.multicall'); 01793 $multicall->addParam(new xmlrpcval($calls, 'array')); 01794 01795 // Attempt RPC call 01796 $result =& $this->send($multicall, $timeout, $method); 01797 01798 if($result->faultCode() != 0) 01799 { 01800 // call to system.multicall failed 01801 return $result; 01802 } 01803 01804 // Unpack responses. 01805 $rets = $result->value(); 01806 01807 if ($this->return_type == 'xml') 01808 { 01809 return $rets; 01810 } 01811 else if ($this->return_type == 'phpvals') 01812 { 01814 $rets = $result->value(); 01815 if(!is_array($rets)) 01816 { 01817 return false; // bad return type from system.multicall 01818 } 01819 $numRets = count($rets); 01820 if($numRets != count($msgs)) 01821 { 01822 return false; // wrong number of return values. 01823 } 01824 01825 $response = array(); 01826 for($i = 0; $i < $numRets; $i++) 01827 { 01828 $val = $rets[$i]; 01829 if (!is_array($val)) { 01830 return false; 01831 } 01832 switch(count($val)) 01833 { 01834 case 1: 01835 if(!isset($val[0])) 01836 { 01837 return false; // Bad value 01838 } 01839 // Normal return value 01840 $response[$i] =& new xmlrpcresp($val[0], 0, '', 'phpvals'); 01841 break; 01842 case 2: 01844 $code = @$val['faultCode']; 01845 if(!is_int($code)) 01846 { 01847 return false; 01848 } 01849 $str = @$val['faultString']; 01850 if(!is_string($str)) 01851 { 01852 return false; 01853 } 01854 $response[$i] =& new xmlrpcresp(0, $code, $str); 01855 break; 01856 default: 01857 return false; 01858 } 01859 } 01860 return $response; 01861 } 01862 else // return type == 'xmlrpcvals' 01863 { 01864 $rets = $result->value(); 01865 if($rets->kindOf() != 'array') 01866 { 01867 return false; // bad return type from system.multicall 01868 } 01869 $numRets = $rets->arraysize(); 01870 if($numRets != count($msgs)) 01871 { 01872 return false; // wrong number of return values. 01873 } 01874 01875 $response = array(); 01876 for($i = 0; $i < $numRets; $i++) 01877 { 01878 $val = $rets->arraymem($i); 01879 switch($val->kindOf()) 01880 { 01881 case 'array': 01882 if($val->arraysize() != 1) 01883 { 01884 return false; // Bad value 01885 } 01886 // Normal return value 01887 $response[$i] =& new xmlrpcresp($val->arraymem(0)); 01888 break; 01889 case 'struct': 01890 $code = $val->structmem('faultCode'); 01891 if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') 01892 { 01893 return false; 01894 } 01895 $str = $val->structmem('faultString'); 01896 if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') 01897 { 01898 return false; 01899 } 01900 $response[$i] =& new xmlrpcresp(0, $code->scalarval(), $str->scalarval()); 01901 break; 01902 default: 01903 return false; 01904 } 01905 } 01906 return $response; 01907 } 01908 } 01909 } // end class xmlrpc_client 01910 01911 class xmlrpcresp 01912 { 01913 var $val = 0; 01914 var $valtyp; 01915 var $errno = 0; 01916 var $errstr = ''; 01917 var $payload; 01918 var $hdrs = array(); 01919 var $_cookies = array(); 01920 var $content_type = 'text/xml'; 01921 var $raw_data = ''; 01922 01933 function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='') 01934 { 01935 if($fcode != 0) 01936 { 01937 // error response 01938 $this->errno = $fcode; 01939 $this->errstr = $fstr; 01940 //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later. 01941 } 01942 else 01943 { 01944 // successful response 01945 $this->val = $val; 01946 if ($valtyp == '') 01947 { 01948 // user did not declare type of response value: try to guess it 01949 if (is_object($this->val) && is_a($this->val, 'xmlrpcval')) 01950 { 01951 $this->valtyp = 'xmlrpcvals'; 01952 } 01953 else if (is_string($this->val)) 01954 { 01955 $this->valtyp = 'xml'; 01956 01957 } 01958 else 01959 { 01960 $this->valtyp = 'phpvals'; 01961 } 01962 } 01963 else 01964 { 01965 // user declares type of resp value: believe him 01966 $this->valtyp = $valtyp; 01967 } 01968 } 01969 } 01970 01976 function faultCode() 01977 { 01978 return $this->errno; 01979 } 01980 01986 function faultString() 01987 { 01988 return $this->errstr; 01989 } 01990 01996 function value() 01997 { 01998 return $this->val; 01999 } 02000 02012 function cookies() 02013 { 02014 return $this->_cookies; 02015 } 02016 02023 function serialize($charset_encoding='') 02024 { 02025 if ($charset_encoding != '') 02026 $this->content_type = 'text/xml; charset=' . $charset_encoding; 02027 else 02028 $this->content_type = 'text/xml'; 02029 $result = "<methodResponse>\n"; 02030 if($this->errno) 02031 { 02032 // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients 02033 // by xml-encoding non ascii chars 02034 $result .= "<fault>\n" . 02035 "<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno . 02036 "</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" . 02037 xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "</string></value>\n</member>\n" . 02038 "</struct>\n</value>\n</fault>"; 02039 } 02040 else 02041 { 02042 if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval')) 02043 { 02044 if (is_string($this->val) && $this->valtyp == 'xml') 02045 { 02046 $result .= "<params>\n<param>\n" . 02047 $this->val . 02048 "</param>\n</params>"; 02049 } 02050 else 02051 { 02053 die('cannot serialize xmlrpcresp objects whose content is native php values'); 02054 } 02055 } 02056 else 02057 { 02058 $result .= "<params>\n<param>\n" . 02059 $this->val->serialize($charset_encoding) . 02060 "</param>\n</params>"; 02061 } 02062 } 02063 $result .= "\n</methodResponse>"; 02064 $this->payload = $result; 02065 return $result; 02066 } 02067 } 02068 02069 class xmlrpcmsg 02070 { 02071 var $payload; 02072 var $methodname; 02073 var $params=array(); 02074 var $debug=0; 02075 var $content_type = 'text/xml'; 02076 02081 function xmlrpcmsg($meth, $pars=0) 02082 { 02083 $this->methodname=$meth; 02084 if(is_array($pars) && count($pars)>0) 02085 { 02086 for($i=0; $i<count($pars); $i++) 02087 { 02088 $this->addParam($pars[$i]); 02089 } 02090 } 02091 } 02092 02096 function xml_header($charset_encoding='') 02097 { 02098 if ($charset_encoding != '') 02099 { 02100 return "<?xml version=\"1.0\" encoding=\"$charset_encoding\" ?" . ">\n<methodCall>\n"; 02101 } 02102 else 02103 { 02104 return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n"; 02105 } 02106 } 02107 02111 function xml_footer() 02112 { 02113 return '</methodCall>'; 02114 } 02115 02119 function kindOf() 02120 { 02121 return 'msg'; 02122 } 02123 02127 function createPayload($charset_encoding='') 02128 { 02129 if ($charset_encoding != '') 02130 $this->content_type = 'text/xml; charset=' . $charset_encoding; 02131 else 02132 $this->content_type = 'text/xml'; 02133 $this->payload=$this->xml_header($charset_encoding); 02134 $this->payload.='<methodName>' . $this->methodname . "</methodName>\n"; 02135 $this->payload.="<params>\n"; 02136 for($i=0; $i<count($this->params); $i++) 02137 { 02138 $p=$this->params[$i]; 02139 $this->payload.="<param>\n" . $p->serialize($charset_encoding) . 02140 "</param>\n"; 02141 } 02142 $this->payload.="</params>\n"; 02143 $this->payload.=$this->xml_footer(); 02144 } 02145 02152 function method($meth='') 02153 { 02154 if($meth!='') 02155 { 02156 $this->methodname=$meth; 02157 } 02158 return $this->methodname; 02159 } 02160 02166 function serialize($charset_encoding='') 02167 { 02168 $this->createPayload($charset_encoding); 02169 return $this->payload; 02170 } 02171 02178 function addParam($par) 02179 { 02180 // add check: do not add to self params which are not xmlrpcvals 02181 if(is_object($par) && is_a($par, 'xmlrpcval')) 02182 { 02183 $this->params[]=$par; 02184 return true; 02185 } 02186 else 02187 { 02188 return false; 02189 } 02190 } 02191 02198 function getParam($i) { return $this->params[$i]; } 02199 02205 function getNumParams() { return count($this->params); } 02206 02219 function &parseResponseFile($fp) 02220 { 02221 $ipd=''; 02222 while($data=fread($fp, 32768)) 02223 { 02224 $ipd.=$data; 02225 } 02226 //fclose($fp); 02227 $r =& $this->parseResponse($ipd); 02228 return $r; 02229 } 02230 02235 function &parseResponseHeaders(&$data, $headers_processed=false) 02236 { 02237 // Support "web-proxy-tunelling" connections for https through proxies 02238 if(preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data)) 02239 { 02240 // Look for CR/LF or simple LF as line separator, 02241 // (even though it is not valid http) 02242 $pos = strpos($data,"\r\n\r\n"); 02243 if($pos || is_int($pos)) 02244 { 02245 $bd = $pos+4; 02246 } 02247 else 02248 { 02249 $pos = strpos($data,"\n\n"); 02250 if($pos || is_int($pos)) 02251 { 02252 $bd = $pos+2; 02253 } 02254 else 02255 { 02256 // No separation between response headers and body: fault? 02257 $bd = 0; 02258 } 02259 } 02260 if ($bd) 02261 { 02262 // this filters out all http headers from proxy. 02263 // maybe we could take them into account, too? 02264 $data = substr($data, $bd); 02265 } 02266 else 02267 { 02268 error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTPS via proxy error, tunnel connection possibly failed'); 02269 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)'); 02270 return $r; 02271 } 02272 } 02273 02274 // Strip HTTP 1.1 100 Continue header if present 02275 while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data)) 02276 { 02277 $pos = strpos($data, 'HTTP', 12); 02278 // server sent a Continue header without any (valid) content following... 02279 // give the client a chance to know it 02280 if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5 02281 { 02282 break; 02283 } 02284 $data = substr($data, $pos); 02285 } 02286 if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data)) 02287 { 02288 $errstr= substr($data, 0, strpos($data, "\n")-1); 02289 error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .$errstr); 02290 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')'); 02291 return $r; 02292 } 02293 02294 $GLOBALS['_xh']['headers'] = array(); 02295 $GLOBALS['_xh']['cookies'] = array(); 02296 02297 // be tolerant to usage of \n instead of \r\n to separate headers and data 02298 // (even though it is not valid http) 02299 $pos = strpos($data,"\r\n\r\n"); 02300 if($pos || is_int($pos)) 02301 { 02302 $bd = $pos+4; 02303 } 02304 else 02305 { 02306 $pos = strpos($data,"\n\n"); 02307 if($pos || is_int($pos)) 02308 { 02309 $bd = $pos+2; 02310 } 02311 else 02312 { 02313 // No separation between response headers and body: fault? 02314 // we could take some action here instead of going on... 02315 $bd = 0; 02316 } 02317 } 02318 // be tolerant to line endings, and extra empty lines 02319 $ar = split("\r?\n", trim(substr($data, 0, $pos))); 02320 while(list(,$line) = @each($ar)) 02321 { 02322 // take care of multi-line headers and cookies 02323 $arr = explode(':',$line,2); 02324 if(count($arr) > 1) 02325 { 02326 $header_name = strtolower(trim($arr[0])); 02331 if ($header_name == 'set-cookie' || $header_name == 'set-cookie2') 02332 { 02333 if ($header_name == 'set-cookie2') 02334 { 02335 // version 2 cookies: 02336 // there could be many cookies on one line, comma separated 02337 $cookies = explode(',', $arr[1]); 02338 } 02339 else 02340 { 02341 $cookies = array($arr[1]); 02342 } 02343 foreach ($cookies as $cookie) 02344 { 02345 // glue together all received cookies, using a comma to separate them 02346 // (same as php does with getallheaders()) 02347 if (isset($GLOBALS['_xh']['headers'][$header_name])) 02348 $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie); 02349 else 02350 $GLOBALS['_xh']['headers'][$header_name] = trim($cookie); 02351 // parse cookie attributes, in case user wants to correctly honour them 02352 // feature creep: only allow rfc-compliant cookie attributes? 02353 // @todo support for server sending multiple time cookie with same name, but using different PATHs 02354 $cookie = explode(';', $cookie); 02355 foreach ($cookie as $pos => $val) 02356 { 02357 $val = explode('=', $val, 2); 02358 $tag = trim($val[0]); 02359 $val = trim(@$val[1]); 02361 if ($pos == 0) 02362 { 02363 $cookiename = $tag; 02364 $GLOBALS['_xh']['cookies'][$tag] = array(); 02365 $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val); 02366 } 02367 else 02368 { 02369 if ($tag != 'value') 02370 { 02371 $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val; 02372 } 02373 } 02374 } 02375 } 02376 } 02377 else 02378 { 02379 $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]); 02380 } 02381 } 02382 elseif(isset($header_name)) 02383 { 02385 $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line); 02386 } 02387 } 02388 02389 $data = substr($data, $bd); 02390 02391 if($this->debug && count($GLOBALS['_xh']['headers'])) 02392 { 02393 print '<PRE>'; 02394 foreach($GLOBALS['_xh']['headers'] as $header => $value) 02395 { 02396 print htmlentities("HEADER: $header: $value\n"); 02397 } 02398 foreach($GLOBALS['_xh']['cookies'] as $header => $value) 02399 { 02400 print htmlentities("COOKIE: $header={$value['value']}\n"); 02401 } 02402 print "</PRE>\n"; 02403 } 02404 02405 // if CURL was used for the call, http headers have been processed, 02406 // and dechunking + reinflating have been carried out 02407 if(!$headers_processed) 02408 { 02409 // Decode chunked encoding sent by http 1.1 servers 02410 if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked') 02411 { 02412 if(!$data = decode_chunked($data)) 02413 { 02414 error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server'); 02415 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']); 02416 return $r; 02417 } 02418 } 02419 02420 // Decode gzip-compressed stuff 02421 // code shamelessly inspired from nusoap library by Dietrich Ayala 02422 if(isset($GLOBALS['_xh']['headers']['content-encoding'])) 02423 { 02424 $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']); 02425 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip') 02426 { 02427 // if decoding works, use it. else assume data wasn't gzencoded 02428 if(function_exists('gzinflate')) 02429 { 02430 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) 02431 { 02432 $data = $degzdata; 02433 if($this->debug) 02434 print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>"; 02435 } 02436 elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) 02437 { 02438 $data = $degzdata; 02439 if($this->debug) 02440 print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>"; 02441 } 02442 else 02443 { 02444 error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server'); 02445 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']); 02446 return $r; 02447 } 02448 } 02449 else 02450 { 02451 error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); 02452 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']); 02453 return $r; 02454 } 02455 } 02456 } 02457 } // end of 'if needed, de-chunk, re-inflate response' 02458 02459 // real stupid hack to avoid PHP 4 complaining about returning NULL by ref 02460 $r = null; 02461 $r =& $r; 02462 return $r; 02463 } 02464 02473 function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals') 02474 { 02475 if($this->debug) 02476 { 02477 //by maHo, replaced htmlspecialchars with htmlentities 02478 print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>"; 02479 } 02480 02481 if($data == '') 02482 { 02483 error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.'); 02484 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']); 02485 return $r; 02486 } 02487 02488 $GLOBALS['_xh']=array(); 02489 02490 $raw_data = $data; 02491 // parse the HTTP headers of the response, if present, and separate them from data 02492 if(substr($data, 0, 4) == 'HTTP') 02493 { 02494 $r =& $this->parseResponseHeaders($data, $headers_processed); 02495 if ($r) 02496 { 02497 // failed processing of HTTP response headers 02498 // save into response obj the full payload received, for debugging 02499 $r->raw_data = $data; 02500 return $r; 02501 } 02502 } 02503 else 02504 { 02505 $GLOBALS['_xh']['headers'] = array(); 02506 $GLOBALS['_xh']['cookies'] = array(); 02507 } 02508 02509 if($this->debug) 02510 { 02511 $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):'); 02512 if ($start) 02513 { 02514 $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):'); 02515 $end = strpos($data, '-->', $start); 02516 $comments = substr($data, $start, $end-$start); 02517 print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n</PRE>"; 02518 } 02519 } 02520 02521 // be tolerant of extra whitespace in response body 02522 $data = trim($data); 02523 02525 02526 // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts) 02527 // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib 02528 $bd = false; 02529 // Poor man's version of strrpos for php 4... 02530 $pos = strpos($data, '</methodResponse>'); 02531 while($pos || is_int($pos)) 02532 { 02533 $bd = $pos+17; 02534 $pos = strpos($data, '</methodResponse>', $bd); 02535 } 02536 if($bd) 02537 { 02538 $data = substr($data, 0, $bd); 02539 } 02540 02541 // if user wants back raw xml, give it to him 02542 if ($return_type == 'xml') 02543 { 02544 $r =& new xmlrpcresp($data, 0, '', 'xml'); 02545 $r->hdrs = $GLOBALS['_xh']['headers']; 02546 $r->_cookies = $GLOBALS['_xh']['cookies']; 02547 $r->raw_data = $raw_data; 02548 return $r; 02549 } 02550 02551 // try to 'guestimate' the character encoding of the received response 02552 $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data); 02553 02554 $GLOBALS['_xh']['ac']=''; 02555 //$GLOBALS['_xh']['qt']=''; //unused... 02556 $GLOBALS['_xh']['stack'] = array(); 02557 $GLOBALS['_xh']['valuestack'] = array(); 02558 $GLOBALS['_xh']['isf']=0; // 0 = OK, 1 for xmlrpc fault responses, 2 = invalid xmlrpc 02559 $GLOBALS['_xh']['isf_reason']=''; 02560 $GLOBALS['_xh']['rt']=''; // 'methodcall or 'methodresponse' 02561 02562 // if response charset encoding is not known / supported, try to use 02563 // the default encoding and parse the xml anyway, but log a warning... 02564 if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) 02565 // the following code might be better for mb_string enabled installs, but 02566 // makes the lib about 200% slower... 02567 //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) 02568 { 02569 error_log('XML-RPC: xmlrpcmsg::parseResponse: invalid charset encoding of received response: '.$resp_encoding); 02570 $resp_encoding = $GLOBALS['xmlrpc_defencoding']; 02571 } 02572 $parser = xml_parser_create($resp_encoding); 02573 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); 02574 // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell 02575 // the xml parser to give us back data in the expected charset. 02576 // What if internal encoding is not in one of the 3 allowed? 02577 // we use the broadest one, ie. utf8 02578 // This allows to send data which is native in various charset, 02579 // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding 02580 if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) 02581 { 02582 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); 02583 } 02584 else 02585 { 02586 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']); 02587 } 02588 02589 if ($return_type == 'phpvals') 02590 { 02591 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); 02592 } 02593 else 02594 { 02595 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); 02596 } 02597 02598 xml_set_character_data_handler($parser, 'xmlrpc_cd'); 02599 xml_set_default_handler($parser, 'xmlrpc_dh'); 02600 02601 // first error check: xml not well formed 02602 if(!xml_parse($parser, $data, count($data))) 02603 { 02604 // thanks to Peter Kocks <peter.kocks@baygate.com> 02605 if((xml_get_current_line_number($parser)) == 1) 02606 { 02607 $errstr = 'XML error at line 1, check URL'; 02608 } 02609 else 02610 { 02611 $errstr = sprintf('XML error: %s at line %d, column %d', 02612 xml_error_string(xml_get_error_code($parser)), 02613 xml_get_current_line_number($parser), xml_get_current_column_number($parser)); 02614 } 02615 error_log($errstr); 02616 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')'); 02617 xml_parser_free($parser); 02618 if($this->debug) 02619 { 02620 print $errstr; 02621 } 02622 $r->hdrs = $GLOBALS['_xh']['headers']; 02623 $r->_cookies = $GLOBALS['_xh']['cookies']; 02624 $r->raw_data = $raw_data; 02625 return $r; 02626 } 02627 xml_parser_free($parser); 02628 // second error check: xml well formed but not xml-rpc compliant 02629 if ($GLOBALS['_xh']['isf'] > 1) 02630 { 02631 if ($this->debug) 02632 { 02634 } 02635 02636 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], 02637 $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']); 02638 } 02639 // third error check: parsing of the response has somehow gone boink. 02640 // NB: shall we omit this check, since we trust the parsing code? 02641 elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value'])) 02642 { 02643 // something odd has happened 02644 // and it's time to generate a client side error 02645 // indicating something odd went on 02646 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], 02647 $GLOBALS['xmlrpcstr']['invalid_return']); 02648 } 02649 else 02650 { 02651 if ($this->debug) 02652 { 02653 print "<PRE>---PARSED---\n"; 02654 // somehow htmlentities chokes on var_export, and some full html string... 02655 //print htmlentitites(var_export($GLOBALS['_xh']['value'], true)); 02656 print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true)); 02657 print "\n---END---</PRE>"; 02658 } 02659 02660 // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object. 02661 $v =& $GLOBALS['_xh']['value']; 02662 02663 if($GLOBALS['_xh']['isf']) 02664 { 02667 if ($return_type == 'xmlrpcvals') 02668 { 02669 $errno_v = $v->structmem('faultCode'); 02670 $errstr_v = $v->structmem('faultString'); 02671 $errno = $errno_v->scalarval(); 02672 $errstr = $errstr_v->scalarval(); 02673 } 02674 else 02675 { 02676 $errno = $v['faultCode']; 02677 $errstr = $v['faultString']; 02678 } 02679 02680 if($errno == 0) 02681 { 02682 // FAULT returned, errno needs to reflect that 02683 $errno = -1; 02684 } 02685 02686 $r =& new xmlrpcresp(0, $errno, $errstr); 02687 } 02688 else 02689 { 02690 $r=&new xmlrpcresp($v, 0, '', $return_type); 02691 } 02692 } 02693 02694 $r->hdrs = $GLOBALS['_xh']['headers']; 02695 $r->_cookies = $GLOBALS['_xh']['cookies']; 02696 $r->raw_data = $raw_data; 02697 return $r; 02698 } 02699 } 02700 02701 class xmlrpcval 02702 { 02703 var $me=array(); 02704 var $mytype=0; 02705 var $_php_class=null; 02706 02711 function xmlrpcval($val=-1, $type='') 02712 { 02715 if($val!==-1 || $type!='') 02716 { 02717 // optimization creep: inlined all work done by constructor 02718 switch($type) 02719 { 02720 case '': 02721 $this->mytype=1; 02722 $this->me['string']=$val; 02723 break; 02724 case 'i4': 02725 case 'int': 02726 case 'double': 02727 case 'string': 02728 case 'boolean': 02729 case 'dateTime.iso8601': 02730 case 'base64': 02731 case 'null': 02732 $this->mytype=1; 02733 $this->me[$type]=$val; 02734 break; 02735 case 'array': 02736 $this->mytype=2; 02737 $this->me['array']=$val; 02738 break; 02739 case 'struct': 02740 $this->mytype=3; 02741 $this->me['struct']=$val; 02742 break; 02743 default: 02744 error_log("XML-RPC: xmlrpcval::xmlrpcval: not a known type ($type)"); 02745 } 02746 /*if($type=='') 02747 { 02748 $type='string'; 02749 } 02750 if($GLOBALS['xmlrpcTypes'][$type]==1) 02751 { 02752 $this->addScalar($val,$type); 02753 } 02754 elseif($GLOBALS['xmlrpcTypes'][$type]==2) 02755 { 02756 $this->addArray($val); 02757 } 02758 elseif($GLOBALS['xmlrpcTypes'][$type]==3) 02759 { 02760 $this->addStruct($val); 02761 }*/ 02762 } 02763 } 02764 02771 function addScalar($val, $type='string') 02772 { 02773 $typeof=@$GLOBALS['xmlrpcTypes'][$type]; 02774 if($typeof!=1) 02775 { 02776 error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($type)"); 02777 return 0; 02778 } 02779 02780 // coerce booleans into correct values 02781 // NB: we should iether do it for datetimes, integers and doubles, too, 02782 // or just plain remove this check, implemnted on booleans only... 02783 if($type==$GLOBALS['xmlrpcBoolean']) 02784 { 02785 if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false'))) 02786 { 02787 $val=true; 02788 } 02789 else 02790 { 02791 $val=false; 02792 } 02793 } 02794 02795 switch($this->mytype) 02796 { 02797 case 1: 02798 error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value'); 02799 return 0; 02800 case 3: 02801 error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval'); 02802 return 0; 02803 case 2: 02804 // we're adding a scalar value to an array here 02805 //$ar=$this->me['array']; 02806 //$ar[]=&new xmlrpcval($val, $type); 02807 //$this->me['array']=$ar; 02808 // Faster (?) avoid all the costly array-copy-by-val done here... 02809 $this->me['array'][]=&new xmlrpcval($val, $type); 02810 return 1; 02811 default: 02812 // a scalar, so set the value and remember we're scalar 02813 $this->me[$type]=$val; 02814 $this->mytype=$typeof; 02815 return 1; 02816 } 02817 } 02818 02827 function addArray($vals) 02828 { 02829 if($this->mytype==0) 02830 { 02831 $this->mytype=$GLOBALS['xmlrpcTypes']['array']; 02832 $this->me['array']=$vals; 02833 return 1; 02834 } 02835 elseif($this->mytype==2) 02836 { 02837 // we're adding to an array here 02838 $this->me['array'] = array_merge($this->me['array'], $vals); 02839 return 1; 02840 } 02841 else 02842 { 02843 error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']'); 02844 return 0; 02845 } 02846 } 02847 02856 function addStruct($vals) 02857 { 02858 if($this->mytype==0) 02859 { 02860 $this->mytype=$GLOBALS['xmlrpcTypes']['struct']; 02861 $this->me['struct']=$vals; 02862 return 1; 02863 } 02864 elseif($this->mytype==3) 02865 { 02866 // we're adding to a struct here 02867 $this->me['struct'] = array_merge($this->me['struct'], $vals); 02868 return 1; 02869 } 02870 else 02871 { 02872 error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']'); 02873 return 0; 02874 } 02875 } 02876 02877 // poor man's version of print_r ??? 02878 // DEPRECATED! 02879 function dump($ar) 02880 { 02881 foreach($ar as $key => $val) 02882 { 02883 echo "$key => $val<br />"; 02884 if($key == 'array') 02885 { 02886 while(list($key2, $val2) = each($val)) 02887 { 02888 echo "-- $key2 => $val2<br />"; 02889 } 02890 } 02891 } 02892 } 02893 02899 function kindOf() 02900 { 02901 switch($this->mytype) 02902 { 02903 case 3: 02904 return 'struct'; 02905 break; 02906 case 2: 02907 return 'array'; 02908 break; 02909 case 1: 02910 return 'scalar'; 02911 break; 02912 default: 02913 return 'undef'; 02914 } 02915 } 02916 02920 function serializedata($typ, $val, $charset_encoding='') 02921 { 02922 $rs=''; 02923 switch(@$GLOBALS['xmlrpcTypes'][$typ]) 02924 { 02925 case 1: 02926 switch($typ) 02927 { 02928 case $GLOBALS['xmlrpcBase64']: 02929 $rs.="<${typ}>" . base64_encode($val) . "</${typ}>"; 02930 break; 02931 case $GLOBALS['xmlrpcBoolean']: 02932 $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>"; 02933 break; 02934 case $GLOBALS['xmlrpcString']: 02935 // G. Giunta 2005/2/13: do NOT use htmlentities, since 02936 // it will produce named html entities, which are invalid xml 02937 $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "</${typ}>"; 02938 break; 02939 case $GLOBALS['xmlrpcInt']: 02940 case $GLOBALS['xmlrpcI4']: 02941 $rs.="<${typ}>".(int)$val."</${typ}>"; 02942 break; 02943 case $GLOBALS['xmlrpcDouble']: 02944 // avoid using standard conversion of float to string because it is locale-dependent, 02945 // and also because the xmlrpc spec forbids exponential notation 02946 // sprintf('%F') would be most likely ok but it is only available since PHP 4.3.10 and PHP 5.0.3. 02947 // The code below tries its best at keeping max precision while avoiding exp notation, 02948 // but there is of course no limit in the number of decimal places to be used... 02949 $rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', ''))."</${typ}>"; 02950 break; 02951 case $GLOBALS['xmlrpcNull']: 02952 $rs.="<nil/>"; 02953 break; 02954 default: 02955 // no standard type value should arrive here, but provide a possibility 02956 // for xmlrpcvals of unknown type... 02957 $rs.="<${typ}>${val}</${typ}>"; 02958 } 02959 break; 02960 case 3: 02961 // struct 02962 if ($this->_php_class) 02963 { 02964 $rs.='<struct php_class="' . $this->_php_class . "\">\n"; 02965 } 02966 else 02967 { 02968 $rs.="<struct>\n"; 02969 } 02970 foreach($val as $key2 => $val2) 02971 { 02972 $rs.='<member><name>'.xmlrpc_encode_entitites($key2, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding)."</name>\n"; 02973 //$rs.=$this->serializeval($val2); 02974 $rs.=$val2->serialize($charset_encoding); 02975 $rs.="</member>\n"; 02976 } 02977 $rs.='</struct>'; 02978 break; 02979 case 2: 02980 // array 02981 $rs.="<array>\n<data>\n"; 02982 for($i=0; $i<count($val); $i++) 02983 { 02984 //$rs.=$this->serializeval($val[$i]); 02985 $rs.=$val[$i]->serialize($charset_encoding); 02986 } 02987 $rs.="</data>\n</array>"; 02988 break; 02989 default: 02990 break; 02991 } 02992 return $rs; 02993 } 02994 03001 function serialize($charset_encoding='') 03002 { 03003 // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals... 03004 //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval'))) 03005 //{ 03006 reset($this->me); 03007 list($typ, $val) = each($this->me); 03008 return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n"; 03009 //} 03010 } 03011 03012 // DEPRECATED 03013 function serializeval($o) 03014 { 03015 // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals... 03016 //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval'))) 03017 //{ 03018 $ar=$o->me; 03019 reset($ar); 03020 list($typ, $val) = each($ar); 03021 return '<value>' . $this->serializedata($typ, $val) . "</value>\n"; 03022 //} 03023 } 03024 03032 function structmemexists($m) 03033 { 03034 return array_key_exists($m, $this->me['struct']); 03035 } 03036 03044 function structmem($m) 03045 { 03046 return $this->me['struct'][$m]; 03047 } 03048 03053 function structreset() 03054 { 03055 reset($this->me['struct']); 03056 } 03057 03063 function structeach() 03064 { 03065 return each($this->me['struct']); 03066 } 03067 03068 // DEPRECATED! this code looks like it is very fragile and has not been fixed 03069 // for a long long time. Shall we remove it for 2.0? 03070 function getval() 03071 { 03072 // UNSTABLE 03073 reset($this->me); 03074 list($a,$b)=each($this->me); 03075 // contributed by I Sofer, 2001-03-24 03076 // add support for nested arrays to scalarval 03077 // i've created a new method here, so as to 03078 // preserve back compatibility 03079 03080 if(is_array($b)) 03081 { 03082 @reset($b); 03083 while(list($id,$cont) = @each($b)) 03084 { 03085 $b[$id] = $cont->scalarval(); 03086 } 03087 } 03088 03089 // add support for structures directly encoding php objects 03090 if(is_object($b)) 03091 { 03092 $t = get_object_vars($b); 03093 @reset($t); 03094 while(list($id,$cont) = @each($t)) 03095 { 03096 $t[$id] = $cont->scalarval(); 03097 } 03098 @reset($t); 03099 while(list($id,$cont) = @each($t)) 03100 { 03101 @$b->$id = $cont; 03102 } 03103 } 03104 // end contrib 03105 return $b; 03106 } 03107 03113 function scalarval() 03114 { 03115 reset($this->me); 03116 list(,$b)=each($this->me); 03117 return $b; 03118 } 03119 03126 function scalartyp() 03127 { 03128 reset($this->me); 03129 list($a,)=each($this->me); 03130 if($a==$GLOBALS['xmlrpcI4']) 03131 { 03132 $a=$GLOBALS['xmlrpcInt']; 03133 } 03134 return $a; 03135 } 03136 03143 function arraymem($m) 03144 { 03145 return $this->me['array'][$m]; 03146 } 03147 03153 function arraysize() 03154 { 03155 return count($this->me['array']); 03156 } 03157 03163 function structsize() 03164 { 03165 return count($this->me['struct']); 03166 } 03167 } 03168 03169 03170 // date helpers 03171 03189 function iso8601_encode($timet, $utc=0) 03190 { 03191 if(!$utc) 03192 { 03193 $t=strftime("%Y%m%dT%H:%M:%S", $timet); 03194 } 03195 else 03196 { 03197 if(function_exists('gmstrftime')) 03198 { 03199 // gmstrftime doesn't exist in some versions 03200 // of PHP 03201 $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet); 03202 } 03203 else 03204 { 03205 $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z')); 03206 } 03207 } 03208 return $t; 03209 } 03210 03217 function iso8601_decode($idate, $utc=0) 03218 { 03219 $t=0; 03220 if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs)) 03221 { 03222 if($utc) 03223 { 03224 $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); 03225 } 03226 else 03227 { 03228 $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); 03229 } 03230 } 03231 return $t; 03232 } 03233 03255 function php_xmlrpc_decode($xmlrpc_val, $options=array()) 03256 { 03257 switch($xmlrpc_val->kindOf()) 03258 { 03259 case 'scalar': 03260 if (in_array('extension_api', $options)) 03261 { 03262 reset($xmlrpc_val->me); 03263 list($typ,$val) = each($xmlrpc_val->me); 03264 switch ($typ) 03265 { 03266 case 'dateTime.iso8601': 03267 $xmlrpc_val->scalar = $val; 03268 $xmlrpc_val->xmlrpc_type = 'datetime'; 03269 $xmlrpc_val->timestamp = iso8601_decode($val); 03270 return $xmlrpc_val; 03271 case 'base64': 03272 $xmlrpc_val->scalar = $val; 03273 $xmlrpc_val->type = $typ; 03274 return $xmlrpc_val; 03275 default: 03276 return $xmlrpc_val->scalarval(); 03277 } 03278 } 03279 return $xmlrpc_val->scalarval(); 03280 case 'array': 03281 $size = $xmlrpc_val->arraysize(); 03282 $arr = array(); 03283 for($i = 0; $i < $size; $i++) 03284 { 03285 $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options); 03286 } 03287 return $arr; 03288 case 'struct': 03289 $xmlrpc_val->structreset(); 03290 // If user said so, try to rebuild php objects for specific struct vals. 03292 // shall we check for proper subclass of xmlrpcval instead of 03293 // presence of _php_class to detect what we can do? 03294 if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != '' 03295 && class_exists($xmlrpc_val->_php_class)) 03296 { 03297 $obj = @new $xmlrpc_val->_php_class; 03298 while(list($key,$value)=$xmlrpc_val->structeach()) 03299 { 03300 $obj->$key = php_xmlrpc_decode($value, $options); 03301 } 03302 return $obj; 03303 } 03304 else 03305 { 03306 $arr = array(); 03307 while(list($key,$value)=$xmlrpc_val->structeach()) 03308 { 03309 $arr[$key] = php_xmlrpc_decode($value, $options); 03310 } 03311 return $arr; 03312 } 03313 case 'msg': 03314 $paramcount = $xmlrpc_val->getNumParams(); 03315 $arr = array(); 03316 for($i = 0; $i < $paramcount; $i++) 03317 { 03318 $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i)); 03319 } 03320 return $arr; 03321 } 03322 } 03323 03324 // This constant left here only for historical reasons... 03325 // it was used to decide if we have to define xmlrpc_encode on our own, but 03326 // we do not do it anymore 03327 if(function_exists('xmlrpc_decode')) 03328 { 03329 define('XMLRPC_EPI_ENABLED','1'); 03330 } 03331 else 03332 { 03333 define('XMLRPC_EPI_ENABLED','0'); 03334 } 03335 03353 function &php_xmlrpc_encode($php_val, $options=array()) 03354 { 03355 $type = gettype($php_val); 03356 switch($type) 03357 { 03358 case 'string': 03359 if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val)) 03360 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcDateTime']); 03361 else 03362 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcString']); 03363 break; 03364 case 'integer': 03365 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcInt']); 03366 break; 03367 case 'double': 03368 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcDouble']); 03369 break; 03370 // <G_Giunta_2001-02-29> 03371 // Add support for encoding/decoding of booleans, since they are supported in PHP 03372 case 'boolean': 03373 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcBoolean']); 03374 break; 03375 // </G_Giunta_2001-02-29> 03376 case 'array': 03377 // PHP arrays can be encoded to either xmlrpc structs or arrays, 03378 // depending on wheter they are hashes or plain 0..n integer indexed 03379 // A shorter one-liner would be 03380 // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1)); 03381 // but execution time skyrockets! 03382 $j = 0; 03383 $arr = array(); 03384 $ko = false; 03385 foreach($php_val as $key => $val) 03386 { 03387 $arr[$key] =& php_xmlrpc_encode($val, $options); 03388 if(!$ko && $key !== $j) 03389 { 03390 $ko = true; 03391 } 03392 $j++; 03393 } 03394 if($ko) 03395 { 03396 $xmlrpc_val =& new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']); 03397 } 03398 else 03399 { 03400 $xmlrpc_val =& new xmlrpcval($arr, $GLOBALS['xmlrpcArray']); 03401 } 03402 break; 03403 case 'object': 03404 if(is_a($php_val, 'xmlrpcval')) 03405 { 03406 $xmlrpc_val = $php_val; 03407 } 03408 else 03409 { 03410 $arr = array(); 03411 while(list($k,$v) = each($php_val)) 03412 { 03413 $arr[$k] = php_xmlrpc_encode($v, $options); 03414 } 03415 $xmlrpc_val =& new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']); 03416 if (in_array('encode_php_objs', $options)) 03417 { 03418 // let's save original class name into xmlrpcval: 03419 // might be useful later on... 03420 $xmlrpc_val->_php_class = get_class($php_val); 03421 } 03422 } 03423 break; 03424 case 'NULL': 03425 if (in_array('extension_api', $options)) 03426 { 03427 $xmlrpc_val =& new xmlrpcval('', $GLOBALS['xmlrpcString']); 03428 } 03429 if (in_array('null_extension', $options)) 03430 { 03431 $xmlrpc_val =& new xmlrpcval('', $GLOBALS['xmlrpcNull']); 03432 } 03433 else 03434 { 03435 $xmlrpc_val =& new xmlrpcval(); 03436 } 03437 break; 03438 case 'resource': 03439 if (in_array('extension_api', $options)) 03440 { 03441 $xmlrpc_val =& new xmlrpcval((int)$php_val, $GLOBALS['xmlrpcInt']); 03442 } 03443 else 03444 { 03445 $xmlrpc_val =& new xmlrpcval(); 03446 } 03447 // catch "user function", "unknown type" 03448 default: 03449 // giancarlo pinerolo <ping@alt.it> 03450 // it has to return 03451 // an empty object in case, not a boolean. 03452 $xmlrpc_val =& new xmlrpcval(); 03453 break; 03454 } 03455 return $xmlrpc_val; 03456 } 03457 03465 function php_xmlrpc_decode_xml($xml_val, $options=array()) 03466 { 03467 $GLOBALS['_xh'] = array(); 03468 $GLOBALS['_xh']['ac'] = ''; 03469 $GLOBALS['_xh']['stack'] = array(); 03470 $GLOBALS['_xh']['valuestack'] = array(); 03471 $GLOBALS['_xh']['params'] = array(); 03472 $GLOBALS['_xh']['pt'] = array(); 03473 $GLOBALS['_xh']['isf'] = 0; 03474 $GLOBALS['_xh']['isf_reason'] = ''; 03475 $GLOBALS['_xh']['method'] = false; 03476 $GLOBALS['_xh']['rt'] = ''; 03478 $parser = xml_parser_create(); 03479 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); 03480 // What if internal encoding is not in one of the 3 allowed? 03481 // we use the broadest one, ie. utf8! 03482 if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) 03483 { 03484 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); 03485 } 03486 else 03487 { 03488 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']); 03489 } 03490 xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee'); 03491 xml_set_character_data_handler($parser, 'xmlrpc_cd'); 03492 xml_set_default_handler($parser, 'xmlrpc_dh'); 03493 if(!xml_parse($parser, $xml_val, 1)) 03494 { 03495 $errstr = sprintf('XML error: %s at line %d, column %d', 03496 xml_error_string(xml_get_error_code($parser)), 03497 xml_get_current_line_number($parser), xml_get_current_column_number($parser)); 03498 error_log($errstr); 03499 xml_parser_free($parser); 03500 return false; 03501 } 03502 xml_parser_free($parser); 03503 if ($GLOBALS['_xh']['isf'] > 1) // test that $GLOBALS['_xh']['value'] is an obj, too??? 03504 { 03505 error_log($GLOBALS['_xh']['isf_reason']); 03506 return false; 03507 } 03508 switch ($GLOBALS['_xh']['rt']) 03509 { 03510 case 'methodresponse': 03511 $v =& $GLOBALS['_xh']['value']; 03512 if ($GLOBALS['_xh']['isf'] == 1) 03513 { 03514 $vc = $v->structmem('faultCode'); 03515 $vs = $v->structmem('faultString'); 03516 $r =& new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval()); 03517 } 03518 else 03519 { 03520 $r =& new xmlrpcresp($v); 03521 } 03522 return $r; 03523 case 'methodcall': 03524 $m =& new xmlrpcmsg($GLOBALS['_xh']['method']); 03525 for($i=0; $i < count($GLOBALS['_xh']['params']); $i++) 03526 { 03527 $m->addParam($GLOBALS['_xh']['params'][$i]); 03528 } 03529 return $m; 03530 case 'value': 03531 return $GLOBALS['_xh']['value']; 03532 default: 03533 return false; 03534 } 03535 } 03536 03545 function decode_chunked($buffer) 03546 { 03547 // length := 0 03548 $length = 0; 03549 $new = ''; 03550 03551 // read chunk-size, chunk-extension (if any) and crlf 03552 // get the position of the linebreak 03553 $chunkend = strpos($buffer,"\r\n") + 2; 03554 $temp = substr($buffer,0,$chunkend); 03555 $chunk_size = hexdec( trim($temp) ); 03556 $chunkstart = $chunkend; 03557 while($chunk_size > 0) 03558 { 03559 $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size); 03560 03561 // just in case we got a broken connection 03562 if($chunkend == false) 03563 { 03564 $chunk = substr($buffer,$chunkstart); 03565 // append chunk-data to entity-body 03566 $new .= $chunk; 03567 $length += strlen($chunk); 03568 break; 03569 } 03570 03571 // read chunk-data and crlf 03572 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart); 03573 // append chunk-data to entity-body 03574 $new .= $chunk; 03575 // length := length + chunk-size 03576 $length += strlen($chunk); 03577 // read chunk-size and crlf 03578 $chunkstart = $chunkend + 2; 03579 03580 $chunkend = strpos($buffer,"\r\n",$chunkstart)+2; 03581 if($chunkend == false) 03582 { 03583 break; //just in case we got a broken connection 03584 } 03585 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart); 03586 $chunk_size = hexdec( trim($temp) ); 03587 $chunkstart = $chunkend; 03588 } 03589 return $new; 03590 } 03591 03605 function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null) 03606 { 03607 // discussion: see http://www.yale.edu/pclt/encoding/ 03608 // 1 - test if encoding is specified in HTTP HEADERS 03609 03610 //Details: 03611 // LWS: (\13\10)?( |\t)+ 03612 // token: (any char but excluded stuff)+ 03613 // quoted string: " (any char but double quotes and cointrol chars)* " 03614 // header: Content-type = ...; charset=value(; ...)* 03615 // where value is of type token, no LWS allowed between 'charset' and value 03616 // Note: we do not check for invalid chars in VALUE: 03617 // this had better be done using pure ereg as below 03618 // Note 2: we might be removing whitespace/tabs that ought to be left in if 03619 // the received charset is a quoted string. But nobody uses such charset names... 03620 03622 $matches = array(); 03623 if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches)) 03624 { 03625 return strtoupper(trim($matches[1], " \t\"")); 03626 } 03627 03628 // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern 03629 // (source: http://www.w3.org/TR/2000/REC-xml-20001006) 03630 // NOTE: actually, according to the spec, even if we find the BOM and determine 03631 // an encoding, we should check if there is an encoding specified 03632 // in the xml declaration, and verify if they match. 03635 if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk)) 03636 { 03637 return 'UCS-4'; 03638 } 03639 elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk)) 03640 { 03641 return 'UTF-16'; 03642 } 03643 elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk)) 03644 { 03645 return 'UTF-8'; 03646 } 03647 03648 // 3 - test if encoding is specified in the xml declaration 03649 // Details: 03650 // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+ 03651 // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]* 03652 if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))". 03653 '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/", 03654 $xmlchunk, $matches)) 03655 { 03656 return strtoupper(substr($matches[2], 1, -1)); 03657 } 03658 03659 // 4 - if mbstring is available, let it do the guesswork 03660 // NB: we favour finding an encoding that is compatible with what we can process 03661 if(extension_loaded('mbstring')) 03662 { 03663 if($encoding_prefs) 03664 { 03665 $enc = mb_detect_encoding($xmlchunk, $encoding_prefs); 03666 } 03667 else 03668 { 03669 $enc = mb_detect_encoding($xmlchunk); 03670 } 03671 // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII... 03672 // IANA also likes better US-ASCII, so go with it 03673 if($enc == 'ASCII') 03674 { 03675 $enc = 'US-'.$enc; 03676 } 03677 return $enc; 03678 } 03679 else 03680 { 03681 // no encoding specified: as per HTTP1.1 assume it is iso-8859-1? 03682 // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types 03683 // this should be the standard. And we should be getting text/xml as request and response. 03684 // BUT we have to be backward compatible with the lib, which always used UTF-8 as default... 03685 return $GLOBALS['xmlrpc_defencoding']; 03686 } 03687 } 03688 03695 function is_valid_charset($encoding, $validlist) 03696 { 03697 $charset_supersets = array( 03698 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', 03699 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 03700 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12', 03701 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8', 03702 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN') 03703 ); 03704 if (is_string($validlist)) 03705 $validlist = explode(',', $validlist); 03706 if (@in_array(strtoupper($encoding), $validlist)) 03707 return true; 03708 else 03709 { 03710 if (array_key_exists($encoding, $charset_supersets)) 03711 foreach ($validlist as $allowed) 03712 if (in_array($allowed, $charset_supersets[$encoding])) 03713 return true; 03714 return false; 03715 } 03716 } 03717 03718 ?>
Copyright © 2003 - 2009 MyOOS [Shopsystem]. All rights reserved. MyOOS [Shopsystem] is Free Software released under the GNU/GPL License. Webmaster: info@r23.de (Impressum) |
|