\n";
}
else
{
return "\n\n";
}
}
function xml_footer()
{
return "";
}
function kindOf()
{
return 'msg';
}
function createPayload($charset_encoding='')
{
if ($charset_encoding != '')
$this->content_type = 'text/xml; charset=' . $charset_encoding;
else
$this->content_type = 'text/xml';
$this->payload=$this->xml_header($charset_encoding);
$this->payload.='' . $this->methodname . "\n";
// if(sizeof($this->params)) {
$this->payload.="\n";
for($i=0; $iparams); $i++)
{
$p=$this->params[$i];
$this->payload.="\n" . $p->serialize($charset_encoding) .
"\n";
}
$this->payload.="\n";
// }
$this->payload.=$this->xml_footer();
//$this->payload=str_replace("\n", "\r\n", $this->payload);
}
/*
* Gets/sets the xmlrpc method to be invoked
* @param string $meth the method to be set (leave empty not to set it)
* @return string the method that will be invoked
* @access public
*/
function method($meth='')
{
if($meth!='')
{
$this->methodname=$meth;
}
return $this->methodname;
}
/*
* @return string the xml representation of the message
*/
function serialize($charset_encoding='')
{
$this->createPayload($charset_encoding);
return $this->payload;
}
/*
* Add a parameter to the list of parameters to be used upon method invocation
* @param xmlrpcval $par
* @return boolean false on failure
*/
function addParam($par)
{
// add check: do not add to self params which are not xmlrpcvals
if(is_object($par) && is_a($par, 'xmlrpcval'))
{
$this->params[]=$par;
return true;
}
else
{
return false;
}
}
/*
* @param integer $i the index of the parameter to fetch (zero based)
* @return xmlrpcval the i-th parameter
*/
function getParam($i) { return $this->params[$i]; }
/*
* @return integer the number of parameters currently set
*/
function getNumParams() { return sizeof($this->params); }
/*
* @access private
* @todo add 2nd & 3rd param to be passed to ParseResponse() ???
*/
function &parseResponseFile($fp)
{
$ipd='';
while($data=fread($fp, 32768))
{
$ipd.=$data;
}
//fclose($fp);
$r =& $this->parseResponse($ipd);
return $r;
}
/**
* Parses HTTP headers and separates them from data.
* @access private
*/
function &parseResponseHeaders(&$data, $headers_processed=false)
{
// 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('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .$errstr);
$r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
return $r;
}
$GLOBALS['_xh']['headers'] = array();
$GLOBALS['_xh']['cookies'] = array();
// 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
{
$pos = strpos($data,"\n\n");
if($pos || is_int($pos))
{
$bd = $pos+2;
}
else
{
// No separation between response headers and body: fault?
$bd = 0;
}
}
// be tolerant to line endings, and extra empty lines
$ar = split("\r?\n", trim(substr($data, 0, $pos)));
while(list(,$line) = @each($ar))
{
// take care of multi-line headers and cookies
$arr = explode(':',$line,2);
if(count($arr) > 1)
{
$header_name = strtolower(trim($arr[0]));
/// @todo some other 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 $GLOBALS['_xh']['headers'][$header_name]
/// instead of replacing it for those...
if ($header_name == 'set-cookie' || $header_name == 'set-cookie2')
{
if ($header_name == 'set-cookie2')
{
// version 2 cookies:
// there could be many cookies on one line, comma separated
$cookies = explode(',', $arr[1]);
}
else
{
$cookies = array($arr[1]);
}
foreach ($cookies as $cookie)
{
// glue together all received cookies, using a comma to separate them
// (same as php does with getallheaders())
if (isset($GLOBALS['_xh']['headers'][$header_name]))
$GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie);
else
$GLOBALS['_xh']['headers'][$header_name] = trim($cookie);
// parse cookie attributes, in case user wants to coorectly honour then
// feature creep: only allow rfc-compliant cookie attributes?
$cookie = explode(';', $cookie);
foreach ($cookie as $pos => $val)
{
$val = explode('=', $val, 2);
$tag = trim($val[0]);
$val = trim(@$val[1]);
/// @todo with version 1 cookies, we should strip leading and trailing " chars
if ($pos == 0)
{
$cookiename = $tag;
$GLOBALS['_xh']['cookies'][$tag] = array();
$GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val);
}
else
{
$GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val;
}
}
}
}
else
{
$GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]);
}
}
elseif(isset($header_name))
{
/// @todo version1 cookies might span multiple lines, thus breaking the parsing above
$GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line);
}
}
// rebuild full cookie set
/*if (isset($GLOBALS['_xh']['headers']['set-cookie']))
{
$cookies = array();
$received = explode(';', $GLOBALS['_xh']['headers']['set-cookie']);
foreach($received as $cookie)
{
list($name, $value) = explode('=', $cookie);
$name = trim($name);
$value = trim($value);
// these values are in fact attributes
if ($name != 'Comment' && $name != 'Comment' && $name != 'Comment' && $name != 'Comment' && $name != 'Comment' && $name != 'Comment')
{
$cookies[$name] = $value;
}
}
}*/
$data = substr($data, $bd);
if($this->debug && count($GLOBALS['_xh']['headers']))
{
print '';
foreach($GLOBALS['_xh']['headers'] as $header => $value)
{
print "HEADER: $header: $value\n";
}
foreach($GLOBALS['_xh']['cookies'] as $header => $value)
{
print "COOKIE: $header={$value['value']}\n";
}
print "
\n";
}
// if CURL was used for the call, http headers have been processed,
// and dechunking + reinflating have been carried out
if(!$headers_processed)
{
// Decode chunked encoding sent by http 1.1 servers
if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
{
if(!$data = decode_chunked($data))
{
error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server');
$r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
return $r;
}
}
// Decode gzip-compressed stuff
// code shamelessly inspired from nusoap library by Dietrich Ayala
if(isset($GLOBALS['_xh']['headers']['content-encoding']))
{
if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
{
// if decoding works, use it. else assume data wasn't gzencoded
if(function_exists('gzinflate'))
{
if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzinflate($data))
{
$data = $degzdata;
if($this->debug)
print "---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---
";
}
elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
{
$data = $degzdata;
if($this->debug)
print "---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---
";
}
else
{
error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server');
$r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
return $r;
}
}
else
{
error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
$r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
return $r;
}
}
}
} // end of 'if needed, de-chunk, re-inflate response'
// real stupid hack to avoid PHP 4 complaining about returning NULL by ref
$r = null;
$r =& $r;
return $r;
}
/*
* @param string $data the xmlrpc response, eventually including http headers
* @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and conseuqent decoding
* @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
* @access private
*/
function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals')
{
//$hdrfnd = 0;
if($this->debug)
{
//by maHo, replaced htmlspecialchars with htmlentities
print "---GOT---\n" . htmlentities($data) . "\n---END---\n
";
$start = strpos($data, '', $start);
$comments = substr($data, $start, $end-$start);
print "---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n
";
}
}
if($data == '')
{
error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.');
$r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
return $r;
}
$GLOBALS['_xh']=array();
// parse the HTTP headers of the response, if present, and separate them from data
if(ereg("^HTTP",$data))
{
$r =& $this->parseResponseHeaders($data, $headers_processed);
if ($r)
{
return $r;
}
}
else
{
$GLOBALS['_xh']['headers'] = array();
$GLOBALS['_xh']['cookies'] = array();
}
// be tolerant of extra whitespace in response body
$data = trim($data);
/// @todo return an error msg if $data=='' ?
// be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
// idea from Luca Mariano originally in PEARified version of the lib
$bd = false;
// Poor man's version of strrpos for php 4...
$pos = strpos($data, '');
while($pos || is_int($pos))
{
$bd = $pos+17;
$pos = strpos($data, '', $bd);
}
if($bd)
{
$data = substr($data, 0, $bd);
}
// if user wants back raw xml, give it to him
if ($return_type == 'xml')
{
$r =& new xmlrpcresp($data, 0, '', 'xml');
$r->hdrs = $GLOBALS['_xh']['headers'];
$r->_cookies = $GLOBALS['_xh']['cookies'];
return $r;
}
// try to 'guestimate' the character encoding of the received response
$resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data);
$GLOBALS['_xh']['stack'] = array();
$GLOBALS['_xh']['valuestack'] = array();
$GLOBALS['_xh']['isf']=0;
$GLOBALS['_xh']['isf_reason']='';
$GLOBALS['_xh']['ac']='';
$GLOBALS['_xh']['qt']='';
// if response charset encoding is not known / supported, try to use
// the default encoding and parse the xml anyway, but log a warning...
if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
// the following code might be better for mb_string enabled installs, but
// makes the lib about 200% slower...
//if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
{
error_log('XML-RPC: xmlrpcmsg::parseResponse: invalid charset encoding of received response: '.$resp_encoding);
$resp_encoding = $GLOBALS['xmlrpc_defencoding'];
}
$parser = xml_parser_create($resp_encoding);
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, $GLOBALS['xmlrpc_internalencoding']);
if ($return_type == 'phpvals')
{
xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
}
else
{
xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
}
xml_set_character_data_handler($parser, 'xmlrpc_cd');
xml_set_default_handler($parser, 'xmlrpc_dh');
// first error check: xml not well formed
if(!xml_parse($parser, $data, sizeof($data)))
{
// thanks to Peter Kocks
if((xml_get_current_line_number($parser)) == 1)
{
$errstr = 'XML error at line 1, check URL';
}
else
{
$errstr = sprintf('XML error: %s at line %d, column %d',
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser), xml_get_current_column_number($parser));
}
error_log($errstr);
$r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
xml_parser_free($parser);
if($this->debug)
{
print $errstr;
}
$r->hdrs = $GLOBALS['_xh']['headers'];
$r->_cookies = $GLOBALS['_xh']['cookies'];
return $r;
}
xml_parser_free($parser);
// second error check: xml well formed but not xml-rpc compliant
if ($GLOBALS['_xh']['isf'] > 1)
{
if ($this->debug)
{
/// @todo echo something for user?
}
$r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
$GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
}
// third error check: parsing of the response has somehow gone boink.
// NB: shall we omit this check, since we trust the parsing code?
elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value']))
{
// something odd has happened
// and it's time to generate a client side error
// indicating something odd went on
$r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
$GLOBALS['xmlrpcstr']['invalid_return']);
}
else
{
if ($this->debug)
{
print "---PARSED---\n" ;
var_export($GLOBALS['_xh']['value']);
print "\n---END---
";
}
// note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object.
$v =& $GLOBALS['_xh']['value'];
if($GLOBALS['_xh']['isf'])
{
if ($return_type == 'xmlrpcvals')
{
$errno_v = $v->structmem('faultCode');
$errstr_v = $v->structmem('faultString');
$errno = $errno_v->scalarval();
$errstr = $errstr_v->scalarval();
}
else
{
$errno = $v['faultCode'];
$errstr = $v['faultString'];
}
if($errno == 0)
{
// FAULT returned, errno needs to reflect that
$errno = -1;
}
$r =& new xmlrpcresp(0, $errno, $errstr);
}
else
{
$r=&new xmlrpcresp($v, 0, '', $return_type);
}
}
$r->hdrs = $GLOBALS['_xh']['headers'];
$r->_cookies = $GLOBALS['_xh']['cookies'];
return $r;
}
}
class xmlrpcval
{
var $me=array();
var $mytype=0;
var $_php_class=null;
function xmlrpcval($val=-1, $type='')
{
//$this->me=array();
//$this->mytype=0;
if($val!==-1 || $type!='')
{
if($type=='')
{
$type='string';
}
if($GLOBALS['xmlrpcTypes'][$type]==1)
{
$this->addScalar($val,$type);
}
elseif($GLOBALS['xmlrpcTypes'][$type]==2)
{
$this->addArray($val);
}
elseif($GLOBALS['xmlrpcTypes'][$type]==3)
{
$this->addStruct($val);
}
}
}
function addScalar($val, $type='string')
{
$typeof=@$GLOBALS['xmlrpcTypes'][$type];
if($typeof!=1)
{
error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($typeof)");
return 0;
}
// coerce booleans into correct values
// NB: shall we do it for datetimes, integers and doubles, too?
if($type==$GLOBALS['xmlrpcBoolean'])
{
if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
{
$val=true;
}
else
{
$val=false;
}
}
switch($this->mytype)
{
case 1:
error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value');
return 0;
case 3:
error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval');
return 0;
case 2:
// we're adding a scalar value to an array here
//$ar=$this->me['array'];
//$ar[]=&new xmlrpcval($val, $type);
//$this->me['array']=$ar;
// Faster (?) avoid all the costly array-copy-by-val done here...
$this->me['array'][]=&new xmlrpcval($val, $type);
return 1;
default:
// a scalar, so set the value and remember we're scalar
$this->me[$type]=$val;
$this->mytype=$typeof;
return 1;
}
}
/// @todo add some checking for $vals to be an array of xmlrpcvals?
function addArray($vals)
{
if($this->mytype==0)
{
$this->mytype=$GLOBALS['xmlrpcTypes']['array'];
$this->me['array']=$vals;
return 1;
}
elseif($this->mytype==2)
{
// we're adding to an array here
$this->me['array'] = array_merge($this->me['array'], $vals);
}
else
{
error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']');
return 0;
}
}
/// @todo add some checking for $vals to be an array?
function addStruct($vals)
{
if($this->mytype==0)
{
$this->mytype=$GLOBALS['xmlrpcTypes']['struct'];
$this->me['struct']=$vals;
return 1;
}
elseif($this->mytype==3)
{
// we're adding to a struct here
$this->me['struct'] = array_merge($this->me['struct'], $vals);
}
else
{
error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']');
return 0;
}
}
// poor man's version of print_r ???
// DEPRECATED!
function dump($ar)
{
foreach($ar as $key => $val)
{
echo "$key => $val
";
if($key == 'array')
{
while(list($key2, $val2) = each($val))
{
echo "-- $key2 => $val2
";
}
}
}
}
function kindOf()
{
switch($this->mytype)
{
case 3:
return 'struct';
break;
case 2:
return 'array';
break;
case 1:
return 'scalar';
break;
default:
return 'undef';
}
}
function serializedata($typ, $val, $charset_encoding='')
{
$rs='';
switch(@$GLOBALS['xmlrpcTypes'][$typ])
{
case 3:
// struct
if ($this->_php_class)
{
$rs.='\n";
}
else
{
$rs.="\n";
}
foreach($val as $key2 => $val2)
{
$rs.="${key2}\n";
//$rs.=$this->serializeval($val2);
$rs.=$val2->serialize($charset_encoding);
$rs.="\n";
}
$rs.='';
break;
case 2:
// array
$rs.="\n\n";
for($i=0; $iserializeval($val[$i]);
$rs.=$val[$i]->serialize($charset_encoding);
}
$rs.="\n";
break;
case 1:
switch($typ)
{
case $GLOBALS['xmlrpcBase64']:
$rs.="<${typ}>" . base64_encode($val) . "${typ}>";
break;
case $GLOBALS['xmlrpcBoolean']:
$rs.="<${typ}>" . ($val ? '1' : '0') . "${typ}>";
break;
case $GLOBALS['xmlrpcString']:
// 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, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "${typ}>";
// $rs.="<${typ}>" . htmlentities($val). "${typ}>";
break;
case $GLOBALS['xmlrpcInt']:
case $GLOBALS['xmlrpcI4']:
$rs.="<${typ}>".(int)$val."${typ}>";
break;
case $GLOBALS['xmlrpcDouble']:
$rs.="<${typ}>".(double)$val."${typ}>";
break;
default:
// no standard type value should arrive here, but provide a possibility
// for xmlrpcvals of unknown type...
$rs.="<${typ}>${val}${typ}>";
}
break;
default:
break;
}
return $rs;
}
/**
* Return xml representation of the value
* @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
*/
function serialize($charset_encoding='')
{
// add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
//if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
//{
reset($this->me);
list($typ, $val) = each($this->me);
return '' . $this->serializedata($typ, $val, $charset_encoding) . "\n";
//}
}
// DEPRECATED
function serializeval($o)
{
// add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
//if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
//{
$ar=$o->me;
reset($ar);
list($typ, $val) = each($ar);
return '' . $this->serializedata($typ, $val) . "\n";
//}
}
/**
* Checks wheter a struct member with a given name is present.
* Works only on xmlrpcvals of type struct.
* @param string $m the name of the struct member to be looked up
* @return boolean
*/
function structmemexists($m)
{
return array_key_exists($m, $this->me['struct']);
}
/*
* Returns the value of a given struct member (an xmlrpcval object in itself).
* Will raise a php warning if struct member of given name does not exist
* @param string $m the name of the struct member to be looked up
* @return xmlrpcval
*/
function structmem($m)
{
return $this->me['struct'][$m];
}
function structreset()
{
reset($this->me['struct']);
}
function structeach()
{
return each($this->me['struct']);
}
// DEPRECATED! this code looks like it is very fragile and has not been fixed
// for a long long time. Shall we remove it for 2.0?
function getval()
{
// UNSTABLE
reset($this->me);
list($a,$b)=each($this->me);
// contributed by I Sofer, 2001-03-24
// add support for nested arrays to scalarval
// i've created a new method here, so as to
// preserve back compatibility
if(is_array($b))
{
@reset($b);
while(list($id,$cont) = @each($b))
{
$b[$id] = $cont->scalarval();
}
}
// add support for structures directly encoding php objects
if(is_object($b))
{
$t = get_object_vars($b);
@reset($t);
while(list($id,$cont) = @each($t))
{
$t[$id] = $cont->scalarval();
}
@reset($t);
while(list($id,$cont) = @each($t))
{
//@eval('$b->'.$id.' = $cont;');
@$b->$id = $cont;
}
}
// end contrib
return $b;
}
/**
* Returns the value of a scalar xmlrpcval
* @return mixed
*/
function scalarval()
{
reset($this->me);
list(,$b)=each($this->me);
return $b;
}
/**
* Returns the type of the xmlrpcval.
* For integers, 'int' is always returned in place of 'i4'
* @return string
*/
function scalartyp()
{
reset($this->me);
list($a,$b)=each($this->me);
if($a==$GLOBALS['xmlrpcI4'])
{
$a=$GLOBALS['xmlrpcInt'];
}
return $a;
}
/**
* Returns the m-th member of an xmlrpcval of struct type
* @param integer $m the index of the value to be retrieved (zero based)
* @return xmlrpcval
*/
function arraymem($m)
{
return $this->me['array'][$m];
}
/**
* Returns the number of members in an xmlrpcval of array type
* @return integer
*/
function arraysize()
{
return count($this->me['array']);
}
/**
* Returns the number of members in an xmlrpcval of struct type
* @return integer
*/
function structsize()
{
return count($this->me['struct']);
}
}
// date helpers
function iso8601_encode($timet, $utc=0)
{
// return an ISO8601 encoded string
// really, timezones ought to be supported
// but the XML-RPC spec says:
//
// "Don't assume a timezone. It should be specified by the server in its
// documentation what assumptions it makes about timezones."
//
// these routines always assume localtime unless
// $utc is set to 1, in which case UTC is assumed
// and an adjustment for locale is made when encoding
if(!$utc)
{
$t=strftime("%Y%m%dT%H:%M:%S", $timet);
}
else
{
if(function_exists('gmstrftime'))
{
// gmstrftime doesn't exist in some versions
// of PHP
$t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
}
else
{
$t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
}
}
return $t;
}
function iso8601_decode($idate, $utc=0)
{
// return a timet in the localtime, or UTC
$t=0;
if(ereg("([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})", $idate, $regs))
{
if($utc)
{
$t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
}
else
{
$t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
}
}
return $t;
}
/**
* Takes an xmlrpc value in PHP xmlrpcval object format
* and translates it into native PHP types.
* Works with xmlrpc message objects as input, too.
*
* @author Dan Libby (dan@libby.com)
*
* @param xmlrpcval $xmlrpc_val
* @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects
* @return mixed
*/
function php_xmlrpc_decode($xmlrpc_val, $options=array())
{
switch($xmlrpc_val->kindOf())
{
case 'scalar':
return $xmlrpc_val->scalarval();
case 'array':
$size = $xmlrpc_val->arraysize();
$arr = array();
for($i = 0; $i < $size; $i++)
{
$arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
}
return $arr;
case 'struct':
$xmlrpc_val->structreset();
// If user said so, try to rebuild php objects for specific struct vals.
/// @todo should we raise a warning for class not found?
// shall we check for proper subclass of xmlrpcval instead of
// presence of _php_class to detect what we can do?
if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
&& class_exists($xmlrpc_val->_php_class))
{
$obj = @new $xmlrpc_val->_php_class;
while(list($key,$value)=$xmlrpc_val->structeach())
{
$obj->$key = php_xmlrpc_decode($value, $options);
}
return $obj;
}
else
{
$arr = array();
while(list($key,$value)=$xmlrpc_val->structeach())
{
$arr[$key] = php_xmlrpc_decode($value, $options);
}
return $arr;
}
case 'msg':
$paramcount = $xmlrpc_val->getNumParams();
$arr = array();
for($i = 0; $i < $paramcount; $i++)
{
$arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
}
return $arr;
}
}
if(function_exists('xmlrpc_decode'))
{
define('XMLRPC_EPI_ENABLED','1');
}
else
{
define('XMLRPC_EPI_ENABLED','0');
}
/**
* Takes native php types and encodes them into xmlrpc PHP object format.
* It will not re-encode xmlrpcval objects.
* Feature creep -- could support more types via optional type argument
* (string => datetime support has been added, ??? => base64 not yet)
*
* @author Dan Libby (dan@libby.com)
*
* @param mixed $php_val the value to be converted into an xmlrpcval object
* @param array $options can include 'encode_php_objs' and 'auto_dates'
* @return xmlrpcval
*/
function &php_xmlrpc_encode($php_val, $options=array())
{
$type = gettype($php_val);
$xmlrpc_val =& new xmlrpcval;
switch($type)
{
case 'array':
// PHP arrays can be encoded to either xmlrpc structs or arrays,
// depending on wheter they are hashes or plain 0..n integer indexed
// A shorter one-liner would be
// $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
// but execution time skyrockets!
$j = 0;
$arr = array();
$ko = false;
foreach($php_val as $key => $val)
{
$arr[$key] =& php_xmlrpc_encode($val, $options);
if(!$ko && $key !== $j)
{
$ko = true;
}
$j++;
}
if($ko)
{
$xmlrpc_val->addStruct($arr);
}
else
{
$xmlrpc_val->addArray($arr);
}
break;
case 'object':
if(is_a($php_val, 'xmlrpcval'))
{
$xmlrpc_val = $php_val;
}
else
{
$arr = array();
while(list($k,$v) = each($php_val))
{
$arr[$k] = php_xmlrpc_encode($v, $options);
}
$xmlrpc_val->addStruct($arr);
if (in_array('encode_php_objs', $options))
{
// let's save original class name into xmlrpcval:
// might be useful later on...
$xmlrpc_val->_php_class = get_class($php_val);
}
}
break;
case 'integer':
$xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcInt']);
break;
case 'double':
$xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcDouble']);
break;
case 'string':
if (in_array('auto_dates', $options) && ereg("^[0-9]{8}\T{1}[0-9]{2}\:[0-9]{2}\:[0-9]{2}$", $php_val))
$xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcDateTime']);
else
$xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcString']);
break;
//
// Add support for encoding/decoding of booleans, since they are supported in PHP
case 'boolean':
$xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcBoolean']);
break;
//
// catch "resource", "NULL", "user function", "unknown type"
//case 'unknown type':
default:
// giancarlo pinerolo
// it has to return
// an empty object in case (which is already
// at this point), not a boolean.
break;
}
return $xmlrpc_val;
}
/**
* decode a string that is encoded w/ "chunked" transfer encoding
* as defined in rfc2068 par. 19.4.6
* code shamelessly stolen from nusoap library by Dietrich Ayala
*
* @param string $buffer the string to be decoded
* @return string
*/
function decode_chunked($buffer)
{
// length := 0
$length = 0;
$new = '';
// read chunk-size, chunk-extension (if any) and crlf
// get the position of the linebreak
$chunkend = strpos($buffer,"\r\n") + 2;
$temp = substr($buffer,0,$chunkend);
$chunk_size = hexdec( trim($temp) );
$chunkstart = $chunkend;
// while(chunk-size > 0) {
while($chunk_size > 0)
{
$chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
// just in case we got a broken connection
if($chunkend == false)
{
$chunk = substr($buffer,$chunkstart);
// append chunk-data to entity-body
$new .= $chunk;
$length += strlen($chunk);
break;
}
// read chunk-data and crlf
$chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
// append chunk-data to entity-body
$new .= $chunk;
// length := length + chunk-size
$length += strlen($chunk);
// read chunk-size and crlf
$chunkstart = $chunkend + 2;
$chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
if($chunkend == false)
{
break; //just in case we got a broken connection
}
$temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
$chunk_size = hexdec( trim($temp) );
$chunkstart = $chunkend;
}
return $new;
}
/**
* Given a string defining a php type or phpxmlrpc type (loosely defined: strings
* accepted come from javadoc blocks), return corresponding phpxmlrpc type.
* NB: for php 'resource' types returns empty string, since resources cannot be serialized;
* for php class names returns 'struct', since php objects can be serialized as xmlrpc structs
* @param string $phptype
* @return string
*/
function php_2_xmlrpc_type($phptype)
{
switch(strtolower($phptype))
{
case 'string':
return $GLOBALS['xmlrpcString'];
case 'integer':
case $GLOBALS['xmlrpcInt']: // 'int'
case $GLOBALS['xmlrpcI4']:
return $GLOBALS['xmlrpcInt'];
case 'double':
return $GLOBALS['xmlrpcDouble'];
case 'boolean':
return $GLOBALS['xmlrpcBoolean'];
case 'array':
return $GLOBALS['xmlrpcArray'];
case 'object':
return $GLOBALS['xmlrpcStruct'];
case $GLOBALS['xmlrpcBase64']:
case $GLOBALS['xmlrpcStruct']:
return strtolower($phptype);
case 'resource':
return '';
default:
if(class_exists($phptype))
{
return $GLOBALS['xmlrpcStruct'];
}
else
{
// unknown: might be any xmlrpc type
return $GLOBALS['xmlrpcValue'];
}
}
}
/**
* Given a user-defined PHP function, create a PHP 'wrapper' function that can
* be exposed as xmlrpc method from an xmlrpc_server object and called from remote
* clients.
*
* Since php is a typeless language, to infer types of input and output parameters,
* it relies on parsing the javadoc-style comment block associated with the given
* function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
* in the @param tag is also allowed, if you need the php function to receive/send
* data in that particular format (note that base64 enncoding/decoding is transparently
* carried out by the lib, while datetime vals are passed around as strings)
*
* Known limitations:
* - requires PHP 5.0.3 +
* - only works for user-defined functions, not for PHP internal functions
* (reflection does not support retrieving number/type of params for those)
* - functions returning php objects will generate special xmlrpc responses:
* when the xmlrpc decoding of those responses is carried out by this same lib, using
* the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt.
* In short: php objects can be serialized, too (except for their resource members),
* using this function.
* Other libs might choke on the very same xml that will be generated in this case
* (i.e. it has a nonstandard attribute on struct element tags)
* - usage of javadoc @param tags using param names in a different order from the
* function prototype is not considered valid (to be fixed?)
*
* Note that since rel. 2.0RC3 the preferred method to have the server call 'standard'
* php functions (ie. functions not expecting a single xmlrpcmsg obj as parameter)
* is by making use of the functions_parameters_type class member.
*
* @param string $funcname the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') might be ok too, in the future...
* @return false on error, or an array containing the name of the new php function,
* its signature and docs, to be used in the server dispatch map
*
* @todo decide how to deal with params passed by ref: bomb out or allow?
* @todo finish using javadoc info to build method sig if all params are named but out of order
* @done switch to some automagic object encoding scheme
* @todo add a check for params of 'resource' type
* @todo add some trigger_errors when returning false?
* @todo what to do when the PHP function returns NULL? we are currently returning bogus responses!!!
*/
function wrap_php_function($funcname, $newfuncname='')
{
if(version_compare(phpversion(), '5.0.3') == -1)
{
// up to php 5.0.3 some useful reflection methods were missing
return false;
}
if((is_array($funcname) && !method_exists($funcname[0], $funcname[1])) || !function_exists($funcname))
{
return false;
}
else
{
// determine name of new php function
if($newfuncname == '')
{
if(is_array($funcname))
{
$xmlrpcfuncname = "xmlrpc_".implode('_', $funcname);
}
else
{
$xmlrpcfuncname = "xmlrpc_$funcname";
}
}
else
{
$xmlrpcfuncname = $newfuncname;
}
while(function_exists($xmlrpcfuncname))
{
$xmlrpcfuncname .= 'x';
}
$code = "function $xmlrpcfuncname(\$msg) {\n";
// start to introspect PHP code
$func =& new ReflectionFunction($funcname);
if($func->isInternal())
{
// Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
// instead of getparameters to fully reflect internal php functions ?
return false;
}
// retrieve parameter names, types and description from javadoc comments
// function description
$desc = '';
// type of return val: by default 'any'
$returns = $GLOBALS['xmlrpcValue'];
// type + name of function parameters
$paramDocs = array();
$docs = $func->getDocComment();
if($docs != '')
{
$docs = explode("\n", $docs);
$i = 0;
foreach($docs as $doc)
{
$doc = trim($doc, " \r\t/*");
if(strlen($doc) && strpos($doc, '@') !== 0 && !$i)
{
if($desc)
{
$desc .= "\n";
}
$desc .= $doc;
}
elseif(strpos($doc, '@param') === 0)
{
// syntax: @param type [$name] desc
if(preg_match('/@param\s+(\S+)(\s+\$\S+)?\s+(.+)/', $doc, $matches))
{
if(strpos($matches[1], '|'))
{
//$paramDocs[$i]['type'] = explode('|', $matches[1]);
$paramDocs[$i]['type'] = 'mixed';
}
else
{
$paramDocs[$i]['type'] = $matches[1];
}
$paramDocs[$i]['name'] = trim($matches[2]);
$paramDocs[$i]['doc'] = $matches[3];
}
$i++;
}
elseif(strpos($doc, '@return') === 0)
{
$returns = preg_split("/\s+/", $doc);
if(isset($returns[1]))
{
$returns = php_2_xmlrpc_type($returns[1]);
}
}
}
}
// start introspection of actual function prototype and building of PHP code
// to be eval'd
$params = $func->getParameters();
$innercode = '';
$i = 0;
$parsvariations = array();
$pars = array();
$pnum = count($params);
foreach($params as $param)
{
if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != '$'.strtolower($param->getName()))
{
// param name from phpdoc info does not match param definition!
$paramDocs[$i]['type'] = 'mixed';
}
if($param->isOptional())
{
// this particular parameter is optional. save as valid previous list of parameters
$innercode .= "if (\$paramcount > $i) {\n";
$parsvariations[] = $pars;
}
$innercode .= "\$p$i = \$msg->getParam($i);\n";
$innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_xmlrpc_decode(\$p$i);\n";
$pars[] = "\$p$i";
$i++;
if($param->isOptional())
{
$innercode .= "}\n";
}
if($i == $pnum)
{
// last allowed parameters combination
$parsvariations[] = $pars;
}
}
$sigs = array();
if(count($parsvariations) == 0)
{
// only known good synopsis = no parameters
$parsvariations[] = array();
$minpars = 0;
}
else
{
$minpars = count($parsvariations[0]);
}
if($minpars)
{
// add to code the check for min params number
// NB: this check needs to be done BEFORE decoding param values
$innercode = "\$paramcount = \$msg->getNumParams();\n" .
"if (\$paramcount < $minpars) return new xmlrpcresp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode;
}
else
{
$innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode;
}
$innercode .= "\$np = false;";
foreach($parsvariations as $pars)
{
$innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = $funcname(" . implode(',', $pars) . "); else\n";
// build a 'generic' signature (only use an appropriate return type)
$sig = array($returns);
for($i=0; $i < count($pars); $i++)
{
if (isset($paramDocs[$i]['type']))
{
$sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']);
}
else
{
$sig[] = $GLOBALS['xmlrpcValue'];
}
}
$sigs[] = $sig;
}
$innercode .= "\$np = true;\n";
$innercode .= "if (\$np) return new xmlrpcresp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else\n";
//$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n";
if($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64'])
{
$innercode .= "return new xmlrpcresp(new xmlrpcval(\$retval, '$returns'));";
}
else
{
$innercode .= "return new xmlrpcresp(php_xmlrpc_encode(\$retval, array('encode_php_objs')));";
}
// shall we exclude functions returning by ref?
// if($func->returnsReference())
// return false;
$code = $code . $innercode . "\n}\n \$allOK=1;";
//print_r($code);
$allOK = 0;
eval($code);
// alternative
//$xmlrpcfuncname = create_function('$m', $innercode);
if(!$allOK)
{
return false;
}
/// @todo examine if $paramDocs matches $parsvariations and build array for
/// usage as method signature, plus put together a nice string for docs
$ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc);
return $ret;
}
}
/**
* Given an xmlrpc client and a method name, register a php wrapper function
* that will call it and return results using native php types for both
* params and results. The generated php function will return an xmlrpcresp
* oject for failed xmlrpc calls
*
* Known limitations:
* - server must support system.methodsignature for the wanted xmlrpc method
* - for methods that expose many signatures, only one can be picked (we
* could in priciple check if signatures differ only by number of params
* and not by type, but it would be more complication than we can spare time)
* - nested xmlrpc params: the caller of the generated php function has to
* encode on its own the params passed to the php function if these are structs
* or arrays whose (sub)members include values of type datetime or base64
*
* Notes: the connection properties of the given client will be copied
* and reused for the connection used during the call to the generated
* php function.
* Calling the generated php function 'might' be slow: a new xmlrpc client
* is created on every invocation and an xmlrpc-connection opened+closed.
* An extra 'debug' param is appended to param list of xmlrpc method, useful
* for debugging purposes.
*
* @param xmlrpc_client $client an xmlrpc client set up correctly to communicate with target server
* @param string $methodname the xmlrpc method to be mapped to a php function
* @param integer $signum the index of the method signature to use in mapping (if method exposes many sigs)
* @return string the name of the generated php function (or false)
*/
function wrap_xmlrpc_method($client, $methodname, $signum=0, $timeout=0, $protocol='', $newfuncname='')
{
$msg =& new xmlrpcmsg('system.methodSignature');
$msg->addparam(new xmlrpcval($methodname));
$response =& $client->send($msg, $timeout, $protocol);
if(!$response || $response->faultCode())
{
return false;
}
else
{
$desc = $response->value();
if(($client->return_type == 'xmlrpcvals' && ($desc->kindOf() != 'array' || $desc->arraysize() <= $signum)) ||
($client->return_type == 'phpvals' && (!is_array($desc) || count($desc) <= $signum)))
{
return false;
}
else
{
if($newfuncname != '')
{
$xmlrpcfuncname = $newfuncname;
}
else
{
$xmlrpcfuncname = 'xmlrpc_'.str_replace('.', '_', $methodname);
}
while(function_exists($xmlrpcfuncname))
{
$xmlrpcfuncname .= 'x';
}
if ($client->return_type == 'phpvals')
{
$desc = $desc[$signum];
}
else
{
$desc = $desc->arraymem($signum);
}
$code = "function $xmlrpcfuncname (";
$innercode = "\$client =& new xmlrpc_client('$client->path', '$client->server');\n";
// copy all client fields to the client that will be generated runtime
// (this provides for future expansion of client obj)
foreach($client as $fld => $val)
{
if($fld != 'debug' && $fld != 'return_type')
{
$val = var_export($val, true);
$innercode .= "\$client->$fld = $val;\n";
}
}
$innercode .= "\$client->setDebug(\$debug);\n";
$innercode .= "\$client->return_type = 'xmlrpcvals';\n";
$innercode .= "\$msg =& new xmlrpcmsg('$methodname');\n";
// param parsing
$plist = array();
if ($client->return_type == 'phpvals')
{
$pcount = count($desc);
}
else
{
$pcount = $desc->arraysize();
}
for($i = 1; $i < $pcount; $i++)
{
$plist[] = "\$p$i";
if ($client->return_type == 'phpvals')
{
$ptype = $desc[$i];
}
else
{
$ptype = $desc->arraymem($i);
$ptype = $ptype->scalarval();
}
if($ptype == 'dateTime.iso8601' || $ptype == 'base64')
{
$innercode .= "\$p$i =& new xmlrpcval(\$p$i, '$ptype');\n";
}
else
{
$innercode .= "\$p$i =& php_xmlrpc_encode(\$p$i);\n";
}
$innercode .= "\$msg->addparam(\$p$i);\n";
}
$plist[] = '$debug = 0';
$plist = implode(',', $plist);
$innercode .= "\$res =& \$client->send(\$msg, $timeout, '$protocol');\n";
$innercode .= "if (\$res->faultcode()) return \$res; else return php_xmlrpc_decode(\$res->value(), array('decode_php_objs'));";
$code = $code . $plist. ") {\n" . $innercode . "\n}\n\$allOK=1;";
//print_r($code);
$allOK = 0;
eval($code);
// alternative
//$xmlrpcfuncname = create_function('$m', $innercode);
if($allOK)
{
return $xmlrpcfuncname;
}
else
{
return false;
}
}
}
}
/**
* xml charset encoding guessing helper function.
* Tries to determine the charset encoding of an XML chunk
* received over HTTP.
* NB: according to the spec (RFC 3023, if text/xml content-type is received over HTTP without a content-type,
* we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
* which will be most probably using UTF-8 anyway...
*
* @param string $httpheaders the http Content-type header
* @param string $xmlchunk xml content buffer
* @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
*
* @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
*/
function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
{
// discussion: see http://www.yale.edu/pclt/encoding/
// 1 - test if encoding is specified in HTTP HEADERS
//Details:
// LWS: (\13\10)?( |\t)+
// token: (any char but excluded stuff)+
// header: Content-type = ...; charset=value(; ...)*
// where value is of type token, no LWS allowed between 'charset' and value
// Note: we do not check for invalid chars in VALUE:
// this had better be done using pure ereg as below
/// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
if(eregi(";((\\xD\\xA)?[ \\x9]+)*charset=", $httpheader))
{
/// @BUG if charset is received uppercase, this line will fail!
$in = strpos($httpheader, 'charset=')+8;
$out = strpos($httpheader, ';', $in) ? strpos($httpheader, ';', $in) : strlen($httpheader);
return strtoupper(trim(substr($httpheader, $in, $out-$in)));
}
// 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
// (source: http://www.w3.org/TR/2000/REC-xml-20001006)
// NOTE: actually, according to the spec, even if we find the BOM and determine
// an encoding, we should check if there is an encoding specified
// in the xml declaration, and verify if they match.
/// @todo implement check as described above?
/// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
if(@ereg("^(\\x00\\x00\\xFE\\xFF|\\xFF\\xFE\\x00\\x00|\\x00\\x00\\xFF\\xFE|\\xFE\\xFF\\x00\\x00)", $xmlchunk))
// if (preg_match("/^(\\x00\\x00\\xFE\\xFF|\\xFF\\xFE\\x00\\x00|\\x00\\x00\\xFF\\xFE|\\xFE\\xFF\\x00\\x00)/", $xmlchunk))
{
return 'UCS-4';
}
elseif(ereg("^(\\xFE\\xFF|\\xFF\\xFE)", $xmlchunk))
{
return 'UTF-16';
}
elseif(ereg("^(\\xEF\\xBB\\xBF)", $xmlchunk))
{
return 'UTF-8';
}
// 3 - test if encoding is specified in the xml declaration
// Details:
// SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
// EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
if (ereg("^<\?xml".
"[ \\x9\\xD\\xA]+" . "version" . "[ \\x9\\xD\\xA]*=[ \\x9\\xD\\xA]*" . "((\"[a-zA-Z0-9_.:-]+\")|('[a-zA-Z0-9_.:-]+'))".
"[ \\x9\\xD\\xA]+" . "encoding" . "[ \\x9\\xD\\xA]*=[ \\x9\\xD\\xA]*" . "((\"[A-Za-z][A-Za-z0-9._-]*\")|('[A-Za-z][A-Za-z0-9._-]*'))",
$xmlchunk, $regs))
{
return strtoupper(substr($regs[4], 1, strlen($regs[4])-2));
}
// 4 - if mbstring is available, let it do the guesswork
// NB: we favour finding an encoding that is compatible with what we can process
if(extension_loaded('mbstring'))
{
if($encoding_prefs)
{
$enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
}
else
{
$enc = mb_detect_encoding($xmlchunk);
}
// NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
// IANA also likes better US-ASCII, so go with it
if($enc == 'ASCII')
{
$enc = 'US-'.$enc;
}
return $enc;
}
else
{
// no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
// Both RFC 2616 (HTTP 1.1) and 1945(http 1.0) clearly state that for text/xxx content types
// this should be the standard. And we should be getting text/xml as request and response.
// BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
return $GLOBALS['xmlrpc_defencoding'];
}
}
/**
* Checks if a given charset encoding is present in a list of encodings or
* if it is a valid subset of any encoding in the list
* @param string $encoding charset to be tested
* @param mixed $validlist comma separated list of valid charsets (or array of charsets)
*/
function is_valid_charset($encoding, $validlist)
{
$charset_supersets = array(
'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
);
if (is_string($validlist))
$validlist = split(',', $validlist);
if (@in_array(strtoupper($encoding), $validlist))
return true;
else
{
if (array_key_exists($encoding, $charset_supersets))
foreach ($validlist as $allowed)
if (in_array($allowed, $charset_supersets[$encoding]))
return true;
return false;
}
}
?>