c02292fe9d045af5e1606e57dcd8b5d57038dbe2
[atutor.git] / mods / wiki / plugins / lib / xmlrpc.php
1 <?php define("XMLRPC_VERSION", "0.3.10");
2 #
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.
6 #
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
16 #
17 #  This is Public Domain. (c) 2004 WhoEver wants to. [milky*erphesfurt·de]
18
19
20 #-- config
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
29
30 #-- _server() settings
31 define("XMLRPC_LOG", "/tmp/xmlrpc.".@$_SERVER["SERVER_NAME"].".log");
32
33 #-- general data
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"));
41
42 #-- init
43 error_reporting(0);
44 if (isset($_SERVER["HTTP_CONTENT_TYPE"]) && empty($_SERVER["CONTENT_TYPE"])) {
45    $_SERVER["CONTENT_TYPE"] = $_SERVER["HTTP_CONTENT_TYPE"];   // older CGI implementations
46 }
47
48
49
50
51 ############################################################################
52 #                                                                          #
53 #  client part                                                             #
54 #                                                                          #
55 ############################################################################
56
57
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.
63 #
64 function xmlrpc($server, $method=NULL /*, ... */) {
65    if ($method) {
66       $params = func_get_args();
67       shift($params); shift($params);
68       return
69         xmlrpc_request($server, $method, $params);
70    }
71    else {
72       return
73         new xmlrpc_connection($server);
74    }
75 }
76
77
78
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;
82    
83    #-- init whole lib for request (we are not-OO here)
84    $xmlrpc_error = false;
85    $xmlrpc_response_headers = array();
86    
87    #-- encapsulate req, transmit it
88    $socket = xmlrpc_request_send($url, $method, $params, $plus, $gzip);
89    if (!$socket) {
90       return xmlrpc_error(-32768, "no connection", 0, "GLOBALVARS");
91    }
92
93    #-- wait for, read response
94    $response = "";
95    while (!feof($socket) && (strlen($DATA) <= 768<<10)) {
96       $response .= fread($socket, 4<<10);
97    }
98    fclose($socket);
99    if (XMLRPC_DEBUG >= 3) {
100       echo "<code>$response</code>";
101    }
102
103    #-- decode answer and give results
104    return xmlrpc_response_decode($response);
105 }
106
107
108 #-- an alias
109 function xmlrpc_call($url, $method, $params=array(), $plus=XMLRPC_PLUS, $gzip=0) {
110    return xmlrpc_request($url, $method, $params, $plus, $gzip);
111 }
112
113
114
115 #-- marshall request parameters into array, hash, xml string
116 function xmlrpc_request_send($url, $method, &$params, $plus, $gzip, $blocking=true) {
117
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"], "+")) {
124       $plus++;
125    }
126    if (strpos($c["scheme"], "gzip")) {
127       $gzip++;
128    }
129    if (!$host) { return(NULL); }
130    $inj = "";
131    if ($str = $c["user"]) {
132       if ($c["pass"]) { $str .= ":" . $c["pass"]; }
133       $inj = "Authorization: Basic " . base64_encode($str) . "\n";
134    }
135    
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);
139
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);
144    }
145    else {
146       echo "Could not connect to '<b>$host</b>:$port$path' - error $io_err: $io_err_s.<br>\n";
147       return(NULL);
148    }
149    fputs($socket, $request);
150
151    #-- done here
152    return($socket);
153 }
154
155
156 #-- marshall function call into XML+HTTP string
157 function xmlrpc_request_marshall($method, &$params) {
158
159    #-- use xmlrpc-epi
160    if (XMLRPC_FAST && XMLRPC_EPI) {
161       $query = xmlrpc_encode_request($method, $params);
162       return($query);
163    }
164
165    #-- build query
166    $query = array(
167       "methodCall" => array(
168          "methodName" => array( ",0"=>$method ),
169          "params" => array()
170       )
171    );
172    foreach ($params as $i=>$p) {
173       $query["methodCall"]["params"]["param,$i"] = xmlrpc_compact_value($p);
174    }
175    $query = array2xml($query, 1, 'encoding="'.XMLRPC_CHARSET.'" ');
176
177    #-- encode?
178    if (XMLRPC_AUTO_UTF8) {
179       $query = utf8_encode($query);
180    }
181    
182    return($query);   
183 }
184
185
186 #-- enclose body into HTTP request string
187 function xmlrpc_request_http(&$query, $path, $host, $plus, $gzip, $inj_header="") {
188
189    #-- build request
190    $n = "\015\012";
191    $request = "POST $path HTTP/1.0$n"
192             . "Host: $host$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";
199
200    #-- compress?
201    if ($gzip) {
202       $query = gzdeflate($query);
203       $request .= "Content-Encoding: deflate$n";
204    }
205    $request .= "Content-Length: " . strlen($query) . "$n" . "$n";
206    $request .= $query . "$n";
207
208    return($request);
209 }
210
211
212 #-- unpack response from HTTP and XML representation
213 function xmlrpc_response_decode(&$response) {
214    global $xmlrpc_response_headers;
215
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);
222    }
223    else {
224       $head = substr($response, 0, $l1);
225       $response = substr($response, $l2+2);
226    }
227
228    #-- decode headers, decompress body
229    foreach (explode("\n", $head) as $line) {
230       $xmlrpc_response_headers[strtolower(trim(strtok($line, ":")))] = trim(strtok("\000"));
231    }
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));
235       }
236       elseif (($enc == "compress") || ($enc == "x-compress")) {
237          $response = gzuncompress($response);
238       }
239       elseif (($enc == "deflate") || ($enc == "x-deflate")) {
240          $response = gzinflate($response);
241       }
242    }
243
244    $r = xmlrpc_response_unmarshall($response);
245    if (XMLRPC_DEBUG) {var_dump($r);}
246    return($r);
247 }
248
249
250 #-- decode XML-RPC from string into array and extract its actual meaning
251 function xmlrpc_response_unmarshall(&$response) {
252    global $xmlrpc_response_headers;
253
254    #-- strip encoding
255    if (XMLRPC_AUTO_UTF8) {
256       xmlrpc_decode_utf8xml($response, @$xmlrpc_response_headers["content-type"].@$xmlrpc_response_headers["content-charset"]);
257    }
258
259    if (XMLRPC_DEBUG >= 4) { fwrite(fopen("/tmp/xmlrpc:resp_in_xml","w"), $response); }
260    
261    #-- use xmlrpc-epi
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");
267       }
268       else {
269          return($r);
270       }
271    }
272
273
274    #-- unmarshall XML
275    $response = xml2array($response);
276
277    #-- fetch content (one returned element)
278    if ($r = @$response["methodResponse,0"]["params,0"]["param,0"]["value,0"]) {
279       $r = xmlrpc_decode_value($r);
280       return($r);
281    }
282   
283    #-- error cases
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");
287    }
288    else {
289       return xmlrpc_error(-32600, "xml+rpc: invalid response", 0, "GLBLVRS");
290    }
291    return(NULL);
292 }
293
294
295
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 {
299
300    #-- init
301    function xmlrpc_connection($url, $autodiscovery=0) {
302       global $xmlrpc_response_headers;
303       $this->server = $url;
304       $this->plus = 0;
305       $this->gzip = 0;
306
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);
311       
312       #-- auto-discovery, create 'method' names
313       if ($funcs && (XMLRPC_AUTODISCOVERY || $autodiscovery)) {
314          foreach ($funcs as $fn) {
315             $short = $fn;
316             if ($l = strpos($fn, ".")) {
317                $short = substr($fn, $l + 1);
318                if (substr($fn, 0, $l) == "system") { continue; }
319             }
320             $this->short = create_function("", "return xmlrpc_request('{$this->server}','$fn',func_get_args(),{$this->plus},{$this->gzip});");
321          }
322       }
323    }
324    
325    #-- generical call (needs func name)
326    function call($method /*, ... */) {
327       $params = func_get_args();
328       shift($params);
329       $r = xmlrpc_request($this->serverm, $method, $params, $this->plus, $this->gzip);
330       return($r);
331    }
332 }
333
334 #-- an alias
335 class xmlrpc extends xmlrpc_connection {
336 }
337
338
339
340
341 ############################################################################
342 #                                                                          #
343 #  server implementation                                                   #
344 #                                                                          #
345 ############################################################################
346
347
348 #-- Check request and execute function if registered in $xmlrpc_methods[]
349 #   array.
350 function xmlrpc_server() {
351
352    global $xmlrpc_methods;
353
354    #-- server is active
355    define("XMLRPC_SERVER", getmypid());
356    if (XMLRPC_DEBUG) { error_reporting(E_ALL^E_NOTICE); }
357    ob_start();
358
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");
365
366    #-- fixes for PHP/Apache
367    if (function_exists("getallheaders")) {
368       foreach (getallheaders() as $i=>$v) {
369          $_SERVER[strtoupper(strtr("HTTP_$i", "-", "_"))] = $v;
370       }
371    }
372
373    #-- check and get call
374    $allowed = array(
375       "REQUEST_METHOD" => array("POST", "PUT", "CALL"),
376       "CONTENT_TYPE" => array(XMLRPC_MIME_NEW, XMLRPC_MIME_OLD),
377    );
378    foreach ($allowed as $WHAT=>$WHICH) {
379       if (!in_array(trim(strtok($WRONG=$_SERVER[$WHAT], ";,(")), $WHICH)) {
380          header("Status: 400 Go Away, Stupid!");
381          if (!$WRONG) {
382             $WRONG = "undefined";
383          }
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");
386       }
387    }
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");
391    }
392
393    #-- decipher incoming XML request string
394    $method = "";
395    if (XMLRPC_FAST && XMLRPC_EPI) {
396       $params = xmlrpc_decode_request($xml_request, $method);
397       xmlrpc_epi_decode_xtypes($params);
398    }
399    else {
400       $params = xmlrpc_request_unmarshall($xml_request, $method);
401    }
402
403    
404    #-- add the few system.methods()
405    //if (empty($xmlrpc_methods)) {
406    //   $xmlrpc_methods = get_defined_functions();
407    //}
408    $xmlrpc_methods["system"] = "xmlrpc_system_methods";   # a class
409
410    #-- call
411    $result = xmlrpc_exec_method($method, $params);
412
413    #-- send back result
414    if (isset($result)) {
415       if (isset($result)) {
416          $resp["methodResponse"]["params"]["param"] = xmlrpc_compact_value($result);
417       }
418       else {
419          $resp["methodResponse"]["params"] = array();
420       }
421
422       xmlrpc_send_response($resp);
423    }
424    else {
425       $result = xmlrpc_error(0, "No Result");
426       xmlrpc_send_response($result);
427    }
428 }
429
430
431
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) {
435
436    #-- mangle charset
437    if (XMLRPC_AUTO_UTF8) {
438       xmlrpc_decode_utf8xml($xml_request, $_SERVER["CONTENT_TYPE"].$_SERVER["HTTP_CONTENT_CHARSET"]);
439    }
440
441    #-- decode XML string into PHP arrays
442    $call = xml2array($xml_request, 1);
443    $xml_request = NULL;
444
445    $call = $call["methodCall,0"];
446    if (!$call) {
447       xmlrpc_send_response(xmlrpc_error(-32600, "Bad Request, <methodCall> missing"));
448    }
449    $method = $call["methodName,0"][",0"];
450    if (!$method) {
451       xmlrpc_send_response(xmlrpc_error(-32600, "Bad Request, <methodName> missing"));
452    } 
453
454    $params = array();
455    foreach ($call["params,1"] as $uu => $param) {
456       $params[] = xmlrpc_decode_value($param["value,0"]);
457    }
458
459    return($params);
460 }
461
462
463
464 #-- Call the requested method (using the XML-method to PHP-function mapping
465 #   table and hints).
466 function xmlrpc_exec_method($method, $params) {
467
468    global $xmlrpc_methods;
469    if (XMLRPC_DEBUG >= 2) { error_reporting(E_ALL^E_NOTICE); }
470
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) )
477    {
478       xmlrpc_send_response(xmlrpc_error(-32601));
479    }
480
481    #-- real function call
482    if ($php_func_name = $xmlrpc_methods[$method]) {
483       $rf = $method = $php_func_name;
484    }
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))); }
488       return($result);
489    }
490    #-- PHP object method calls
491    else {
492       $class = strtok($method, ".");
493       $method = strtok("\000");
494       if ($uu = $xmlrpc_methods[$class]) {
495          $class = $uu;
496       }
497       if ($class && class_exists($class) && $method) {
498          $obj = new $class;
499          if (method_exists($obj, $method)) {
500             $result = call_user_method_array($method, $obj, $params);  //<DEPRECATED>
501             return($result);
502          }
503       }
504    }
505
506    #-- else error
507    xmlrpc_send_response(xmlrpc_error(-32601));
508 }
509
510
511
512 #-- Get POST data from PHP (if it gives it to us).
513 function xmlrpc_fetch_post_chunk() {
514    global $HTTP_RAW_POST_DATA;
515
516    $data = false;
517    if ($f = fopen("php://input", "rb")) {
518       $data = fread($f, 0x0100000);
519       fclose($f);
520    }
521    if (empty($data)) {
522       ini_set("always_populate_raw_post_data", "true");  // well, maybe(!?)
523       $data = $HTTP_RAW_POST_DATA;
524       $HTTP_RAW_POST_DATA = "";
525    }
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)) {
529       $data = $pf($data);
530    }
531    return($data);
532 }
533
534
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);
540    }
541 }
542
543
544
545 #-- Creates an error object.
546 function xmlrpc_error($no=-32500, $str="", $type=1, $into_vars=0) {
547    global $xmlrpc_error, $xmlrpc_errorcode;
548    $errors = array(
549            0 => "No Result",
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",
561    );
562    #-- build response xml/array
563    if (!($str) && !($str = $errors[$no])) {
564       $str = "Unknown Error";
565    }
566    if ($into_vars && !XMLRPC_OO) {
567       $xmlrpc_error = $str;
568       $xmlrpc_errorcode = $no;
569       return(NULL);
570    }
571    else {
572       return new xmlrpc_error($no, $str, $type);
573    }
574 }
575
576
577 #-- error object
578 class xmlrpc_error {
579
580    var $type = 1;   // else an HTTP error
581    var $no;
582    var $str;
583    
584    function xmlrpc_error($no, $str, $type=1) {
585       $this->type = $type;
586       $this->no = $no;
587       $this->str = $str;
588    }
589    
590    function send() {
591       $error = xmlrpc_compact_value(array(
592          "faultCode" => $no,
593          "faultString" => $str,
594       ));
595       $resp = array(
596          "methodResponse" => array(
597             "fault" => $error
598          )
599       );
600       xmlrpc_send_response($resp);
601    }
602 }
603
604
605 #-- Sends a response.
606 function xmlrpc_send_response($r) {
607
608    #-- error objects send itself (by calling _send_response() again ;-)
609    if (is_object($r)) {
610       $r->send();
611    }
612
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\"");
617    
618    #-- make XML document from it
619    if (is_array($r)) {
620       $r = array2xml($r, 1, 'encoding="'.$cs.'" ');
621    }
622
623    #-- compress answer?
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");
629          $r = $pf($r);
630       }
631    }
632
633    #-- send
634    if (ob_get_level()) {
635       #-- this prevents that PHP errors appear as garbage in our response
636       $add .= "<!--\n" . ob_get_contents() . "\n-->";
637       ob_end_clean();
638    }
639    header("Content-Length: " . strlen($r));
640    print $r . $add;
641    die;
642 }
643
644
645
646 #-- Provides "system.*" method namespace.
647 class xmlrpc_system_methods {
648
649    function listMethods() {
650       global $xmlrpc_methods;
651       $r = array();
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;
657             }
658          }
659          else {
660             $r[] = $real;
661          }
662       }
663       return($r);
664    }
665
666    function time() {
667       return new xmlrpc_datetime(time());
668    }
669 }
670
671
672 ############################################################################
673 #                                                                          #
674 #  misc functions                                                          #
675 #                                                                          #
676 ############################################################################
677
678
679 function xmlrpc_log($message) {
680 }
681
682 function xmlrpc_die($error="", $str="") {
683 }
684
685
686
687 ############################################################################
688 #                                                                          #
689 #  data representation mangling                                            #
690 #                                                                          #
691 ############################################################################
692
693
694 #-- Makes compact-array2xml datavar from a PHP variable.
695 function xmlrpc_compact_value($var, $n=0) {
696
697    #-- create compact-array2xml tree
698    $root = array(
699       "value,$n" => array(),
700    );
701    $r = &$root["value,$n"];
702
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);
707       }
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);
710       }
711    }
712
713    #-- complex types
714    if (is_object($var)) {
715       $r = $var->out();
716    }
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));
724          }
725       }
726       else {
727          $r = array("struct,$n"=>array());
728          $r = &$r["struct,$n"];
729          $n = 0;
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));
734             $n++;
735          }
736       }
737    }
738    #-- simple types
739    elseif (is_bool($var)) {
740       $r = array(
741          "boolean,$n" => array(",0" => ($var?1:0)),
742       );
743    }
744    elseif (is_int($var)) {
745       $r = array(
746          "int,$n" => array(",0" => $var),
747       );
748    }
749    elseif (is_float($var)) {
750       $r = array(
751          "double,$n" => array(",0" => $var),
752       );
753    }
754    elseif (is_string($var)) {
755       $r = array(
756          "string,$n" => array(",0" => $var),
757       );
758    }
759    return($root);
760 }
761
762
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) {
767    $val = NULL;
768    foreach ($value as $i=>$d) {
769
770       #-- use single (text) content xml2array entry as actual $d var
771       if (is_array($d) && isset($d[",0"])) {
772          $d = $d[",0"];
773       }
774
775       #-- convert into PHP var based on type
776       $type = strtok($i, ",");
777       switch ($type) {
778
779          case "array":
780             $val = array();
781             foreach ($d["data,0"] as $i=>$value) {
782                $val[] = xmlrpc_decode_value($value);
783             }
784             break;
785
786          case "struct":
787             $val = array();
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"]);
791                }
792             }
793             break;
794
795          case "":    # handles also '<value>s</value>' instead
796          case "0":   # of '<value><string>s</string></value>'
797          case "string":
798             $val =  is_array($d) ? "" : (string)$d;
799             break;
800
801          case "base64":
802             $val = (XMLRPC_AUTO_TYPES>=2) ? base64_decode($d) : (string)$d;
803             if ((XMLRPC_AUTO_UTF8 >= 2) && ($uu = utf8_decode($val))) {
804                $val = $uu;
805             }
806             break;
807             
808       // case "real":  case "float":   // neither is allowed
809          case "double":
810             $val = (double)$d;
811             break;
812          case "i4":
813          case "int":
814             $val = (int)$d;
815             break;
816
817          case "boolean":
818             $val = (boolean)$d;
819             break;
820
821          case "dateTime.iso8601":
822             $val = xmlrpc_strtotime($d);
823             break;
824
825          default:
826             if (defined("XMLRPC_SERVER")) {
827                xmlrpc_send_response(xmlrpc_error(-32600, "Unknown data type '$type'"));
828             }
829             else {
830                echo $xmlrpc_error = "UNKNOWN XML-RPC DATA TYPE '$type'<br>\n";
831                $xmlrpc_errorcode = -32207;
832             }
833 #           echo "<!-- UNKNOWN TYPE $type -->\n";
834 #           xmlrpc_log("bad data type '$type' enountered");
835       }
836    }
837    return($val);
838 }
839
840
841 #-- More complex XML-RPC data types need object representation to
842 #   distinguish them from ordinary string and integer vars.
843 class xmlrpc_xtype {
844    var $scalar = "";
845    var $xmlrpc_type = "string";
846    var $tag = "string";
847    function xmlrpc_type($str) {
848       $this->data = $str;
849    }
850    function out() {
851       return array($this->tag.",0" => array(",0"=>$this->scalar));
852    }
853 }
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);
860       }
861       if (!preg_match("/^[=\w\s]+$/", $str)) {
862          $this->encode=1;
863       }
864       $this->scalar = $str;
865    }
866    function out() {
867       if (isset($this->encode)) {
868          $this->scalar = chunk_split(base64_encode($this->scalar), 74, "\n");
869       }
870       return xmlrpc_xtype::out();
871    }
872 }
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);
880       }
881       $this->scalar = $t;
882    }
883 }
884
885 #-- Further simplify use of the above ones.
886 function xmlrpc_base64($string) {
887    return(new xmlrpc_base64($string));
888 }
889 function xmlrpc_datetime($timestr) {
890    return(new xmlrpc_datetime($timestr));
891 }
892
893
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));
898    return($t);
899 }
900 function xmlrpc_timetostr($time) {
901    return(gmstrftime("%Y%m%dT%T", $time));
902 }
903
904
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)) {
909          $r = $r->timestamp;
910       }
911       else {
912          $r = $r->scalar;
913       }
914    }
915    elseif (is_array($r)) {
916       foreach ($r as $i=>$v) {
917          xmlrpc_epi_decode_xtypes($r[$i]);
918       }
919    }
920 }
921
922
923
924
925 ############################################################################
926 #                                                                          #
927 #  simplified XML parser                                                   #
928 #                                                                          #
929 ############################################################################
930
931
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) {
935    $e = array(
936       "&" => "&amp;",
937       "<" => "&lt;",
938 //      ">" => "&gt;",
939    );
940    return(strtr($str, $e));
941 }
942 function xml_decode_entities($str) {
943    $e = array(
944       "&lt;" => "<",
945       "&gt;" => ">",
946       "&apos;" => "'",
947       "&quot;" => '"',
948       "&amp;" => "&",
949    );
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);
953    }
954    return(strtr($str, $e));
955 }
956
957
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") {
962    $r = array();
963    if (function_exists("xml_parser_create") && (strlen($xml) >= 512)) {
964       $r = xml2array_php($xml);
965    }
966    else {
967       xml2array_parse($xml, $r, $compact);
968    }
969    return($r);
970 }
971
972
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) {
976    $n = 0;
977    do {
978       #-- split chunks
979       $l = strpos($string, "<");
980       $p = strpos($string, ">", $l);
981       $text = $attr=$close = $tag = false;
982       if ($l === false) {
983          $text = $string;
984          $string = false;
985       }
986       else {
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);
990          }
991          else {
992             if ($l) {
993                $text = xml_decode_entities(substr($string, 0, $l));
994             }
995             $attr = strtok("\000");
996             $close = $attr && ($attr[strlen($attr)-1]=="/");
997             $string = substr($string, $p+1);
998          }
999       }
1000       #-- insert text/body content into array
1001       if (trim($text)) {
1002 #         if ($compact) {
1003              $r[",$n"] = $text;
1004 #         }
1005 #         else {
1006 #            $r[] = $text;
1007 #         }
1008          $n++;
1009       }
1010       #-- recurse for tags
1011       if ($tag && ($tag[0] >= 'A')) {    #-- don't read <? <! </ pseudo-tags
1012 #         if ($compact) {
1013              $r["$tag,$n"] = array();
1014              $new = &$r["$tag,$n"];
1015 #         } else {
1016 #            $r[] = array($tag => array());
1017 #            $new = &$r[count($r)-1][$tag];
1018 #         }
1019          if (!$close) {
1020             xml2array_parse($string, $new, $compact);
1021          }
1022          $n++;
1023       }
1024       #-- stop if no more tags or content
1025       if (empty($tag) && empty($text) || empty($string)) {
1026          $tag = "/";
1027       }
1028    } while ($tag[0] != "/");
1029 }
1030
1031
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) {
1035
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");
1039
1040    xml_parse_into_struct($p, $xml, $struct);
1041
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
1045
1046    foreach ($struct as $t) {
1047       unset($value);
1048       extract($t);
1049
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";
1054
1055       switch ($type[1]) {
1056
1057          #--  OpEN
1058          case "p":
1059             $in[$tag] = array();
1060             if ($type=="open") {
1061                $tree[] = &$in[$tag];
1062             }
1063             if (isset($value) && trim($value)) {
1064                $in[$tag][",0"] = $value;
1065             }
1066             break;
1067
1068          #--  CoMPLETE
1069          case "o":
1070             $in[$tag] = array();
1071             if (isset($value) && trim($value)) {
1072                $in[$tag][",0"] = $value;
1073             }
1074             break;
1075
1076          #--  ClOSE
1077          case "l":
1078             array_pop($tree);
1079             break;
1080
1081          #--  CdATA - usually just whitespace
1082          case "d":
1083             if (isset($value) && trim($value)) {
1084                $in[",".count($in)] = $value;
1085             }
1086             break;
1087          
1088          default:
1089             // case "attribute":
1090             // and anything else we do not want
1091       }
1092       
1093    }
1094    
1095    return($a);
1096 }
1097
1098
1099
1100 function xml_which_charset(&$xml) {
1101    return( strpos(strtok($xml, "\n"), '-8859-1"') ? "iso-8859-1" : "utf-8" );
1102 }
1103
1104
1105
1106 ############################################################################
1107 #                                                                          #
1108 #  simplified XML creator                                                  #
1109 #                                                                          #
1110 ############################################################################
1111
1112
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);
1118    return($string);
1119 }
1120
1121
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) {
1128       $d = &$r[$i];
1129       if (is_scalar($d)) {
1130          $string .= xml_entities($d);
1131       }
1132       elseif (is_array($d)) {
1133          if ($compact) {
1134             $i = strtok($i, ","); 
1135          }
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";
1141       }
1142    }
1143 }
1144
1145
1146
1147
1148 ?>