diff options
Diffstat (limited to 'modules/xmlrpc/xmlrpc.inc')
-rwxr-xr-x | modules/xmlrpc/xmlrpc.inc | 794 |
1 files changed, 673 insertions, 121 deletions
diff --git a/modules/xmlrpc/xmlrpc.inc b/modules/xmlrpc/xmlrpc.inc index 5b4c0150..bdb818e2 100755 --- a/modules/xmlrpc/xmlrpc.inc +++ b/modules/xmlrpc/xmlrpc.inc @@ -1,7 +1,7 @@ <?php // -*-c++-*- // by Edd Dumbill (C) 1999-2002 // <edd@usefulinc.com> -// $Id: xmlrpc.inc,v 1.20 2003/01/10 22:01:56 dilinger Exp $ +// $Id: xmlrpc.inc,v 1.40 2005/04/24 18:17:18 ggiunta Exp $ // Copyright (c) 1999,2000,2002 Edd Dumbill. @@ -49,6 +49,34 @@ } } + // G. Giunta 2005/01/29: declare global these variables, + // so that xmlrpc.inc will work even if included from within a function + // NB: it will give warnings in PHP3, so we comment it out + // Milosch: Next round, maybe we should explicitly request these via $GLOBALS where used. + if (phpversion() >= '4') + { + global $xmlrpcI4; + global $xmlrpcInt; + global $xmlrpcDouble; + global $xmlrpcString; + global $xmlrpcDateTime; + global $xmlrpcBase64; + global $xmlrpcArray; + global $xmlrpcStruct; + + global $xmlrpcTypes; + global $xmlEntities; + global $xmlrpcerr; + global $xmlrpcstr; + global $xmlrpc_defencoding; + global $xmlrpc_internalencoding; + global $xmlrpcName; + global $xmlrpcVersion; + global $xmlrpcerruser; + global $xmlrpcerrxml; + global $xmlrpc_backslash; + global $_xh; + } $xmlrpcI4='i4'; $xmlrpcInt='int'; $xmlrpcBoolean='boolean'; @@ -82,7 +110,7 @@ $xmlrpcerr['unknown_method']=1; $xmlrpcstr['unknown_method']='Unknown method'; $xmlrpcerr['invalid_return']=2; - $xmlrpcstr['invalid_return']='Invalid return payload: enabling debugging to examine incoming payload'; + $xmlrpcstr['invalid_return']='Invalid return payload: enable debugging to examine incoming payload'; $xmlrpcerr['incorrect_params']=3; $xmlrpcstr['incorrect_params']='Incorrect parameters passed to method'; $xmlrpcerr['introspect_unknown']=4; @@ -110,13 +138,18 @@ $xmlrpcerr['multicall_notarray'] = 14; $xmlrpcstr['multicall_notarray'] = 'params is not an array'; + // The charset encoding expected by the server for received messages and + // by the client for received responses $xmlrpc_defencoding='UTF-8'; + // The encoding used by PHP. + // String values received will be converted to this. + $xmlrpc_internalencoding='ISO-8859-1'; $xmlrpcName='XML-RPC for PHP'; - $xmlrpcVersion='1.0.99'; + $xmlrpcVersion='1.1.1'; // let user errors start at 800 - $xmlrpcerruser=800; + $xmlrpcerruser=800; // let XML parse errors start at 100 $xmlrpcerrxml=100; @@ -137,13 +170,19 @@ $_xh=array(); - if (!function_exists('xmlrpc_entity_decode')) { - + /** + * To help correct communication of non-ascii chars inside strings, regardless + * of the charset used when sending requests, parsing them, sending responses + * and parsing responses, convert all non-ascii chars present in the message + * into their equivalent 'charset entity'. Charset entities enumerated this way + * are independent of the charset encoding used to transmit them, and all XML + * parsers are bound to understand them. + */ function xmlrpc_entity_decode($string) { $top=split('&', $string); $op=''; - $i=0; + $i=0; while($i<sizeof($top)) { if (ereg("^([#a-zA-Z0-9]+);", $top[$i], $regs)) @@ -168,8 +207,6 @@ return $op; } - } // if xmlrpc_entity_decode doesn't exist - function xmlrpc_lookup_entity($ent) { global $xmlEntities; @@ -185,6 +222,299 @@ return '?'; } + /** + * These entities originate from HTML specs (1.1, proposed 2.0, etc), + * and are taken directly from php-4.3.1/ext/mbstring/html_entities.c. + * Until php provides functionality to translate these entities in its + * core library, use this function. + */ + function xmlrpc_html_entity_xlate($data = '') + { + $entities = array( + " " => " ", + "¡" => "¡", + "¢" => "¢", + "£" => "£", + "¤" => "¤", + "¥" => "¥", + "¦" => "¦", + "§" => "§", + "¨" => "¨", + "©" => "©", + "ª" => "ª", + "«" => "«", + "¬" => "¬", + "­" => "­", + "®" => "®", + "¯" => "¯", + "°" => "°", + "±" => "±", + "²" => "²", + "³" => "³", + "´" => "´", + "µ" => "µ", + "¶" => "¶", + "·" => "·", + "¸" => "¸", + "¹" => "¹", + "º" => "º", + "»" => "»", + "¼" => "¼", + "½" => "½", + "¾" => "¾", + "¿" => "¿", + "À" => "À", + "Á" => "Á", + "Â" => "Â", + "Ã" => "Ã", + "Ä" => "Ä", + "Å" => "Å", + "Æ" => "Æ", + "Ç" => "Ç", + "È" => "È", + "É" => "É", + "Ê" => "Ê", + "Ë" => "Ë", + "Ì" => "Ì", + "Í" => "Í", + "Î" => "Î", + "Ï" => "Ï", + "Ð" => "Ð", + "Ñ" => "Ñ", + "Ò" => "Ò", + "Ó" => "Ó", + "Ô" => "Ô", + "Õ" => "Õ", + "Ö" => "Ö", + "×" => "×", + "Ø" => "Ø", + "Ù" => "Ù", + "Ú" => "Ú", + "Û" => "Û", + "Ü" => "Ü", + "Ý" => "Ý", + "Þ" => "Þ", + "ß" => "ß", + "à" => "à", + "á" => "á", + "â" => "â", + "ã" => "ã", + "ä" => "ä", + "å" => "å", + "æ" => "æ", + "ç" => "ç", + "è" => "è", + "é" => "é", + "ê" => "ê", + "ë" => "ë", + "ì" => "ì", + "í" => "í", + "î" => "î", + "ï" => "ï", + "ð" => "ð", + "ñ" => "ñ", + "ò" => "ò", + "ó" => "ó", + "ô" => "ô", + "õ" => "õ", + "ö" => "ö", + "÷" => "÷", + "ø" => "ø", + "ù" => "ù", + "ú" => "ú", + "û" => "û", + "ü" => "ü", + "ý" => "ý", + "þ" => "þ", + "ÿ" => "ÿ", + "Œ" => "Œ", + "œ" => "œ", + "Š" => "Š", + "š" => "š", + "Ÿ" => "Ÿ", + "ƒ" => "ƒ", + "ˆ" => "ˆ", + "˜" => "˜", + "Α" => "Α", + "Β" => "Β", + "Γ" => "Γ", + "Δ" => "Δ", + "Ε" => "Ε", + "Ζ" => "Ζ", + "Η" => "Η", + "Θ" => "Θ", + "Ι" => "Ι", + "Κ" => "Κ", + "Λ" => "Λ", + "Μ" => "Μ", + "Ν" => "Ν", + "Ξ" => "Ξ", + "Ο" => "Ο", + "Π" => "Π", + "Ρ" => "Ρ", + "Σ" => "Σ", + "Τ" => "Τ", + "Υ" => "Υ", + "Φ" => "Φ", + "Χ" => "Χ", + "Ψ" => "Ψ", + "Ω" => "Ω", + "β" => "β", + "γ" => "γ", + "δ" => "δ", + "ε" => "ε", + "ζ" => "ζ", + "η" => "η", + "θ" => "θ", + "ι" => "ι", + "κ" => "κ", + "λ" => "λ", + "μ" => "μ", + "ν" => "ν", + "ξ" => "ξ", + "ο" => "ο", + "π" => "π", + "ρ" => "ρ", + "ς" => "ς", + "σ" => "σ", + "τ" => "τ", + "υ" => "υ", + "φ" => "φ", + "χ" => "χ", + "ψ" => "ψ", + "ω" => "ω", + "ϑ" => "ϑ", + "ϒ" => "ϒ", + "ϖ" => "ϖ", + " " => " ", + " " => " ", + " " => " ", + "‌" => "‌", + "‍" => "‍", + "‎" => "‎", + "‏" => "‏", + "–" => "–", + "—" => "—", + "‘" => "‘", + "’" => "’", + "‚" => "‚", + "“" => "“", + "”" => "”", + "„" => "„", + "†" => "†", + "‡" => "‡", + "•" => "•", + "…" => "…", + "‰" => "‰", + "′" => "′", + "″" => "″", + "‹" => "‹", + "›" => "›", + "‾" => "‾", + "⁄" => "⁄", + "€" => "€", + "℘" => "℘", + "ℑ" => "ℑ", + "ℜ" => "ℜ", + "™" => "™", + "ℵ" => "ℵ", + "←" => "←", + "↑" => "↑", + "→" => "→", + "↓" => "↓", + "↔" => "↔", + "↵" => "↵", + "⇐" => "⇐", + "⇑" => "⇑", + "⇒" => "⇒", + "⇓" => "⇓", + "⇔" => "⇔", + "∀" => "∀", + "∂" => "∂", + "∃" => "∃", + "∅" => "∅", + "∇" => "∇", + "∈" => "∈", + "∉" => "∉", + "∋" => "∋", + "∏" => "∏", + "∑" => "∑", + "−" => "−", + "∗" => "∗", + "√" => "√", + "∝" => "∝", + "∞" => "∞", + "∠" => "∠", + "∧" => "∧", + "∨" => "∨", + "∩" => "∩", + "∪" => "∪", + "∫" => "∫", + "∴" => "∴", + "∼" => "∼", + "≅" => "≅", + "≈" => "≈", + "≠" => "≠", + "≡" => "≡", + "≤" => "≤", + "≥" => "≥", + "⊂" => "⊂", + "⊃" => "⊃", + "⊄" => "⊄", + "⊆" => "⊆", + "⊇" => "⊇", + "⊕" => "⊕", + "⊗" => "⊗", + "⊥" => "⊥", + "⋅" => "⋅", + "⌈" => "⌈", + "⌉" => "⌉", + "⌊" => "⌊", + "⌋" => "⌋", + "⟨" => "〈", + "⟩" => "〉", + "◊" => "◊", + "♠" => "♠", + "♣" => "♣", + "♥" => "♥", + "♦" => "♦"); + return strtr($data, $entities); + } + + function xmlrpc_encode_entitites($data) + { + $length = strlen($data); + $escapeddata = ""; + for($position = 0; $position < $length; $position++) + { + $character = substr($data, $position, 1); + $code = Ord($character); + switch($code) { + case 34: + $character = """; + break; + case 38: + $character = "&"; + break; + case 39: + $character = "'"; + break; + case 60: + $character = "<"; + break; + case 62: + $character = ">"; + break; + default: + if ($code < 32 || $code > 159) + $character = ("&#".strval($code).";"); + break; + } + $escapeddata .= $character; + } + return $escapeddata; + } + function xmlrpc_se($parser, $name, $attrs) { global $_xh, $xmlrpcDateTime, $xmlrpcString; @@ -196,12 +526,13 @@ $_xh[$parser]['st'].='array('; $_xh[$parser]['cm']++; // this last line turns quoting off - // this means if we get an empty array we'll + // this means if we get an empty array we'll // simply get a bit of whitespace in the eval $_xh[$parser]['qt']=0; break; case 'NAME': - $_xh[$parser]['st'].="'"; $_xh[$parser]['ac']=''; + $_xh[$parser]['st'].='"'; + $_xh[$parser]['ac']=''; break; case 'FAULT': $_xh[$parser]['isf']=1; @@ -278,11 +609,13 @@ $_xh[$parser]['cm']--; break; case 'NAME': - $_xh[$parser]['st'].= $_xh[$parser]['ac'] . "' => "; + $_xh[$parser]['st'].= $_xh[$parser]['ac'] . '" => '; break; case 'BOOLEAN': // special case here: we translate boolean 1 or 0 into PHP // constants true or false + // NB: this simple checks helps a lot sanitizing input, ie no + // security problems around here if ($_xh[$parser]['ac']=='1') { $_xh[$parser]['ac']='true'; @@ -290,9 +623,9 @@ else { $_xh[$parser]['ac']='false'; - $_xh[$parser]['vt']=strtolower($name); - // Drop through intentionally. } + $_xh[$parser]['vt']=strtolower($name); + // Drop through intentionally. case 'I4': case 'INT': case 'STRING': @@ -306,30 +639,48 @@ } elseif ($_xh[$parser]['qt']==2) { - $_xh[$parser]['st'].="base64_decode('". $_xh[$parser]['ac'] . "')"; + $_xh[$parser]['st'].='base64_decode("'. $_xh[$parser]['ac'] . '")'; } elseif ($name=='BOOLEAN') { $_xh[$parser]['st'].=$_xh[$parser]['ac']; } - else + elseif ($name=='DOUBLE') { - // we have an I4, INT or a DOUBLE + // we have a DOUBLE // we must check that only 0123456789-.<space> are characters here - if (!ereg("^\-?[0123456789 \t\.]+$", $_xh[$parser]['ac'])) + if (!ereg("^[+-]?[eE0123456789 \\t\\.]+$", $_xh[$parser]['ac'])) { // TODO: find a better way of throwing an error // than this! - error_log('XML-RPC: non numeric value received in INT or DOUBLE'); - $_xh[$parser]['st'].='ERROR_NON_NUMERIC_FOUND'; + error_log('XML-RPC: non numeric value received in DOUBLE: '.$_xh[$parser]['ac']); + $_xh[$parser]['st'].="'ERROR_NON_NUMERIC_FOUND'"; } else { // it's ok, add it on - $_xh[$parser]['st'].=$_xh[$parser]['ac']; + $_xh[$parser]['st'].=(double)$_xh[$parser]['ac']; } } - $_xh[$parser]['ac']=''; $_xh[$parser]['qt']=0; + else + { + // we have an I4/INT + // we must check that only 0123456789-<space> are characters here + if (!ereg("^[+-]?[0123456789 \\t]+$", $_xh[$parser]['ac'])) + { + // TODO: find a better way of throwing an error + // than this! + error_log('XML-RPC: non numeric value received in INT: '.$_xh[$parser]['ac']); + $_xh[$parser]['st'].="'ERROR_NON_NUMERIC_FOUND'"; + } + else + { + // it's ok, add it on + $_xh[$parser]['st'].=(int)$_xh[$parser]['ac']; + } + } + $_xh[$parser]['ac']=''; + $_xh[$parser]['qt']=0; $_xh[$parser]['lv']=3; // indicate we've found a value break; case 'VALUE': @@ -337,7 +688,7 @@ if (strlen($_xh[$parser]['ac'])>0 && $_xh[$parser]['vt']==$xmlrpcString) { - $_xh[$parser]['st'].='"'. $_xh[$parser]['ac'] . '"'; + $_xh[$parser]['st'].='"'. $_xh[$parser]['ac'] . '"'; } // This if() detects if no scalar was inside <VALUE></VALUE> // and pads an empty ''. @@ -345,17 +696,23 @@ { $_xh[$parser]['st'].= '""'; } - $_xh[$parser]['st'].=", '" . $_xh[$parser]['vt'] . "')"; + // G. Giunta 2005/03/12 save some chars in the reconstruction of string vals... + if ($_xh[$parser]['vt'] != $xmlrpcString) + $_xh[$parser]['st'].=", '" . $_xh[$parser]['vt'] . "')"; + else + $_xh[$parser]['st'].=")"; if ($_xh[$parser]['cm']) { $_xh[$parser]['st'].=','; } break; case 'MEMBER': - $_xh[$parser]['ac']=''; $_xh[$parser]['qt']=0; + $_xh[$parser]['ac']=''; + $_xh[$parser]['qt']=0; break; case 'DATA': - $_xh[$parser]['ac']=''; $_xh[$parser]['qt']=0; + $_xh[$parser]['ac']=''; + $_xh[$parser]['qt']=0; break; case 'PARAM': $_xh[$parser]['params'][]=$_xh[$parser]['st']; @@ -363,7 +720,8 @@ case 'METHODNAME': $_xh[$parser]['method']=ereg_replace("^[\n\r\t ]+", '', $_xh[$parser]['ac']); break; - case 'BOOLEAN': + // BOOLEAN HAS BEEN ENUMERATED ABOVE! + /*case 'BOOLEAN': // special case here: we translate boolean 1 or 0 into PHP // constants true or false if ($_xh[$parser]['ac']=='1') @@ -375,7 +733,7 @@ $_xh[$parser]['ac']='false'; $_xh[$parser]['vt']=strtolower($name); } - break; + break;*/ default: break; } @@ -401,9 +759,9 @@ { // if we've found text and we're just in a <value> then // turn quoting on, as this will be a string - $_xh[$parser]['qt']=1; + $_xh[$parser]['qt']=1; // and say we've found a value - $_xh[$parser]['lv']=2; + $_xh[$parser]['lv']=2; } if(!@isset($_xh[$parser]['ac'])) { @@ -415,13 +773,13 @@ function xmlrpc_dh($parser, $data) { - global $_xh; + global $_xh, $xmlrpc_backslash; if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') { if ($_xh[$parser]['lv']==1) { - $_xh[$parser]['qt']=1; - $_xh[$parser]['lv']=2; + $_xh[$parser]['qt']=1; + $_xh[$parser]['lv']=2; } $_xh[$parser]['ac'].=str_replace('$', '\$', str_replace('"', '\"', str_replace(chr(92),$xmlrpc_backslash, $data))); } @@ -433,7 +791,7 @@ var $server; var $port; var $errno; - var $errstring; + var $errstr; var $debug=0; var $username=''; var $password=''; @@ -512,20 +870,25 @@ function sendPayloadHTTP10($msg, $server, $port, $timeout=0,$username='', $password='') { - global $xmlrpcerr, $xmlrpcstr; + global $xmlrpcerr, $xmlrpcstr, $xmlrpcName, $xmlrpcVersion, $xmlrpc_defencoding; if ($port==0) { $port=80; } if($timeout>0) { - $fp=fsockopen($server, $port,$this->errno, $this->errstr, $timeout); + $fp=@fsockopen($server, $port,$this->errno, $this->errstr, $timeout); } else { - $fp=fsockopen($server, $port,$this->errno, $this->errstr); + $fp=@fsockopen($server, $port,$this->errno, $this->errstr); } - if (!$fp) + if ($fp) + { + if ($timeout>0 && function_exists('stream_set_timeout')) + stream_set_timeout($fp, $timeout); + } + else { $this->errstr='Connect error'; $r=new xmlrpcresp(0, $xmlrpcerr['http_error'],$xmlrpcstr['http_error']); @@ -545,9 +908,11 @@ $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n"; } - $op= "POST " . $this->path. " HTTP/1.0\r\nUser-Agent: PHP XMLRPC 1.0\r\n" . - "Host: ". $this->server . "\r\n" . + $op= "POST " . $this->path. " HTTP/1.0\r\n" . + "User-Agent: " . $xmlrpcName . " " . $xmlrpcVersion . "\r\n" . + "Host: ". $server . "\r\n" . $credentials . + "Accept-Charset: " . $xmlrpc_defencoding . "\r\n" . "Content-Type: text/xml\r\nContent-Length: " . strlen($msg->payload) . "\r\n\r\n" . $msg->payload; @@ -567,7 +932,7 @@ // requires curl to be built into PHP function sendPayloadHTTPS($msg, $server, $port, $timeout=0,$username='', $password='', $cert='',$certpass='') { - global $xmlrpcerr, $xmlrpcstr; + global $xmlrpcerr, $xmlrpcstr, $xmlrpcVersion, $xmlrpc_internalencoding; if ($port == 0) { $port = 443; @@ -594,7 +959,7 @@ { curl_setopt($curl, CURLOPT_VERBOSE, 1); } - curl_setopt($curl, CURLOPT_USERAGENT, 'PHP XMLRPC 1.0'); + curl_setopt($curl, CURLOPT_USERAGENT, 'PHP XMLRPC '.$xmlrpcVersion); // required for XMLRPC curl_setopt($curl, CURLOPT_POST, 1); // post the data @@ -602,7 +967,7 @@ // the data curl_setopt($curl, CURLOPT_HEADER, 1); // return the header too - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/xml')); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/xml', 'Accept-Charset: '.$xmlrpc_internalencoding)); // whether to verify remote host's cert curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer); // 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 @@ -635,12 +1000,13 @@ { $this->errstr='no response'; $resp=new xmlrpcresp(0, $xmlrpcerr['curl_fail'], $xmlrpcstr['curl_fail']. ': '. curl_error($curl)); + curl_close($curl); } else { + curl_close($curl); $resp = $msg->parseResponse($result); } - curl_close($curl); return $resp; } @@ -653,10 +1019,10 @@ $results = $this->_try_multicall($msgs, $timeout, $method); /* TODO - this is not php3-friendly */ // if($results !== false) - if($results != false) + if(is_array($results)) { // Either the system.multicall succeeded, or the send - // failed (e.g. due to HTTP timeout). In either case, + // failed (e.g. due to HTTP timeout). In either case, // we're done for now. return $results; } @@ -704,19 +1070,27 @@ // Attempt RPC call $result = $this->send($multicall, $timeout, $method); - if (!is_object($result)) + if(!is_object($result)) + { return ($result || 0); // transport failed + } - if ($result->faultCode() != 0) + if($result->faultCode() != 0) + { return false; // system.multicall failed + } // Unpack responses. $rets = $result->value(); - if ($rets->kindOf() != 'array') + if($rets->kindOf() != 'array') + { return false; // bad return type from system.multicall + } $numRets = $rets->arraysize(); - if ($numRets != count($msgs)) + if($numRets != count($msgs)) + { return false; // wrong number of return values. + } $response = array(); for ($i = 0; $i < $numRets; $i++) @@ -725,18 +1099,24 @@ switch ($val->kindOf()) { case 'array': - if ($val->arraysize() != 1) + if($val->arraysize() != 1) + { return false; // Bad value + } // Normal return value $response[$i] = new xmlrpcresp($val->arraymem(0)); break; case 'struct': $code = $val->structmem('faultCode'); - if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') + if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') + { return false; + } $str = $val->structmem('faultString'); - if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') + if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') + { return false; + } $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval()); break; default: @@ -760,12 +1140,13 @@ { // error $this->errno = $fcode; - $this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later. + $this->errstr = $fstr; + //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later. } - else if (!is_object($val)) + elseif (!is_object($val)) { // programmer error - error_log("Invalid type '" . gettype($val) . "' (value: $val) passed to xmlrpcresp. Defaulting to empty value."); + error_log("Invalid type '" . gettype($val) . "' (value: $val) passed to xmlrpcresp. Defaulting to empty value."); $this->val = new xmlrpcval(); } else @@ -795,6 +1176,7 @@ $result = "<methodResponse>\n"; if ($this->errno) { + // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients $result .= '<fault> <value> <struct> @@ -804,7 +1186,7 @@ </member> <member> <name>faultString</name> -<value><string>' . $this->errstr . '</string></value> +<value><string>' . xmlrpc_encode_entitites($this->errstr) . '</string></value> </member> </struct> </value> @@ -813,8 +1195,8 @@ else { $result .= "<params>\n<param>\n" . - $this->val->serialize() . - "</param>\n</params>"; + $this->val->serialize() . + "</param>\n</params>"; } $result .= "\n</methodResponse>"; return $result; @@ -842,7 +1224,7 @@ function xml_header() { - return "<?xml version=\"1.0\"?>\n<methodCall>\n"; + return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n"; } function xml_footer() @@ -865,7 +1247,7 @@ $this->payload.="</params>\n"; // } $this->payload.=$this->xml_footer(); - $this->payload=str_replace("\n", "\r\n", $this->payload); + //$this->payload=str_replace("\n", "\r\n", $this->payload); } function method($meth='') @@ -900,24 +1282,7 @@ function parseResponse($data='') { global $_xh,$xmlrpcerr,$xmlrpcstr; - global $xmlrpc_defencoding; - - $parser = xml_parser_create($xmlrpc_defencoding); - - $_xh[$parser]=array(); - - $_xh[$parser]['st']=''; - $_xh[$parser]['cm']=0; - $_xh[$parser]['isf']=0; - $_xh[$parser]['ac']=''; - $_xh[$parser]['qt']=''; - $_xh[$parser]['headers'] = array(); - - xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); - xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); - xml_set_character_data_handler($parser, 'xmlrpc_cd'); - xml_set_default_handler($parser, 'xmlrpc_dh'); - $xmlrpc_value=new xmlrpcval; + global $xmlrpc_defencoding, $xmlrpc_internalencoding; $hdrfnd = 0; if($this->debug) @@ -930,45 +1295,128 @@ { error_log('No response received from server.'); $r = new xmlrpcresp(0, $xmlrpcerr['no_data'], $xmlrpcstr['no_data']); - xml_parser_free($parser); return $r; } // see if we got an HTTP 200 OK, else bomb // but only do this if we're using the HTTP protocol. - if(ereg("^HTTP",$data) && !ereg("^HTTP/[0-9\.]+ 200 ", $data)) + if(ereg("^HTTP",$data)) { - $errstr= substr($data, 0, strpos($data, "\n")-1); - error_log('HTTP error, got response: ' .$errstr); - $r=new xmlrpcresp(0, $xmlrpcerr['http_error'], $xmlrpcstr['http_error']. ' (' . $errstr . ')'); - xml_parser_free($parser); - return $r; + // Strip HTTP 1.1 100 Continue header if present + while (ereg('^HTTP/1.1 1[0-9]{2}', $data)) + { + $pos = strpos($data, 'HTTP', 12); + // server sent a Continue header without any (valid) content following... + // give the client a chance to know it + if (!$pos && !is_int($pos)) // works fine in php 3, 4 and 5 + break; + $data = substr($data, $pos); + } + if (!ereg("^HTTP/[0-9\\.]+ 200 ", $data)) + { + $errstr= substr($data, 0, strpos($data, "\n")-1); + error_log('HTTP error, got response: ' .$errstr); + $r=new xmlrpcresp(0, $xmlrpcerr['http_error'], $xmlrpcstr['http_error']. ' (' . $errstr . ')'); + return $r; + } } + $parser = xml_parser_create($xmlrpc_defencoding); + + // G. Giunta 2004/04/06 + // Clean up the accumulator, or it will grow indefinitely long + // if making xmlrpc calls for a while + $_xh=array(); + $_xh[$parser]=array(); + $_xh[$parser]['headers'] = array(); // separate HTTP headers from data if (ereg("^HTTP", $data)) { - $ar = split("\r\n", $data); - while (($line = array_shift($ar))) + // be tolerant to usage of \n instead of \r\n to separate headers and data + // (even though it is not valid http) + $pos = strpos($data,"\r\n\r\n"); + if($pos || is_int($pos)) + $bd = $pos+4; + else { - if (strlen($line) < 1) + $pos = strpos($data,"\n\n"); + if($pos || is_int($pos)) + $bd = $pos+2; + else { - break; + // No separation between response headers and body: fault? + $bd = 0; } - $_xh[$parser]['headers'][] = $line; } - $data = join("\r\n", $ar); - } - - if ($this->debug && count($_xh[$parser]['headers'])) - { - print "<PRE>"; - foreach ($_xh[$parser]['headers'] as $header) + // be tolerant to line endings, and extra empty lines + $ar = split("\r?\n", trim(substr($data, 0, $pos))); + while (list(,$line) = @each($ar)) { - print "HEADER: $header\n"; + // take care of multi-line headers + $arr = explode(':',$line); + if(count($arr) > 1) + { + $header_name = trim($arr[0]); + // TO DO: some headers (the ones that allow a CSV list of values) + // do allow many values to be passed using multiple header lines. + // We should add content to $_xh[$parser]['headers'][$header_name] + // instead of replacing it for those... + $_xh[$parser]['headers'][$header_name] = $arr[1]; + for ($i = 2; $i < count($arr); $i++) + { + $_xh[$parser]['headers'][$header_name] .= ':'.$arr[$i]; + } // while + $_xh[$parser]['headers'][$header_name] = trim($_xh[$parser]['headers'][$header_name]); + } else if (isset($header_name)) + { + $_xh[$parser]['headers'][$header_name] .= ' ' . trim($line); + } + } + $data = substr($data, $bd); + + if ($this->debug && count($_xh[$parser]['headers'])) + { + print '<PRE>'; + //foreach ($_xh[$parser]['headers'] as $header) + @reset($_xh[$parser]['headers']); + while(list($header, $value) = @each($_xh[$parser]['headers'])) + { + print "HEADER: $header: $value\n"; + } + print "</PRE>\n"; } - print "</PRE>\n"; } + // be tolerant of extra whitespace in response body + $data = trim($data); + + // be tolerant of junk after methodResponse (e.g. javascript automatically inserted by free hosts) + // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib + $bd = false; + $pos = strpos($data, "</methodResponse>"); + while ($pos || is_int($pos)) + { + $bd = $pos+17; + $pos = strpos($data, "</methodResponse>", $bd); + } + if ($bd) + $data = substr($data, 0, $bd); + + $_xh[$parser]['st']=''; + $_xh[$parser]['cm']=0; + $_xh[$parser]['isf']=0; + $_xh[$parser]['ac']=''; + $_xh[$parser]['qt']=''; + + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell + // the xml parser to give us back data in the expected charset + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $xmlrpc_internalencoding); + + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + //$xmlrpc_value=new xmlrpcval; + if (!xml_parse($parser, $data, sizeof($data))) { // thanks to Peter Kocks <peter.kocks@baygate.com> @@ -981,18 +1429,20 @@ $errstr = sprintf('XML error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser)); - error_log($errstr); - $r=new xmlrpcresp(0, $xmlrpcerr['invalid_return'], $xmlrpcstr['invalid_return']); - xml_parser_free($parser); - echo $errstr; - return $r; } + error_log($errstr); + $r=new xmlrpcresp(0, $xmlrpcerr['invalid_return'], $xmlrpcstr['invalid_return'].' ('.$errstr.')'); + xml_parser_free($parser); + if ($this->debug) + echo $errstr; + $r->hdrs = $_xh[$parser]['headers']; + return $r; } xml_parser_free($parser); if ($this->debug) { - print "<PRE>---EVALING---[" . - strlen($_xh[$parser]['st']) . " chars]---\n" . + print "<PRE>---EVALING---[" . + strlen($_xh[$parser]['st']) . " chars]---\n" . htmlspecialchars($_xh[$parser]['st']) . ";\n---END---</PRE>"; } if (strlen($_xh[$parser]['st'])==0) @@ -1005,7 +1455,13 @@ } else { - eval('$v=' . $_xh[$parser]['st'] . '; $allOK=1;'); + $allOK=0; + @eval('$v=' . $_xh[$parser]['st'] . '; $allOK=1;'); + if (!$allOK) + { + $r = new xmlrpcresp(0, $xmlrpcerr['invalid_return'], $xmlrpcstr['invalid_return']); + } + else if ($_xh[$parser]['isf']) { $errno_v = $v->structmem('faultCode'); @@ -1041,7 +1497,7 @@ global $xmlrpcTypes; $this->me=array(); $this->mytype=0; - if ($val!=-1 || $type!='') + if ($val!=-1 || !is_int($val) || $type!='') { if ($type=='') { @@ -1172,7 +1628,7 @@ $rs=''; global $xmlrpcTypes, $xmlrpcBase64, $xmlrpcString, $xmlrpcBoolean; - switch($xmlrpcTypes[$typ]) + switch(@$xmlrpcTypes[$typ]) { case 3: // struct @@ -1205,7 +1661,10 @@ $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>"; break; case $xmlrpcString: - $rs.="<${typ}>" . htmlspecialchars($val). "</${typ}>"; + // G. Giunta 2005/2/13: do NOT use htmlentities, since + // it will produce named html entities, which are invalid xml + $rs.="<${typ}>" . xmlrpc_encode_entitites($val). "</${typ}>"; + // $rs.="<${typ}>" . htmlentities($val). "</${typ}>"; break; default: $rs.="<${typ}>${val}</${typ}>"; @@ -1224,7 +1683,7 @@ function serializeval($o) { - global $xmlrpcTypes; + //global $xmlrpcTypes; $rs=''; $ar=$o->me; reset($ar); @@ -1292,7 +1751,7 @@ function scalarval() { - global $xmlrpcBoolean, $xmlrpcBase64; + //global $xmlrpcBoolean, $xmlrpcBase64; reset($this->me); list($a,$b)=each($this->me); return $b; @@ -1381,7 +1840,7 @@ * * * author: Dan Libby (dan@libby.com) * ****************************************************************/ - function old_xmlrpc_decode($xmlrpc_val) + function php_xmlrpc_decode($xmlrpc_val) { $kind = $xmlrpc_val->kindOf(); @@ -1396,9 +1855,9 @@ for($i = 0; $i < $size; $i++) { - $arr[]=old_xmlrpc_decode($xmlrpc_val->arraymem($i)); + $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i)); } - return $arr; + return $arr; } elseif($kind == 'struct') { @@ -1407,12 +1866,52 @@ while(list($key,$value)=$xmlrpc_val->structeach()) { - $arr[$key] = old_xmlrpc_decode($value); + $arr[$key] = php_xmlrpc_decode($value); } return $arr; } } + if(function_exists('xmlrpc_decode')) + { + define('XMLRPC_EPI_ENABLED','1'); + } + else + { + define('XMLRPC_EPI_ENABLED','0'); + function xmlrpc_decode($xmlrpc_val) + { + $kind = $xmlrpc_val->kindOf(); + + if($kind == 'scalar') + { + return $xmlrpc_val->scalarval(); + } + elseif($kind == 'array') + { + $size = $xmlrpc_val->arraysize(); + $arr = array(); + + for($i = 0; $i < $size; $i++) + { + $arr[]=xmlrpc_decode($xmlrpc_val->arraymem($i)); + } + return $arr; + } + elseif($kind == 'struct') + { + $xmlrpc_val->structreset(); + $arr = array(); + + while(list($key,$value)=$xmlrpc_val->structeach()) + { + $arr[$key] = xmlrpc_decode($value); + } + return $arr; + } + } + } + /**************************************************************** * xmlrpc_encode takes native php types and encodes them into * * xmlrpc PHP object format. * @@ -1425,7 +1924,7 @@ * * * author: Dan Libby (dan@libby.com) * ****************************************************************/ - function old_xmlrpc_encode($php_val) + function php_xmlrpc_encode($php_val) { global $xmlrpcInt; global $xmlrpcDouble; @@ -1444,7 +1943,7 @@ $arr = array(); while (list($k,$v) = each($php_val)) { - $arr[$k] = old_xmlrpc_encode($v); + $arr[$k] = php_xmlrpc_encode($v); } $xmlrpc_val->addStruct($arr); break; @@ -1463,14 +1962,67 @@ $xmlrpc_val->addScalar($php_val, $xmlrpcBoolean); break; // </G_Giunta_2001-02-29> - case 'unknown type': + // catch "resource", "NULL", "user function", "unknown type" + //case 'unknown type': default: // giancarlo pinerolo <ping@alt.it> // it has to return // an empty object in case (which is already // at this point), not a boolean. break; + } + return $xmlrpc_val; + } + + if(XMLRPC_EPI_ENABLED == '0') + { + function xmlrpc_encode($php_val) + { + global $xmlrpcInt; + global $xmlrpcDouble; + global $xmlrpcString; + global $xmlrpcArray; + global $xmlrpcStruct; + global $xmlrpcBoolean; + + $type = gettype($php_val); + $xmlrpc_val = new xmlrpcval; + + switch($type) + { + case 'array': + case 'object': + $arr = array(); + while (list($k,$v) = each($php_val)) + { + $arr[$k] = xmlrpc_encode($v); + } + $xmlrpc_val->addStruct($arr); + break; + case 'integer': + $xmlrpc_val->addScalar($php_val, $xmlrpcInt); + break; + case 'double': + $xmlrpc_val->addScalar($php_val, $xmlrpcDouble); + break; + case 'string': + $xmlrpc_val->addScalar($php_val, $xmlrpcString); + break; + // <G_Giunta_2001-02-29> + // Add support for encoding/decoding of booleans, since they are supported in PHP + case 'boolean': + $xmlrpc_val->addScalar($php_val, $xmlrpcBoolean); + break; + // </G_Giunta_2001-02-29> + //case 'unknown type': + default: + // giancarlo pinerolo <ping@alt.it> + // it has to return + // an empty object in case (which is already + // at this point), not a boolean. + break; + } + return $xmlrpc_val; } - return $xmlrpc_val; } ?> |