1 <?php define("XMLRPC_VERSION", "0.3.10");
3 # Supports XML-RPC (text/xml) and XML+RPC (application/rpc+xml) compressed,
4 # and can be used as client or server interface. Works without XMLRPC and
5 # XML extensions, but utilizes them for optimal speed whenever available.
7 # XXXX XXXX MMM MMM LLL RRRRRRR PPPPPPP CCCCCCC
8 # XXXX XXXX MMMM MMMM LLL +++ RRRRRRRR PPPPPPPP CCCCCCCCC
9 # XXXXXXX MMMMM MMMMM LLL +++ RRR RRR PPP PPP CCC CCC
10 # XXXXX MMMMMMMMMMM LLL +++++++++++ RRR RRR PPP PPP CCC
11 # XXX MMM MMM MMM LLL +++++++++++ RRRRRRRR PPPPPPPP CCC
12 # XXXXX MMM M MMM LLL +++ RRRRRRR PPPPPPP CCC
13 # XXXXXXX MMM MMM LLL +++ RRR RRR PPP CCC CCC
14 # XXXX XXXX MMM MMM LLLLLLL RRR RRR PPP CCCCCCCCC
15 # XXXX XXXX MMM MMM LLLLLLL RRR RRR PPP CCCCCCC
17 # This is Public Domain. (c) 2004 WhoEver wants to. [milky*erphesfurt·de]
21 define("XMLRPC_PLUS", 0); # use XML+RPC per default
22 define("XMLRPC_AUTO_TYPES", 0); # detect base64+datetime strings and automatically generate the according xmlrpc object representations then
23 define("XMLRPC_AUTO_UTF8", 1); # de/convert anything from and to UTF-8 automatically - if yourscripts use Latin1 natively, but the RPC server expects/sends UTF-8
24 define("XMLRPC_CHARSET", "utf-8"); # used in responses and requests
25 define("XMLRPC_AUTODISCOVERY", 0); # "connections" automatically create methods
26 define("XMLRPC_FAST", 1); # use PHPs XML-RPC extension where possible
27 define("XMLRPC_OO", 1); # return XML-RPC/HTTP errors as objects
28 define("XMLRPC_DEBUG", 0); # output error hints, write /tmp dumps - set this to 1, 2 or 3
30 #-- _server() settings
31 define("XMLRPC_LOG", "/tmp/xmlrpc.".@$_SERVER["SERVER_NAME"].".log");
34 # (don't change the following, most are auto-configured values)
35 define("XMLRPC_UA", "xml+rpc/".XMLRPC_VERSION." (PHP/".PHP_VERSION."; ".PHP_OS.")");
36 define("XMLRPC_MIME_NEW", "application/rpc+xml");
37 define("XMLRPC_MIME_OLD", "text/xml");
38 define("XMLRPC_MIME", XMLRPC_MIME_OLD);
39 define("XMLRPC_ACCEPT", XMLRPC_MIME_NEW.", ".XMLRPC_MIME_OLD."; q=0.5");
40 define("XMLRPC_EPI", function_exists("xmlrpc_decode_request"));
44 if (isset($_SERVER["HTTP_CONTENT_TYPE"]) && empty($_SERVER["CONTENT_TYPE"])) {
45 $_SERVER["CONTENT_TYPE"] = $_SERVER["HTTP_CONTENT_TYPE"]; // older CGI implementations
51 ############################################################################
55 ############################################################################
58 #-- Issue a request, call can take any number of arguments.
59 # $result = xmlrpc("http://example.com/RPC2/", "method1", $arg1 ...);
60 # $result = xmlrpc("xml+rpc://here.org/RPC3/", "ns.function", ...);
61 # Results automatically have <datetime> values converted into Unix
62 # timestamps and <base64> unpacked into strings.
64 function xmlrpc($server, $method=NULL /*, ... */) {
66 $params = func_get_args();
67 shift($params); shift($params);
69 xmlrpc_request($server, $method, $params);
73 new xmlrpc_connection($server);
79 #-- Generate and send request, decode response.
80 function xmlrpc_request($url, $method, $params=array(), $plus=XMLRPC_PLUS, $gzip=0) {
81 global $xmlrpc_response_headers, $xmlrpc_error;
83 #-- init whole lib for request (we are not-OO here)
84 $xmlrpc_error = false;
85 $xmlrpc_response_headers = array();
87 #-- encapsulate req, transmit it
88 $socket = xmlrpc_request_send($url, $method, $params, $plus, $gzip);
90 return xmlrpc_error(-32768, "no connection", 0, "GLOBALVARS");
93 #-- wait for, read response
95 while (!feof($socket) && (strlen($DATA) <= 768<<10)) {
96 $response .= fread($socket, 4<<10);
99 if (XMLRPC_DEBUG >= 3) {
100 echo "<code>$response</code>";
103 #-- decode answer and give results
104 return xmlrpc_response_decode($response);
109 function xmlrpc_call($url, $method, $params=array(), $plus=XMLRPC_PLUS, $gzip=0) {
110 return xmlrpc_request($url, $method, $params, $plus, $gzip);
115 #-- marshall request parameters into array, hash, xml string
116 function xmlrpc_request_send($url, $method, &$params, $plus, $gzip, $blocking=true) {
118 #-- get connection data
119 $c = parse_url($url);
120 ($host = $c["host"]);
121 ($port = @$c["port"]) or ($port = 80);
122 ($path = $c["path"]) or ($path = "/");
123 if (strpos($c["scheme"], "+")) {
126 if (strpos($c["scheme"], "gzip")) {
129 if (!$host) { return(NULL); }
131 if ($str = $c["user"]) {
132 if ($c["pass"]) { $str .= ":" . $c["pass"]; }
133 $inj = "Authorization: Basic " . base64_encode($str) . "\n";
136 #-- mk request HTTP+XML block from params
137 $request = xmlrpc_request_marshall($method, $params);
138 $request = xmlrpc_request_http($request, $path, $host, $plus, $gzip, $inj);
140 #-- connect, send request
141 if ($socket = fsockopen($host, $port, $io_err, $io_err_s, 30)) {
142 socket_set_blocking($socket, $blocking);
143 socket_set_timeout($socket, 17, 555);
146 echo "Could not connect to '<b>$host</b>:$port$path' - error $io_err: $io_err_s.<br>\n";
149 fputs($socket, $request);
156 #-- marshall function call into XML+HTTP string
157 function xmlrpc_request_marshall($method, &$params) {
160 if (XMLRPC_FAST && XMLRPC_EPI) {
161 $query = xmlrpc_encode_request($method, $params);
167 "methodCall" => array(
168 "methodName" => array( ",0"=>$method ),
172 foreach ($params as $i=>$p) {
173 $query["methodCall"]["params"]["param,$i"] = xmlrpc_compact_value($p);
175 $query = array2xml($query, 1, 'encoding="'.XMLRPC_CHARSET.'" ');
178 if (XMLRPC_AUTO_UTF8) {
179 $query = utf8_encode($query);
186 #-- enclose body into HTTP request string
187 function xmlrpc_request_http(&$query, $path, $host, $plus, $gzip, $inj_header="") {
191 $request = "POST $path HTTP/1.0$n"
193 . ($inj_header ? str_replace("\n", $n, $inj_header) : "")
194 . "User-Agent: " . XMLRPC_UA . "$n"
195 . "Accept: ".XMLRPC_ACCEPT."$n"
196 . (!XMLRPC_DEBUG ? "Accept-Encoding: deflate$n" : "")
197 . "Content-Type: ".($plus ? XMLRPC_MIME_NEW : XMLRPC_MIME_OLD)
198 ."; charset=".XMLRPC_CHARSET."$n";
202 $query = gzdeflate($query);
203 $request .= "Content-Encoding: deflate$n";
205 $request .= "Content-Length: " . strlen($query) . "$n" . "$n";
206 $request .= $query . "$n";
212 #-- unpack response from HTTP and XML representation
213 function xmlrpc_response_decode(&$response) {
214 global $xmlrpc_response_headers;
216 #-- split into headers and content
217 $l1 = strpos($response, "\n\n");
218 $l2 = strpos($response, "\n\r\n");
219 if ($l2 && (!$l1 || ($l2<$l1))) {
220 $head = substr($response, 0, $l2);
221 $response = substr($response, $l2+3);
224 $head = substr($response, 0, $l1);
225 $response = substr($response, $l2+2);
228 #-- decode headers, decompress body
229 foreach (explode("\n", $head) as $line) {
230 $xmlrpc_response_headers[strtolower(trim(strtok($line, ":")))] = trim(strtok("\000"));
232 if ($enc = trim(@$xmlrpc_response_headers["content-encoding"])) {
233 if (($enc == "gzip") || ($enc == "x-gzip")) {
234 $response = gzinflate(substr($response, 10, strlen($response)-18));
236 elseif (($enc == "compress") || ($enc == "x-compress")) {
237 $response = gzuncompress($response);
239 elseif (($enc == "deflate") || ($enc == "x-deflate")) {
240 $response = gzinflate($response);
244 $r = xmlrpc_response_unmarshall($response);
245 if (XMLRPC_DEBUG) {var_dump($r);}
250 #-- decode XML-RPC from string into array and extract its actual meaning
251 function xmlrpc_response_unmarshall(&$response) {
252 global $xmlrpc_response_headers;
255 if (XMLRPC_AUTO_UTF8) {
256 xmlrpc_decode_utf8xml($response, @$xmlrpc_response_headers["content-type"].@$xmlrpc_response_headers["content-charset"]);
259 if (XMLRPC_DEBUG >= 4) { fwrite(fopen("/tmp/xmlrpc:resp_in_xml","w"), $response); }
262 if (XMLRPC_FAST && XMLRPC_EPI) {
263 $r = xmlrpc_decode_request($response, $uu);
264 xmlrpc_epi_decode_xtypes($r);
265 if (is_array($r) && (count($r)==2) && isset($r["faultCode"]) && isset($r["faultString"])) {
266 return xmlrpc_error($r["faultCode"], $r["faultString"], 1, "GLOBALVARS");
275 $response = xml2array($response);
277 #-- fetch content (one returned element)
278 if ($r = @$response["methodResponse,0"]["params,0"]["param,0"]["value,0"]) {
279 $r = xmlrpc_decode_value($r);
284 # (we should rather return an error object here)
285 if (($r = @$response["methodResponse,0"]["fault,0"]["value,0"]) && ($r = xmlrpc_decode_value($r))) {
286 return xmlrpc_error($r["faultCode"], $r["faultString"], 1, "GLOBALVARS");
289 return xmlrpc_error(-32600, "xml+rpc: invalid response", 0, "GLBLVRS");
296 #-- Establish a virtual XML+RPC or XML-RPC server connection (a pseudo
297 # handshake is used to determine supported protocol / extensions).
298 class xmlrpc_connection {
301 function xmlrpc_connection($url, $autodiscovery=0) {
302 global $xmlrpc_response_headers;
303 $this->server = $url;
307 #-- handshake to check supported protocol
308 $funcs = $this->call("system.getVersion");
309 $this->plus = (strpos($xmlrpc_response_headers["accept"], XMLRPC_MIME_NEW) !== false);
310 $this->gzip = (strpos($xmlrpc_response_headers["accept_encoding"], "deflate") !== false);
312 #-- auto-discovery, create 'method' names
313 if ($funcs && (XMLRPC_AUTODISCOVERY || $autodiscovery)) {
314 foreach ($funcs as $fn) {
316 if ($l = strpos($fn, ".")) {
317 $short = substr($fn, $l + 1);
318 if (substr($fn, 0, $l) == "system") { continue; }
320 $this->short = create_function("", "return xmlrpc_request('{$this->server}','$fn',func_get_args(),{$this->plus},{$this->gzip});");
325 #-- generical call (needs func name)
326 function call($method /*, ... */) {
327 $params = func_get_args();
329 $r = xmlrpc_request($this->serverm, $method, $params, $this->plus, $this->gzip);
335 class xmlrpc extends xmlrpc_connection {
341 ############################################################################
343 # server implementation #
345 ############################################################################
348 #-- Check request and execute function if registered in $xmlrpc_methods[]
350 function xmlrpc_server() {
352 global $xmlrpc_methods;
355 define("XMLRPC_SERVER", getmypid());
356 if (XMLRPC_DEBUG) { error_reporting(E_ALL^E_NOTICE); }
359 #-- standard reply headers
360 header("Accept: ".XMLRPC_MIME_NEW.", ".XMLRPC_MIME_OLD."; q=0.5");
361 header("Accept-Encoding: deflate");
362 header("X-Server: " . XMLRPC_UA);
363 header("Connection: close");
364 header("Cache-Control: private");
366 #-- fixes for PHP/Apache
367 if (function_exists("getallheaders")) {
368 foreach (getallheaders() as $i=>$v) {
369 $_SERVER[strtoupper(strtr("HTTP_$i", "-", "_"))] = $v;
373 #-- check and get call
375 "REQUEST_METHOD" => array("POST", "PUT", "CALL"),
376 "CONTENT_TYPE" => array(XMLRPC_MIME_NEW, XMLRPC_MIME_OLD),
378 foreach ($allowed as $WHAT=>$WHICH) {
379 if (!in_array(trim(strtok($WRONG=$_SERVER[$WHAT], ";,(")), $WHICH)) {
380 header("Status: 400 Go Away, Stupid!");
382 $WRONG = "undefined";
384 die("<h2>Error</h2>Your request was bogus, <b>$WHAT</b> must be <i>"
385 . implode("</i> or <i>", $WHICH) . "</i>, but yours was '<tt>$WRONG</tt>'.\n");
388 if (!($xml_request = xmlrpc_fetch_post_chunk())) {
389 header("Status: 500 How Sad");
390 die("<h2>Error</h2>Could not fetch POST data.\n");
393 #-- decipher incoming XML request string
395 if (XMLRPC_FAST && XMLRPC_EPI) {
396 $params = xmlrpc_decode_request($xml_request, $method);
397 xmlrpc_epi_decode_xtypes($params);
400 $params = xmlrpc_request_unmarshall($xml_request, $method);
404 #-- add the few system.methods()
405 //if (empty($xmlrpc_methods)) {
406 // $xmlrpc_methods = get_defined_functions();
408 $xmlrpc_methods["system"] = "xmlrpc_system_methods"; # a class
411 $result = xmlrpc_exec_method($method, $params);
414 if (isset($result)) {
415 if (isset($result)) {
416 $resp["methodResponse"]["params"]["param"] = xmlrpc_compact_value($result);
419 $resp["methodResponse"]["params"] = array();
422 xmlrpc_send_response($resp);
425 $result = xmlrpc_error(0, "No Result");
426 xmlrpc_send_response($result);
432 #-- decode <methodCall> XML string into understandable chunks,
433 # gives $params as return value and $method name via pass-by-ref
434 function xmlrpc_request_unmarshall(&$xml_request, &$method) {
437 if (XMLRPC_AUTO_UTF8) {
438 xmlrpc_decode_utf8xml($xml_request, $_SERVER["CONTENT_TYPE"].$_SERVER["HTTP_CONTENT_CHARSET"]);
441 #-- decode XML string into PHP arrays
442 $call = xml2array($xml_request, 1);
445 $call = $call["methodCall,0"];
447 xmlrpc_send_response(xmlrpc_error(-32600, "Bad Request, <methodCall> missing"));
449 $method = $call["methodName,0"][",0"];
451 xmlrpc_send_response(xmlrpc_error(-32600, "Bad Request, <methodName> missing"));
455 foreach ($call["params,1"] as $uu => $param) {
456 $params[] = xmlrpc_decode_value($param["value,0"]);
464 #-- Call the requested method (using the XML-method to PHP-function mapping
466 function xmlrpc_exec_method($method, $params) {
468 global $xmlrpc_methods;
469 if (XMLRPC_DEBUG >= 2) { error_reporting(E_ALL^E_NOTICE); }
471 #-- check if allowed call
472 $rf = strtr($method, ".", "_");
473 $cl = strtok($method, ".");
474 if (!$xmlrpc_methods[$method] && !$xmlrpc_methods[$cl]
475 && !in_array($method, $xmlrpc_methods)
476 && !in_array($rf, $xmlrpc_methods) && !in_array($cl, $xmlrpc_methods) )
478 xmlrpc_send_response(xmlrpc_error(-32601));
481 #-- real function call
482 if ($php_func_name = $xmlrpc_methods[$method]) {
483 $rf = $method = $php_func_name;
485 if (function_exists($rf)) {
486 $result = call_user_func_array($rf, $params);
487 if (XMLRPC_DEBUG >= 4) { fwrite(fopen("/tmp/xmlrpc:func_call_res","w"),serialize(array($rf,$result,$params))); }
490 #-- PHP object method calls
492 $class = strtok($method, ".");
493 $method = strtok("\000");
494 if ($uu = $xmlrpc_methods[$class]) {
497 if ($class && class_exists($class) && $method) {
499 if (method_exists($obj, $method)) {
500 $result = call_user_method_array($method, $obj, $params); //<DEPRECATED>
507 xmlrpc_send_response(xmlrpc_error(-32601));
512 #-- Get POST data from PHP (if it gives it to us).
513 function xmlrpc_fetch_post_chunk() {
514 global $HTTP_RAW_POST_DATA;
517 if ($f = fopen("php://input", "rb")) {
518 $data = fread($f, 0x0100000);
522 ini_set("always_populate_raw_post_data", "true"); // well, maybe(!?)
523 $data = $HTTP_RAW_POST_DATA;
524 $HTTP_RAW_POST_DATA = "";
526 $enc = trim(strtolower($_SERVER["HTTP_CONTENT_ENCODING"]));
527 $funcs = array("deflate"=>"gzinflate", "gzip"=>"gzdecode", "compress"=>"gzuncompress", "x-gzip"=>"gzdecode", "x-bzip2"=>"bzuncompress");
528 if ($enc && ($pf = $funcs[$enc]) && function_exists($pf)) {
535 #-- converts UTF-8 documents into Latin-1 ones
536 function xmlrpc_decode_utf8xml(&$xml, $ct) {
537 if (strpos(strtolower($ct), "utf-8") or preg_match('/<\?xml[^>]+encoding=["\']utf-8/i', $xml)) {
538 $xml = utf8_decode($xml);
539 $xml = preg_replace('/(<\?xml[^>]+encoding=["\'])utf-8(["\'])/i', '$1iso-8859-1$2', $xml, 1);
545 #-- Creates an error object.
546 function xmlrpc_error($no=-32500, $str="", $type=1, $into_vars=0) {
547 global $xmlrpc_error, $xmlrpc_errorcode;
550 -32300 => "Transport error",
551 -32400 => "Internal Server Error",
552 -32500 => "Application error",
553 -32600 => "Invalid message format / Bad request",
554 -32601 => "Method does not exist",
555 -32602 => "Parameter type mismatch",
556 -32603 => "Internal XML-RPC error",
557 -32604 => "Too many parameters",
558 -32700 => "Not well-formed XML",
559 -32701 => "Unsupported encoding - only ISO-8859-1 and UTF-8 capable",
560 -32702 => "Invalid characters, encoding mismatch",
562 #-- build response xml/array
563 if (!($str) && !($str = $errors[$no])) {
564 $str = "Unknown Error";
566 if ($into_vars && !XMLRPC_OO) {
567 $xmlrpc_error = $str;
568 $xmlrpc_errorcode = $no;
572 return new xmlrpc_error($no, $str, $type);
580 var $type = 1; // else an HTTP error
584 function xmlrpc_error($no, $str, $type=1) {
591 $error = xmlrpc_compact_value(array(
593 "faultString" => $str,
596 "methodResponse" => array(
600 xmlrpc_send_response($resp);
605 #-- Sends a response.
606 function xmlrpc_send_response($r) {
608 #-- error objects send itself (by calling _send_response() again ;-)
613 #-- answer XML-RPC and XML+RPC requests
614 $ct = trim(strtok(strtolower($_SERVER["CONTENT_TYPE"]), ";,(")); // from original request
615 $cs = XMLRPC_CHARSET;
616 header("Content-Type: $ct; charset=\"$cs\"");
618 #-- make XML document from it
620 $r = array2xml($r, 1, 'encoding="'.$cs.'" ');
624 if (!headers_sent()) {
625 $enc = trim(strtolower($_SERVER["HTTP_ACCEPT_ENCODING"]));
626 $funcs = array("deflate"=>"gzdeflate", "gzip"=>"gzencode", "compress"=>"gzcompress", "x-gzip"=>"gzencode", "x-bzip2"=>"bzcompress");
627 if ($enc && ($pf = $funcs[$enc]) && function_exists($pf)) {
628 header("Content-Encoding: $enc");
634 if (ob_get_level()) {
635 #-- this prevents that PHP errors appear as garbage in our response
636 $add .= "<!--\n" . ob_get_contents() . "\n-->";
639 header("Content-Length: " . strlen($r));
646 #-- Provides "system.*" method namespace.
647 class xmlrpc_system_methods {
649 function listMethods() {
650 global $xmlrpc_methods;
652 foreach ($xmlrpc_methods as $i=>$i2) {
653 $real = is_int($i) ? $i2 : $i;
654 if (class_exists($real) && ($i2=$real) || class_exists($i2)) {
655 foreach (get_class_methods($i2) as $add) {
656 $r[] = $real.".".$add;
667 return new xmlrpc_datetime(time());
672 ############################################################################
676 ############################################################################
679 function xmlrpc_log($message) {
682 function xmlrpc_die($error="", $str="") {
687 ############################################################################
689 # data representation mangling #
691 ############################################################################
694 #-- Makes compact-array2xml datavar from a PHP variable.
695 function xmlrpc_compact_value($var, $n=0) {
697 #-- create compact-array2xml tree
699 "value,$n" => array(),
701 $r = &$root["value,$n"];
703 #-- detect PHP values to be complex types in XML-RPC
704 if (XMLRPC_AUTO_TYPES && is_string($var)) {
705 if ((strlen($var) >= 64) && preg_match('/^[\w]+=*$/', $var)) {
706 $var = new xmlrpc_base64($var);
708 elseif ((strlen($var)==17) && ($var[8]=="T") && preg_match('/^\d{8}T\d\d:\d\d:\d\d$/', $var)) {
709 $var = new xmlrpc_datetime($var);
714 if (is_object($var)) {
717 #-- arrays and hashes(structs)
718 elseif (is_array($var)) {
719 if (isset($var[0]) || empty($var)) {
720 $r = array("array,$n" => array("data,0" => array()));
721 $r = &$r["array,$n"]["data,0"];
722 foreach ($var as $n=>$val) {
723 $r = array_merge($r, xmlrpc_compact_value($val, $n));
727 $r = array("struct,$n"=>array());
728 $r = &$r["struct,$n"];
730 foreach ($var as $i=>$val) {
731 $r["member,$n"] = array_merge(array(
732 "name,0" => array(",0" => "$i"),
733 ), xmlrpc_compact_value($val, 1));
739 elseif (is_bool($var)) {
741 "boolean,$n" => array(",0" => ($var?1:0)),
744 elseif (is_int($var)) {
746 "int,$n" => array(",0" => $var),
749 elseif (is_float($var)) {
751 "double,$n" => array(",0" => $var),
754 elseif (is_string($var)) {
756 "string,$n" => array(",0" => $var),
763 #-- Makes a PHP array from a compact-xml2array representation. $value must
764 # always be the xml2array elements _below_ the ["value,0"] or ["data,0"]
765 # or ["member,N"] entry.
766 function xmlrpc_decode_value($value) {
768 foreach ($value as $i=>$d) {
770 #-- use single (text) content xml2array entry as actual $d var
771 if (is_array($d) && isset($d[",0"])) {
775 #-- convert into PHP var based on type
776 $type = strtok($i, ",");
781 foreach ($d["data,0"] as $i=>$value) {
782 $val[] = xmlrpc_decode_value($value);
788 foreach ($d as $uu=>$d2) {
789 if (($in=$d2["name,0"][",0"]) && ($pos2=1) || ($in=$d2["name,1"][",0"]) && ($pos2=0)) {
790 $val[$in] = xmlrpc_decode_value($d2["value,$pos2"]);
795 case "": # handles also '<value>s</value>' instead
796 case "0": # of '<value><string>s</string></value>'
798 $val = is_array($d) ? "" : (string)$d;
802 $val = (XMLRPC_AUTO_TYPES>=2) ? base64_decode($d) : (string)$d;
803 if ((XMLRPC_AUTO_UTF8 >= 2) && ($uu = utf8_decode($val))) {
808 // case "real": case "float": // neither is allowed
821 case "dateTime.iso8601":
822 $val = xmlrpc_strtotime($d);
826 if (defined("XMLRPC_SERVER")) {
827 xmlrpc_send_response(xmlrpc_error(-32600, "Unknown data type '$type'"));
830 echo $xmlrpc_error = "UNKNOWN XML-RPC DATA TYPE '$type'<br>\n";
831 $xmlrpc_errorcode = -32207;
833 # echo "<!-- UNKNOWN TYPE $type -->\n";
834 # xmlrpc_log("bad data type '$type' enountered");
841 #-- More complex XML-RPC data types need object representation to
842 # distinguish them from ordinary string and integer vars.
845 var $xmlrpc_type = "string";
847 function xmlrpc_type($str) {
851 return array($this->tag.",0" => array(",0"=>$this->scalar));
854 class xmlrpc_base64 extends xmlrpc_xtype {
855 function xmlrpc_base64($str) {
856 $this->tag = "base64";
857 $this->xmlrpc_type = "base64";
858 if (XMLRPC_AUTO_UTF8 >= 2) {
859 $str = utf8_encode($str);
861 if (!preg_match("/^[=\w\s]+$/", $str)) {
864 $this->scalar = $str;
867 if (isset($this->encode)) {
868 $this->scalar = chunk_split(base64_encode($this->scalar), 74, "\n");
870 return xmlrpc_xtype::out();
873 class xmlrpc_datetime extends xmlrpc_xtype {
874 function xmlrpc_datetime($t) {
875 $this->tag = "dateTime.iso8601";
876 $this->xmlrpc_type = "datetime";
877 if (($t > 0) && ($t[8] != "T")) {
878 $this->timestamp = $t;
879 $t = xmlrpc_timetostr($t);
885 #-- Further simplify use of the above ones.
886 function xmlrpc_base64($string) {
887 return(new xmlrpc_base64($string));
889 function xmlrpc_datetime($timestr) {
890 return(new xmlrpc_datetime($timestr));
894 #-- Deciphers ISO datetime string into UNIX timestamp.
895 function xmlrpc_strtotime($str) {
896 $tm = explode(":", substr($str, 9));
897 $t = mktime($tm[0], $tm[1], $tm[2], substr($str, 4, 2), substr($str, 6, 2), substr($str, 0, 4));
900 function xmlrpc_timetostr($time) {
901 return(gmstrftime("%Y%m%dT%T", $time));
905 #-- helping hand for the xmlrpc-epi extension of php
906 function xmlrpc_epi_decode_xtypes(&$r) {
907 if (is_object($r) && isset($r->xmlrpc_type)) {
908 if (isset($r->timestamp)) {
915 elseif (is_array($r)) {
916 foreach ($r as $i=>$v) {
917 xmlrpc_epi_decode_xtypes($r[$i]);
925 ############################################################################
927 # simplified XML parser #
929 ############################################################################
932 #-- Encode the two chars & and < into htmlentities (there is nothing said
933 # about the possible other entities in the XML-RPC spec).
934 function xml_entities($str) {
940 return(strtr($str, $e));
942 function xml_decode_entities($str) {
950 if (strpos($e, "&#") !== false) {
951 $e = preg_replace('/&#(\d+);/e', 'chr($1)', $e);
952 $e = preg_replace('/&#x([\da-fA-F]+);/e', 'chr(hexdec("$1"))', $e);
954 return(strtr($str, $e));
958 #-- Can split simplified XML into a PHP array structure. The now used
959 # 'compact' format will yield tag sub arrays with an "*,0" index and
960 # just [",0"] for text nodes.
961 function xml2array($xml, $compact="ALWAYS") {
963 if (function_exists("xml_parser_create") && (strlen($xml) >= 512)) {
964 $r = xml2array_php($xml);
967 xml2array_parse($xml, $r, $compact);
973 #-- Recursively builds an array of the chunks fetched via strtok() from
974 # the original XML input string.
975 function xml2array_parse(&$string, &$r, $compact=1) {
979 $l = strpos($string, "<");
980 $p = strpos($string, ">", $l);
981 $text = $attr=$close = $tag = false;
987 $tag = strtok(substr($string, $l+1, $p-$l-1), " ");
988 if ((strncmp($tag, "![CDATA[", 8)==0) && ($p = strpos($string, "]]>", $l))) {
989 $text = substr($string, $l+9, $p-$l-9);
993 $text = xml_decode_entities(substr($string, 0, $l));
995 $attr = strtok("\000");
996 $close = $attr && ($attr[strlen($attr)-1]=="/");
997 $string = substr($string, $p+1);
1000 #-- insert text/body content into array
1010 #-- recurse for tags
1011 if ($tag && ($tag[0] >= 'A')) { #-- don't read <? <! </ pseudo-tags
1013 $r["$tag,$n"] = array();
1014 $new = &$r["$tag,$n"];
1016 # $r[] = array($tag => array());
1017 # $new = &$r[count($r)-1][$tag];
1020 xml2array_parse($string, $new, $compact);
1024 #-- stop if no more tags or content
1025 if (empty($tag) && empty($text) || empty($string)) {
1028 } while ($tag[0] != "/");
1032 #-- Uses the XML extension of PHP to convert an XML stream into the
1033 # compact array representation.
1034 function xml2array_php(&$xml, $compact=1) {
1036 $p = xml_parser_create(xml_which_charset($xml));
1037 xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, false);
1038 xml_parser_set_option($p, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
1040 xml_parse_into_struct($p, $xml, $struct);
1042 $a = array(); // will hold all tag nodes
1043 $tree = array(&$a); // stack of pointers to last node of any tree level
1044 $in = &$a; // pointer to last created node
1046 foreach ($struct as $t) {
1050 $depth = count($tree) - 1;
1051 $in = &$tree[$depth];
1052 $tag .= "," . count($in);
1053 //echo "#$depth, TAG=\"$tag\", TYP=$type, LEV=$level, VAL=$value\n";
1059 $in[$tag] = array();
1060 if ($type=="open") {
1061 $tree[] = &$in[$tag];
1063 if (isset($value) && trim($value)) {
1064 $in[$tag][",0"] = $value;
1070 $in[$tag] = array();
1071 if (isset($value) && trim($value)) {
1072 $in[$tag][",0"] = $value;
1081 #-- CdATA - usually just whitespace
1083 if (isset($value) && trim($value)) {
1084 $in[",".count($in)] = $value;
1089 // case "attribute":
1090 // and anything else we do not want
1100 function xml_which_charset(&$xml) {
1101 return( strpos(strtok($xml, "\n"), '-8859-1"') ? "iso-8859-1" : "utf-8" );
1106 ############################################################################
1108 # simplified XML creator #
1110 ############################################################################
1113 #-- This is the opposite of the above xml2array, and can also work with the
1114 # so called $compact format.
1115 function array2xml($r, $compact=1, $ins="") {
1116 $string = "<?xml version=\"1.0\" $ins?>";
1117 array2xml_push($string, $r, $compact);
1122 #-- Recursively throws out the XMLified tree generated by the xml2array()
1123 # 'parser' function.
1124 function array2xml_push(&$string, &$r, $compact, $ind=-1) {
1125 $old_ind = ++$ind - 1;
1126 if ($old_ind < 0) { $old_ind = 0; }
1127 foreach ($r as $i=>$d) {
1129 if (is_scalar($d)) {
1130 $string .= xml_entities($d);
1132 elseif (is_array($d)) {
1134 $i = strtok($i, ",");
1136 $ls = str_repeat(" ", $ind);
1137 $string .= "\n$ls<$i>";
1138 $string .= array2xml_push($string, $d, $compact, $ind);
1139 $ls = str_repeat(" ", $old_ind);
1140 $string .= "</$i>\n$ls";