topical media & game development
mashup-delicious-06-example6-9-lib-xmlrpc.inc / inc
<?php
// by Edd Dumbill (C) 1999-2002
// <edd@usefulinc.com>
//
// Copyright (c) 1999,2000,2002 Edd Dumbill.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// * Neither the name of the "XML-RPC for PHP" nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
if(!function_exists('xml_parser_create'))
{
// For PHP 4 onward, XML functionality is always compiled-in on windows:
// no more need to dl-open it. It might have been compiled out on *nix...
if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN'))
{
dl('xml.so');
}
}
// Try to be backward compat with php < 4.2 (are we not being nice ?)
phpversion[0] == '4' && phpversion[2] == '0')
{
if(GLOBALS where used.
GLOBALS['xmlrpcInt']='int';
GLOBALS['xmlrpcDouble']='double';
GLOBALS['xmlrpcDateTime']='dateTime.iso8601';
GLOBALS['xmlrpcArray']='array';
GLOBALS['xmlrpcValue']='undefined';
GLOBALS['xmlrpcI4'] => 1,
GLOBALS['xmlrpcBoolean'] => 1,
GLOBALS['xmlrpcDouble'] => 1,
GLOBALS['xmlrpcBase64'] => 1,
GLOBALS['xmlrpcStruct'] => 3
);
GLOBALS['xmlrpcNull']='null';
@deprecated
GLOBALS['xmlEntities']=array(
'amp' => '&',
'quot' => '"',
'lt' => '<',
'gt' => '>',
'apos' => "'"
);
// tables used for transcoding different charsets into us-ascii xml
GLOBALS['xml_iso88591_Entities']['in'] = array();
i = 0; i++)
{
i);
i.';';
}
for (i < 256; GLOBALS['xml_iso88591_Entities']['in'][] = chr(GLOBALS['xml_iso88591_Entities']['out'][] = '&#'. @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159.
These will NOT be present in true ISO-8859-1, but will save the unwary
windows user from sending junk.
/*
cp1252_to_xmlent =
array(
'\x80'=>'€', '\x81'=>'?', '\x82'=>'‚', '\x83'=>'ƒ',
'\x84'=>'„', '\x85'=>'…', '\x86'=>'†', \x87'=>'‡',
'\x88'=>'ˆ', '\x89'=>'‰', '\x8A'=>'Š', '\x8B'=>'‹',
'\x8C'=>'Œ', '\x8D'=>'?', '\x8E'=>'Ž', '\x8F'=>'?',
'\x90'=>'?', '\x91'=>'‘', '\x92'=>'’', '\x93'=>'“',
'\x94'=>'”', '\x95'=>'•', '\x96'=>'–', '\x97'=>'—',
'\x98'=>'˜', '\x99'=>'™', '\x9A'=>'š', '\x9B'=>'›',
'\x9C'=>'œ', '\x9D'=>'?', '\x9E'=>'ž', '\x9F'=>'Ÿ'
);
GLOBALS['xmlrpcstr']['unknown_method']='Unknown method';
GLOBALS['xmlrpcstr']['invalid_return']='Invalid return payload: enable debugging to examine incoming payload';
GLOBALS['xmlrpcstr']['incorrect_params']='Incorrect parameters passed to method';
GLOBALS['xmlrpcstr']['introspect_unknown']="Can't introspect: method unknown";
GLOBALS['xmlrpcstr']['http_error']="Didn't receive 200 OK from remote server.";
GLOBALS['xmlrpcstr']['no_data']='No data received from server.';
GLOBALS['xmlrpcstr']['no_ssl']='No SSL support compiled in.';
GLOBALS['xmlrpcstr']['curl_fail']='CURL error';
GLOBALS['xmlrpcstr']['invalid_request']='Invalid request payload';
GLOBALS['xmlrpcstr']['no_curl']='No CURL support compiled in.';
GLOBALS['xmlrpcstr']['server_error']='Internal server error';
GLOBALS['xmlrpcstr']['multicall_error']='Received from server invalid multicall response';
GLOBALS['xmlrpcstr']['multicall_notstruct'] = 'system.multicall expected struct';
GLOBALS['xmlrpcstr']['multicall_nomethod'] = 'missing methodName';
GLOBALS['xmlrpcstr']['multicall_notstring'] = 'methodName is not a string';
GLOBALS['xmlrpcstr']['multicall_recursion'] = 'recursive system.multicall forbidden';
GLOBALS['xmlrpcstr']['multicall_noparams'] = 'missing params';
GLOBALS['xmlrpcstr']['multicall_notarray'] = 'params is not an array';
GLOBALS['xmlrpcstr']['cannot_decompress']='Received from server compressed HTTP and cannot decompress';
GLOBALS['xmlrpcstr']['decompress_fail']='Received from server invalid compressed HTTP';
GLOBALS['xmlrpcstr']['dechunk_fail']='Received from server invalid chunked HTTP';
GLOBALS['xmlrpcstr']['server_cannot_decompress']='Received from client compressed HTTP request and cannot decompress';
GLOBALS['xmlrpcstr']['server_decompress_fail']='Received from client invalid compressed HTTP request';
// The charset encoding used by the server for received messages and
// by the client for received responses when received charset cannot be determined
// or is not supported
GLOBALS['xmlrpc_internalencoding']='ISO-8859-1';
GLOBALS['xmlrpcVersion']='2.1';
// let user errors start at 800
GLOBALS['xmlrpcerrxml']=100;
// formulate backslashes for escaping regexp
// Not in use anymore since 2.0. Shall we remove it?
@deprecated
GLOBALS['_xh']=null;
Convert a string to the correct XML representation in a target charset
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, an option is to 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.
Note that in the std case we are not sending a charset encoding mime type
along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
@todo do a bit of basic benchmarking (strtr vs. str_replace)
@todo make usage of iconv() or recode_string() or mb_string() where available
function xmlrpc_encode_entitites(src_encoding='', src_encoding == '')
{
// lame, but we know no better...
GLOBALS['xmlrpc_internalencoding'];
}
switch(strtoupper(dest_encoding))
{
case 'ISO-8859-1_':
case 'ISO-8859-1_US-ASCII':
data);
GLOBALS['xml_iso88591_Entities']['in'], escaped_data);
break;
case 'ISO-8859-1_UTF-8':
data);
escaped_data);
break;
case 'ISO-8859-1_ISO-8859-1':
case 'US-ASCII_US-ASCII':
case 'US-ASCII_UTF-8':
case 'US-ASCII_':
case 'US-ASCII_ISO-8859-1':
case 'UTF-8_UTF-8':
data);
break;
case 'UTF-8_':
case 'UTF-8_US-ASCII':
case 'UTF-8_ISO-8859-1':
// NB: this will choke on invalid UTF-8, going most likely beyond EOF
data = (string) ns = strlen (nn = 0; ns; ch = nn];
ch);
//1 7 0bbbbbbb (127)
if ( @todo shall we replace this with a (supposedly) faster str_replace?
switch(ii){
case 34:
escaped_data .= '&';
break;
case 39:
escaped_data .= '<';
break;
case 62:
escaped_data .= ii>>5 == 6)
{
ii & 31);
data[b2 = (ii = (b2;
ii);
ent;
}
//3 16 1110bbbb 10bbbbbb 10bbbbbb
else if (b1 = (ii = ord(nn+1]);
ii & 63);
data[b3 = (ii = (((b2) * 64) + ent = sprintf ('&#%d;', escaped_data .= ii>>3 == 30)
{
ii & 31);
data[b2 = (ii = ord(nn+2]);
ii & 63);
data[b4 = (ii = (((((b2) * 64) + b4;
ii);
ent;
}
}
break;
default:
src_encoding to escaped_data;
}
xml parser handler function for opening element tags
function xmlrpc_se(name, accept_single_vals=false)
{
// if invalid xmlrpc already detected, skip all processing
if (GLOBALS['_xh']['stack']) == 0)
{
if (name != 'METHODCALL' && (
accept_single_vals))
{
GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element';
return;
}
else
{
name);
}
}
else
{
// not top level element: see if parent is OK
GLOBALS['_xh']['stack']);
if (!array_key_exists(GLOBALS['xmlrpc_valid_parents']) || !in_array(GLOBALS['xmlrpc_valid_parents'][GLOBALS['_xh']['isf'] = 2;
name cannot be child of name)
{
// optimize for speed switch cases: most common cases first
case 'VALUE':
@todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
GLOBALS['_xh']['ac']='';
GLOBALS['_xh']['php_class']=null;
break;
case 'I4':
case 'INT':
case 'STRING':
case 'BOOLEAN':
case 'DOUBLE':
case 'DATETIME.ISO8601':
case 'BASE64':
if (GLOBALS['_xh']['isf'] = 2;
name element following a {GLOBALS['_xh']['ac']=''; // reset the accumulator
break;
case 'STRUCT':
case 'ARRAY':
if (GLOBALS['_xh']['isf'] = 2;
name element following a {cur_val = array();
cur_val['type'] = attrs['PHP_CLASS']))
{
attrs['PHP_CLASS'];
}
cur_val;
GLOBALS['_xh']['vt']!='data')
{
//two data elements inside a value: an error occurred!
GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element";
return;
}
case 'METHODCALL':
case 'METHODRESPONSE':
case 'PARAMS':
// valid elements that add little to processing
break;
case 'METHODNAME':
case 'NAME':
@todo we could check for 2 NAME elements inside a MEMBER element
GLOBALS['_xh']['isf']=1;
break;
case 'MEMBER':
GLOBALS['_xh']['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on
//GLOBALS['_xh']['vt']=null;
break;
case 'NIL':
// we do not support the <NIL/> extension yet, so
// drop through intentionally
default:
INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element GLOBALS['_xh']['stack'][] = name!='VALUE')
{
parser, attrs)
{
xmlrpc_se(name, parser, rebuild_xmlrpcvals = true)
{
if (name == curr_elem = array_pop(name)
{
case 'VALUE':
// This if() detects if no scalar was inside <VALUE></VALUE>
if (GLOBALS['_xh']['value']=GLOBALS['_xh']['vt']=rebuild_xmlrpcvals)
{
// build the xmlrpc val out of the data received, and substitute it
GLOBALS['_xh']['value'], GLOBALS['_xh']['php_class']))
GLOBALS['_xh']['php_class'];
// check if we are inside an array or struct:
// if value just built is inside an array, let's move it into array on the stack
GLOBALS['_xh']['valuestack']);
if (GLOBALS['_xh']['valuestack'][GLOBALS['_xh']['valuestack'][temp;
}
else
{
temp;
}
}
else
{
@todo this needs to treat correctly php-serialized objects,
since std deserializing is done by php_xmlrpc_decode,
which we will not be calling...
if (isset(vscount = count(vscount && vscount-1]['type']=='ARRAY')
{
vscount-1]['values'][] = GLOBALS['_xh']['vt']=strtolower(name=='STRING')
{
GLOBALS['_xh']['ac'];
}
elseif (/', GLOBALS['_xh']['ac']);
}
GLOBALS['xmlrpcDateTime'];
GLOBALS['_xh']['ac'];
}
elseif (GLOBALS['_xh']['value']=base64_decode(name=='BOOLEAN')
{
// special case here: we translate boolean 1 or 0 into PHP
// constants true or false.
// Strings 'true' and 'false' are accepted, even though the
// spec never mentions them (see eg. Blogger api docs)
// NB: this simple checks helps a lot sanitizing input, ie no
// security problems around here
if (GLOBALS['_xh']['ac'], 'true') == 0)
{
GLOBALS['_xh']['ac']!='0' && strcasecmp(parser]['ac'], 'false') != 0)
error_log('XML-RPC: invalid value received in BOOLEAN: '.GLOBALS['_xh']['value']=false;
}
}
elseif (/', GLOBALS['_xh']['ac']);
GLOBALS['_xh']['value']=(double)/', GLOBALS['_xh']['ac']);
GLOBALS['_xh']['value']=(int)GLOBALS['_xh']['ac']=''; // is this necessary?
GLOBALS['_xh']['valuestack'][count(GLOBALS['_xh']['ac'];
break;
case 'MEMBER':
//GLOBALS['_xh']['vt'])
{
GLOBALS['_xh']['valuestack']);
vscount-1]['values'][vscount-1]['name']] = GLOBALS['_xh']['ac']=''; // is this necessary?
curr_val = array_pop(GLOBALS['_xh']['value'] = GLOBALS['_xh']['vt']=strtolower(curr_val['php_class']))
{
curr_val['php_class'];
}
break;
case 'PARAM':
// add to array of params the current value,
// unless no VALUE was found
if (GLOBALS['_xh']['params'][]=GLOBALS['_xh']['pt'][]=GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', parser, parser, parser, GLOBALS['_xh']['isf'] < 2)
{
// "lookforvalue==3" means that we've found an entire value
// and should discard any further character data
if(GLOBALS['_xh']['lv']==1)
//{
// if we've found text and we're just in a <value> then
// say we've found a value
//GLOBALS['_xh']['ac']))
//{
// GLOBALS['_xh']['ac'].=parser, GLOBALS['_xh']['isf'] < 2)
{
if(substr(data, -1, 1) == ';')
{
// G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2
//if(GLOBALS['_xh']['lv']=2;
//}
data;
}
}
return true;
}
class xmlrpc_client
{
var server;
var method='http';
var errstr;
var username='';
var authtype=1;
var certpass='';
var cacertdir='';
var keypass='';
var verifyhost=1;
var proxy='';
var proxy_user='';
var proxy_authtype=1;
var
List of http compression methods accepted by the client for responses.
NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
in those cases it will be up to CURL to decide the compression methods
it supports. You might check for the presence of 'zlib' in the output of
curl_version() to determine wheter compression is supported or not
var accepted_compression = array();
Name of compression scheme to be used for sending requests.
Either null, gzip or deflate
var
CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
http://curl.haxx.se/docs/faq.html#7.3)
*/
var xmlrpc_curl_handle = null;
Wheter to use persistent connections for http 1.1 and https
var accepted_charset_encodings = array();
Charset encoding to be used in serializing request. NULL = use ASCII
var return_type = 'xmlrpcvals';
parameter: string server the server name / ip address
parameter: integer method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed
function xmlrpc_client(server='', method='')
{
// allow user to specify all params in server == '' and method == '')
{
path);
parts['host'];
parts['path'];
if(isset(path .= '?'.parts['fragment']))
{
parts['fragment'];
}
if(isset(port = parts['scheme']))
{
parts['scheme'];
}
if(isset(this->username = parts['pass']))
{
parts['pass'];
}
}
if(path[0] != '/')
{
path;
}
else
{
path;
}
server;
if(this->port=method != '')
{
method;
}
// if ZLIB is enabled, let the client by default accept compressed responses
if(function_exists('gzinflate') || (
function_exists('curl_init') && ((info) && strpos(info['libz_version'])))
))
{
this->keepalive = true;
}
// by default the xml parser can support these 3 charset encodings
Enables/disables the echoing to screen of the xmlrpc responses received
parameter: integer debug values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
@access public
function setDebug(this->debug=
Add some http BASIC AUTH credentials, used by the client to authenticate
parameter: string u username
parameter: string t auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC (basic auth)
@access public
function setCredentials(p, this->username=this->password=this->authtype=
Add a client-side https certificate
parameter: string cert
parameter: string
function setCertificate(cert, this->cert = this->certpass =
Add a CA certificate to verify server with (see man page about
CURLOPT_CAINFO for more details
parameter: string cacert certificate file name (or dir holding certificates)
parameter: bool
function setCaCertificate(cacert, is_dir)
{
cacert;
}
else
{
cacert;
}
}
Set attributes for SSL communication: private SSL key
parameter: string keypass The secret password needed to use the private SSL key
@access public
NB: does not work in older php/curl installs
Thanks to Daniel Convissor
function setKey(keypass)
{
key;
keypass;
}
Set attributes for SSL communication: verify server certificate
parameter: bool
function setSSLVerifyPeer(i)
{
i;
}
Set attributes for SSL communication: verify match of server cert w. hostname
parameter: int
function setSSLVerifyHost(i)
{
i;
}
Set proxy info
parameter: string proxyport Defaults to 8080 for HTTP and 443 for HTTPS
parameter: string proxypassword Leave blank if proxy has public access
parameter: int
function setProxy(proxyhost, proxyusername = '', proxyauthtype = 1)
{
proxyhost;
proxyport;
proxyusername;
proxypassword;
proxyauthtype;
}
Enables/disables reception of compressed xmlrpc responses.
Note that enabling reception of compressed responses merely adds some standard
http headers to xmlrpc requests. It is up to the xmlrpc server to return
compressed responses when receiving such requests.
parameter: string
function setAcceptedCompression(compmethod)
{
if (this->accepted_compression = array('gzip', 'deflate');
else
compmethod);
}
Enables/disables http compression of xmlrpc request.
Take care when sending compressed requests: servers might not support them
(and automatic fallback to uncompressed requests is not yet implemented)
parameter: string
function setRequestCompression(compmethod)
{
compmethod;
}
Adds a cookie to list of cookies that will be sent to server.
NB: setting any param but name and value will turn the cookie into a 'version 1' cookie:
do not do it unless you know what you are doing
parameter: string value
parameter: string domain
parameter: int
function setCookie(name, path='', port=null)
{
name]['value'] = urlencode(path || port)
{
name]['path'] = this->cookies[domain;
name]['port'] = this->cookies[this->cookies[
Send an xmlrpc request
parameter: mixed msg The message object, or an array of messages for using multicall, or the complete xml representation of a request
parameter: integer method if left unspecified, the http protocol chosen during creation of the object will be used
returns: xmlrpcresp
@access public
function& send(timeout=0, method == '')
{
this->method;
}
if(is_array(msg is an array of xmlrpcmsg's
this->multicall(timeout, r;
}
elseif(is_string(n =& new xmlrpcmsg('');
msg;
n;
}
// where msg is an xmlrpcmsg
this->debug;
if(r =& msg,
this->port,
this->username,
this->authtype,
this->certpass,
this->cacertdir,
this->proxyport,
this->proxy_pass,
this->keepalive,
this->keypass
);
}
elseif(r =& msg,
this->port,
this->username,
this->authtype,
null,
null,
null,
null,
this->proxyport,
this->proxy_pass,
this->keepalive
);
}
else
{
this->sendPayloadHTTP10(
this->server,
timeout,
this->password,
this->proxy,
this->proxy_user,
this->proxy_authtype
);
}
return
@access private
function &sendPayloadHTTP10(msg, port, username='', authtype=1, proxyport=0, proxypassword='', port==0)
{
msg->payload))
{
this->request_charset_encoding);
}
msg->payload;
// Deflate request body and set appropriate request headers
if(function_exists('gzdeflate') && (this->request_compression == 'deflate'))
{
if(a = @gzencode(a)
{
a;
a = @gzcompress(a)
{
a;
encoding_hdr = '';
}
// thanks to Grant Rauscher <grant7@firstworld.net> for this
username!='')
{
username . ':' . authtype != 1)
{
error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported with HTTP 1.0');
}
}
this->accepted_compression) && count(accepted_encoding = 'Accept-Encoding: ' . implode(', ', proxy_credentials = '';
if(proxyport == 0)
{
connectserver = connectport = uri = 'http://'.$server.':'.$port.$this->path;
if(proxyauthtype != 1)
{
error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported with HTTP 1.0');
}
proxyusername.':'.connectserver = connectport = uri = cookieheader='';
foreach (name => cookie['version'])
{
Version="' . cookieheader .= cookie['value'] . '";';
if (cookieheader .= ' cookie['path'] . '";';
if (cookieheader .= ' cookie['domain'] . '";';
if (cookieheader .= ' cookie['domain'] . '";';
cookieheader, 0, -1) . "\r\n";
}
else
{
name . '=' . op= 'POST ' . GLOBALS['xmlrpcName'] . ' ' . server . ':' . credentials .
accepted_encoding .
this->accepted_charset_encodings) . "\r\n" .
msg->content_type . "\r\nContent-Length: " .
strlen(payload;
if(op) . "\n---END---\n</PRE>";
// let the client see this now in case http times out...
flush();
}
if(fp=@fsockopen(connectport, this->errstr, fp=@fsockopen(connectport, this->errstr);
}
if(timeout>0 && function_exists('stream_set_timeout'))
{
stream_set_timeout(timeout);
}
}
else
{
this->errstr;
GLOBALS['xmlrpcerr']['http_error'], this->errno . ')');
return fp, op)))
{
r=&new xmlrpcresp(0, this->errstr);
return this->errstr = '';
}
// G. Giunta 2005/10/24: close socket before parsing.
// should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
data=fread(data === FALSE?
// as per the manual, it signals an error
data;
}
fclose(r =& ipd, false, r;
}
@access private
function &sendPayloadHTTPS(server, timeout=0, password='', cert='',cacert='', proxyhost='', proxyusername='', proxyauthtype=1,
key='', r =& msg, port, username,
authtype, certpass, cacertdir, proxyport,
proxypassword, keepalive, keypass);
return
Contributed by Justin Miller <justin@voxel.net>
Requires curl to be built into PHP
NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
@access private
function &sendPayloadCURL(msg, port, username='',
authtype=1, certpass='', cacertdir='',
proxyport=0, proxypassword='', method='https',
key='', this->errstr='CURL unavailable on this install';
GLOBALS['xmlrpcerr']['no_curl'], r;
}
if(info = curl_version()) &&
((is_string(info, 'OpenSSL') === null) || (is_array(info['ssl_version']))))
{
r=&new xmlrpcresp(0, GLOBALS['xmlrpcstr']['no_ssl']);
return port == 0)
{
if(port = 80;
}
else
{
msg->payload))
{
this->request_charset_encoding);
}
// Deflate request body and set appropriate request headers
msg->payload;
if(function_exists('gzdeflate') && (this->request_compression == 'deflate'))
{
if(a = @gzencode(a)
{
a;
a = @gzcompress(a)
{
a;
encoding_hdr = '';
}
if(payload) . "\n---END---\n</PRE>";
// let the client see this now in case http times out...
flush();
}
if(!this->xmlrpc_curl_handle)
{
method . '://' . port . keepalive)
{
curl;
}
}
else
{
this->xmlrpc_curl_handle;
}
// results into variable
curl_setopt(this->debug)
{
curl_setopt(curl, CURLOPT_USERAGENT, GLOBALS['xmlrpcVersion']);
// required for XMLRPC: post the data
curl_setopt(curl, CURLOPT_POSTFIELDS, curl, CURLOPT_HEADER, 1);
// will only work with PHP >= 5.0
// NB: if we set an empty string, CURL will add http header indicating
// ALL methods it is supporting. This is possibly a better option than
// letting the user tell what curl can / cannot do...
if(is_array(this->accepted_compression))
{
//curl_setopt(this->accepted_compression));
// empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
if (count(curl, CURLOPT_ENCODING, curl, CURLOPT_ENCODING, '');
}
// extra headers
msg->content_type , 'Accept-Charset: ' . implode(',', keepalive)
{
encoding_hdr)
{
encoding_hdr;
}
curl_setopt(headers);
// timeout is borked
if(curl, CURLOPT_TIMEOUT, timeout - 1);
}
if(password)
{
curl_setopt(username.':'.curl, CURLOPT_HTTPAUTH, authtype != 1)
{
error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported by the current PHP/curl install');
}
}
if(cert)
{
curl_setopt(cert);
}
// set cert password
if(curl, CURLOPT_SSLCERTPASSWD, curl, CURLOPT_SSL_VERIFYPEER, cacert)
{
curl_setopt(cacert);
}
if(curl, CURLOPT_CAPATH, key)
{
curl_setopt(key);
}
// set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
if(curl, CURLOPT_SSLKEYPASSWD, curl, CURLOPT_SSL_VERIFYHOST, proxyhost)
{
if(proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080
}
curl_setopt(proxyhost.':'.curl, CURLOPT_PROXYPORT,proxyusername)
{
curl_setopt(proxyusername.':'.curl, CURLOPT_PROXYAUTH, proxyauthtype != 1)
{
error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported by the current PHP/curl install');
}
}
}
// NB: should we build cookie http headers by hand rather than let CURL do it?
// the following code does not honour 'expires', 'path' and 'domain' cookie attributes
// set to clint obj the the user...
if (count(cookieheader = '';
foreach (name => cookieheader .= cookie['value'] . ', ';
}
curl_setopt(cookieheader, 0, -2));
}
curl);
if(!this->errstr='no response';
GLOBALS['xmlrpcerr']['curl_fail'], curl));
if(!curl);
}
}
else
{
if(!curl);
}
msg->parseResponse(this->return_type);
}
return
Send an array of request messages and return an array of responses.
Unless this->no_multicall has been set to true, it will try first
to use one single xmlrpc call to server method system.multicall, and
revert to sending many successive calls in case of failure.
This failure is also stored in msgs an array of xmlrpcmsg objects
parameter: integer method the http protocol variant to be used
parameter: boolen fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted
returns: array
@access public
function multicall(timeout=0, fallback=true)
{
if(!results = msgs, method);
if(is_array(results;
}
else
{
// either system.multicall is unsupported by server,
// or call failed for some other reason.
if (this->no_multicall = true;
}
else
{
if (is_a(result = result =& new xmlrpcresp(0, GLOBALS['xmlrpcstr']['multicall_error']);
}
}
}
}
else
{
// override fallback, in case careless user tries to do two
// opposite things at the same time
results = array();
if (msgs as results[] =& msg, method);
}
}
else
{
// user does NOT want to fallback on many single calls:
// since we should always return an array of responses,
// return an array with the same error repeated n times
foreach(msg)
{
result;
}
}
return
Attempt to boxcar msgs via system.multicall.
Returns either an array of xmlrpcreponses, an xmlrpc error response
or false (when recived response does not respect valid multiccall syntax)
@access private
function _try_multicall(timeout, calls = array();
foreach(msg)
{
msg->method(),'string');
msg->getNumParams();
i = 0; numParams; params[msg->getParam(call['params'] =& new xmlrpcval(calls[] =& new xmlrpcval(multicall =& new xmlrpcmsg('system.multicall');
calls, 'array'));
// Attempt RPC call
this->send(timeout, result->faultCode() != 0)
{
// call to system.multicall failed
return rets = this->return_type == 'xml')
{
return this->return_type == 'phpvals')
{
@todo test this code branch...
result->value();
if(!is_array(numRets = count(numRets != count(response = array();
for(i < i++)
{
rets[val)) {
return false;
}
switch(count(val[0]))
{
return false; // Bad value
}
// Normal return value
i] =& new xmlrpcresp(code = @code))
{
return false;
}
val['faultString'];
if(!is_string(response[code, response;
}
else // return type == 'xmlrpcvals'
{
result->value();
if(numRets = numRets != count(response = array();
for(i < i++)
{
rets->arraymem(val->kindOf())
{
case 'array':
if(response[val->arraymem(0));
break;
case 'struct':
val->structmem('faultCode');
if(code->scalartyp() != 'int')
{
return false;
}
val->structmem('faultString');
if(str->scalartyp() != 'string')
{
return false;
}
i] =& new xmlrpcresp(0, str->scalarval());
break;
default:
return false;
}
}
return val = 0;
var errno = 0;
var payload;
var _cookies = array();
var raw_data = '';
parameter: mixed fcode set it to anything but 0 to create an error response
parameter: string valtyp either 'xmlrpcvals', 'phpvals' or 'xml'
@todo add check that fcode /
function xmlrpcresp(val, fstr = '', fcode != 0)
{
// error response
fcode;
fstr;
//fstr); // XXX: encoding probably shouldn't be done here; fix later.
}
else
{
// successful response
val;
if (this->val) && is_a(this->valtyp = 'xmlrpcvals';
}
else if (is_string(this->valtyp = 'xml';
}
else
{
this->valtyp =
Returns the error code of the response.
returns: integer the error code of this response (0 for not-error responses)
@access public
function faultCode()
{
return this->errno;
}
Returns the error code of the response.
returns: string the error string of this response ('' for not-error responses)
@access public
function faultString()
{
return
Returns the value received by the server.
returns: mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects
@access public
function value()
{
return this->val;
}
Returns an array with the cookies received from the server.
Array has the form: val, val1, val2, ...)
with attributes being e.g. 'expires', 'path', domain'.
NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past)
are still present in the array. It is up to the user-defined code to decide
how to use the received cookies, and wheter they have to be sent back with the next
request to the server (using xmlrpc_client::setCookie) or not
returns: array array of cookies received from the server
@access public
function cookies()
{
return
Returns xml representation of the response. XML prologue not included
parameter: string charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
returns: string the xml representation of the response
@access public
function serialize(charset_encoding != '')
charset_encoding;
else
result = "<methodResponse>\n";
if(result .= "<fault>\n" .
"<value>\n<struct><member><name>faultCode</name>\n<value><int>" . this->errstr, charset_encoding) . "</string></value>\n</member>\n" .
"</struct>\n</value>\n</fault>";
}
else
{
if(!is_object(this->val, 'xmlrpcval'))
{
if (is_string(this->valtyp == 'xml')
{
this->val .
"</param>\n</params>";
}
else
{
@todo try to build something serializable?
die('cannot serialize xmlrpcresp objects whose content is native php values');
}
}
else
{
this->val->serialize(result .= "\n</methodResponse>";
result;
return payload;
var params=array();
var content_type = 'text/xml';
parameter: string pars array of parameters to be paased to the method (xmlrpcval objects)
function xmlrpcmsg(pars=0)
{
meth;
if(is_array(pars)>0)
{
for(i<count(i++)
{
pars[
@access private
function xml_header(charset_encoding='')
{
if (charset_encoding\" ?" . ">\n<methodCall>\n";
}
else
{
return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
}
}
@access private
function xml_footer()
{
return '</methodCall>';
}
@access private
function kindOf()
{
return 'msg';
}
@access private
function createPayload(charset_encoding != '')
charset_encoding;
else
this->payload=charset_encoding);
this->methodname . "</methodName>\n";
i=0; this->params); p=i];
p->serialize(this->payload.="</params>\n";
this->xml_footer();
}
Gets/sets the xmlrpc method to be invoked
parameter: string
function method(meth='')
{
if(this->methodname=this->methodname;
}
Returns xml representation of the message. XML prologue included
returns: string the xml representation of the message, xml prologue included
@access public
function serialize(this->createPayload(this->payload;
}
Add a parameter to the list of parameters to be used upon method invocation
parameter: xmlrpcval
function addParam(par)
{
// add check: do not add to self params which are not xmlrpcvals
if(is_object(par, 'xmlrpcval'))
{
par;
return true;
}
else
{
return false;
}
}
Returns the nth parameter in the message. The index zero-based.
parameter: integer
function getParam(i) { return i]; }
Returns the number of parameters in the messge.
returns: integer the number of parameters currently set
@access public
function getNumParams() { return count(
Given an open file handle, read all data available and parse it as axmlrpc response.
NB: the file handle is not closed by this function.
@access public
returns: xmlrpcresp
@todo add 2nd & 3rd param to be passed to ParseResponse() ???
function &parseResponseFile(fp)
{
data=fread(ipd.=fp);
this->parseResponse(r;
}
Parses HTTP headers and separates them from data.
@access private
function &parseResponseHeaders(&headers_processed=false)
{
// Strip HTTP 1.1 100 Continue header if present
while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', pos = strpos(pos && !is_int(data = substr(pos);
}
if(!preg_match('/^HTTP\/[0-9.]+ 200 /', errstr= substr(data, "\n")-1);
error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .r=&new xmlrpcresp(0, GLOBALS['xmlrpcstr']['http_error']. ' (' . r;
}
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)
data,"\r\n\r\n");
if(pos))
{
pos+4;
}
else
{
data,"\n\n");
if(pos))
{
pos+2;
}
else
{
// No separation between response headers and body: fault?
ar = split("\r?\n", trim(substr(pos)));
while(list(,ar))
{
// take care of multi-line headers and cookies
line,2);
if(count(header_name = strtolower(trim( @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 == 'set-cookie' || header_name == 'set-cookie2')
{
// version 2 cookies:
// there could be many cookies on one line, comma separated
arr[1]);
}
else
{
arr[1]);
}
foreach (cookie)
{
// glue together all received cookies, using a comma to separate them
// (same as php does with getallheaders())
if (isset(header_name]))
header_name] .= ', ' . trim(GLOBALS['_xh']['headers'][cookie);
// parse cookie attributes, in case user wants to coorectly honour then
// feature creep: only allow rfc-compliant cookie attributes?
cookie);
foreach (pos => val = explode('=', tag = trim(val = trim(@pos == 0)
{
tag;
tag] = array();
cookiename]['value'] = urldecode(GLOBALS['_xh']['cookies'][tag] = GLOBALS['_xh']['headers'][arr[1]);
}
}
elseif(isset(GLOBALS['_xh']['headers'][line);
}
}
data, this->debug && count(GLOBALS['_xh']['headers'] as value)
{
print "HEADER: value\n";
}
foreach(header => header={headers_processed)
{
// Decode chunked encoding sent by http 1.1 servers
if(isset(GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
{
if(!data))
{
error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server');
GLOBALS['xmlrpcerr']['dechunk_fail'], r;
}
}
// Decode gzip-compressed stuff
// code shamelessly inspired from nusoap library by Dietrich Ayala
if(isset(GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && data))
{
degzdata;
if(data)." chars]---\n" . htmlentities(GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && data, 10)))
{
degzdata;
if(data)." chars]---\n" . htmlentities(r =& new xmlrpcresp(0, GLOBALS['xmlrpcstr']['decompress_fail']);
return r =& new xmlrpcresp(0, GLOBALS['xmlrpcstr']['cannot_decompress']);
return r = null;
r;
return
Parse the xmlrpc response containeed in the string data and return an xmlrpcresp object.
parameter: string headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding
parameter: string
function &parseResponse(data='', return_type='xmlrpcvals')
{
if(data) . "\n---END---\n</PRE>";
data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
if (start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
data, '-->', comments = substr(start, end-start);
print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode(data == '')
{
error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.');
GLOBALS['xmlrpcerr']['no_data'], r;
}
raw_data = data, 0, 4) == 'HTTP')
{
this->parseResponseHeaders(headers_processed);
if (r->raw_data = r;
}
}
else
{
GLOBALS['_xh']['cookies'] = array();
}
// be tolerant of extra whitespace in response body
data);
@todo return an error msg if bd = false;
// Poor man's version of strrpos for php 4...
data, '</methodResponse>');
while(pos))
{
pos+17;
data, '</methodResponse>', bd)
{
data, 0, return_type == 'xml')
{
data, 0, '', 'xml');
GLOBALS['_xh']['headers'];
GLOBALS['_xh']['cookies'];
raw_data;
return resp_encoding = guess_encoding(@data);
GLOBALS['_xh']['qt']=''; //unused...
GLOBALS['_xh']['valuestack'] = array();
GLOBALS['_xh']['isf_reason']='';
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);
GLOBALS['xmlrpc_defencoding'];
}
resp_encoding);
xml_parser_set_option(parser, XML_OPTION_TARGET_ENCODING, return_type == 'phpvals')
{
xml_set_element_handler(parser, 'xmlrpc_se', 'xmlrpc_ee');
}
xml_set_character_data_handler(parser, 'xmlrpc_dh');
// first error check: xml not well formed
if(!xml_parse(data, count(parser)) == 1)
{
errstr = sprintf('XML error: %s at line %d, column %d',
xml_error_string(xml_get_error_code(parser), xml_get_current_column_number(errstr);
GLOBALS['xmlrpcerr']['invalid_return'], errstr.')');
xml_parser_free(this->debug)
{
print r->hdrs = r->_cookies = r->raw_data = r;
}
xml_parser_free(GLOBALS['_xh']['isf'] > 1)
{
if (r =& new xmlrpcresp(0, GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . return_type == 'xmlrpcvals' && !is_object(r=&new xmlrpcresp(0, GLOBALS['xmlrpcstr']['invalid_return']);
}
else
{
if (GLOBALS['_xh']['value']);
print "\n---END---</PRE>";
}
// note that using =& will raise an error if v =& GLOBALS['_xh']['isf'])
{
@todo we should test here if server sent an int and a string,
and/or coerce them into such...
if (errno_v = errstr_v = errno = errstr = errno = errstr = errno == 0)
{
// FAULT returned, errno needs to reflect that
r =& new xmlrpcresp(0, errstr);
}
else
{
v, 0, '', r->hdrs = r->_cookies = r->raw_data = r;
}
}
class xmlrpcval
{
var mytype=0;
var val
parameter: string
function xmlrpcval(val=-1, @todo: optimization creep - do not call addXX, do it all inline.
downside: booleans will not be coerced anymore
if(val!==-1 || type)
{
case '':
this->me['string']=this->mytype=1;
type]=this->mytype=2;
val;
break;
case 'struct':
this->me['struct']=type)");
}
/*if(type='string';
}
if(type]==1)
{
val,GLOBALS['xmlrpcTypes'][this->addArray(GLOBALS['xmlrpcTypes'][this->addStruct(val
parameter: string val, typeof=@type];
if(type)");
return 0;
}
// coerce booleans into correct values
// NB: we should iether do it for datetimes, integers and doubles, too,
// or just plain remove this check, implemnted on booleans only...
if(GLOBALS['xmlrpcBoolean'])
{
if(strcasecmp(val==1 || (val,'false')))
{
val=false;
}
}
switch(ar=ar[]=&new xmlrpcval(type);
//ar;
// Faster (?) avoid all the costly array-copy-by-val done here...
val, this->me[val;
typeof;
return 1;
}
}
Add an array of xmlrpcval objects to an xmlrpcval
parameter: array vals to be an array of xmlrpcvals?
function addArray(this->mytype==0)
{
GLOBALS['xmlrpcTypes']['array'];
vals;
return 1;
}
elseif(this->me['array'] = array_merge(vals);
return 1;
}
else
{
error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' .
Add an array of named xmlrpcval objects to an xmlrpcval
parameter: array vals
returns: int 1 or 0 on failure
@access public
@todo add some checking for
function addStruct(vals)
{
if(this->mytype=this->me['struct']=this->mytype==3)
{
// we're adding to a struct here
this->me['struct'], this->kindOf() . ']');
return 0;
}
}
// poor man's version of print_r ???
// DEPRECATED!
function dump(ar as val)
{
echo "val<br />";
if(key2, val))
{
echo "-- val2<br />";
}
}
}
}
Returns a string containing "struct", "array" or "scalar" describing the base type of the value
returns: string
@access public
function kindOf()
{
switch(
@access private
function serializedata(typ, charset_encoding='')
{
GLOBALS['xmlrpcTypes'][typ)
{
case rs.="<val) . "</GLOBALS['xmlrpcBoolean']:
{typ}>" . ({typ}>";
break;
case rs.="<val, charset_encoding). "</GLOBALS['xmlrpcInt']:
case rs.="<val."</GLOBALS['xmlrpcDouble']:
{typ}>".(double){typ}>";
break;
case rs.="<nil/>";
break;
default:
// no standard type value should arrive here, but provide a possibility
// for xmlrpcvals of unknown type...
{typ}>{typ}>";
}
break;
case 3:
// struct
if (rs.='<struct php_class="' . rs.="<struct>\n";
}
foreach(key2 => rs.='<member><name>'.xmlrpc_encode_entitites(GLOBALS['xmlrpc_internalencoding'], rs.=val2);
val2->serialize(rs.="</member>\n";
}
rs.="<array>\n<data>\n";
for(i<count(i++)
{
//this->serializeval(i]);
val[charset_encoding);
}
rs;
}
Returns xml representation of the value. XML prologue not included
parameter: string
function serialize(charset_encoding='')
{
// add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
//if (is_object(o) == 'xmlrpcval' || is_subclass_of(this->me);
list(val) = each(this->serializedata(val, o)
{
// add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
//if (is_object(o) == 'xmlrpcval' || is_subclass_of(ar=ar);
list(val) = each(this->serializedata(val) . "</value>\n";
//}
}
Checks wheter a struct member with a given name is present.
Works only on xmlrpcvals of type struct.
parameter: string
function structmemexists(m)
{
return array_key_exists(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
parameter: string
function structmem(m)
{
return m];
}
Reset internal pointer for xmlrpcvals of type struct.
@access public
function structreset()
{
reset(
Return next member element for xmlrpcvals of type struct.
returns: xmlrpcval
@access public
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(a,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);
while(list(cont) = @each(b[cont->scalarval();
}
}
// add support for structures directly encoding php objects
if(is_object(t = get_object_vars(t);
while(list(cont) = @each(t[cont->scalarval();
}
@reset(id,t))
{
@id = b;
}
Returns the value of a scalar xmlrpcval
returns: mixed
@access public
function scalarval()
{
reset(b)=each(b;
}
Returns the type of the xmlrpcval.
For integers, 'int' is always returned in place of 'i4'
returns: string
@access public
function scalartyp()
{
reset(a,)=each(a==a=a;
}
Returns the m-th member of an xmlrpcval of struct type
parameter: integer
function arraymem(m)
{
return m];
}
Returns the number of members in an xmlrpcval of array type
returns: integer
@access public
function arraysize()
{
return count(
Returns the number of members in an xmlrpcval of struct type
returns: integer
@access public
function structsize()
{
return count(this->me['struct']);
}
}
// date helpers
Given a timestamp, return the corresponding 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
timet (timestamp)
parameter: int
function iso8601_encode(timet, utc)
{
timet);
}
else
{
if(function_exists('gmstrftime'))
{
// gmstrftime doesn't exist in some versions
// of PHP
timet);
}
else
{
timet-date('Z'));
}
}
return
Given an ISO8601 date string, return a timet in the localtime, or UTC
parameter: string idate
parameter: int
function iso8601_decode(idate, t=0;
if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', regs))
{
if(t=gmmktime(regs[5], regs[2], regs[1]);
}
else
{
regs[4], regs[6], regs[3], 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.
Given proper options parameter, can rebuild generic php object instances
(provided those have been encoded to xmlrpc format using a corresponding
option in php_xmlrpc_encode())
PLEASE NOTE that rebuilding php objects involves calling their constructor function.
This means that the remote communication end can decide which php code will
get executed on your server, leaving the door possibly open to 'php-injection'
style of attacks (provided you have some classes defined on your server that
might wreak havoc if instances are built outside an appropriate context).
Make sure you trust the remote server/client before eanbling this!
author: Dan Libby (dan@libby.com)
parameter: xmlrpcval options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects
returns: mixed
function php_xmlrpc_decode(options=array())
{
switch(options))
{
reset(typ,xmlrpc_val->me);
switch (xmlrpc_val->scalar = xmlrpc_val->xmlrpc_type = 'datetime';
val);
return xmlrpc_val->scalar = xmlrpc_val->type = xmlrpc_val;
default:
return xmlrpc_val->scalarval();
case 'array':
xmlrpc_val->arraysize();
i = 0; size; arr[] = php_xmlrpc_decode(i), arr;
case 'struct':
@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))
{
xmlrpc_val->_php_class;
while(list(value)=obj->value, obj;
}
else
{
key,xmlrpc_val->structeach())
{
key] = php_xmlrpc_decode(options);
}
return paramcount = arr = array();
for(i < i++)
{
xmlrpc_val->getParam(arr;
}
}
// This constant left here only for historical reasons...
// it was used to decide if we have to define xmlrpc_encode on our own, but
// we do not do it anymore
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)
If given a proper options parameter, php object instances will be encoded
into 'special' xmlrpc values, that can later be decoded into php objects
by calling php_xmlrpc_decode() with a corresponding option
author: Dan Libby (dan@libby.com)
parameter: mixed options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
returns: xmlrpcval
function &php_xmlrpc_encode(options=array())
{
php_val);
switch(options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}php_val))
php_val, xmlrpc_val =& new xmlrpcval(GLOBALS['xmlrpcString']);
break;
case 'integer':
php_val, xmlrpc_val =& new xmlrpcval(GLOBALS['xmlrpcDouble']);
break;
// <G_Giunta_2001-02-29>
// Add support for encoding/decoding of booleans, since they are supported in PHP
case 'boolean':
php_val, tmp = array_diff(array_keys(php_val)-1));
// but execution time skyrockets!
arr = array();
php_val as val)
{
key] =& php_xmlrpc_encode(options);
if(!key !== ko = true;
}
ko)
{
arr, xmlrpc_val =& new xmlrpcval(GLOBALS['xmlrpcArray']);
}
break;
case 'object':
if(is_a(xmlrpc_val = arr = array();
while(list(v) = each(arr[v, xmlrpc_val =& new xmlrpcval(GLOBALS['xmlrpcStruct']);
if (in_array('encode_php_objs', xmlrpc_val->_php_class = get_class(options))
{
GLOBALS['xmlrpcString']);
}
if (in_array('null_extension', xmlrpc_val =& new xmlrpcval('', xmlrpc_val =& new xmlrpcval();
}
break;
case 'resource':
if (in_array('extension_api', xmlrpc_val =& new xmlrpcval((int)GLOBALS['xmlrpcInt']);
}
else
{
xmlrpc_val =& new xmlrpcval();
break;
}
return
Convert the xml representation of a method call, method request or single
xmlrpc value into the appropriate object (a.k.a. deserialize)
parameter: string xml_val
parameter: array
function php_xmlrpc_decode_xml(xml_val, GLOBALS['_xh'] = array();
GLOBALS['_xh']['stack'] = array();
GLOBALS['_xh']['params'] = array();
GLOBALS['_xh']['isf'] = 0;
GLOBALS['_xh']['method'] = false;
@todo 'guestimate' encoding
parser = xml_parser_create();
xml_parser_set_option(parser, XML_OPTION_TARGET_ENCODING, parser, 'xmlrpc_se_any', 'xmlrpc_ee');
xml_set_character_data_handler(parser, 'xmlrpc_dh');
if(!xml_parse(xml_val, 1))
{
parser)),
xml_get_current_line_number(parser));
error_log(parser);
return false;
}
xml_parser_free(GLOBALS['_xh']['isf'] > 1) // test that GLOBALS['_xh']['isf_reason']);
return false;
}
switch (v =& GLOBALS['_xh']['isf'] == 1)
{
v->structmem('faultCode');
v->structmem('faultString');
vc->scalarval(), r =& new xmlrpcresp(r;
case 'methodcall':
GLOBALS['_xh']['method']);
for(i < count(i++)
{
GLOBALS['_xh']['params'][m;
case 'value':
return buffer the string to be decoded
returns: string
function decode_chunked(length = 0;
chunkend = strpos(temp = substr(chunkend);
temp) );
chunkend;
while(chunkend = strpos(chunkstart + chunkend == false)
{
buffer,new .= length += strlen(chunk = substr(chunkstart,chunkend-chunkstart);
// append chunk-data to entity-body
chunk;
// length := length + chunk-size
chunk);
// read chunk-size and crlf
chunkend + 2;
buffer,"\r\n",chunkend == false)
{
break; //just in case we got a broken connection
}
buffer,chunk_size = hexdec( trim(chunkstart = new;
}
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...
parameter: string xmlchunk xml content buffer
parameter: string
function guess_encoding(httpheader='', 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?
httpheader, matches[1]));
}
// 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(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', xmlchunk))
{
return 'UTF-16';
}
elseif(preg_match('/^(\xEF\xBB\xBF)/', xmlchunk, matches[2], 1, -1));
}
// 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(enc = mb_detect_encoding(encoding_prefs);
}
else
{
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 = 'US-'.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 encoding charset to be tested
parameter: mixed encoding, 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 = explode(',', encoding), encoding, validlist as allowed, encoding]))
return true;
return false;
}
}
?>
(C) Æliens
20/2/2008
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-2780434-1";
urchinTracker();
</script>