diff --git a/phpgwapi/inc/class.xmlrpc_client.inc.php b/phpgwapi/inc/class.xmlrpc_client.inc.php new file mode 100644 index 0000000000..c0cf264491 --- /dev/null +++ b/phpgwapi/inc/class.xmlrpc_client.inc.php @@ -0,0 +1,256 @@ +port = $port; + $this->server = $server; + $this->path = $path; + } + + function setDebug($in) + { + if ($in) + { + $this->debug = 1; + } + else + { + $this->debug = 0; + } + } + + function setCredentials($u, $p) + { + $this->username = $u; + $this->password = $p; + } + + function setCertificate($cert, $certpass) + { + $this->cert = $cert; + $this->certpass = $certpass; + } + + function send($msg, $timeout=0, $method='http') + { + /* where msg is an xmlrpcmsg */ + $msg->debug = $this->debug; + + if ($method == 'https') + { + return $this->sendPayloadHTTPS( + $msg, + $this->server, + $this->port, + $timeout, + $this->username, + $this->password, + $this->cert, + $this->certpass + ); + } + else + { + return $this->sendPayloadHTTP10( + $msg, + $this->server, + $this->port, + $timeout, + $this->username, + $this->password + ); + } + } + + function sendPayloadHTTP10($msg, $server, $port, $timeout=0,$username='', $password='') + { + if($port == 0) + { + $port = 80; + } + if($timeout>0) + { + $fp = fsockopen($server, $port, &$this->errno, &$this->errstr, $timeout); + } + else + { + $fp = fsockopen($server, $port, &$this->errno, &$this->errstr); + } + if (!$fp) + { + $r = CreateObject( + 'phpgwapi.xmlrpcresp', + '', + $GLOBALS['xmlrpcerr']['http_error'], + $GLOBALS['xmlrpcstr']['http_error'] + ); + return $r; + } + // Only create the payload if it was not created previously + if(empty($msg->payload)) + { + $msg->createPayload(); + } + + // thanks to Grant Rauscher + // for this + $credentials = ''; + if ($username && $password) + { + $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n"; + } + + $op = 'POST ' . $this->path . " HTTP/1.0\r\nUser-Agent: PHP XMLRPC 2.0\r\n" + . 'Host: '. $this->server . "\r\n" + . 'X-EGW-Server: ' . $this->server . ' ' . "\r\n" + . 'X-EGW-Version: ' . $GLOBALS['egw_info']['server']['versions']['phpgwapi'] . "\r\n" + . $credentials + . "Content-Type: text/xml\r\nContent-Length: " + . strlen($msg->payload) . "\r\n\r\n" + . $msg->payload; + + if (!fputs($fp, $op, strlen($op))) + { + $this->errstr = 'Write error'; + return CreateObject( + 'phpgwapi.xmlrpcresp', + '', + $GLOBALS['xmlrpcerr']['http_error'], + $GLOBALS['xmlrpcstr']['http_error'] + ); + } + $resp = $msg->parseResponseFile($fp); + fclose($fp); + return $resp; + } + + /* contributed by Justin Miller - requires curl to be built into PHP */ + function sendPayloadHTTPS($msg, $server, $port, $timeout=0,$username='', $password='', $cert='',$certpass='') + { + if (!function_exists('curl_init')) + { + return CreateObject( + 'phpgwapi.xmlrpcresp', + '', + $GLOBALS['xmlrpcerr']['no_ssl'], + $GLOBALS['xmlrpcstr']['no_ssl'] + ); + } + + if ($port == 0) + { + $port = 443; + } + /* Only create the payload if it was not created previously */ + if(empty($msg->payload)) + { + $msg->createPayload(); + } + + $curl = curl_init('https://' . $server . ':' . $port . $this->path); + + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // results into variable + if ($this->debug) + { + curl_setopt($curl, CURLOPT_VERBOSE, 1); + } + curl_setopt($curl, CURLOPT_USERAGENT, 'PHP XMLRPC 1.0'); + // required for XMLRPC + curl_setopt($curl, CURLOPT_POST, 1); + // post the data + curl_setopt($curl, CURLOPT_POSTFIELDS, $msg->payload); + // the data + curl_setopt($curl, CURLOPT_HEADER, 1); + // return the header too + curl_setopt($curl, CURLOPT_HTTPHEADER, array( + 'X-EGW-Server: ' . $this->server, + 'X-EGW-Version: ' . $GLOBALS['egw_info']['server']['versions']['phpgwapi'], + 'Content-Type: text/xml' + )); + if ($timeout) + { + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1); + } + if ($username && $password) + { + curl_setopt($curl, CURLOPT_USERPWD, "$username:$password"); + } + if ($cert) + { + curl_setopt($curl, CURLOPT_SSLCERT, $cert); + } + if ($certpass) + { + curl_setopt($curl, CURLOPT_SSLCERTPASSWD,$certpass); + } + // set cert password + + $result = curl_exec($curl); + + if (!$result) + { + $this->errstr = 'Write error'; + $resp = CreateObject( + 'phpgwapi.xmlrpcresp', + '', + $GLOBALS['xmlrpcerr']['curl_fail'], + $GLOBALS['xmlrpcstr']['curl_fail'] . ': ' . curl_error($curl) + ); + } + else + { + $resp = $msg->parseResponse($result); + } + curl_close($curl); + + return $resp; + } + } +?> diff --git a/phpgwapi/inc/class.xmlrpc_server.inc.php b/phpgwapi/inc/class.xmlrpc_server.inc.php index 30f4547976..80540ea0bb 100644 --- a/phpgwapi/inc/class.xmlrpc_server.inc.php +++ b/phpgwapi/inc/class.xmlrpc_server.inc.php @@ -1,8 +1,9 @@ * - * Copyright (C) 2003 Miles Lott * + * eGroupWare API - XML-RPC Server * + * ------------------------------------------------------------------------ * + * This library is part of the eGroupWare API * + * http://www.egroupware.org/api * * ------------------------------------------------------------------------ * * This library is free software; you can redistribute it and/or modify it * * under the terms of the GNU Lesser General Public License as published by * @@ -65,27 +66,27 @@ return False; } - foreach(array('year','month','mday','hour','min','sec') as $n => $name) - { - $date[$name] = (int)$arr[$n]; + foreach(array('year','month','mday','hour','min','sec') as $n => $name) + { + $date[$name] = (int)$arr[$n]; + } + return $timestamp ? mktime($date['hour'],$date['min'],$date['sec'], + $date['month'],$date['mday'],$date['year']) : $date; } - return $timestamp ? mktime($date['hour'],$date['min'],$date['sec'], - $date['month'],$date['mday'],$date['year']) : $date; - } // translate cat-ids to array with id-name pairs function cats2xmlrpc($cats) { - if (!is_object($GLOBALS['phpgw']->categories)) + if (!is_object($GLOBALS['egw']->categories)) { - $GLOBALS['phpgw']->categories = CreateObject('phpgwapi.categories'); + $GLOBALS['egw']->categories = CreateObject('phpgwapi.categories'); } $xcats = array(); foreach($cats as $cat) { if ($cat) { - $xcats[$cat] = stripslashes($GLOBALS['phpgw']->categories->id2name($cat)); + $xcats[$cat] = stripslashes($GLOBALS['egw']->categories->id2name($cat)); } } return $xcats; @@ -98,29 +99,29 @@ { $xcats = array(); } - elseif (!is_object($GLOBALS['phpgw']->categories)) + elseif (!is_object($GLOBALS['egw']->categories)) { - $GLOBALS['phpgw']->categories = CreateObject('phpgwapi.categories'); + $GLOBALS['egw']->categories = CreateObject('phpgwapi.categories'); } $cats = array(); foreach($xcats as $cat => $name) { - if ($id = $GLOBALS['phpgw']->categories->name2id($name)) + if ($id = $GLOBALS['egw']->categories->name2id($name)) { // existing cat-name use the id $cat = $id; } - elseif (!($org_name = stripslashes($GLOBALS['phpgw']->categories->id2name($cat))) || $org_name == '--') + elseif (!($org_name = stripslashes($GLOBALS['egw']->categories->id2name($cat))) || $org_name == '--') { // new cat - $cat = $GLOBALS['phpgw']->categories->add(array('name' => $name,'parent' => 0)); + $cat = $GLOBALS['egw']->categories->add(array('name' => $name,'parent' => 0)); } elseif ($org_name != $name) { // cat-name edited - list($cat_vals) =$GLOBALS['phpgw']->categories->return_single($cat); + list($cat_vals) =$GLOBALS['egw']->categories->return_single($cat); $cat_vals['name'] = $name; - $GLOBALS['phpgw']->categories->edit($cat_vals); + $GLOBALS['egw']->categories->edit($cat_vals); } $cats[] = (int)$cat; } @@ -130,18 +131,24 @@ // get list (array with id-name pairs) of all cats of $app function categories($complete = False,$app = '') { - if (is_array($complete)) $complete = @$complete[0]; - if (!$app) list($app) = explode('.',$this->last_method); + if (is_array($complete)) + { + $complete = @$complete[0]; + } + if (!$app) + { + list($app) = explode('.',$this->last_method); + } - if (!is_object($GLOBALS['phpgw']->categories)) + if (!is_object($GLOBALS['egw']->categories)) { - $GLOBALS['phpgw']->categories = CreateObject('phpgwapi.categories'); + $GLOBALS['egw']->categories = CreateObject('phpgwapi.categories'); } - if ($GLOBALS['phpgw']->categories->app_name != $app) + if ($GLOBALS['egw']->categories->app_name != $app) { - $GLOBALS['phpgw']->categories->categories('',$app); + $GLOBALS['egw']->categories->categories('',$app); } - $cats_arr = $GLOBALS['phpgw']->categories->return_sorted_array(0,False,'','','',True); + $cats_arr = $GLOBALS['egw']->categories->return_sorted_array(0,False,'','','',True); $cats = array(); if (is_array($cats_arr)) { @@ -157,13 +164,14 @@ return $cats; } - function setSimpleDate($enable=True) { + function setSimpleDate($enable=True) + { $this->simpledate = $enable; } } - if(empty($GLOBALS['phpgw_info']['server']['xmlrpc_type'])) + if(empty($GLOBALS['egw_info']['server']['xmlrpc_type'])) { - $GLOBALS['phpgw_info']['server']['xmlrpc_type'] = 'php'; + $GLOBALS['egw_info']['server']['xmlrpc_type'] = 'php'; } - include_once(PHPGW_API_INC.SEP.'class.xmlrpc_server_' . $GLOBALS['phpgw_info']['server']['xmlrpc_type'] . '.inc.php'); + include_once(EGW_API_INC.SEP.'class.xmlrpc_server_' . $GLOBALS['egw_info']['server']['xmlrpc_type'] . '.inc.php'); diff --git a/phpgwapi/inc/class.xmlrpc_server_epi.inc.php b/phpgwapi/inc/class.xmlrpc_server_epi.inc.php new file mode 100644 index 0000000000..547f922d1e --- /dev/null +++ b/phpgwapi/inc/class.xmlrpc_server_epi.inc.php @@ -0,0 +1,335 @@ + * + * Copyright (C) 2003 Miles Lott * + * -------------------------------------------------------------------------* + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by * + * the Free Software Foundation; either version 2.1 of the License, * + * or any later version. * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU Lesser General Public License for more details. * + * You should have received a copy of the GNU Lesser General Public License * + * along with this library; if not, write to the Free Software Foundation, * + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + \**************************************************************************/ + + /* $Id$ */ + + class xmlrpc_server extends xmlrpc_server_shared + { + var $server = ''; + var $authed = True; + var $log = False; //'/tmp/xmlrpc.log'; + var $last_method = ''; + + function xmlrpc_server($dispMap='', $serviceNow=0) + { + $this->server = xmlrpc_server_create(); + if($dispMap) + { + $this->dmap = $dispMap; + if($serviceNow) + { + $this->service(); + } + } + } + + function serializeDebug() + { + } + + function service($r = False) + { + if (!$r) // do we have a response, or we need to parse the request + { + $r = $this->parseRequest(); + } + if(!$r) + { + header('WWW-Authenticate: Basic realm="eGroupWare xmlrpc"'); + header('HTTP/1.0 401 Unauthorized'); + // for the log: + $payload = "WWW-Authenticate: Basic realm=\"eGroupWare xmlrpc\"\nHTTP/1.0 401 Unauthorized\n"; + echo $payload; + } + else + { +// $payload = '' . "\n" . $this->serializeDebug() . $r->serialize(); +// Header("Content-type: text/xml\r\nContent-length: " . strlen($payload)); +// print $payload; + echo $r; + } + + if($this->log) + { + $fp = fopen($this->log,'a+'); + fwrite($fp,"\n\n" . date('Y-m-d H:i:s') . " authorized=" + . ($this->authed ? $GLOBALS['egw_info']['user']['account_lid'] : 'False') + . ", method='$this->last_method'\n"); + fwrite($fp,"==== GOT ============================\n" . $GLOBALS['HTTP_RAW_POST_DATA'] + . "\n==== RETURNED =======================\n"); + fputs($fp,$payload); + fclose($fp); + } + + if($this->debug) + { + $this->echoInput(); + + $fp = fopen('/tmp/xmlrpc_debug.out','a+'); + fputs($fp,$payload); + fclose($fp); + } + } + + function add_to_map($methodname,$function,$sig,$doc) + { + xmlrpc_server_register_method($this->server,$methodname,$function); +// xmlrpc_server_register_method($this->server,$methodname,'xmlrpc_call_wrapper'); +// $descr = array( +// 'function' => $function, +// 'signature' => $sig, +// 'docstring' => $doc +// ); +// xmlrpc_server_set_method_description($this->server,$methodname,$descr); + + $this->dmap[$methodname] = array( + 'function' => $function, + 'signature' => $sig, + 'docstring' => $doc + ); + } + + function verifySignature($in, $sig) + { + return array(1); + + for($i=0; $igetNumParams()+1) + { + $itsOK = 1; + for($n=0; $n<$in->getNumParams(); $n++) + { + $p = $in->getParam($n); + // print "\n"; + if ($p->kindOf() == 'scalar') + { + $pt = $p->scalartyp(); + } + else + { + $pt = $p->kindOf(); + } + // $n+1 as first type of sig is return type + if ($pt != $cursig[$n+1]) + { + $itsOK = 0; + $pno = $n+1; + $wanted = $cursig[$n+1]; + $got = $pt; + break; + } + } + if ($itsOK) + { + return array(1); + } + } + } + return array(0, "Wanted $wanted, got $got at param $pno)"); + } + + function parseRequest($data='') + { + if($data == '') + { + $data = $GLOBALS['HTTP_RAW_POST_DATA']; + } +// return $this->echoInput($data); + + /* Decode to extract methodName */ + $params = xmlrpc_decode_request($data, &$methName); + $this->last_method = $methName; + $syscall = 0; + + /* Setup dispatch map based on the function, if this is a system call */ + if(ereg('^system\.', $methName)) + { + foreach($GLOBALS['_xmlrpcs_dmap'] as $meth => $dat) + { + $this->add_to_map($meth,$dat['function'],$dat['signature'],$dat['docstring']); + } + $sysCall = 1; + $dmap = $this->dmap; + } + elseif(ereg('^examples\.',$methName) || + ereg('^validator1\.',$methName) || + ereg('^interopEchoTests\.', $methName) + ) + { + $dmap = $this->dmap; + $sysCall = 1; + } + + /* verify dispatch map, or try to fix it for non-trivial system calls */ + if(!isset($this->dmap[$methName]['function'])) + { + if($sysCall) + { + /* Bad or non-existent system call, return error */ + $r = CreateObject('phpgwapi.xmlrpcresp', + '', + $GLOBALS['xmlrpcerr']['unknown_method'], + $GLOBALS['xmlrpcstr']['unknown_method'] . ': ' . $methName + ); + return $r; + } + if($this->authed) + { + $method = $methName; + list($app,$class,$method) = explode('.',$methName); + + switch($app) + { + case 'server': + case 'phpgwapi': + /* Server role functions only - api access */ + if($GLOBALS['egw']->acl->get_role() >= EGW_ACL_SERVER) + { + $dmap = ExecMethod(sprintf('%s.%s.%s','phpgwapi',$class,'list_methods'),'xmlrpc'); + } + break; + case 'service': + /* Service functions, user-level */ + $t = 'phpgwapi.' . $class . '.exec'; + $dmap = ExecMethod($t,array($service,'list_methods','xmlrpc')); + break; + default: + /* User-level application access */ + if($GLOBALS['egw']->acl->check('run',EGW_ACL_READ,$app)) + { + $dmap = ExecMethod(sprintf('',$app,$class,'list_methods'),'xmlrpc'); + } + else + { + $r = CreateObject('phpgwapi.xmlrpcresp', + '', + $GLOBALS['xmlrpcerr']['no_access'], + $GLOBALS['xmlrpcstr']['no_access'] + ); + return $r; + } + } + } + } + + /* add the functions from preset $dmap OR list_methods() to the server map */ + foreach($dmap as $meth => $dat) + { + $this->add_to_map($meth,$dat['function'],$dat['signature'],$dat['docstring']); + } + + /* _debug_array($this->dmap);exit; */ + + /* Now make the call */ + if(isset($dmap[$methName]['function'])) + { + // dispatch if exists + if(isset($dmap[$methName]['signature'])) + { + $sr = $this->verifySignature($m, $dmap[$methName]['signature'] ); + } + if((!isset($dmap[$methName]['signature'])) || $sr[0]) + { + // if no signature or correct signature + $r = xmlrpc_server_call_method($this->server,$data,$params); + } + else + { + $r = CreateObject('phpgwapi.xmlrpcresp', + '', + $GLOBALS['xmlrpcerr']['incorrect_params'], + $GLOBALS['xmlrpcstr']['incorrect_params'] . ': ' . $sr[1] + ); + } + } + else + { + // else prepare error response + if(!$this->authed) + { +// $r = False; + // send 401 header to force authorization + $r = CreateObject('phpgwapi.xmlrpcresp', + CreateObject('phpgwapi.xmlrpcval', + 'UNAUTHORIZED', + 'string' + ) + ); + } + else + { + $r = CreateObject('phpgwapi.xmlrpcresp', + '', + $GLOBALS['xmlrpcerr']['unknown_method'], + $GLOBALS['xmlrpcstr']['unknown_method'] . ': ' . $methName + ); + } + } + xmlrpc_server_destroy($xmlrpc_server); + return $r; + } + + function echoInput() + { + // a debugging routine: just echos back the input + // packet as a string value + + /* TODO */ +// $r = CreateObject('phpgwapi.xmlrpcresp',CreateObject('phpgwapi.xmlrpcval',"'Aha said I: '" . $HTTP_RAW_POST_DATA,'string')); + return $GLOBALS['HTTP_RAW_POST_DATA']; + } + + function xmlrpc_custom_error($error_number, $error_string, $filename, $line, $vars) + { + if(error_reporting() & $error_number) + { + $error_string .= sprintf("\nFilename: %s\nLine: %s",$filename,$line); + + xmlrpc_error(1005,$error_string); + } + } +/* + function xmlrpc_error($error_number, $error_string) + { + $values = array( + 'faultString' => $error_string, + 'faultCode' => $error_number + ); + + echo xmlrpc_encode_request(NULL,$values); + + xmlrpc_server_destroy($GLOBALS['xmlrpc_server']); + exit; + } +*/ + function xmlrpc_error($error_number, $error_string) + { + $r = CreateObject('phpgwapi.xmlrpcresp', + '', + $error_number, + $error_string . ': ' . $this->last_method + ); + $this->service($r); + xmlrpc_server_destroy($GLOBALS['xmlrpc_server']); + exit; + } + } diff --git a/phpgwapi/inc/class.xmlrpc_server_php.inc.php b/phpgwapi/inc/class.xmlrpc_server_php.inc.php index ad2cc35c34..166cce6d3c 100644 --- a/phpgwapi/inc/class.xmlrpc_server_php.inc.php +++ b/phpgwapi/inc/class.xmlrpc_server_php.inc.php @@ -31,7 +31,11 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED // OF THE POSSIBILITY OF SUCH DAMAGE. - /* $Id$ */ + /* + * Incorporated for egroupware by Miles Lott + */ + + /* $Id$ */ /* BEGIN server class */ class xmlrpc_server extends xmlrpc_server_shared @@ -42,13 +46,11 @@ var $resp_struct = array(); var $debug = False; var $method_requested; - var $log = false; //'/tmp/xmlrpc.log'; + var $log = '/tmp/xmlrpc.log'; function xmlrpc_server($dispMap='', $serviceNow=0) { - global $HTTP_RAW_POST_DATA; - - // dispMap is a despatch array of methods + // dispMap is a dispatch array of methods // mapped to function names and signatures // if a method // doesn't appear in the map then an unknown @@ -77,8 +79,6 @@ function service($r=False) { - global $HTTP_RAW_POST_DATA; - if (!$r) // do we have a response, or we need to parse the request { $r = $this->parseRequest(); @@ -94,17 +94,17 @@ { $payload = "\n" . $this->serializeDebug() . $r->serialize(); Header("Content-type: text/xml\r\nContent-length: " . strlen($payload)); - echo $GLOBALS['phpgw']->translation->convert($payload,$GLOBALS['phpgw']->translation->charset(),'utf-8'); + echo $GLOBALS['egw']->translation->convert($payload,$GLOBALS['egw']->translation->charset(),'utf-8'); } if ($this->log) { $fp = fopen($this->log,'a+'); - fwrite($fp,"\n\n".date('Y-m-d H:i:s')." authorized=". - ($this->authed?$GLOBALS['phpgw_info']['user']['account_lid']:'False'). - ", method='$this->last_method'\n"); - fwrite($fp,"==== GOT ============================\n".$HTTP_RAW_POST_DATA. - "\n==== RETURNED =======================\n"); + fwrite($fp,"\n\n".date('Y-m-d H:i:s') . " authorized=" + . ($this->authed?$GLOBALS['egw_info']['user']['account_lid']:'False') + . ", method='$this->last_method'\n"); + fwrite($fp,"==== GOT ============================\n" . $GLOBALS['HTTP_RAW_POST_DATA'] + . "\n==== RETURNED =======================\n"); fputs($fp,$payload); fclose($fp); } @@ -117,7 +117,6 @@ fputs($fp,$payload); fclose($fp); } - } /* @@ -237,8 +236,8 @@ } $_type = (is_integer($_res) ? 'int' : gettype($_res)); - if ($_type == 'string' && (ereg('^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$',$_res) - || ereg('^[0-9]{4}[0-9]{2}[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$',$_res) ) ) + if ($_type == 'string' && (ereg('^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$',$_res) || + ereg('^[0-9]{4}[0-9]{2}[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$',$_res))) { $_type = 'dateTime.iso8601'; } @@ -252,21 +251,20 @@ function parseRequest($data='') { - global $HTTP_RAW_POST_DATA; - $r = False; if ($data == '') { - $data = $HTTP_RAW_POST_DATA; + $data = $GLOBALS['HTTP_RAW_POST_DATA']; } $parser = xml_parser_create($GLOBALS['xmlrpc_defencoding']); $GLOBALS['_xh'][$parser] = array(); - $GLOBALS['_xh'][$parser]['st'] = ''; - $GLOBALS['_xh'][$parser]['cm'] = 0; $GLOBALS['_xh'][$parser]['isf'] = 0; + $GLOBALS['_xh'][$parser]['isf_reason'] = ''; $GLOBALS['_xh'][$parser]['params'] = array(); + $GLOBALS['_xh'][$parser]['stack']=array(); + $GLOBALS['_xh'][$parser]['valuestack'] = array(); $GLOBALS['_xh'][$parser]['method'] = ''; // decompose incoming XML into request structure @@ -285,6 +283,16 @@ ); xml_parser_free($parser); } + elseif ($GLOBALS['_xh'][$parser]['isf']) + { + xml_parser_free($parser); + $r = CreateObject( + 'phpgwapi.xmlrpcresp', + 0, + $GLOBALS['xmlrpcerr']['invalid_request'], + $GLOBALS['xmlrpcstr']['invalid_request'] . ' ' . $GLOBALS['_xh'][$parser]['isf_reason'] + ); + } else { xml_parser_free($parser); @@ -294,24 +302,15 @@ for($i=0; $i\n"); - $plist .= "$i - " . $GLOBALS['_xh'][$parser]['params'][$i]. " \n"; - $code = '$m->addParam(' . $GLOBALS['_xh'][$parser]['params'][$i] . ');'; - $code = str_replace(',,',",'',",$code); - $allok = 0; - @eval($code . '; $allok = 1;'); - if(!$allok) - { - break; - } + $m->addParam($GLOBALS['_xh'][$parser]['params'][$i]); } - // uncomment this to really see what the server's getting! - // xmlrpc_debugmsg($plist); + // now to deal with the method $methName = $GLOBALS['_xh'][$parser]['method']; $_methName = $GLOBALS['_xh'][$parser]['method']; $this->last_method = $methName; - if (ereg("^system\.", $methName)) + if(ereg("^system\.", $methName)) { $dmap = $GLOBALS['_xmlrpcs_dmap']; $sysCall=1; @@ -322,7 +321,7 @@ $sysCall=0; } - if (!isset($dmap[$methName]['function'])) + if(!isset($dmap[$methName]['function'])) { if($sysCall && $this->authed) { @@ -333,7 +332,7 @@ ); return $r; } - if ($this->authed) + if($this->authed) { /* phpgw mod - fetch the (bo) class methods to create the dmap */ // This part is to update session action to match @@ -345,12 +344,12 @@ $service = $tmp[1]; $class = $tmp[0]; - if (ereg('^service',$method)) + if(ereg('^service',$method)) { $t = 'phpgwapi.' . $class . '.exec'; $dmap = ExecMethod($t,array($service,'list_methods','xmlrpc')); } - elseif($GLOBALS['phpgw']->acl->check('run',1,$class)) + elseif($GLOBALS['egw']->acl->check('run',1,$class)) { /* This only happens if they have app access. If not, we will * return a fault below. @@ -378,55 +377,46 @@ // dispatch if exists if (isset($dmap[$methName]['signature'])) { - $sr = $this->verifySignature($m, $dmap[$methName]['signature'] ); + list($sr, $errstr) = $this->verifySignature($m, $dmap[$methName]['signature']); + if(!$sr) + { + // Didn't match. + + return CreateObject( + 'phpgwapi.xmlrpcresp', + 0, + $GLOBALS['xmlrpcerr']['incorrect_params'], + $GLOBALS['xmlrpcstr']['incorrect_params'] . ": ${errstr}" + ); + } } - if ( (!isset($dmap[$methName]['signature'])) || $sr[0]) + if((!isset($dmap[$methName]['signature'])) || $sr) { // if no signature or correct signature - if ($sysCall) + if($sysCall) { - $code = '$r=' . $dmap[$methName]['function'] . '($this, $m);'; - $code = str_replace(',,',",'',",$code); - $allok = 0; - @eval($code . '; $allok = 1;'); - if(!$allok) - { - return CreateObject('phpgwapi.xmlrpcresp','', $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return']); - } + $r = call_user_func($dmap[$methName]['function'], $this, $m); } else { - if (function_exists($dmap[$methName]['function'])) + if(function_exists($dmap[$methName]['function'])) { - $code = '$r =' . $dmap[$methName]['function'] . '($m);'; - $code = str_replace(',,',",'',",$code); - $allok = 0; - @eval($code . '; $allok = 1;'); - if(!$allok) - { - return CreateObject('phpgwapi.xmlrpcresp','', $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return']); - } + $r = call_user_func($dmap[$methName]['function'],$m); } else { /* phpgw mod - finally, execute the function call and return the values */ $params = $GLOBALS['_xh'][$parser]['params'][0]; - $code = '$p = ' . $params . ';'; if(count($params) != 0) { - $allok = 0; - @eval($code . '; $allok = 1;'); - if(!$allok) - { - return CreateObject('phpgwapi.xmlrpcresp','', $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return']); - } + $p = $params; $params = $p->getval(); } // _debug_array($params); $this->reqtoarray($params); // decode from utf-8 to our charset - $this->req_array = $GLOBALS['phpgw']->translation->convert($this->req_array,'utf-8'); + $this->req_array = $GLOBALS['egw']->translation->convert($this->req_array,'utf-8'); //_debug_array($this->req_array); if (ereg('^service',$method)) { @@ -478,22 +468,22 @@ } } } + unset($GLOBALS['_xh'][$parser]); return $r; } function echoInput() { - global $HTTP_RAW_POST_DATA; - // a debugging routine: just echos back the input // packet as a string value - $r = CreateObject('phpgwapi.xmlrpcresp',CreateObject('phpgwapi.xmlrpcval',"'Aha said I: '" . $HTTP_RAW_POST_DATA,'string')); + $r = CreateObject('phpgwapi.xmlrpcresp',CreateObject('phpgwapi.xmlrpcval',"'Aha said I: '" + . $GLOBALS['HTTP_RAW_POST_DATA'],'string')); //echo $r->serialize(); $fp = fopen('/tmp/xmlrpc_debug.in','w'); fputs($fp,$r->serialize); - fputs($fp,$HTTP_RAW_POST_DATA); + fputs($fp,$GLOBALS['HTTP_RAW_POST_DATA']); fclose($fp); } diff --git a/phpgwapi/inc/class.xmlrpcmsg.inc.php b/phpgwapi/inc/class.xmlrpcmsg.inc.php index cfea921dfb..b4e406da6b 100644 --- a/phpgwapi/inc/class.xmlrpcmsg.inc.php +++ b/phpgwapi/inc/class.xmlrpcmsg.inc.php @@ -29,10 +29,10 @@ function xmlrpcmsg($meth, $pars=0) { - $this->methodname = $meth; - if (is_array($pars) && sizeof($pars)>0) + $this->methodname=$meth; + if(is_array($pars) && sizeof($pars)>0) { - for($i=0; $iaddParam($pars[$i]); } @@ -41,37 +41,37 @@ function xml_header() { - return '' . "\n" . '' . "\n"; + return "\n\n"; } function xml_footer() { - return '' . "\n"; + return "\n"; } function createPayload() { - $this->payload = $this->xml_header(); - $this->payload .= '' . $this->methodname . '' . "\n"; - if (sizeof($this->params)) + $this->payload=$this->xml_header(); + $this->payload.='' . $this->methodname . "\n"; + // if(sizeof($this->params)) { + $this->payload.="\n"; + for($i=0; $iparams); $i++) { - $this->payload .= '' . "\n"; - for($i=0; $iparams); $i++) - { - $p = $this->params[$i]; - $this->payload .= '' . "\n" . $p->serialize() . '' . "\n"; - } - $this->payload .= '' . "\n"; + $p=$this->params[$i]; + $this->payload.="\n" . $p->serialize() . + "\n"; } - $this->payload .= $this->xml_footer(); - $this->payload = str_replace("\n", "\r\n", $this->payload); + $this->payload.="\n"; + // } + $this->payload.=$this->xml_footer(); + //$this->payload=str_replace("\n", "\r\n", $this->payload); } function method($meth='') { - if ($meth != '') + if($meth!='') { - $this->methodname = $meth; + $this->methodname=$meth; } return $this->methodname; } @@ -84,111 +84,230 @@ function addParam($par) { - $this->params[] = $par; - } - - function getParam($i) - { - return $this->params[$i]; - } - - function getNumParams() - { - return sizeof($this->params); - } - - function parseResponseFile($fp) - { - $ipd = ''; - - while($data = fread($fp, 32768)) + // add check: do not add to self params which are not xmlrpcvals + if(is_object($par) && (get_class($par) == 'xmlrpcval' || is_subclass_of($par, 'xmlrpcval'))) { - $ipd .= $data; + $this->params[]=$par; + return true; + } + else + { + return false; } - /* echo $ipd;exit; */ - return $this->parseResponse($ipd); } - function parseResponse($data='') + function getParam($i) { return $this->params[$i]; } + function getNumParams() { return sizeof($this->params); } + + function &parseResponseFile($fp) + { + $ipd=''; + while($data=fread($fp, 32768)) + { + $ipd.=$data; + } + //fclose($fp); + $r =& $this->parseResponse($ipd); + return $r; + } + + function &parseResponse($data='', $headers_processed=false) { $parser = xml_parser_create($GLOBALS['xmlrpc_defencoding']); - $GLOBALS['_xh'][$parser] = array(); - $GLOBALS['_xh'][$parser]['st'] = ''; - $GLOBALS['_xh'][$parser]['cm'] = 0; - $GLOBALS['_xh'][$parser]['isf'] = 0; - $GLOBALS['_xh'][$parser]['ac'] = ''; - $GLOBALS['_xh'][$parser]['qt'] = ''; - $GLOBALS['_xh'][$parser]['ha'] = ''; - - xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); - xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); - xml_set_character_data_handler($parser, 'xmlrpc_cd'); - xml_set_default_handler($parser, 'xmlrpc_dh'); -// $xmlrpc_value = CreateObject('phpgwapi.xmlrpcval'); - $hdrfnd = 0; - if ($this->debug) + if($this->debug) { - echo '
---GOT---' . "\n" . htmlspecialchars($data) . "\n" . '---END---' . "\n" . '
'; + //by maHo, replaced htmlspecialchars with htmlentities + print "
---GOT---\n" . htmlentities($data) . "\n---END---\n
"; } - if ($data == '') + + if($data == '') { error_log('No response received from server.'); - $r = CreateObject( - 'phpgwapi.xmlrpcresp', - 0, - $GLOBALS['xmlrpcerr']['no_data'], - $GLOBALS['xmlrpcstr']['no_data'] - ); - xml_parser_free($parser); + $r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']); return $r; } - // see if we got an HTTP 200 OK, else bomb // but only do this if we're using the HTTP protocol. - if (ereg("^HTTP",$data) && !ereg("^HTTP/[0-9.]+ 200 ", $data)) + if(ereg("^HTTP",$data)) { - $errstr = substr($data, 0, strpos($data, "\n")-1); - error_log('HTTP error, got response: ' .$errstr); - $r = CreateObject('phpgwapi.xmlrpcresp','', $GLOBALS['xmlrpcerr']['http_error'], - $GLOBALS['xmlrpcstr']['http_error'] . ' (' . $errstr . ')'); - xml_parser_free($parser); - return $r; + // Strip HTTP 1.1 100 Continue header if present + while(ereg('^HTTP/1.1 1[0-9]{2} ', $data)) + { + $pos = strpos($data, 'HTTP', 12); + // server sent a Continue header without any (valid) content following... + // give the client a chance to know it + if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5 + { + break; + } + $data = substr($data, $pos); + } + if(!ereg("^HTTP/[0-9\\.]+ 200 ", $data)) + { + $errstr= substr($data, 0, strpos($data, "\n")-1); + error_log('HTTP error, got response: ' .$errstr); + $r=& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')'); + return $r; + } } - // if using HTTP, then gotta get rid of HTTP headers here - // and we store them in the 'ha' bit of our data array - if (ereg("^HTTP", $data)) + $GLOBALS['_xh'][$parser] = array(); + $GLOBALS['_xh'][$parser]['headers'] = array(); + $GLOBALS['_xh'][$parser]['stack'] = array(); + $GLOBALS['_xh'][$parser]['valuestack'] = array(); + + // separate HTTP headers from data + if(ereg("^HTTP", $data)) { - $ar=explode("\r\n", $data); - $newdata = ''; - $hdrfnd = 0; - for ($i=0; $i0) - { - $GLOBALS['_xh'][$parser]['ha'] .= $ar[$i]. "\r\n"; - } - else - { - $hdrfnd=1; - } + $bd = $pos+2; } else { - $newdata.=$ar[$i] . "\r\n"; + // No separation between response headers and body: fault? + $bd = 0; } } - $data=$newdata; + // 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 + $arr = explode(':',$line); + if(count($arr) > 1) + { + $header_name = strtolower(trim($arr[0])); + // TO DO: some headers (the ones that allow a CSV list of values) + // do allow many values to be passed using multiple header lines. + // We should add content to $GLOBALS['_xh'][$parser]['headers'][$header_name] + // instead of replacing it for those... + $GLOBALS['_xh'][$parser]['headers'][$header_name] = $arr[1]; + for($i = 2; $i < count($arr); $i++) + { + $GLOBALS['_xh'][$parser]['headers'][$header_name] .= ':'.$arr[$i]; + } // while + $GLOBALS['_xh'][$parser]['headers'][$header_name] = trim($GLOBALS['_xh'][$parser]['headers'][$header_name]); + } + elseif(isset($header_name)) + { + $GLOBALS['_xh'][$parser]['headers'][$header_name] .= ' ' . trim($line); + } + } + $data = substr($data, $bd); + + if($this->debug && count($GLOBALS['_xh'][$parser]['headers'])) + { + print '
';
+					foreach($GLOBALS['_xh'][$parser]['headers'] as $header => $value)
+					{
+						print "HEADER: $header: $value\n";
+					}
+					print "
\n"; + } } - if (!xml_parse($parser, $data, sizeof($data))) + // 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'][$parser]['headers']['transfer-encoding']) && $GLOBALS['_xh'][$parser]['headers']['transfer-encoding'] == 'chunked') + { + if(!$data = decode_chunked($data)) + { + error_log('Errors occurred when trying to rebuild the chunked data received from server'); + $r =& CreateObject('phpgwapi.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'][$parser]['headers']['content-encoding'])) + { + if($GLOBALS['_xh'][$parser]['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh'][$parser]['headers']['content-encoding'] == 'gzip') + { + // if decoding works, use it. else assume data wasn't gzencoded + if(function_exists('gzinflate')) + { + if($GLOBALS['_xh'][$parser]['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'][$parser]['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('Errors occurred when trying to decode the deflated data received from server'); + $r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']); + return $r; + } + } + else + { + error_log('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); + $r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']); + return $r; + } + } + } + } // end of 'de-chunk, re-inflate response' + + // be tolerant of extra whitespace in response body + $data = trim($data); + + // be tolerant of junk after methodResponse (e.g. javascript automatically inserted by free hosts) + // idea from Luca Mariano originally in PEARified version of the lib + $bd = false; + $pos = strpos($data, ''); + while($pos || is_int($pos)) + { + $bd = $pos+17; + $pos = strpos($data, '', $bd); + } + if($bd) + { + $data = substr($data, 0, $bd); + } + + $GLOBALS['_xh'][$parser]['isf']=0; + $GLOBALS['_xh'][$parser]['isf_reason']=''; + $GLOBALS['_xh'][$parser]['ac']=''; + $GLOBALS['_xh'][$parser]['qt']=''; + + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell + // the xml parser to give us back data in the expected charset + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']); + + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + //$xmlrpc_value=new xmlrpcval; + + if(!xml_parse($parser, $data, sizeof($data))) { // thanks to Peter Kocks - if((xml_get_current_line_number($parser)) == 1) + if((xml_get_current_line_number($parser)) == 1) { $errstr = 'XML error at line 1, check URL'; } @@ -199,49 +318,66 @@ xml_get_current_line_number($parser)); } error_log($errstr); - $r = CreateObject('phpgwapi.xmlrpcresp', '', $GLOBALS['xmlrpcerr']['invalid_return'],$GLOBALS['xmlrpcstr']['invalid_return']); + $r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')'); xml_parser_free($parser); + if($this->debug) + { + print $errstr; + } + $r->hdrs = $GLOBALS['_xh'][$parser]['headers']; return $r; } xml_parser_free($parser); - if ($this->debug) + if($GLOBALS['_xh'][$parser]['isf'] > 1) { - echo '
---EVALING---['
-					. strlen($GLOBALS['_xh'][$parser]['st']) . ' chars]---' . "\n"
-					. htmlspecialchars($GLOBALS['_xh'][$parser]['st']) . ';' . "\n" . '---END---
'; + if ($this->debug) + { + ///@todo echo something for user? + } + + $r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['invalid_return'], + $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh'][$parser]['isf_reason']); } - if (strlen($GLOBALS['_xh'][$parser]['st']) == 0) + elseif (!is_object($GLOBALS['_xh'][$parser]['value'])) { // then something odd has happened // and it's time to generate a client side error // indicating something odd went on - $r = CreateObject('phpgwapi.xmlrpcresp', '', $GLOBALS['xmlrpcerr']['invalid_return'],$GLOBALS['xmlrpcstr']['invalid_return']); + $r=& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['invalid_return'], + $GLOBALS['xmlrpcstr']['invalid_return']); } else { - $code = '$v=' . $GLOBALS['_xh'][$parser]['st'] . '; $allok=1;'; - $code = str_replace(',,',",'',",$code); - $allok = 0; - @eval($code); - if(!$allok) + if ($this->debug) { - $r = CreateObject('phpgwapi.xmlrpcresp','', $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return']); + print "
---PARSED---\n" ;
+					var_dump($GLOBALS['_xh'][$parser]['value']);
+					print "\n---END---
"; + } // note that using =& will raise an error if $GLOBALS['_xh'][$parser]['st'] does not generate an object. + + $v = $GLOBALS['_xh'][$parser]['value']; + + if($GLOBALS['_xh'][$parser]['isf']) + { + $errno_v = $v->structmem('faultCode'); + $errstr_v = $v->structmem('faultString'); + $errno = $errno_v->scalarval(); + + if($errno == 0) + { + // FAULT returned, errno needs to reflect that + $errno = -1; + } + + $r =& CreateObject('phpgwapi.xmlrpcresp',$v, $errno, $errstr_v->scalarval()); } else { - if ($GLOBALS['_xh'][$parser]['isf']) - { - $f = $v->structmem('faultCode'); - $fs = $v->structmem('faultString'); - $r = CreateObject('phpgwapi.xmlrpcresp',$v, $f->scalarval(), $fs->scalarval()); - } - else - { - $r = CreateObject('phpgwapi.xmlrpcresp',$v); - } + $r=& CreateObject('phpgwapi.xmlrpcresp',$v); } } - $r->hdrs = $GLOBALS['_xh'][$parser]['ha']; //split("\r?\n", $GLOBALS['_xh'][$parser]['ha'][1]); + + $r->hdrs = $GLOBALS['_xh'][$parser]['headers']; return $r; } } diff --git a/phpgwapi/inc/class.xmlrpcresp.inc.php b/phpgwapi/inc/class.xmlrpcresp.inc.php new file mode 100644 index 0000000000..7e17a23cbc --- /dev/null +++ b/phpgwapi/inc/class.xmlrpcresp.inc.php @@ -0,0 +1,97 @@ + + // xmlrpc.inc,v 1.18 2001/07/06 18:23:57 edmundd + + // License is granted to use or modify this software ("XML-RPC for PHP") + // for commercial or non-commercial use provided the copyright of the author + // is preserved in any distributed or derivative work. + + // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED 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 AUTHOR 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. + + class xmlrpcresp + { + var $val = 0; + var $errno = 0; + var $errstr = ''; + var $hdrs = array(); + + /// @todo add check that $val is of correct type??? + function xmlrpcresp($val, $fcode = 0, $fstr = '') + { + if($fcode != 0) + { + // error + $this->errno = $fcode; + $this->errstr = $fstr; + //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later. + } + elseif(!is_object($val) || (get_class($val) != 'xmlrpcval' && !is_subclass_of($val, 'xmlrpcval'))) + { + // programmer error + // TODO + error_log("Invalid type '" . gettype($val) . "' (value: $val) passed to xmlrpcresp. Defaulting to empty value."); + $this->val =& new xmlrpcval(); + } + else + { + // success + $this->val = $val; + } + } + + function faultCode() + { + return $this->errno; + } + + function faultString() + { + return $this->errstr; + } + + function value() + { + return $this->val; + } + + function serialize() + { + $result = "\n"; + if($this->errno) + { + // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients + $result .= ' + + + +faultCode +' . $this->errno . ' + + +faultString +' . xmlrpc_encode_entitites($this->errstr) . ' + + + +'; + } + else + { + $result .= "\n\n" . + $this->val->serialize() . + "\n"; + } + $result .= "\n"; + return $result; + } + } diff --git a/phpgwapi/inc/class.xmlrpcval.inc.php b/phpgwapi/inc/class.xmlrpcval.inc.php new file mode 100644 index 0000000000..b9408401ea --- /dev/null +++ b/phpgwapi/inc/class.xmlrpcval.inc.php @@ -0,0 +1,333 @@ + + // xmlrpc.inc,v 1.18 2001/07/06 18:23:57 edmundd + + // License is granted to use or modify this software ("XML-RPC for PHP") + // for commercial or non-commercial use provided the copyright of the author + // is preserved in any distributed or derivative work. + + // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED 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 AUTHOR 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. + + /* $Id$ */ + + class xmlrpcval + { + var $me=array(); + var $mytype=0; + + 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("addScalar: not a scalar type ($typeof)"); + return 0; + } + + // coerce booleans into correct values + // NB: shall we do it for datetime too? + if($type == 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('addScalar: scalar xmlrpcval can have only one value'); + return 0; + case 3: + error_log('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'][]=& CreateObject('phpgwapi.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('xmlrpcval: 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('xmlrpcval: already initialized as a [' . $this->kindOf() . ']'); + return 0; + } + } + + 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) + { + $rs=''; + switch(@$GLOBALS['xmlrpcTypes'][$typ]) + { + case 3: + // struct + $rs.="\n"; + foreach($val as $key2 => $val2) + { + $rs.="${key2}\n"; + $rs.=$this->serializeval($val2); + $rs.="\n"; + } + $rs.=''; + break; + case 2: + // array + $rs.="\n\n"; + for($i=0; $iserializeval($val[$i]); + } + $rs.="\n"; + break; + case 1: + switch($typ) + { + case xmlrpcBase64: + $rs.="<${typ}>" . base64_encode($val) . ""; + break; + case xmlrpcBoolean: + $rs.="<${typ}>" . ($val ? '1' : '0') . ""; + break; + case 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). ""; + // $rs.="<${typ}>" . htmlentities($val). ""; + break; + default: + $rs.="<${typ}>${val}"; + } + break; + default: + break; + } + return $rs; + } + + function serialize() + { + return $this->serializeval($this); + } + + 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"; + //} + } + + function structmemexists($m) + { + return array_key_exists($this->me['struct'][$m]); + } + + function structmem($m) + { + return $this->me['struct'][$m]; + } + + function structreset() + { + reset($this->me['struct']); + } + + function structeach() + { + return each($this->me['struct']); + } + + function scalarval() + { + reset($this->me); + list(,$b)=each($this->me); + return $b; + } + + function scalartyp() + { + reset($this->me); + list($a,$b)=each($this->me); + if($a == xmlrpcI4) + { + $a = xmlrpcInt; + } + return $a; + } + + function arraymem($m) + { + return $this->me['array'][$m]; + } + + function arraysize() + { + return count($this->me['array']); + } + + function structsize() + { + return count($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)) + { + 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; + } + } +?> diff --git a/phpgwapi/inc/xml_functions.inc.php b/phpgwapi/inc/xml_functions.inc.php index 1812326e7f..dacf54b7b0 100644 --- a/phpgwapi/inc/xml_functions.inc.php +++ b/phpgwapi/inc/xml_functions.inc.php @@ -33,6 +33,26 @@ } } + $GLOBALS['xmlrpc_valid_parents'] = array( + 'BOOLEAN' => array('VALUE'), + 'I4' => array('VALUE'), + 'INT' => array('VALUE'), + 'STRING' => array('VALUE'), + 'DOUBLE' => array('VALUE'), + 'DATETIME.ISO8601' => array('VALUE'), + 'BASE64' => array('VALUE'), + 'ARRAY' => array('VALUE'), + 'STRUCT' => array('VALUE'), + 'PARAM' => array('PARAMS'), + 'METHODNAME' => array('METHODCALL'), + 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'), + 'MEMBER' => array('STRUCT'), + 'NAME' => array('MEMBER'), + 'DATA' => array('ARRAY'), + 'FAULT' => array('METHODRESPONSE'), + 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'), + ); + define('xmlrpcI4','i4'); define('xmlrpcInt','int'); define('xmlrpcBoolean','boolean'); @@ -63,31 +83,63 @@ 'apos' => "'" ); - $GLOBALS['xmlrpcerr']['unknown_method'] = 1; - $GLOBALS['xmlrpcstr']['unknown_method'] = 'Unknown method'; - $GLOBALS['xmlrpcerr']['invalid_return'] = 2; - $GLOBALS['xmlrpcstr']['invalid_return'] = 'Invalid return payload: enabling debugging to examine incoming payload'; - $GLOBALS['xmlrpcerr']['incorrect_params'] = 3; - $GLOBALS['xmlrpcstr']['incorrect_params'] = 'Incorrect parameters passed to method'; - $GLOBALS['xmlrpcerr']['introspect_unknown'] = 4; - $GLOBALS['xmlrpcstr']['introspect_unknown'] = "Can't introspect: method unknown"; - $GLOBALS['xmlrpcerr']['http_error'] = 5; - $GLOBALS['xmlrpcstr']['http_error'] = "Didn't receive 200 OK from remote server."; - $GLOBALS['xmlrpcerr']['no_data'] = 6; - $GLOBALS['xmlrpcstr']['no_data'] = 'No data received from server.'; - $GLOBALS['xmlrpcerr']['no_ssl'] = 7; - $GLOBALS['xmlrpcstr']['no_ssl'] = 'No SSL support compiled in.'; - $GLOBALS['xmlrpcerr']['curl_fail'] = 8; - $GLOBALS['xmlrpcstr']['curl_fail'] = 'CURL error'; - $GLOBALS['xmlrpcerr']['no_access'] = 9; + $GLOBALS['xmlrpcerr']['unknown_method']=1; + $GLOBALS['xmlrpcstr']['unknown_method']='Unknown method'; + $GLOBALS['xmlrpcerr']['invalid_return']=2; + $GLOBALS['xmlrpcstr']['invalid_return']='Invalid return payload: enable debugging to examine incoming payload'; + $GLOBALS['xmlrpcerr']['incorrect_params']=3; + $GLOBALS['xmlrpcstr']['incorrect_params']='Incorrect parameters passed to method'; + $GLOBALS['xmlrpcerr']['introspect_unknown']=4; + $GLOBALS['xmlrpcstr']['introspect_unknown']="Can't introspect: method unknown"; + $GLOBALS['xmlrpcerr']['http_error']=5; + $GLOBALS['xmlrpcstr']['http_error']="Didn't receive 200 OK from remote server."; + $GLOBALS['xmlrpcerr']['no_data']=6; + $GLOBALS['xmlrpcstr']['no_data']='No data received from server.'; + $GLOBALS['xmlrpcerr']['no_ssl']=7; + $GLOBALS['xmlrpcstr']['no_ssl']='No SSL support compiled in.'; + $GLOBALS['xmlrpcerr']['curl_fail']=8; + $GLOBALS['xmlrpcstr']['curl_fail']='CURL error'; + $GLOBALS['xmlrpcerr']['multicall_notstruct'] = 9; + $GLOBALS['xmlrpcstr']['multicall_notstruct'] = 'system.multicall expected struct'; + $GLOBALS['xmlrpcerr']['multicall_nomethod'] = 10; + $GLOBALS['xmlrpcstr']['multicall_nomethod'] = 'missing methodName'; + $GLOBALS['xmlrpcerr']['multicall_notstring'] = 11; + $GLOBALS['xmlrpcstr']['multicall_notstring'] = 'methodName is not a string'; + $GLOBALS['xmlrpcerr']['multicall_recursion'] = 12; + $GLOBALS['xmlrpcstr']['multicall_recursion'] = 'recursive system.multicall forbidden'; + $GLOBALS['xmlrpcerr']['multicall_noparams'] = 13; + $GLOBALS['xmlrpcstr']['multicall_noparams'] = 'missing params'; + $GLOBALS['xmlrpcerr']['multicall_notarray'] = 14; + $GLOBALS['xmlrpcstr']['multicall_notarray'] = 'params is not an array'; + + $GLOBALS['xmlrpcerr']['invalid_request']=15; + $GLOBALS['xmlrpcstr']['invalid_request']='Invalid request payload'; + $GLOBALS['xmlrpcerr']['no_curl']=16; + $GLOBALS['xmlrpcstr']['no_curl']='No CURL support compiled in.'; + $GLOBALS['xmlrpcerr']['server_error']=17; + $GLOBALS['xmlrpcstr']['server_error']='Internal server error'; + + $GLOBALS['xmlrpcerr']['no_access'] = 18; $GLOBALS['xmlrpcstr']['no_access'] = 'Access denied'; - $GLOBALS['xmlrpcerr']['not_existent'] = 10; + $GLOBALS['xmlrpcerr']['not_existent'] = 19; $GLOBALS['xmlrpcstr']['not_existent'] = 'Entry does not (longer) exist!'; + $GLOBALS['xmlrpcerr']['cannot_decompress']=103; + $GLOBALS['xmlrpcstr']['cannot_decompress']='Received from server compressed HTTP and cannot decompress'; + $GLOBALS['xmlrpcerr']['decompress_fail']=104; + $GLOBALS['xmlrpcstr']['decompress_fail']='Received from server invalid compressed HTTP'; + $GLOBALS['xmlrpcerr']['dechunk_fail']=105; + $GLOBALS['xmlrpcstr']['dechunk_fail']='Received from server invalid chunked HTTP'; + $GLOBALS['xmlrpcerr']['server_cannot_decompress']=106; + $GLOBALS['xmlrpcstr']['server_cannot_decompress']='Received from client compressed HTTP request and cannot decompress'; + $GLOBALS['xmlrpcerr']['server_decompress_fail']=107; + $GLOBALS['xmlrpcstr']['server_decompress_fail']='Received from client invalid compressed HTTP request'; + $GLOBALS['xmlrpc_defencoding'] = 'UTF-8'; + $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1'; $GLOBALS['xmlrpcName'] = 'XML-RPC for PHP'; - $GLOBALS['xmlrpcVersion'] = '1.0b9'; + $GLOBALS['xmlrpcVersion'] = '2.0'; // let user errors start at 800 $GLOBALS['xmlrpcerruser'] = 800; @@ -117,22 +169,65 @@ Header('Content-type: text/xml'); Header('Content-length: ' . strlen($payload)); print $payload; - $GLOBALS['phpgw']->common->phpgw_exit(False); + $GLOBALS['egw']->common->phpgw_exit(False); } // used to store state during parsing // quick explanation of components: - // st - used to build up a string for evaluation // ac - used to accumulate values - // qt - used to decide if quotes are needed for evaluation - // cm - used to denote struct or array (comma needed) // isf - used to indicate a fault // lv - used to indicate "looking for a value": implements // the logic to allow values with no types to be strings // params - used to store parameters in method calls // method - used to store method name + // stack - array with genealogy of xml elements names: + // used to validate nesting of xmlrpc elements + $GLOBALS['_xh'] = null; - $GLOBALS['_xh']=array(); + /** + * To help correct communication of non-ascii chars inside strings, regardless + * of the charset used when sending requests, parsing them, sending responses + * and parsing responses, convert all non-ascii chars present in the message + * into their equivalent 'charset entity'. Charset entities enumerated this way + * are independent of the charset encoding used to transmit them, and all XML + * parsers are bound to understand them. + */ + function xmlrpc_encode_entitites($data) + { + $length = strlen($data); + $escapeddata = ""; + for($position = 0; $position < $length; $position++) + { + $character = substr($data, $position, 1); + $code = Ord($character); + switch($code) + { + case 34: + $character = """; + break; + case 38: + $character = "&"; + break; + case 39: + $character = "'"; + break; + case 60: + $character = "<"; + break; + case 62: + $character = ">"; + break; + default: + if($code < 32 || $code > 159) + { + $character = ("&#".strval($code).";"); + } + break; + } + $escapeddata .= $character; + } + return $escapeddata; + } function xmlrpc_entity_decode($string) { @@ -177,239 +272,298 @@ function xmlrpc_se($parser, $name, $attrs) { - switch($name) + // if invalid xmlrpc already detected, skip all processing + if ($GLOBALS['_xh'][$parser]['isf'] < 2) { - case 'STRUCT': - case 'ARRAY': - $GLOBALS['_xh'][$parser]['st'] .= 'array('; - $GLOBALS['_xh'][$parser]['cm']++; - // this last line turns quoting off - // this means if we get an empty array we'll - // simply get a bit of whitespace in the eval - $GLOBALS['_xh'][$parser]['qt']=0; - break; - case 'NAME': - $GLOBALS['_xh'][$parser]['st'] .= '"'; - $GLOBALS['_xh'][$parser]['ac'] = ''; - break; - case 'FAULT': - $GLOBALS['_xh'][$parser]['isf'] = 1; - break; - case 'PARAM': - $GLOBALS['_xh'][$parser]['st'] = ''; - break; - case 'VALUE': - $GLOBALS['_xh'][$parser]['st'] .= " CreateObject('phpgwapi.xmlrpcval',"; - $GLOBALS['_xh'][$parser]['vt'] = xmlrpcString; - $GLOBALS['_xh'][$parser]['ac'] = ''; - $GLOBALS['_xh'][$parser]['qt'] = 0; - $GLOBALS['_xh'][$parser]['lv'] = 1; - // look for a value: if this is still 1 by the - // time we reach the first data segment then the type is string - // by implication and we need to add in a quote - break; - case 'I4': - case 'INT': - case 'STRING': - case 'BOOLEAN': - case 'DOUBLE': - case 'DATETIME.ISO8601': - case 'BASE64': - $GLOBALS['_xh'][$parser]['ac']=''; // reset the accumulator - - if ($name=='DATETIME.ISO8601' || $name=='STRING') + // check for correct element nesting + // top level element can only be of 2 types + if (count($GLOBALS['_xh'][$parser]['stack']) == 0) + { + if ($name != 'METHODRESPONSE' && $name != 'METHODCALL') { - $GLOBALS['_xh'][$parser]['qt']=1; - if ($name=='DATETIME.ISO8601') + $GLOBALS['_xh'][$parser]['isf'] = 2; + $GLOBALS['_xh'][$parser]['isf_reason'] = 'missing top level xmlrpc element'; + return; + } + } + else + { + // not top level element: see if parent is OK + if (!in_array($GLOBALS['_xh'][$parser]['stack'][0], $GLOBALS['xmlrpc_valid_parents'][$name])) + { + $GLOBALS['_xh'][$parser]['isf'] = 2; + $GLOBALS['_xh'][$parser]['isf_reason'] = "xmlrpc element $name cannot be child of {$GLOBALS['_xh'][$parser]['stack'][0]}"; + return; + } + } + + switch($name) + { + case 'STRUCT': + case 'ARRAY': + // create an empty array to hold child values, and push it onto appropriate stack + $cur_val = array(); + $cur_val['values'] = array(); + $cur_val['type'] = $name; + @array_unshift($GLOBALS['_xh'][$parser]['valuestack'], $cur_val); + break; + case 'DATA': + case 'METHODCALL': + case 'METHODRESPONSE': + case 'PARAMS': + // valid elements that add little to processing + break; + case 'METHODNAME': + case 'NAME': + $GLOBALS['_xh'][$parser]['ac']=''; + break; + case 'FAULT': + $GLOBALS['_xh'][$parser]['isf']=1; + break; + case 'VALUE': + $GLOBALS['_xh'][$parser]['vt']='value'; // indicator: no value found yet + $GLOBALS['_xh'][$parser]['ac']=''; + $GLOBALS['_xh'][$parser]['lv']=1; + break; + case 'I4': + case 'INT': + case 'STRING': + case 'BOOLEAN': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + if ($GLOBALS['_xh'][$parser]['vt']!='value') { - $GLOBALS['_xh'][$parser]['vt']=xmlrpcDateTime; + //two data elements inside a value: an error occurred! + $GLOBALS['_xh'][$parser]['isf'] = 2; + $GLOBALS['_xh'][$parser]['isf_reason'] = "$name element following a {$GLOBALS['_xh'][$parser]['vt']} element inside a single value"; + return; } - } - elseif($name=='BASE64') - { - $GLOBALS['_xh'][$parser]['qt']=2; - } - else - { - // No quoting is required here -- but - // at the end of the element we must check - // for data format errors. - $GLOBALS['_xh'][$parser]['qt']=0; - } - break; - case 'MEMBER': - $GLOBALS['_xh'][$parser]['ac']=''; - break; - default: - break; - } - if ($name!='VALUE') - { - $GLOBALS['_xh'][$parser]['lv']=0; + $GLOBALS['_xh'][$parser]['ac']=''; // reset the accumulator + break; + case 'MEMBER': + $GLOBALS['_xh'][$parser]['valuestack'][0]['name']=''; // set member name to null, in case we do not find in the xml later on + //$GLOBALS['_xh'][$parser]['ac']=''; + // Drop trough intentionally + case 'PARAM': + // clear value, so we can check later if no value will passed for this param/member + $GLOBALS['_xh'][$parser]['value']=null; + break; + default: + /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!! + $GLOBALS['_xh'][$parser]['isf'] = 2; + $GLOBALS['_xh'][$parser]['isf_reason'] = "found not-xmlrpc xml element $name"; + break; + } + + // Save current element name to stack, to validate nesting + @array_unshift($GLOBALS['_xh'][$parser]['stack'], $name); + + if($name!='VALUE') + { + $GLOBALS['_xh'][$parser]['lv']=0; + } } } function xmlrpc_ee($parser, $name) { - switch($name) + if ($GLOBALS['_xh'][$parser]['isf'] < 2) { - case 'STRUCT': - case 'ARRAY': - if ($GLOBALS['_xh'][$parser]['cm'] && substr($GLOBALS['_xh'][$parser]['st'], -1) ==',') - { - $GLOBALS['_xh'][$parser]['st']=substr($GLOBALS['_xh'][$parser]['st'],0,-1); - } - $GLOBALS['_xh'][$parser]['st'].=')'; - $GLOBALS['_xh'][$parser]['vt']=strtolower($name); - $GLOBALS['_xh'][$parser]['cm']--; - break; - case 'NAME': - $GLOBALS['_xh'][$parser]['st'].= $GLOBALS['_xh'][$parser]['ac'] . '" => '; - break; - case 'BOOLEAN': - // special case here: we translate boolean 1 or 0 into PHP - // constants true or false - if ($GLOBALS['_xh'][$parser]['ac']=='1') - { - $GLOBALS['_xh'][$parser]['ac']='True'; - } - else - { - $GLOBALS['_xh'][$parser]['ac']='false'; - } - $GLOBALS['_xh'][$parser]['vt']=strtolower($name); - // Drop through intentionally. - case 'I4': - case 'INT': - case 'STRING': - case 'DOUBLE': - case 'DATETIME.ISO8601': - case 'BASE64': - if ($GLOBALS['_xh'][$parser]['qt']==1) - { - // we use double quotes rather than single so backslashification works OK - $GLOBALS['_xh'][$parser]['st'].='"'. $GLOBALS['_xh'][$parser]['ac'] . '"'; - } - elseif ($GLOBALS['_xh'][$parser]['qt']==2) - { - $GLOBALS['_xh'][$parser]['st'].= 'base64_decode("' . $GLOBALS['_xh'][$parser]['ac'] . '")'; - } - elseif ($name=='BOOLEAN') - { - $GLOBALS['_xh'][$parser]['st'].=$GLOBALS['_xh'][$parser]['ac']; - } - else - { - // we have an I4, INT or a DOUBLE - // we must check that only 0123456789-. are characters here - if (!ereg("^\-?[0123456789 \t\.]+$", $GLOBALS['_xh'][$parser]['ac'])) + // push this element name from stack + // NB: if XML validates, correct opening/closing is guaranteed and + // we do not have to check for $name == $curr_elem. + // we also checked for proper nesting at start of elements... + $curr_elem = array_shift($GLOBALS['_xh'][$parser]['stack']); + + switch($name) + { + case 'STRUCT': + case 'ARRAY': + // fetch out of stack array of values, and promote it to current value + $curr_val = array_shift($GLOBALS['_xh'][$parser]['valuestack']); + $GLOBALS['_xh'][$parser]['value'] = $curr_val['values']; + + $GLOBALS['_xh'][$parser]['vt']=strtolower($name); + break; + case 'NAME': + $GLOBALS['_xh'][$parser]['valuestack'][0]['name'] = $GLOBALS['_xh'][$parser]['ac']; + break; + case 'BOOLEAN': + case 'I4': + case 'INT': + case 'STRING': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + $GLOBALS['_xh'][$parser]['vt']=strtolower($name); + if ($name=='STRING') { - // TODO: find a better way of throwing an error - // than this! - error_log('XML-RPC: non numeric value received in INT or DOUBLE'); - $GLOBALS['_xh'][$parser]['st'].='ERROR_NON_NUMERIC_FOUND'; + $GLOBALS['_xh'][$parser]['value']=$GLOBALS['_xh'][$parser]['ac']; + } + elseif ($name=='DATETIME.ISO8601') + { + $GLOBALS['_xh'][$parser]['vt'] = xmlrpcDateTime; + $GLOBALS['_xh'][$parser]['value']=$GLOBALS['_xh'][$parser]['ac']; + } + elseif ($name=='BASE64') + { + ///@todo check for failure of base64 decoding / catch warnings + $GLOBALS['_xh'][$parser]['value'] = base64_decode($GLOBALS['_xh'][$parser]['ac']); + } + elseif ($name=='BOOLEAN') + { + // special case here: we translate boolean 1 or 0 into PHP + // constants true or false + // NB: this simple checks helps a lot sanitizing input, ie no + // security problems around here + if ($GLOBALS['_xh'][$parser]['ac']=='1') + { + $GLOBALS['_xh'][$parser]['value']=true; + } + else + { + // log if receiveing something strange, even though we set the value to false anyway + if ($GLOBALS['_xh'][$parser]['ac']!='0') + error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh'][$parser]['ac']); + $GLOBALS['_xh'][$parser]['value']=false; + } + } + elseif ($name=='DOUBLE') + { + // we have a DOUBLE + // we must check that only 0123456789-. are characters here + if (!ereg("^[+-]?[eE0123456789 \\t\\.]+$", $GLOBALS['_xh'][$parser]['ac'])) + { + // TODO: find a better way of throwing an error + // than this! + error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh'][$parser]['ac']); + $GLOBALS['_xh'][$parser]['value']='ERROR_NON_NUMERIC_FOUND'; + } + else + { + // it's ok, add it on + $GLOBALS['_xh'][$parser]['value']=(double)$GLOBALS['_xh'][$parser]['ac']; + } } else { - // it's ok, add it on - $GLOBALS['_xh'][$parser]['st'].=$GLOBALS['_xh'][$parser]['ac']; + // we have an I4/INT + // we must check that only 0123456789- are characters here + if (!ereg("^[+-]?[0123456789 \\t]+$", $GLOBALS['_xh'][$parser]['ac'])) + { + // TODO: find a better way of throwing an error + // than this! + error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh'][$parser]['ac']); + $GLOBALS['_xh'][$parser]['value']='ERROR_NON_NUMERIC_FOUND'; + } + else + { + // it's ok, add it on + $GLOBALS['_xh'][$parser]['value'] = (int)$GLOBALS['_xh'][$parser]['ac']; + } } - } - $GLOBALS['_xh'][$parser]['ac']=""; $GLOBALS['_xh'][$parser]['qt']=0; - $GLOBALS['_xh'][$parser]['lv']=3; // indicate we've found a value - break; - case 'VALUE': - // deal with a string value - if (strlen($GLOBALS['_xh'][$parser]['ac'])>0 && - $GLOBALS['_xh'][$parser]['vt']==xmlrpcString) - { - $GLOBALS['_xh'][$parser]['st'].='"'. $GLOBALS['_xh'][$parser]['ac'] . '"'; - } - // This if() detects if no scalar was inside - // and pads an empty "". - if($GLOBALS['_xh'][$parser]['st'][strlen($GLOBALS['_xh'][$parser]['st'])-1] == '(') - { - $GLOBALS['_xh'][$parser]['st'].= '""'; - } - $GLOBALS['_xh'][$parser]['st'].=", '" . $GLOBALS['_xh'][$parser]['vt'] . "')"; - if ($GLOBALS['_xh'][$parser]['cm']) - { - $GLOBALS['_xh'][$parser]['st'].=","; - } - break; - case 'MEMBER': - $GLOBALS['_xh'][$parser]['ac']=""; - $GLOBALS['_xh'][$parser]['qt']=0; - break; - case 'DATA': - $GLOBALS['_xh'][$parser]['ac']=""; - $GLOBALS['_xh'][$parser]['qt']=0; - break; - case 'PARAM': - $GLOBALS['_xh'][$parser]['params'][]=$GLOBALS['_xh'][$parser]['st']; - break; - case 'METHODNAME': - $GLOBALS['_xh'][$parser]['method']=ereg_replace("^[\n\r\t ]+", "", $GLOBALS['_xh'][$parser]['ac']); - break; - case 'BOOLEAN': - // special case here: we translate boolean 1 or 0 into PHP - // constants true or false - if ($GLOBALS['_xh'][$parser]['ac']=='1') - { - $GLOBALS['_xh'][$parser]['ac']='True'; - } - else - { - $GLOBALS['_xh'][$parser]['ac']='false'; - } - $GLOBALS['_xh'][$parser]['vt']=strtolower($name); - break; - default: - break; - } - // if it's a valid type name, set the type - if (isset($GLOBALS['xmlrpcTypes'][strtolower($name)])) - { - $GLOBALS['_xh'][$parser]['vt']=strtolower($name); + $GLOBALS['_xh'][$parser]['ac']=''; // is this necessary? + $GLOBALS['_xh'][$parser]['lv']=3; // indicate we've found a value + break; + case 'VALUE': + // This if() detects if no scalar was inside + if ($GLOBALS['_xh'][$parser]['vt'] == 'value') + { + $GLOBALS['_xh'][$parser]['value'] = $GLOBALS['_xh'][$parser]['ac']; + $GLOBALS['_xh'][$parser]['vt'] = xmlrpcString; + } + + // build the xmlrpc val out of the data received, and substitute it + $temp =& CreateObject('phpgwapi.xmlrpcval',$GLOBALS['_xh'][$parser]['value'], $GLOBALS['_xh'][$parser]['vt']); + // 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 + if (count($GLOBALS['_xh'][$parser]['valuestack']) && $GLOBALS['_xh'][$parser]['valuestack'][0]['type']=='ARRAY') + { + $GLOBALS['_xh'][$parser]['valuestack'][0]['values'][] = $temp; + } + else + { + $GLOBALS['_xh'][$parser]['value'] = $temp; + } + break; + case 'MEMBER': + $GLOBALS['_xh'][$parser]['ac']=''; // is this necessary? + // add to array in the stack the last element built, + // unless no VALUE was found + if ($GLOBALS['_xh'][$parser]['value']) + $GLOBALS['_xh'][$parser]['valuestack'][0]['values'][$GLOBALS['_xh'][$parser]['valuestack'][0]['name']] = $GLOBALS['_xh'][$parser]['value']; + else + error_log('XML-RPC: missing VALUE inside STRUCT in received xml'); + break; + case 'DATA': + $GLOBALS['_xh'][$parser]['ac']=''; // is this necessary? + break; + case 'PARAM': + // add to array of params the current value, + // unless no VALUE was found + if ($GLOBALS['_xh'][$parser]['value']) + $GLOBALS['_xh'][$parser]['params'][]=$GLOBALS['_xh'][$parser]['value']; + else + error_log('XML-RPC: missing VALUE inside PARAM in received xml'); + break; + case 'METHODNAME': + $GLOBALS['_xh'][$parser]['method']=ereg_replace("^[\n\r\t ]+", '', $GLOBALS['_xh'][$parser]['ac']); + break; + case 'PARAMS': + case 'FAULT': + case 'METHODCALL': + case 'METHORESPONSE': + break; + default: + // End of INVALID ELEMENT! + // shall we add an assert here for unreachable code??? + break; + } } } function xmlrpc_cd($parser, $data) { - //if (ereg("^[\n\r \t]+$", $data)) return; + //if(ereg("^[\n\r \t]+$", $data)) return; // print "adding [${data}]\n"; - if ($GLOBALS['_xh'][$parser]['lv']!=3) + // skip processing if xml fault already detected + if ($GLOBALS['_xh'][$parser]['isf'] < 2) { - // "lookforvalue==3" means that we've found an entire value - // and should discard any further character data - if ($GLOBALS['_xh'][$parser]['lv']==1) + if($GLOBALS['_xh'][$parser]['lv']!=3) { - // if we've found text and we're just in a then - // turn quoting on, as this will be a string - $GLOBALS['_xh'][$parser]['qt']=1; - // and say we've found a value - $GLOBALS['_xh'][$parser]['lv']=2; + // "lookforvalue==3" means that we've found an entire value + // and should discard any further character data + if($GLOBALS['_xh'][$parser]['lv']==1) + { + // if we've found text and we're just in a then + // say we've found a value + $GLOBALS['_xh'][$parser]['lv']=2; + } + if(!@isset($GLOBALS['_xh'][$parser]['ac'])) + { + $GLOBALS['_xh'][$parser]['ac'] = ''; + } + $GLOBALS['_xh'][$parser]['ac'].=$data; } - $GLOBALS['_xh'][$parser]['ac'].=str_replace('$', '\$', - str_replace('"', '\"', - str_replace(chr(92),$GLOBALS['xmlrpc_backslash'], $data))); } } function xmlrpc_dh($parser, $data) { - if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') + // skip processing if xml fault already detected + if ($GLOBALS['_xh'][$parser]['isf'] < 2) { - if ($GLOBALS['_xh'][$parser]['lv']==1) + if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') { - $GLOBALS['_xh'][$parser]['qt']=1; - $GLOBALS['_xh'][$parser]['lv']=2; + if($GLOBALS['_xh'][$parser]['lv']==1) + { + $GLOBALS['_xh'][$parser]['lv']=2; + } + $GLOBALS['_xh'][$parser]['ac'].=$data; } - $GLOBALS['_xh'][$parser]['ac'].=str_replace('$', '\$', - str_replace('"', '\"', - str_replace(chr(92),$GLOBALS['xmlrpc_backslash'], $data))); } } @@ -432,15 +586,15 @@ } else { - if (function_exists("gmstrftime")) + if(function_exists('gmstrftime')) { // gmstrftime doesn't exist in some versions // of PHP - $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet); + $t = gmstrftime("%Y%m%dT%H:%M:%S", $timet); } else { - $t=strftime("%Y%m%dT%H:%M:%S", $timet-date("Z")); + $t = strftime('%Y%m%dT%H:%M:%S', $timet-date('Z')); } } return $t; @@ -448,8 +602,8 @@ function iso8601_decode($idate, $utc=0) { - // return a timet in the localtime, or UTC - $t=0; + // return a time 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) @@ -474,22 +628,22 @@ { $kind = @$xmlrpc_val->kindOf(); - if($kind == "scalar") + if($kind == 'scalar') { return $xmlrpc_val->scalarval(); } - elseif($kind == "array") + elseif($kind == 'array') { $size = $xmlrpc_val->arraysize(); $arr = array(); for($i = 0; $i < $size; $i++) { - $arr[]=phpgw_xmlrpc_decode($xmlrpc_val->arraymem($i)); + $arr[] = phpgw_xmlrpc_decode($xmlrpc_val->arraymem($i)); } return $arr; } - elseif($kind == "struct") + elseif($kind == 'struct') { $xmlrpc_val->structreset(); $arr = array(); @@ -521,39 +675,42 @@ switch($type) { - case "array": - case "object": + case 'array': + case 'object': $arr = array(); - while (list($k,$v) = each($php_val)) + while(list($k,$v) = each($php_val)) { $arr[$k] = phpgw_xmlrpc_encode($v); } $xmlrpc_val->addStruct($arr); break; - case "integer": + case 'integer': $xmlrpc_val->addScalar($php_val, xmlrpcInt); break; - case "double": + case 'double': $xmlrpc_val->addScalar($php_val, xmlrpcDouble); break; - case "string": + case 'string': $xmlrpc_val->addScalar($php_val, xmlrpcString); break; // // Add support for encoding/decoding of booleans, since they are supported in PHP - case "boolean": + case 'boolean': $xmlrpc_val->addScalar($php_val, xmlrpcBoolean); break; // - case "unknown type": + case 'unknown type': default: - $xmlrpc_val = false; + $xmlrpc_val = False; break; } return $xmlrpc_val; } - // listMethods: either a string, or nothing + /* The following functions are the system functions for login, logout, etc. + * They are added to the server map at the end of this file. + */ + $GLOBALS['_xmlrpcs_listMethods_sig'] = array(array(xmlrpcArray, xmlrpcString), array(xmlrpcArray)); $GLOBALS['_xmlrpcs_listMethods_doc'] = 'This method lists all the methods that the XML-RPC server knows how to dispatch'; function _xmlrpcs_listMethods($server, $m) @@ -656,38 +813,8 @@ return $r; } - /* - $GLOBALS['_xmlrpcs_listApps_sig'] = array(array(xmlrpcStruct,xmlrpcString)); - $GLOBALS['_xmlrpcs_listApps_doc'] = 'Returns a list of installed phpgw apps'; - function _xmlrpcs_listApps($server,$m) - { - $m->getParam(0); - $GLOBALS['phpgw']->db->query("SELECT * FROM phpgw_applications WHERE app_enabled<3",__LINE__,__FILE__); - if($GLOBALS['phpgw']->db->num_rows()) - { - while ($GLOBALS['phpgw']->db->next_record()) - { - $name = $GLOBALS['phpgw']->db->f('app_name'); - $title = $GLOBALS['phpgw']->db->f('app_title'); - $status = $GLOBALS['phpgw']->db->f('app_enabled'); - $version= $GLOBALS['phpgw']->db->f('app_version'); - $apps[$name] = CreateObject('phpgwapi.xmlrpcval', - array( - 'title' => CreateObject('phpgwapi.xmlrpcval',$title,'string'), - 'name' => CreateObject('phpgwapi.xmlrpcval',$name,'string'), - 'status' => CreateObject('phpgwapi.xmlrpcval',$status,'string'), - 'version'=> CreateObject('phpgwapi.xmlrpcval',$version,'string') - ), - 'struct' - ); - } - } - return CreateObject('phpgwapi.xmlrpcresp',CreateObject('phpgwapi.xmlrpcval',$apps, 'struct')); - } - */ - $GLOBALS['_xmlrpcs_login_sig'] = array(array(xmlrpcStruct,xmlrpcStruct)); - $GLOBALS['_xmlrpcs_login_doc'] = 'phpGroupWare client or server login via XML-RPC'; + $GLOBALS['_xmlrpcs_login_doc'] = 'eGroupWare client or server login via XML-RPC'; function _xmlrpcs_login($server,$m) { $rdata = $m->getParam(0); @@ -701,12 +828,12 @@ { $domain = $data['domain']->scalarval(); } - $username = $data['username']->scalarval(); - $password = $data['password']->scalarval(); + $username = $data['username']->scalarval(); + $password = $data['password']->scalarval(); if($server_name) { - list($sessionid,$kp3) = $GLOBALS['phpgw']->session->create_server($username.'@'.$server_name,$password,"text"); + list($sessionid,$kp3) = $GLOBALS['egw']->session->create_server($username.'@'.$server_name,$password,"text"); } else { @@ -718,9 +845,11 @@ { $user = $username; } - $sessionid = $GLOBALS['phpgw']->session->create($user,$password,"text"); - $kp3 = $GLOBALS['phpgw']->session->kp3; - $domain = $GLOBALS['phpgw']->session->account_domain; + $GLOBALS['login'] = $user; + + $sessionid = $GLOBALS['egw']->session->create($user,$password,"text"); + $kp3 = $GLOBALS['egw']->session->kp3; + $domain = $GLOBALS['egw']->session->account_domain; } if($sessionid && $kp3) @@ -736,18 +865,8 @@ return CreateObject('phpgwapi.xmlrpcresp',CreateObject('phpgwapi.xmlrpcval',$rtrn,'struct')); } - $GLOBALS['_xmlrpcs_phpgw_api_version_sig'] = array(array(xmlrpcString,xmlrpcString)); - $GLOBALS['_xmlrpcs_phpgw_api_version_doc'] = 'Returns the phpGroupWare API version'; - function _xmlrpcs_phpgw_api_version($server,$m) - { - $version = $GLOBALS['phpgw_info']['server']['versions']['phpgwapi']; - - return CreateObject('phpgwapi.xmlrpcresp',CreateObject('phpgwapi.xmlrpcval',$version,'string')); - } - - $GLOBALS['_xmlrpcs_logout_sig'] = array(array(xmlrpcStruct,xmlrpcStruct)); - $GLOBALS['_xmlrpcs_logout_doc'] = 'phpGroupWare client or server logout via XML-RPC'; + $GLOBALS['_xmlrpcs_logout_doc'] = 'eGroupWare client or server logout via XML-RPC'; function _xmlrpcs_logout($server,$m) { $rdata = $m->getParam(0); @@ -756,7 +875,7 @@ $sessionid = $data['sessionid']->scalarval(); $kp3 = $data['kp3']->scalarval(); - $later = $GLOBALS['phpgw']->session->destroy($sessionid,$kp3); + $later = $GLOBALS['egw']->session->destroy($sessionid,$kp3); if ($later) { @@ -770,6 +889,61 @@ return CreateObject('phpgwapi.xmlrpcresp',CreateObject('phpgwapi.xmlrpcval',$rtrn,'struct')); } + $GLOBALS['_xmlrpcs_phpgw_api_version_sig'] = array(array(xmlrpcString)); + $GLOBALS['_xmlrpcs_phpgw_api_version_doc'] = 'Returns the eGroupWare API version'; + function _xmlrpcs_phpgw_api_version($server,$m) + { + $version = $GLOBALS['egw_info']['server']['versions']['phpgwapi']; + + return CreateObject('phpgwapi.xmlrpcresp',CreateObject('phpgwapi.xmlrpcval',$version,'string')); + } + + /* + $GLOBALS['_xmlrpcs_listApps_sig'] = array(array(xmlrpcStruct,xmlrpcString)); + $GLOBALS['_xmlrpcs_listApps_doc'] = 'Returns a list of installed phpgw apps'; + function _xmlrpcs_listApps($server,$m) + { + $m->getParam(0); + $GLOBALS['egw']->db->query("SELECT * FROM phpgw_applications WHERE app_enabled<3",__LINE__,__FILE__); + if($GLOBALS['egw']->db->num_rows()) + { + while($GLOBALS['egw']->db->next_record()) + { + $name = $GLOBALS['egw']->db->f('app_name'); + $title = $GLOBALS['egw']->db->f('app_title'); + $status = $GLOBALS['egw']->db->f('app_enabled'); + $version= $GLOBALS['egw']->db->f('app_version'); + $apps[$name] = CreateObject('phpgwapi.xmlrpcval', + array( + 'title' => CreateObject('phpgwapi.xmlrpcval',$title,'string'), + 'name' => CreateObject('phpgwapi.xmlrpcval',$name,'string'), + 'status' => CreateObject('phpgwapi.xmlrpcval',$status,'string'), + 'version'=> CreateObject('phpgwapi.xmlrpcval',$version,'string') + ), + 'struct' + ); + } + } + return CreateObject('phpgwapi.xmlrpcresp',CreateObject('phpgwapi.xmlrpcval',$apps, 'struct')); + } + */ + + /* + $GLOBALS['_xmlrpcs_egw_time_sig'] = array(array(xmlrpcString,xmlrpcString)); + $GLOBALS['_xmlrpcs_egw_time_doc'] = 'Returns system time based on optional format string'; + function _xmlrpcs_time($server,$m) + { + $format = $m->getParam(0); + $format = $format ? $format : 'Y/m/d H:i'; + + return CreateObject( + 'phpgwapi.xmlrpcresp', + CreateObject('phpgwapi.xmlrpcval', date($format,time()), 'string') + ); + } + */ + + /* Add the system functions to the server map */ $GLOBALS['_xmlrpcs_dmap'] = array( 'system.listMethods' => array( 'function' => '_xmlrpcs_listMethods', @@ -786,13 +960,6 @@ 'signature' => $GLOBALS['_xmlrpcs_methodSignature_sig'], 'docstring' => $GLOBALS['_xmlrpcs_methodSignature_doc'] ), - /* - 'system.listApps' => array( - 'function' => '_xmlrpcs_listApps', - 'signature' => $GLOBALS['_xmlrpcs_listApps_sig'], - 'docstring' => $GLOBALS['_xmlrpcs_listApps_doc'] - ), - */ 'system.login' => array( 'function' => '_xmlrpcs_login', 'signature' => $GLOBALS['_xmlrpcs_login_sig'], @@ -808,6 +975,20 @@ 'signature' => $GLOBALS['_xmlrpcs_phpgw_api_version_sig'], 'docstring' => $GLOBALS['_xmlrpcs_phpgw_api_version_doc'] ) + /* + 'system.listApps' => array( + 'function' => '_xmlrpcs_listApps', + 'signature' => $GLOBALS['_xmlrpcs_listApps_sig'], + 'docstring' => $GLOBALS['_xmlrpcs_listApps_doc'] + ), + */ + /* + 'system.time' => array( + 'function' => '_xmlrpcs_time', + 'signature' => $GLOBALS['_xmlrpcs_time_sig'], + 'docstring' => $GLOBALS['_xmlrpcs_time_doc'] + ) + */ ); $GLOBALS['_xmlrpc_debuginfo'] = '';