* @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License
* @version Release: @package_version@
* @link http://pear.php.net/package/XML_RPC
*/
class XML_RPC_Message extends XML_RPC_Base
{
/**
* Should the payload's content be passed through mb_convert_encoding()?
*
* @see XML_RPC_Message::setConvertPayloadEncoding()
* @since Property available since Release 1.5.1
* @var boolean
*/
var $convert_payload_encoding = false;
/**
* The current debug mode (1 = on, 0 = off)
* @var integer
*/
var $debug = 0;
/**
* The encoding to be used for outgoing messages
*
* Defaults to the value of $GLOBALS['XML_RPC_defencoding']
*
* @var string
* @see XML_RPC_Message::setSendEncoding(),
* $GLOBALS['XML_RPC_defencoding'], XML_RPC_Message::xml_header()
*/
var $send_encoding = '';
/**
* The method presently being evaluated
* @var string
*/
var $methodname = '';
/**
* @var array
*/
var $params = array();
/**
* The XML message being generated
* @var string
*/
var $payload = '';
/**
* Should extra line breaks be removed from the payload?
* @since Property available since Release 1.4.6
* @var boolean
*/
var $remove_extra_lines = true;
/**
* The XML response from the remote server
* @since Property available since Release 1.4.6
* @var string
*/
var $response_payload = '';
/**
* @return void
*/
function XML_RPC_Message($meth, $pars = 0)
{
$this->methodname = $meth;
if (is_array($pars) && sizeof($pars) > 0) {
for ($i = 0; $i < sizeof($pars); $i++) {
$this->addParam($pars[$i]);
}
}
}
/**
* Produces the XML declaration including the encoding attribute
*
* The encoding is determined by this class' $send_encoding
* property. If the $send_encoding property is not set, use
* $GLOBALS['XML_RPC_defencoding'].
*
* @return string the XML declaration and element
*
* @see XML_RPC_Message::setSendEncoding(),
* XML_RPC_Message::$send_encoding, $GLOBALS['XML_RPC_defencoding']
*/
function xml_header()
{
global $XML_RPC_defencoding;
if (!$this->send_encoding) {
$this->send_encoding = $XML_RPC_defencoding;
}
return 'send_encoding . '"?>'
. "\n\n";
}
/**
* @return string the closing tag
*/
function xml_footer()
{
return "\n";
}
/**
* Fills the XML_RPC_Message::$payload property
*
* Part of the process makes sure all line endings are in DOS format
* (CRLF), which is probably required by specifications.
*
* If XML_RPC_Message::setConvertPayloadEncoding() was set to true,
* the payload gets passed through mb_convert_encoding()
* to ensure the payload matches the encoding set in the
* XML declaration. The encoding type can be manually set via
* XML_RPC_Message::setSendEncoding().
*
* @return void
*
* @uses XML_RPC_Message::xml_header(), XML_RPC_Message::xml_footer()
* @see XML_RPC_Message::setSendEncoding(), $GLOBALS['XML_RPC_defencoding'],
* XML_RPC_Message::setConvertPayloadEncoding()
*/
function createPayload()
{
$this->payload = $this->xml_header();
$this->payload .= '' . $this->methodname . "\n";
$this->payload .= "\n";
for ($i = 0; $i < sizeof($this->params); $i++) {
$p = $this->params[$i];
$this->payload .= "\n" . $p->serialize() . "\n";
}
$this->payload .= "\n";
$this->payload .= $this->xml_footer();
if ($this->remove_extra_lines) {
$this->payload = preg_replace("@[\r\n]+@", "\r\n", $this->payload);
} else {
$this->payload = preg_replace("@\r\n|\n|\r|\n\r@", "\r\n", $this->payload);
}
if ($this->convert_payload_encoding) {
$this->payload = mb_convert_encoding($this->payload, $this->send_encoding);
}
}
/**
* @return string the name of the method
*/
function method($meth = '')
{
if ($meth != '') {
$this->methodname = $meth;
}
return $this->methodname;
}
/**
* @return string the payload
*/
function serialize()
{
$this->createPayload();
return $this->payload;
}
/**
* @return void
*/
function addParam($par)
{
$this->params[] = $par;
}
/**
* Obtains an XML_RPC_Value object for the given parameter
*
* @param int $i the index number of the parameter to obtain
*
* @return object the XML_RPC_Value object.
* If the parameter doesn't exist, an XML_RPC_Response object.
*
* @since Returns XML_RPC_Response object on error since Release 1.3.0
*/
function getParam($i)
{
global $XML_RPC_err, $XML_RPC_str;
if (isset($this->params[$i])) {
return $this->params[$i];
} else {
$this->raiseError('The submitted request did not contain this parameter',
XML_RPC_ERROR_INCORRECT_PARAMS);
return new XML_RPC_Response(0, $XML_RPC_err['incorrect_params'],
$XML_RPC_str['incorrect_params']);
}
}
/**
* @return int the number of parameters
*/
function getNumParams()
{
return sizeof($this->params);
}
/**
* Sets whether the payload's content gets passed through
* mb_convert_encoding()
*
* Returns PEAR_ERROR object if mb_convert_encoding() isn't available.
*
* @param int $in where 1 = on, 0 = off
*
* @return void
*
* @see XML_RPC_Message::setSendEncoding()
* @since Method available since Release 1.5.1
*/
function setConvertPayloadEncoding($in)
{
if ($in && !function_exists('mb_convert_encoding')) {
return $this->raiseError('mb_convert_encoding() is not available',
XML_RPC_ERROR_PROGRAMMING);
}
$this->convert_payload_encoding = $in;
}
/**
* Sets the XML declaration's encoding attribute
*
* @param string $type the encoding type (ISO-8859-1, UTF-8 or US-ASCII)
*
* @return void
*
* @see XML_RPC_Message::setConvertPayloadEncoding(), XML_RPC_Message::xml_header()
* @since Method available since Release 1.2.0
*/
function setSendEncoding($type)
{
$this->send_encoding = $type;
}
/**
* Determine the XML's encoding via the encoding attribute
* in the XML declaration
*
* If the encoding parameter is not set or is not ISO-8859-1, UTF-8
* or US-ASCII, $XML_RPC_defencoding will be returned.
*
* @param string $data the XML that will be parsed
*
* @return string the encoding to be used
*
* @link http://php.net/xml_parser_create
* @since Method available since Release 1.2.0
*/
function getEncoding($data)
{
global $XML_RPC_defencoding;
if (preg_match('@<\?xml[^>]*\s*encoding\s*=\s*[\'"]([^"\']*)[\'"]@',
$data, $match))
{
$match[1] = trim(strtoupper($match[1]));
switch ($match[1]) {
case 'ISO-8859-1':
case 'UTF-8':
case 'US-ASCII':
return $match[1];
break;
default:
return $XML_RPC_defencoding;
}
} else {
return $XML_RPC_defencoding;
}
}
/**
* @return object a new XML_RPC_Response object
*/
function parseResponseFile($fp)
{
$ipd = '';
while ($data = @fread($fp, 8192)) {
$ipd .= $data;
}
return $this->parseResponse($ipd);
}
/**
* @return object a new XML_RPC_Response object
*/
function parseResponse($data = '')
{
global $XML_RPC_xh, $XML_RPC_err, $XML_RPC_str, $XML_RPC_defencoding;
$encoding = $this->getEncoding($data);
$parser_resource = xml_parser_create($encoding);
$parser = (int) $parser_resource;
$XML_RPC_xh = array();
$XML_RPC_xh[$parser] = array();
$XML_RPC_xh[$parser]['cm'] = 0;
$XML_RPC_xh[$parser]['isf'] = 0;
$XML_RPC_xh[$parser]['ac'] = '';
$XML_RPC_xh[$parser]['qt'] = '';
$XML_RPC_xh[$parser]['stack'] = array();
$XML_RPC_xh[$parser]['valuestack'] = array();
xml_parser_set_option($parser_resource, XML_OPTION_CASE_FOLDING, true);
xml_set_element_handler($parser_resource, 'XML_RPC_se', 'XML_RPC_ee');
xml_set_character_data_handler($parser_resource, 'XML_RPC_cd');
$hdrfnd = 0;
if ($this->debug) {
print "\n---GOT---\n";
print isset($_SERVER['SERVER_PROTOCOL']) ? htmlspecialchars($data) : $data;
print "\n---END---
\n";
}
// See if response is a 200 or a 100 then a 200, else raise error.
// But only do this if we're using the HTTP protocol.
if (preg_match('@^HTTP@', $data) &&
!preg_match('@^HTTP/[0-9\.]+ 200 @', $data) &&
!preg_match('@^HTTP/[0-9\.]+ 10[0-9]([A-Z ]+)?[\r\n]+HTTP/[0-9\.]+ 200@', $data))
{
$errstr = substr($data, 0, strpos($data, "\n") - 1);
error_log('HTTP error, got response: ' . $errstr);
$r = new XML_RPC_Response(0, $XML_RPC_err['http_error'],
$XML_RPC_str['http_error'] . ' (' .
$errstr . ')');
xml_parser_free($parser_resource);
return $r;
}
// gotta get rid of headers here
if (!$hdrfnd && ($brpos = strpos($data,"\r\n\r\n"))) {
$XML_RPC_xh[$parser]['ha'] = substr($data, 0, $brpos);
$data = substr($data, $brpos + 4);
$hdrfnd = 1;
}
/*
* be tolerant of junk after methodResponse
* (e.g. javascript automatically inserted by free hosts)
* thanks to Luca Mariano
*/
$data = substr($data, 0, strpos($data, "") + 17);
$this->response_payload = $data;
if (!xml_parse($parser_resource, $data, sizeof($data))) {
// thanks to Peter Kocks
if (xml_get_current_line_number($parser_resource) == 1) {
$errstr = 'XML error at line 1, check URL';
} else {
$errstr = sprintf('XML error: %s at line %d',
xml_error_string(xml_get_error_code($parser_resource)),
xml_get_current_line_number($parser_resource));
}
error_log($errstr);
$r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'],
$XML_RPC_str['invalid_return']);
xml_parser_free($parser_resource);
return $r;
}
xml_parser_free($parser_resource);
if ($this->debug) {
print "\n---PARSED---\n";
var_dump($XML_RPC_xh[$parser]['value']);
print "---END---
\n";
}
if ($XML_RPC_xh[$parser]['isf'] > 1) {
$r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'],
$XML_RPC_str['invalid_return'].' '.$XML_RPC_xh[$parser]['isf_reason']);
} elseif (!is_object($XML_RPC_xh[$parser]['value'])) {
// then something odd has happened
// and it's time to generate a client side error
// indicating something odd went on
$r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'],
$XML_RPC_str['invalid_return']);
} else {
$v = $XML_RPC_xh[$parser]['value'];
if ($XML_RPC_xh[$parser]['isf']) {
$f = $v->structmem('faultCode');
$fs = $v->structmem('faultString');
$r = new XML_RPC_Response($v, $f->scalarval(),
$fs->scalarval());
} else {
$r = new XML_RPC_Response($v);
}
}
$r->hdrs = preg_split("@\r?\n@", $XML_RPC_xh[$parser]['ha']);
return $r;
}
}
/**
* The methods and properties that represent data in XML RPC format
*
* @category Web Services
* @package XML_RPC
* @author Edd Dumbill
* @author Stig Bakken
* @author Martin Jansen
* @author Daniel Convissor
* @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License
* @version Release: @package_version@
* @link http://pear.php.net/package/XML_RPC
*/
class XML_RPC_Value extends XML_RPC_Base
{
var $me = array();
var $mytype = 0;
/**
* @return void
*/
function XML_RPC_Value($val = -1, $type = '')
{
$this->me = array();
$this->mytype = 0;
if ($val != -1 || $type != '') {
if ($type == '') {
$type = 'string';
}
if (!array_key_exists($type, $GLOBALS['XML_RPC_Types'])) {
// XXX
// need some way to report this error
} elseif ($GLOBALS['XML_RPC_Types'][$type] == 1) {
$this->addScalar($val, $type);
} elseif ($GLOBALS['XML_RPC_Types'][$type] == 2) {
$this->addArray($val);
} elseif ($GLOBALS['XML_RPC_Types'][$type] == 3) {
$this->addStruct($val);
}
}
}
/**
* @return int returns 1 if successful or 0 if there are problems
*/
function addScalar($val, $type = 'string')
{
if ($this->mytype == 1) {
$this->raiseError('Scalar can have only one value',
XML_RPC_ERROR_INVALID_TYPE);
return 0;
}
$typeof = $GLOBALS['XML_RPC_Types'][$type];
if ($typeof != 1) {
$this->raiseError("Not a scalar type (${typeof})",
XML_RPC_ERROR_INVALID_TYPE);
return 0;
}
if ($type == $GLOBALS['XML_RPC_Boolean']) {
if (strcasecmp($val, 'true') == 0
|| $val == 1
|| ($val == true && strcasecmp($val, 'false')))
{
$val = 1;
} else {
$val = 0;
}
}
if ($this->mytype == 2) {
// we're adding to an array here
$ar = $this->me['array'];
$ar[] = new XML_RPC_Value($val, $type);
$this->me['array'] = $ar;
} else {
// a scalar, so set the value and remember we're scalar
$this->me[$type] = $val;
$this->mytype = $typeof;
}
return 1;
}
/**
* @return int returns 1 if successful or 0 if there are problems
*/
function addArray($vals)
{
if ($this->mytype != 0) {
$this->raiseError(
'Already initialized as a [' . $this->kindOf() . ']',
XML_RPC_ERROR_ALREADY_INITIALIZED);
return 0;
}
$this->mytype = $GLOBALS['XML_RPC_Types']['array'];
$this->me['array'] = $vals;
return 1;
}
/**
* @return int returns 1 if successful or 0 if there are problems
*/
function addStruct($vals)
{
if ($this->mytype != 0) {
$this->raiseError(
'Already initialized as a [' . $this->kindOf() . ']',
XML_RPC_ERROR_ALREADY_INITIALIZED);
return 0;
}
$this->mytype = $GLOBALS['XML_RPC_Types']['struct'];
$this->me['struct'] = $vals;
return 1;
}
/**
* @return void
*/
function dump($ar)
{
reset($ar);
foreach ($ar as $key => $val) {
echo "$key => $val
";
if ($key == 'array') {
foreach ($val as $key2 => $val2) {
echo "-- $key2 => $val2
";
}
}
}
}
/**
* @return string the data type of the current value
*/
function kindOf()
{
switch ($this->mytype) {
case 3:
return 'struct';
case 2:
return 'array';
case 1:
return 'scalar';
default:
return 'undef';
}
}
/**
* @return string the data in XML format
*/
function serializedata($typ, $val)
{
$rs = '';
if (!array_key_exists($typ, $GLOBALS['XML_RPC_Types'])) {
// XXX
// need some way to report this error
return;
}
switch ($GLOBALS['XML_RPC_Types'][$typ]) {
case 3:
// struct
$rs .= "\n";
reset($val);
foreach ($val as $key2 => $val2) {
$rs .= "" . htmlspecialchars($key2) . "\n";
$rs .= $this->serializeval($val2);
$rs .= "\n";
}
$rs .= '';
break;
case 2:
// array
$rs .= "\n\n";
foreach ($val as $value) {
$rs .= $this->serializeval($value);
}
$rs .= "\n";
break;
case 1:
switch ($typ) {
case $GLOBALS['XML_RPC_Base64']:
$rs .= "<${typ}>" . base64_encode($val) . "${typ}>";
break;
case $GLOBALS['XML_RPC_Boolean']:
$rs .= "<${typ}>" . ($val ? '1' : '0') . "${typ}>";
break;
case $GLOBALS['XML_RPC_String']:
$rs .= "<${typ}>" . htmlspecialchars($val). "${typ}>";
break;
default:
$rs .= "<${typ}>${val}${typ}>";
}
}
return $rs;
}
/**
* @return string the data in XML format
*/
function serialize()
{
return $this->serializeval($this);
}
/**
* @return string the data in XML format
*/
function serializeval($o)
{
if (!is_object($o) || empty($o->me) || !is_array($o->me)) {
return '';
}
$ar = $o->me;
reset($ar);
list($typ, $val) = each($ar);
return '' . $this->serializedata($typ, $val) . "\n";
}
/**
* @return mixed the contents of the element requested
*/
function structmem($m)
{
return $this->me['struct'][$m];
}
/**
* @return void
*/
function structreset()
{
reset($this->me['struct']);
}
/**
* @return the key/value pair of the struct's current element
*/
function structeach()
{
return each($this->me['struct']);
}
/**
* @return mixed the current value
*/
function getval()
{
// UNSTABLE
reset($this->me);
$b = current($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)) {
foreach ($b as $id => $cont) {
$b[$id] = $cont->scalarval();
}
}
// add support for structures directly encoding php objects
if (is_object($b)) {
$t = get_object_vars($b);
foreach ($t as $id => $cont) {
$t[$id] = $cont->scalarval();
}
foreach ($t as $id => $cont) {
$b->$id = $cont;
}
}
// end contrib
return $b;
}
/**
* @return mixed the current element's scalar value. If the value is
* not scalar, FALSE is returned.
*/
function scalarval()
{
reset($this->me);
$v = current($this->me);
if (!is_scalar($v)) {
$v = false;
}
return $v;
}
/**
* @return string
*/
function scalartyp()
{
reset($this->me);
$a = key($this->me);
if ($a == $GLOBALS['XML_RPC_I4']) {
$a = $GLOBALS['XML_RPC_Int'];
}
return $a;
}
/**
* @return mixed the struct's current element
*/
function arraymem($m)
{
return $this->me['array'][$m];
}
/**
* @return int the number of elements in the array
*/
function arraysize()
{
reset($this->me);
list($a, $b) = each($this->me);
return sizeof($b);
}
/**
* Determines if the item submitted is an XML_RPC_Value object
*
* @param mixed $val the variable to be evaluated
*
* @return bool TRUE if the item is an XML_RPC_Value object
*
* @static
* @since Method available since Release 1.3.0
*/
function isValue($val)
{
return (strtolower(get_class($val)) == 'xml_rpc_value');
}
}
/**
* Return an ISO8601 encoded string
*
* While timezones ought to be supported, 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."
*
* This routine always assumes localtime unless $utc is set to 1, in which
* case UTC is assumed and an adjustment for locale is made when encoding.
*
* @return string the formatted date
*/
function XML_RPC_iso8601_encode($timet, $utc = 0)
{
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;
}
/**
* Convert a datetime string into a Unix timestamp
*
* While timezones ought to be supported, 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."
*
* This routine always assumes localtime unless $utc is set to 1, in which
* case UTC is assumed and an adjustment for locale is made when encoding.
*
* @return int the unix timestamp of the date submitted
*/
function XML_RPC_iso8601_decode($idate, $utc = 0)
{
$t = 0;
if (preg_match('@([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;
}
/**
* Converts an XML_RPC_Value object into native PHP types
*
* @param object $XML_RPC_val the XML_RPC_Value object to decode
*
* @return mixed the PHP values
*/
function XML_RPC_decode($XML_RPC_val)
{
$kind = $XML_RPC_val->kindOf();
if ($kind == 'scalar') {
return $XML_RPC_val->scalarval();
} elseif ($kind == 'array') {
$size = $XML_RPC_val->arraysize();
$arr = array();
for ($i = 0; $i < $size; $i++) {
$arr[] = XML_RPC_decode($XML_RPC_val->arraymem($i));
}
return $arr;
} elseif ($kind == 'struct') {
$XML_RPC_val->structreset();
$arr = array();
while (list($key, $value) = $XML_RPC_val->structeach()) {
$arr[$key] = XML_RPC_decode($value);
}
return $arr;
}
}
/**
* Converts native PHP types into an XML_RPC_Value object
*
* @param mixed $php_val the PHP value or variable you want encoded
*
* @return object the XML_RPC_Value object
*/
function XML_RPC_encode($php_val)
{
$type = gettype($php_val);
$XML_RPC_val = new XML_RPC_Value;
switch ($type) {
case 'array':
if (empty($php_val)) {
$XML_RPC_val->addArray($php_val);
break;
}
$tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
if (empty($tmp)) {
$arr = array();
foreach ($php_val as $k => $v) {
$arr[$k] = XML_RPC_encode($v);
}
$XML_RPC_val->addArray($arr);
break;
}
// fall though if it's not an enumerated array
case 'object':
$arr = array();
foreach ($php_val as $k => $v) {
$arr[$k] = XML_RPC_encode($v);
}
$XML_RPC_val->addStruct($arr);
break;
case 'integer':
$XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Int']);
break;
case 'double':
$XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Double']);
break;
case 'string':
case 'NULL':
if (preg_match('@^[0-9]{8}\T{1}[0-9]{2}\:[0-9]{2}\:[0-9]{2}$@', $php_val)) {
$XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_DateTime']);
} elseif ($GLOBALS['XML_RPC_auto_base64']
&& preg_match("@[^ -~\t\r\n]@", $php_val))
{
// Characters other than alpha-numeric, punctuation, SP, TAB,
// LF and CR break the XML parser, encode value via Base 64.
$XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Base64']);
} else {
$XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_String']);
}
break;
case 'boolean':
// Add support for encoding/decoding of booleans, since they
// are supported in PHP
// by
$XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Boolean']);
break;
case 'unknown type':
default:
$XML_RPC_val = false;
}
return $XML_RPC_val;
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* c-hanging-comment-ender-p: nil
* End:
*/
?>