1<?php
2
3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5/**
6 * PHP implementation of the XML-RPC protocol
7 *
8 * This is a PEAR-ified version of Useful inc's XML-RPC for PHP.
9 * It has support for HTTP transport, proxies and authentication.
10 *
11 * PHP versions 4 and 5
12 *
13 * @category   Web Services
14 * @package    XML_RPC
15 * @author     Edd Dumbill <edd@usefulinc.com>
16 * @author     Stig Bakken <stig@php.net>
17 * @author     Martin Jansen <mj@php.net>
18 * @author     Daniel Convissor <danielc@php.net>
19 * @copyright  1999-2001 Edd Dumbill, 2001-2010 The PHP Group
20 * @license    http://www.php.net/license/3_01.txt  PHP License
21 * @version    SVN: $Id: RPC.php 315594 2011-08-27 01:03:57Z danielc $
22 * @link       http://pear.php.net/package/XML_RPC
23 */
24
25
26if (!function_exists('xml_parser_create')) {
27    include_once 'PEAR.php';
28    PEAR::loadExtension('xml');
29}
30
31/**#@+
32 * Error constants
33 */
34/**
35 * Parameter values don't match parameter types
36 */
37define('XML_RPC_ERROR_INVALID_TYPE', 101);
38/**
39 * Parameter declared to be numeric but the values are not
40 */
41define('XML_RPC_ERROR_NON_NUMERIC_FOUND', 102);
42/**
43 * Communication error
44 */
45define('XML_RPC_ERROR_CONNECTION_FAILED', 103);
46/**
47 * The array or struct has already been started
48 */
49define('XML_RPC_ERROR_ALREADY_INITIALIZED', 104);
50/**
51 * Incorrect parameters submitted
52 */
53define('XML_RPC_ERROR_INCORRECT_PARAMS', 105);
54/**
55 * Programming error by developer
56 */
57define('XML_RPC_ERROR_PROGRAMMING', 106);
58/**#@-*/
59
60
61/**
62 * Data types
63 * @global string $GLOBALS['XML_RPC_I4']
64 */
65$GLOBALS['XML_RPC_I4'] = 'i4';
66
67/**
68 * Data types
69 * @global string $GLOBALS['XML_RPC_Int']
70 */
71$GLOBALS['XML_RPC_Int'] = 'int';
72
73/**
74 * Data types
75 * @global string $GLOBALS['XML_RPC_Boolean']
76 */
77$GLOBALS['XML_RPC_Boolean'] = 'boolean';
78
79/**
80 * Data types
81 * @global string $GLOBALS['XML_RPC_Double']
82 */
83$GLOBALS['XML_RPC_Double'] = 'double';
84
85/**
86 * Data types
87 * @global string $GLOBALS['XML_RPC_String']
88 */
89$GLOBALS['XML_RPC_String'] = 'string';
90
91/**
92 * Data types
93 * @global string $GLOBALS['XML_RPC_DateTime']
94 */
95$GLOBALS['XML_RPC_DateTime'] = 'dateTime.iso8601';
96
97/**
98 * Data types
99 * @global string $GLOBALS['XML_RPC_Base64']
100 */
101$GLOBALS['XML_RPC_Base64'] = 'base64';
102
103/**
104 * Data types
105 * @global string $GLOBALS['XML_RPC_Array']
106 */
107$GLOBALS['XML_RPC_Array'] = 'array';
108
109/**
110 * Data types
111 * @global string $GLOBALS['XML_RPC_Struct']
112 */
113$GLOBALS['XML_RPC_Struct'] = 'struct';
114
115
116/**
117 * Data type meta-types
118 * @global array $GLOBALS['XML_RPC_Types']
119 */
120$GLOBALS['XML_RPC_Types'] = array(
121    $GLOBALS['XML_RPC_I4']       => 1,
122    $GLOBALS['XML_RPC_Int']      => 1,
123    $GLOBALS['XML_RPC_Boolean']  => 1,
124    $GLOBALS['XML_RPC_String']   => 1,
125    $GLOBALS['XML_RPC_Double']   => 1,
126    $GLOBALS['XML_RPC_DateTime'] => 1,
127    $GLOBALS['XML_RPC_Base64']   => 1,
128    $GLOBALS['XML_RPC_Array']    => 2,
129    $GLOBALS['XML_RPC_Struct']   => 3,
130);
131
132
133/**
134 * Error message numbers
135 * @global array $GLOBALS['XML_RPC_err']
136 */
137$GLOBALS['XML_RPC_err'] = array(
138    'unknown_method'      => 1,
139    'invalid_return'      => 2,
140    'incorrect_params'    => 3,
141    'introspect_unknown'  => 4,
142    'http_error'          => 5,
143    'not_response_object' => 6,
144    'invalid_request'     => 7,
145);
146
147/**
148 * Error message strings
149 * @global array $GLOBALS['XML_RPC_str']
150 */
151$GLOBALS['XML_RPC_str'] = array(
152    'unknown_method'      => 'Unknown method',
153    'invalid_return'      => 'Invalid return payload: enable debugging to examine incoming payload',
154    'incorrect_params'    => 'Incorrect parameters passed to method',
155    'introspect_unknown'  => 'Can\'t introspect: method unknown',
156    'http_error'          => 'Didn\'t receive 200 OK from remote server.',
157    'not_response_object' => 'The requested method didn\'t return an XML_RPC_Response object.',
158    'invalid_request'     => 'Invalid request payload',
159);
160
161
162/**
163 * Default XML encoding (ISO-8859-1, UTF-8 or US-ASCII)
164 * @global string $GLOBALS['XML_RPC_defencoding']
165 */
166$GLOBALS['XML_RPC_defencoding'] = 'UTF-8';
167
168/**
169 * User error codes start at 800
170 * @global int $GLOBALS['XML_RPC_erruser']
171 */
172$GLOBALS['XML_RPC_erruser'] = 800;
173
174/**
175 * XML parse error codes start at 100
176 * @global int $GLOBALS['XML_RPC_errxml']
177 */
178$GLOBALS['XML_RPC_errxml'] = 100;
179
180
181/**
182 * Compose backslashes for escaping regexp
183 * @global string $GLOBALS['XML_RPC_backslash']
184 */
185$GLOBALS['XML_RPC_backslash'] = chr(92) . chr(92);
186
187
188/**
189 * Should we automatically base64 encode strings that contain characters
190 * which can cause PHP's SAX-based XML parser to break?
191 * @global boolean $GLOBALS['XML_RPC_auto_base64']
192 */
193$GLOBALS['XML_RPC_auto_base64'] = false;
194
195
196/**
197 * Valid parents of XML elements
198 * @global array $GLOBALS['XML_RPC_valid_parents']
199 */
200$GLOBALS['XML_RPC_valid_parents'] = array(
201    'BOOLEAN' => array('VALUE'),
202    'I4' => array('VALUE'),
203    'INT' => array('VALUE'),
204    'STRING' => array('VALUE'),
205    'DOUBLE' => array('VALUE'),
206    'DATETIME.ISO8601' => array('VALUE'),
207    'BASE64' => array('VALUE'),
208    'ARRAY' => array('VALUE'),
209    'STRUCT' => array('VALUE'),
210    'PARAM' => array('PARAMS'),
211    'METHODNAME' => array('METHODCALL'),
212    'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
213    'MEMBER' => array('STRUCT'),
214    'NAME' => array('MEMBER'),
215    'DATA' => array('ARRAY'),
216    'FAULT' => array('METHODRESPONSE'),
217    'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
218);
219
220
221/**
222 * Stores state during parsing
223 *
224 * quick explanation of components:
225 *   + ac     = accumulates values
226 *   + qt     = decides if quotes are needed for evaluation
227 *   + cm     = denotes struct or array (comma needed)
228 *   + isf    = indicates a fault
229 *   + lv     = indicates "looking for a value": implements the logic
230 *               to allow values with no types to be strings
231 *   + params = stores parameters in method calls
232 *   + method = stores method name
233 *
234 * @global array $GLOBALS['XML_RPC_xh']
235 */
236$GLOBALS['XML_RPC_xh'] = array();
237
238
239/**
240 * Start element handler for the XML parser
241 *
242 * @return void
243 */
244function XML_RPC_se($parser_resource, $name, $attrs)
245{
246    global $XML_RPC_xh, $XML_RPC_valid_parents;
247
248    $parser = (int) $parser_resource;
249
250    // if invalid xmlrpc already detected, skip all processing
251    if ($XML_RPC_xh[$parser]['isf'] >= 2) {
252        return;
253    }
254
255    // check for correct element nesting
256    // top level element can only be of 2 types
257    if (count($XML_RPC_xh[$parser]['stack']) == 0) {
258        if ($name != 'METHODRESPONSE' && $name != 'METHODCALL') {
259            $XML_RPC_xh[$parser]['isf'] = 2;
260            $XML_RPC_xh[$parser]['isf_reason'] = 'missing top level xmlrpc element';
261            return;
262        }
263    } else {
264        // not top level element: see if parent is OK
265        if (!in_array($XML_RPC_xh[$parser]['stack'][0], $XML_RPC_valid_parents[$name])) {
266            $name = preg_replace('@[^a-zA-Z0-9._-]@', '', $name);
267            $XML_RPC_xh[$parser]['isf'] = 2;
268            $XML_RPC_xh[$parser]['isf_reason'] = "xmlrpc element $name cannot be child of {$XML_RPC_xh[$parser]['stack'][0]}";
269            return;
270        }
271    }
272
273    switch ($name) {
274    case 'STRUCT':
275        $XML_RPC_xh[$parser]['cm']++;
276
277        // turn quoting off
278        $XML_RPC_xh[$parser]['qt'] = 0;
279
280        $cur_val = array();
281        $cur_val['value'] = array();
282        $cur_val['members'] = 1;
283        array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val);
284        break;
285
286    case 'ARRAY':
287        $XML_RPC_xh[$parser]['cm']++;
288
289        // turn quoting off
290        $XML_RPC_xh[$parser]['qt'] = 0;
291
292        $cur_val = array();
293        $cur_val['value'] = array();
294        $cur_val['members'] = 0;
295        array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val);
296        break;
297
298    case 'NAME':
299        $XML_RPC_xh[$parser]['ac'] = '';
300        break;
301
302    case 'FAULT':
303        $XML_RPC_xh[$parser]['isf'] = 1;
304        break;
305
306    case 'PARAM':
307        $XML_RPC_xh[$parser]['valuestack'] = array();
308        break;
309
310    case 'VALUE':
311        $XML_RPC_xh[$parser]['lv'] = 1;
312        $XML_RPC_xh[$parser]['vt'] = $GLOBALS['XML_RPC_String'];
313        $XML_RPC_xh[$parser]['ac'] = '';
314        $XML_RPC_xh[$parser]['qt'] = 0;
315        // look for a value: if this is still 1 by the
316        // time we reach the first data segment then the type is string
317        // by implication and we need to add in a quote
318        break;
319
320    case 'I4':
321    case 'INT':
322    case 'STRING':
323    case 'BOOLEAN':
324    case 'DOUBLE':
325    case 'DATETIME.ISO8601':
326    case 'BASE64':
327        $XML_RPC_xh[$parser]['ac'] = ''; // reset the accumulator
328
329        if ($name == 'DATETIME.ISO8601' || $name == 'STRING') {
330            $XML_RPC_xh[$parser]['qt'] = 1;
331
332            if ($name == 'DATETIME.ISO8601') {
333                $XML_RPC_xh[$parser]['vt'] = $GLOBALS['XML_RPC_DateTime'];
334            }
335
336        } elseif ($name == 'BASE64') {
337            $XML_RPC_xh[$parser]['qt'] = 2;
338        } else {
339            // No quoting is required here -- but
340            // at the end of the element we must check
341            // for data format errors.
342            $XML_RPC_xh[$parser]['qt'] = 0;
343        }
344        break;
345
346    case 'MEMBER':
347        $XML_RPC_xh[$parser]['ac'] = '';
348        break;
349
350    case 'DATA':
351    case 'METHODCALL':
352    case 'METHODNAME':
353    case 'METHODRESPONSE':
354    case 'PARAMS':
355        // valid elements that add little to processing
356        break;
357    }
358
359
360    // Save current element to stack
361    array_unshift($XML_RPC_xh[$parser]['stack'], $name);
362
363    if ($name != 'VALUE') {
364        $XML_RPC_xh[$parser]['lv'] = 0;
365    }
366}
367
368/**
369 * End element handler for the XML parser
370 *
371 * @return void
372 */
373function XML_RPC_ee($parser_resource, $name)
374{
375    global $XML_RPC_xh;
376
377    $parser = (int) $parser_resource;
378
379    if ($XML_RPC_xh[$parser]['isf'] >= 2) {
380        return;
381    }
382
383    // push this element from stack
384    // NB: if XML validates, correct opening/closing is guaranteed and
385    // we do not have to check for $name == $curr_elem.
386    // we also checked for proper nesting at start of elements...
387    $curr_elem = array_shift($XML_RPC_xh[$parser]['stack']);
388
389    switch ($name) {
390    case 'STRUCT':
391    case 'ARRAY':
392    $cur_val = array_shift($XML_RPC_xh[$parser]['valuestack']);
393    $XML_RPC_xh[$parser]['value'] = $cur_val['value'];
394        $XML_RPC_xh[$parser]['vt'] = strtolower($name);
395        $XML_RPC_xh[$parser]['cm']--;
396        break;
397
398    case 'NAME':
399    $XML_RPC_xh[$parser]['valuestack'][0]['name'] = $XML_RPC_xh[$parser]['ac'];
400        break;
401
402    case 'BOOLEAN':
403        // special case here: we translate boolean 1 or 0 into PHP
404        // constants true or false
405        if ($XML_RPC_xh[$parser]['ac'] == '1') {
406            $XML_RPC_xh[$parser]['ac'] = 'true';
407        } else {
408            $XML_RPC_xh[$parser]['ac'] = 'false';
409        }
410
411        $XML_RPC_xh[$parser]['vt'] = strtolower($name);
412        // Drop through intentionally.
413
414    case 'I4':
415    case 'INT':
416    case 'STRING':
417    case 'DOUBLE':
418    case 'DATETIME.ISO8601':
419    case 'BASE64':
420        if ($XML_RPC_xh[$parser]['qt'] == 1) {
421            // we use double quotes rather than single so backslashification works OK
422            $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac'];
423        } elseif ($XML_RPC_xh[$parser]['qt'] == 2) {
424            $XML_RPC_xh[$parser]['value'] = base64_decode($XML_RPC_xh[$parser]['ac']);
425        } elseif ($name == 'BOOLEAN') {
426            $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac'];
427        } else {
428            // we have an I4, INT or a DOUBLE
429            // we must check that only 0123456789-.<space> are characters here
430            if (!preg_match("@^[+-]?[0123456789 \t\.]+$@", $XML_RPC_xh[$parser]['ac'])) {
431                XML_RPC_Base::raiseError('Non-numeric value received in INT or DOUBLE',
432                                         XML_RPC_ERROR_NON_NUMERIC_FOUND);
433                $XML_RPC_xh[$parser]['value'] = XML_RPC_ERROR_NON_NUMERIC_FOUND;
434            } else {
435                // it's ok, add it on
436                $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac'];
437            }
438        }
439
440        $XML_RPC_xh[$parser]['ac'] = '';
441        $XML_RPC_xh[$parser]['qt'] = 0;
442        $XML_RPC_xh[$parser]['lv'] = 3; // indicate we've found a value
443        break;
444
445    case 'VALUE':
446        if ($XML_RPC_xh[$parser]['vt'] == $GLOBALS['XML_RPC_String']) {
447            if (strlen($XML_RPC_xh[$parser]['ac']) > 0) {
448                $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac'];
449            } elseif ($XML_RPC_xh[$parser]['lv'] == 1) {
450                // The <value> element was empty.
451                $XML_RPC_xh[$parser]['value'] = '';
452            }
453        }
454
455        $temp = new XML_RPC_Value($XML_RPC_xh[$parser]['value'], $XML_RPC_xh[$parser]['vt']);
456
457        $cur_val = array_shift($XML_RPC_xh[$parser]['valuestack']);
458        if (is_array($cur_val)) {
459            if ($cur_val['members']==0) {
460                $cur_val['value'][] = $temp;
461            } else {
462                $XML_RPC_xh[$parser]['value'] = $temp;
463            }
464            array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val);
465        } else {
466            $XML_RPC_xh[$parser]['value'] = $temp;
467        }
468        break;
469
470    case 'MEMBER':
471        $XML_RPC_xh[$parser]['ac'] = '';
472        $XML_RPC_xh[$parser]['qt'] = 0;
473
474        $cur_val = array_shift($XML_RPC_xh[$parser]['valuestack']);
475        if (is_array($cur_val)) {
476            if ($cur_val['members']==1) {
477                $cur_val['value'][$cur_val['name']] = $XML_RPC_xh[$parser]['value'];
478            }
479            array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val);
480        }
481        break;
482
483    case 'DATA':
484        $XML_RPC_xh[$parser]['ac'] = '';
485        $XML_RPC_xh[$parser]['qt'] = 0;
486        break;
487
488    case 'PARAM':
489        $XML_RPC_xh[$parser]['params'][] = $XML_RPC_xh[$parser]['value'];
490        break;
491
492    case 'METHODNAME':
493    case 'RPCMETHODNAME':
494        $XML_RPC_xh[$parser]['method'] = preg_replace("@^[\n\r\t ]+@", '',
495                                                      $XML_RPC_xh[$parser]['ac']);
496        break;
497    }
498
499    // if it's a valid type name, set the type
500    if (isset($GLOBALS['XML_RPC_Types'][strtolower($name)])) {
501        $XML_RPC_xh[$parser]['vt'] = strtolower($name);
502    }
503}
504
505/**
506 * Character data handler for the XML parser
507 *
508 * @return void
509 */
510function XML_RPC_cd($parser_resource, $data)
511{
512    global $XML_RPC_xh, $XML_RPC_backslash;
513
514    $parser = (int) $parser_resource;
515
516    if ($XML_RPC_xh[$parser]['lv'] != 3) {
517        // "lookforvalue==3" means that we've found an entire value
518        // and should discard any further character data
519
520        if ($XML_RPC_xh[$parser]['lv'] == 1) {
521            // if we've found text and we're just in a <value> then
522            // turn quoting on, as this will be a string
523            $XML_RPC_xh[$parser]['qt'] = 1;
524            // and say we've found a value
525            $XML_RPC_xh[$parser]['lv'] = 2;
526        }
527
528        // replace characters that eval would
529        // do special things with
530        if (!isset($XML_RPC_xh[$parser]['ac'])) {
531            $XML_RPC_xh[$parser]['ac'] = '';
532        }
533        $XML_RPC_xh[$parser]['ac'] .= $data;
534    }
535}
536
537/**
538 * The common methods and properties for all of the XML_RPC classes
539 *
540 * @category   Web Services
541 * @package    XML_RPC
542 * @author     Edd Dumbill <edd@usefulinc.com>
543 * @author     Stig Bakken <stig@php.net>
544 * @author     Martin Jansen <mj@php.net>
545 * @author     Daniel Convissor <danielc@php.net>
546 * @copyright  1999-2001 Edd Dumbill, 2001-2010 The PHP Group
547 * @license    http://www.php.net/license/3_01.txt  PHP License
548 * @version    Release: @package_version@
549 * @link       http://pear.php.net/package/XML_RPC
550 */
551class XML_RPC_Base {
552
553    /**
554     * PEAR Error handling
555     *
556     * @return object  PEAR_Error object
557     */
558    function raiseError($msg, $code)
559    {
560        include_once 'PEAR.php';
561        if (is_object(@$this)) {
562            return PEAR::raiseError(get_class($this) . ': ' . $msg, $code);
563        } else {
564            return PEAR::raiseError('XML_RPC: ' . $msg, $code);
565        }
566    }
567
568    /**
569     * Tell whether something is a PEAR_Error object
570     *
571     * @param mixed $value  the item to check
572     *
573     * @return bool  whether $value is a PEAR_Error object or not
574     *
575     * @access public
576     */
577    function isError($value)
578    {
579        return is_object($value) && is_a($value, 'PEAR_Error');
580    }
581}
582
583/**
584 * The methods and properties for submitting XML RPC requests
585 *
586 * @category   Web Services
587 * @package    XML_RPC
588 * @author     Edd Dumbill <edd@usefulinc.com>
589 * @author     Stig Bakken <stig@php.net>
590 * @author     Martin Jansen <mj@php.net>
591 * @author     Daniel Convissor <danielc@php.net>
592 * @copyright  1999-2001 Edd Dumbill, 2001-2010 The PHP Group
593 * @license    http://www.php.net/license/3_01.txt  PHP License
594 * @version    Release: @package_version@
595 * @link       http://pear.php.net/package/XML_RPC
596 */
597class XML_RPC_Client extends XML_RPC_Base {
598
599    /**
600     * The path and name of the RPC server script you want the request to go to
601     * @var string
602     */
603    var $path = '';
604
605    /**
606     * The name of the remote server to connect to
607     * @var string
608     */
609    var $server = '';
610
611    /**
612     * The protocol to use in contacting the remote server
613     * @var string
614     */
615    var $protocol = 'http://';
616
617    /**
618     * The port for connecting to the remote server
619     *
620     * The default is 80 for http:// connections
621     * and 443 for https:// and ssl:// connections.
622     *
623     * @var integer
624     */
625    var $port = 80;
626
627    /**
628     * A user name for accessing the RPC server
629     * @var string
630     * @see XML_RPC_Client::setCredentials()
631     */
632    var $username = '';
633
634    /**
635     * A password for accessing the RPC server
636     * @var string
637     * @see XML_RPC_Client::setCredentials()
638     */
639    var $password = '';
640
641    /**
642     * The name of the proxy server to use, if any
643     * @var string
644     */
645    var $proxy = '';
646
647    /**
648     * The protocol to use in contacting the proxy server, if any
649     * @var string
650     */
651    var $proxy_protocol = 'http://';
652
653    /**
654     * The port for connecting to the proxy server
655     *
656     * The default is 8080 for http:// connections
657     * and 443 for https:// and ssl:// connections.
658     *
659     * @var integer
660     */
661    var $proxy_port = 8080;
662
663    /**
664     * A user name for accessing the proxy server
665     * @var string
666     */
667    var $proxy_user = '';
668
669    /**
670     * A password for accessing the proxy server
671     * @var string
672     */
673    var $proxy_pass = '';
674
675    /**
676     * The error number, if any
677     * @var integer
678     */
679    var $errno = 0;
680
681    /**
682     * The error message, if any
683     * @var string
684     */
685    var $errstr = '';
686
687    /**
688     * The current debug mode (1 = on, 0 = off)
689     * @var integer
690     */
691    var $debug = 0;
692
693    /**
694     * The HTTP headers for the current request.
695     * @var string
696     */
697    var $headers = '';
698
699
700    /**
701     * Sets the object's properties
702     *
703     * @param string  $path        the path and name of the RPC server script
704     *                              you want the request to go to
705     * @param string  $server      the URL of the remote server to connect to.
706     *                              If this parameter doesn't specify a
707     *                              protocol and $port is 443, ssl:// is
708     *                              assumed.
709     * @param integer $port        a port for connecting to the remote server.
710     *                              Defaults to 80 for http:// connections and
711     *                              443 for https:// and ssl:// connections.
712     * @param string  $proxy       the URL of the proxy server to use, if any.
713     *                              If this parameter doesn't specify a
714     *                              protocol and $port is 443, ssl:// is
715     *                              assumed.
716     * @param integer $proxy_port  a port for connecting to the remote server.
717     *                              Defaults to 8080 for http:// connections and
718     *                              443 for https:// and ssl:// connections.
719     * @param string  $proxy_user  a user name for accessing the proxy server
720     * @param string  $proxy_pass  a password for accessing the proxy server
721     *
722     * @return void
723     */
724    function XML_RPC_Client($path, $server, $port = 0,
725                            $proxy = '', $proxy_port = 0,
726                            $proxy_user = '', $proxy_pass = '')
727    {
728        $this->path       = $path;
729        $this->proxy_user = $proxy_user;
730        $this->proxy_pass = $proxy_pass;
731
732        preg_match('@^(http://|https://|ssl://)?(.*)$@', $server, $match);
733        if ($match[1] == '') {
734            if ($port == 443) {
735                $this->server   = $match[2];
736                $this->protocol = 'ssl://';
737                $this->port     = 443;
738            } else {
739                $this->server = $match[2];
740                if ($port) {
741                    $this->port = $port;
742                }
743            }
744        } elseif ($match[1] == 'http://') {
745            $this->server = $match[2];
746            if ($port) {
747                $this->port = $port;
748            }
749        } else {
750            $this->server   = $match[2];
751            $this->protocol = 'ssl://';
752            if ($port) {
753                $this->port = $port;
754            } else {
755                $this->port = 443;
756            }
757        }
758
759        if ($proxy) {
760            preg_match('@^(http://|https://|ssl://)?(.*)$@', $proxy, $match);
761            if ($match[1] == '') {
762                if ($proxy_port == 443) {
763                    $this->proxy          = $match[2];
764                    $this->proxy_protocol = 'ssl://';
765                    $this->proxy_port     = 443;
766                } else {
767                    $this->proxy = $match[2];
768                    if ($proxy_port) {
769                        $this->proxy_port = $proxy_port;
770                    }
771                }
772            } elseif ($match[1] == 'http://') {
773                $this->proxy = $match[2];
774                if ($proxy_port) {
775                    $this->proxy_port = $proxy_port;
776                }
777            } else {
778                $this->proxy          = $match[2];
779                $this->proxy_protocol = 'ssl://';
780                if ($proxy_port) {
781                    $this->proxy_port = $proxy_port;
782                } else {
783                    $this->proxy_port = 443;
784                }
785            }
786        }
787    }
788
789    /**
790     * Change the current debug mode
791     *
792     * @param int $in  where 1 = on, 0 = off
793     *
794     * @return void
795     */
796    function setDebug($in)
797    {
798        if ($in) {
799            $this->debug = 1;
800        } else {
801            $this->debug = 0;
802        }
803    }
804
805    /**
806     * Sets whether strings that contain characters which may cause PHP's
807     * SAX-based XML parser to break should be automatically base64 encoded
808     *
809     * This is is a workaround for systems that don't have PHP's mbstring
810     * extension available.
811     *
812     * @param int $in  where 1 = on, 0 = off
813     *
814     * @return void
815     */
816    function setAutoBase64($in)
817    {
818        if ($in) {
819            $GLOBALS['XML_RPC_auto_base64'] = true;
820        } else {
821            $GLOBALS['XML_RPC_auto_base64'] = false;
822        }
823    }
824
825    /**
826     * Set username and password properties for connecting to the RPC server
827     *
828     * @param string $u  the user name
829     * @param string $p  the password
830     *
831     * @return void
832     *
833     * @see XML_RPC_Client::$username, XML_RPC_Client::$password
834     */
835    function setCredentials($u, $p)
836    {
837        $this->username = $u;
838        $this->password = $p;
839    }
840
841    /**
842     * Transmit the RPC request via HTTP 1.0 protocol
843     *
844     * @param object $msg       the XML_RPC_Message object
845     * @param int    $timeout   how many seconds to wait for the request
846     *
847     * @return object  an XML_RPC_Response object.  0 is returned if any
848     *                  problems happen.
849     *
850     * @see XML_RPC_Message, XML_RPC_Client::XML_RPC_Client(),
851     *      XML_RPC_Client::setCredentials()
852     */
853    function send($msg, $timeout = 0)
854    {
855        if (!is_object($msg) || !is_a($msg, 'XML_RPC_Message')) {
856            $this->errstr = 'send()\'s $msg parameter must be an'
857                          . ' XML_RPC_Message object.';
858            $this->raiseError($this->errstr, XML_RPC_ERROR_PROGRAMMING);
859            return 0;
860        }
861        $msg->debug = $this->debug;
862        return $this->sendPayloadHTTP10($msg, $this->server, $this->port,
863                                        $timeout, $this->username,
864                                        $this->password);
865    }
866
867    /**
868     * Transmit the RPC request via HTTP 1.0 protocol
869     *
870     * Requests should be sent using XML_RPC_Client send() rather than
871     * calling this method directly.
872     *
873     * @param object $msg       the XML_RPC_Message object
874     * @param string $server    the server to send the request to
875     * @param int    $port      the server port send the request to
876     * @param int    $timeout   how many seconds to wait for the request
877     *                           before giving up
878     * @param string $username  a user name for accessing the RPC server
879     * @param string $password  a password for accessing the RPC server
880     *
881     * @return object  an XML_RPC_Response object.  0 is returned if any
882     *                  problems happen.
883     *
884     * @access protected
885     * @see XML_RPC_Client::send()
886     */
887    function sendPayloadHTTP10($msg, $server, $port, $timeout = 0,
888                               $username = '', $password = '')
889    {
890        // Pre-emptive BC hacks for fools calling sendPayloadHTTP10() directly
891        if ($username != $this->username) {
892            $this->setCredentials($username, $password);
893        }
894
895        // Only create the payload if it was not created previously
896        if (empty($msg->payload)) {
897            $msg->createPayload();
898        }
899        $this->createHeaders($msg);
900
901        $op  = $this->headers . "\r\n\r\n";
902        $op .= $msg->payload;
903
904        if ($this->debug) {
905            print "\n<pre>---SENT---\n";
906            print $op;
907            print "\n---END---</pre>\n";
908        }
909
910        /*
911         * If we're using a proxy open a socket to the proxy server
912         * instead to the xml-rpc server
913         */
914        if ($this->proxy) {
915            if ($this->proxy_protocol == 'http://') {
916                $protocol = '';
917            } else {
918                $protocol = $this->proxy_protocol;
919            }
920            if ($timeout > 0) {
921                $fp = @fsockopen($protocol . $this->proxy, $this->proxy_port,
922                                 $this->errno, $this->errstr, $timeout);
923            } else {
924                $fp = @fsockopen($protocol . $this->proxy, $this->proxy_port,
925                                 $this->errno, $this->errstr);
926            }
927        } else {
928            if ($this->protocol == 'http://') {
929                $protocol = '';
930            } else {
931                $protocol = $this->protocol;
932            }
933            if ($timeout > 0) {
934                $fp = @fsockopen($protocol . $server, $port,
935                                 $this->errno, $this->errstr, $timeout);
936            } else {
937                $fp = @fsockopen($protocol . $server, $port,
938                                 $this->errno, $this->errstr);
939            }
940        }
941
942        /*
943         * Just raising the error without returning it is strange,
944         * but keep it here for backwards compatibility.
945         */
946        if (!$fp && $this->proxy) {
947            $this->raiseError('Connection to proxy server '
948                              . $this->proxy . ':' . $this->proxy_port
949                              . ' failed. ' . $this->errstr,
950                              XML_RPC_ERROR_CONNECTION_FAILED);
951            return 0;
952        } elseif (!$fp) {
953            $this->raiseError('Connection to RPC server '
954                              . $server . ':' . $port
955                              . ' failed. ' . $this->errstr,
956                              XML_RPC_ERROR_CONNECTION_FAILED);
957            return 0;
958        }
959
960        if ($timeout) {
961            /*
962             * Using socket_set_timeout() because stream_set_timeout()
963             * was introduced in 4.3.0, but we need to support 4.2.0.
964             */
965            socket_set_timeout($fp, $timeout);
966        }
967
968        if (!fputs($fp, $op, strlen($op))) {
969            $this->errstr = 'Write error';
970            return 0;
971        }
972        $resp = $msg->parseResponseFile($fp);
973
974        $meta = socket_get_status($fp);
975        if ($meta['timed_out']) {
976            fclose($fp);
977            $this->errstr = 'RPC server did not send response before timeout.';
978            $this->raiseError($this->errstr, XML_RPC_ERROR_CONNECTION_FAILED);
979            return 0;
980        }
981
982        fclose($fp);
983        return $resp;
984    }
985
986    /**
987     * Determines the HTTP headers and puts it in the $headers property
988     *
989     * @param object $msg       the XML_RPC_Message object
990     *
991     * @return boolean  TRUE if okay, FALSE if the message payload isn't set.
992     *
993     * @access protected
994     */
995    function createHeaders($msg)
996    {
997        if (empty($msg->payload)) {
998            return false;
999        }
1000        if ($this->proxy) {
1001            $this->headers = 'POST ' . $this->protocol . $this->server;
1002            if ($this->proxy_port) {
1003                $this->headers .= ':' . $this->port;
1004            }
1005        } else {
1006           $this->headers = 'POST ';
1007        }
1008        $this->headers .= $this->path. " HTTP/1.0\r\n";
1009
1010        $this->headers .= "User-Agent: PEAR XML_RPC\r\n";
1011        $this->headers .= 'Host: ' . $this->server . "\r\n";
1012
1013        if ($this->proxy && $this->proxy_user) {
1014            $this->headers .= 'Proxy-Authorization: Basic '
1015                     . base64_encode("$this->proxy_user:$this->proxy_pass")
1016                     . "\r\n";
1017        }
1018
1019        // thanks to Grant Rauscher <grant7@firstworld.net> for this
1020        if ($this->username) {
1021            $this->headers .= 'Authorization: Basic '
1022                     . base64_encode("$this->username:$this->password")
1023                     . "\r\n";
1024        }
1025
1026        $this->headers .= "Content-Type: text/xml\r\n";
1027        $this->headers .= 'Content-Length: ' . strlen($msg->payload);
1028        return true;
1029    }
1030}
1031
1032/**
1033 * The methods and properties for interpreting responses to XML RPC requests
1034 *
1035 * @category   Web Services
1036 * @package    XML_RPC
1037 * @author     Edd Dumbill <edd@usefulinc.com>
1038 * @author     Stig Bakken <stig@php.net>
1039 * @author     Martin Jansen <mj@php.net>
1040 * @author     Daniel Convissor <danielc@php.net>
1041 * @copyright  1999-2001 Edd Dumbill, 2001-2010 The PHP Group
1042 * @license    http://www.php.net/license/3_01.txt  PHP License
1043 * @version    Release: @package_version@
1044 * @link       http://pear.php.net/package/XML_RPC
1045 */
1046class XML_RPC_Response extends XML_RPC_Base
1047{
1048    var $xv;
1049    var $fn;
1050    var $fs;
1051    var $hdrs;
1052
1053    /**
1054     * @return void
1055     */
1056    function XML_RPC_Response($val, $fcode = 0, $fstr = '')
1057    {
1058        if ($fcode != 0) {
1059            $this->fn = $fcode;
1060            $this->fs = htmlspecialchars($fstr);
1061        } else {
1062            $this->xv = $val;
1063        }
1064    }
1065
1066    /**
1067     * @return int  the error code
1068     */
1069    function faultCode()
1070    {
1071        if (isset($this->fn)) {
1072            return $this->fn;
1073        } else {
1074            return 0;
1075        }
1076    }
1077
1078    /**
1079     * @return string  the error string
1080     */
1081    function faultString()
1082    {
1083        return $this->fs;
1084    }
1085
1086    /**
1087     * @return mixed  the value
1088     */
1089    function value()
1090    {
1091        return $this->xv;
1092    }
1093
1094    /**
1095     * @return string  the error message in XML format
1096     */
1097    function serialize()
1098    {
1099        $rs = "<methodResponse>\n";
1100        if ($this->fn) {
1101            $rs .= "<fault>
1102  <value>
1103    <struct>
1104      <member>
1105        <name>faultCode</name>
1106        <value><int>" . $this->fn . "</int></value>
1107      </member>
1108      <member>
1109        <name>faultString</name>
1110        <value><string>" . $this->fs . "</string></value>
1111      </member>
1112    </struct>
1113  </value>
1114</fault>";
1115        } else {
1116            $rs .= "<params>\n<param>\n" . $this->xv->serialize() .
1117        "</param>\n</params>";
1118        }
1119        $rs .= "\n</methodResponse>";
1120        return $rs;
1121    }
1122}
1123
1124/**
1125 * The methods and properties for composing XML RPC messages
1126 *
1127 * @category   Web Services
1128 * @package    XML_RPC
1129 * @author     Edd Dumbill <edd@usefulinc.com>
1130 * @author     Stig Bakken <stig@php.net>
1131 * @author     Martin Jansen <mj@php.net>
1132 * @author     Daniel Convissor <danielc@php.net>
1133 * @copyright  1999-2001 Edd Dumbill, 2001-2010 The PHP Group
1134 * @license    http://www.php.net/license/3_01.txt  PHP License
1135 * @version    Release: @package_version@
1136 * @link       http://pear.php.net/package/XML_RPC
1137 */
1138class XML_RPC_Message extends XML_RPC_Base
1139{
1140    /**
1141     * Should the payload's content be passed through mb_convert_encoding()?
1142     *
1143     * @see XML_RPC_Message::setConvertPayloadEncoding()
1144     * @since Property available since Release 1.5.1
1145     * @var boolean
1146     */
1147    var $convert_payload_encoding = false;
1148
1149    /**
1150     * The current debug mode (1 = on, 0 = off)
1151     * @var integer
1152     */
1153    var $debug = 0;
1154
1155    /**
1156     * The encoding to be used for outgoing messages
1157     *
1158     * Defaults to the value of <var>$GLOBALS['XML_RPC_defencoding']</var>
1159     *
1160     * @var string
1161     * @see XML_RPC_Message::setSendEncoding(),
1162     *      $GLOBALS['XML_RPC_defencoding'], XML_RPC_Message::xml_header()
1163     */
1164    var $send_encoding = '';
1165
1166    /**
1167     * The method presently being evaluated
1168     * @var string
1169     */
1170    var $methodname = '';
1171
1172    /**
1173     * @var array
1174     */
1175    var $params = array();
1176
1177    /**
1178     * The XML message being generated
1179     * @var string
1180     */
1181    var $payload = '';
1182
1183    /**
1184     * Should extra line breaks be removed from the payload?
1185     * @since Property available since Release 1.4.6
1186     * @var boolean
1187     */
1188    var $remove_extra_lines = true;
1189
1190    /**
1191     * The XML response from the remote server
1192     * @since Property available since Release 1.4.6
1193     * @var string
1194     */
1195    var $response_payload = '';
1196
1197
1198    /**
1199     * @return void
1200     */
1201    function XML_RPC_Message($meth, $pars = 0)
1202    {
1203        $this->methodname = $meth;
1204        if (is_array($pars) && sizeof($pars) > 0) {
1205            for ($i = 0; $i < sizeof($pars); $i++) {
1206                $this->addParam($pars[$i]);
1207            }
1208        }
1209    }
1210
1211    /**
1212     * Produces the XML declaration including the encoding attribute
1213     *
1214     * The encoding is determined by this class' <var>$send_encoding</var>
1215     * property.  If the <var>$send_encoding</var> property is not set, use
1216     * <var>$GLOBALS['XML_RPC_defencoding']</var>.
1217     *
1218     * @return string  the XML declaration and <methodCall> element
1219     *
1220     * @see XML_RPC_Message::setSendEncoding(),
1221     *      XML_RPC_Message::$send_encoding, $GLOBALS['XML_RPC_defencoding']
1222     */
1223    function xml_header()
1224    {
1225        global $XML_RPC_defencoding;
1226
1227        if (!$this->send_encoding) {
1228            $this->send_encoding = $XML_RPC_defencoding;
1229        }
1230        return '<?xml version="1.0" encoding="' . $this->send_encoding . '"?>'
1231               . "\n<methodCall>\n";
1232    }
1233
1234    /**
1235     * @return string  the closing </methodCall> tag
1236     */
1237    function xml_footer()
1238    {
1239        return "</methodCall>\n";
1240    }
1241
1242    /**
1243     * Fills the XML_RPC_Message::$payload property
1244     *
1245     * Part of the process makes sure all line endings are in DOS format
1246     * (CRLF), which is probably required by specifications.
1247     *
1248     * If XML_RPC_Message::setConvertPayloadEncoding() was set to true,
1249     * the payload gets passed through mb_convert_encoding()
1250     * to ensure the payload matches the encoding set in the
1251     * XML declaration.  The encoding type can be manually set via
1252     * XML_RPC_Message::setSendEncoding().
1253     *
1254     * @return void
1255     *
1256     * @uses XML_RPC_Message::xml_header(), XML_RPC_Message::xml_footer()
1257     * @see XML_RPC_Message::setSendEncoding(), $GLOBALS['XML_RPC_defencoding'],
1258     *      XML_RPC_Message::setConvertPayloadEncoding()
1259     */
1260    function createPayload()
1261    {
1262        $this->payload = $this->xml_header();
1263        $this->payload .= '<methodName>' . $this->methodname . "</methodName>\n";
1264        $this->payload .= "<params>\n";
1265        for ($i = 0; $i < sizeof($this->params); $i++) {
1266            $p = $this->params[$i];
1267            $this->payload .= "<param>\n" . $p->serialize() . "</param>\n";
1268        }
1269        $this->payload .= "</params>\n";
1270        $this->payload .= $this->xml_footer();
1271        if ($this->remove_extra_lines) {
1272            $this->payload = preg_replace("@[\r\n]+@", "\r\n", $this->payload);
1273        } else {
1274            $this->payload = preg_replace("@\r\n|\n|\r|\n\r@", "\r\n", $this->payload);
1275        }
1276        if ($this->convert_payload_encoding) {
1277            $this->payload = mb_convert_encoding($this->payload, $this->send_encoding);
1278        }
1279    }
1280
1281    /**
1282     * @return string  the name of the method
1283     */
1284    function method($meth = '')
1285    {
1286        if ($meth != '') {
1287            $this->methodname = $meth;
1288        }
1289        return $this->methodname;
1290    }
1291
1292    /**
1293     * @return string  the payload
1294     */
1295    function serialize()
1296    {
1297        $this->createPayload();
1298        return $this->payload;
1299    }
1300
1301    /**
1302     * @return void
1303     */
1304    function addParam($par)
1305    {
1306        $this->params[] = $par;
1307    }
1308
1309    /**
1310     * Obtains an XML_RPC_Value object for the given parameter
1311     *
1312     * @param int $i  the index number of the parameter to obtain
1313     *
1314     * @return object  the XML_RPC_Value object.
1315     *                  If the parameter doesn't exist, an XML_RPC_Response object.
1316     *
1317     * @since Returns XML_RPC_Response object on error since Release 1.3.0
1318     */
1319    function getParam($i)
1320    {
1321        global $XML_RPC_err, $XML_RPC_str;
1322
1323        if (isset($this->params[$i])) {
1324            return $this->params[$i];
1325        } else {
1326            $this->raiseError('The submitted request did not contain this parameter',
1327                              XML_RPC_ERROR_INCORRECT_PARAMS);
1328            return new XML_RPC_Response(0, $XML_RPC_err['incorrect_params'],
1329                                        $XML_RPC_str['incorrect_params']);
1330        }
1331    }
1332
1333    /**
1334     * @return int  the number of parameters
1335     */
1336    function getNumParams()
1337    {
1338        return sizeof($this->params);
1339    }
1340
1341    /**
1342     * Sets whether the payload's content gets passed through
1343     * mb_convert_encoding()
1344     *
1345     * Returns PEAR_ERROR object if mb_convert_encoding() isn't available.
1346     *
1347     * @param int $in  where 1 = on, 0 = off
1348     *
1349     * @return void
1350     *
1351     * @see XML_RPC_Message::setSendEncoding()
1352     * @since Method available since Release 1.5.1
1353     */
1354    function setConvertPayloadEncoding($in)
1355    {
1356        if ($in && !function_exists('mb_convert_encoding')) {
1357            return $this->raiseError('mb_convert_encoding() is not available',
1358                              XML_RPC_ERROR_PROGRAMMING);
1359        }
1360        $this->convert_payload_encoding = $in;
1361    }
1362
1363    /**
1364     * Sets the XML declaration's encoding attribute
1365     *
1366     * @param string $type  the encoding type (ISO-8859-1, UTF-8 or US-ASCII)
1367     *
1368     * @return void
1369     *
1370     * @see XML_RPC_Message::setConvertPayloadEncoding(), XML_RPC_Message::xml_header()
1371     * @since Method available since Release 1.2.0
1372     */
1373    function setSendEncoding($type)
1374    {
1375        $this->send_encoding = $type;
1376    }
1377
1378    /**
1379     * Determine the XML's encoding via the encoding attribute
1380     * in the XML declaration
1381     *
1382     * If the encoding parameter is not set or is not ISO-8859-1, UTF-8
1383     * or US-ASCII, $XML_RPC_defencoding will be returned.
1384     *
1385     * @param string $data  the XML that will be parsed
1386     *
1387     * @return string  the encoding to be used
1388     *
1389     * @link   http://php.net/xml_parser_create
1390     * @since  Method available since Release 1.2.0
1391     */
1392    function getEncoding($data)
1393    {
1394        global $XML_RPC_defencoding;
1395
1396        if (preg_match('@<\?xml[^>]*\s*encoding\s*=\s*[\'"]([^"\']*)[\'"]@',
1397                       $data, $match))
1398        {
1399            $match[1] = trim(strtoupper($match[1]));
1400            switch ($match[1]) {
1401                case 'ISO-8859-1':
1402                case 'UTF-8':
1403                case 'US-ASCII':
1404                    return $match[1];
1405                    break;
1406
1407                default:
1408                    return $XML_RPC_defencoding;
1409            }
1410        } else {
1411            return $XML_RPC_defencoding;
1412        }
1413    }
1414
1415    /**
1416     * @return object  a new XML_RPC_Response object
1417     */
1418    function parseResponseFile($fp)
1419    {
1420        $ipd = '';
1421        while ($data = @fread($fp, 8192)) {
1422            $ipd .= $data;
1423        }
1424        return $this->parseResponse($ipd);
1425    }
1426
1427    /**
1428     * @return object  a new XML_RPC_Response object
1429     */
1430    function parseResponse($data = '')
1431    {
1432        global $XML_RPC_xh, $XML_RPC_err, $XML_RPC_str, $XML_RPC_defencoding;
1433
1434        $encoding = $this->getEncoding($data);
1435        $parser_resource = xml_parser_create($encoding);
1436        $parser = (int) $parser_resource;
1437
1438        $XML_RPC_xh = array();
1439        $XML_RPC_xh[$parser] = array();
1440
1441        $XML_RPC_xh[$parser]['cm'] = 0;
1442        $XML_RPC_xh[$parser]['isf'] = 0;
1443        $XML_RPC_xh[$parser]['ac'] = '';
1444        $XML_RPC_xh[$parser]['qt'] = '';
1445        $XML_RPC_xh[$parser]['stack'] = array();
1446        $XML_RPC_xh[$parser]['valuestack'] = array();
1447
1448        xml_parser_set_option($parser_resource, XML_OPTION_CASE_FOLDING, true);
1449        xml_set_element_handler($parser_resource, 'XML_RPC_se', 'XML_RPC_ee');
1450        xml_set_character_data_handler($parser_resource, 'XML_RPC_cd');
1451
1452        $hdrfnd = 0;
1453        if ($this->debug) {
1454            print "\n<pre>---GOT---\n";
1455            print isset($_SERVER['SERVER_PROTOCOL']) ? htmlspecialchars($data) : $data;
1456            print "\n---END---</pre>\n";
1457        }
1458
1459        // See if response is a 200 or a 100 then a 200, else raise error.
1460        // But only do this if we're using the HTTP protocol.
1461        if (preg_match('@^HTTP@', $data) &&
1462            !preg_match('@^HTTP/[0-9\.]+ 200 @', $data) &&
1463            !preg_match('@^HTTP/[0-9\.]+ 10[0-9]([A-Z ]+)?[\r\n]+HTTP/[0-9\.]+ 200@', $data))
1464        {
1465                $errstr = substr($data, 0, strpos($data, "\n") - 1);
1466                error_log('HTTP error, got response: ' . $errstr);
1467                $r = new XML_RPC_Response(0, $XML_RPC_err['http_error'],
1468                                          $XML_RPC_str['http_error'] . ' (' .
1469                                          $errstr . ')');
1470                xml_parser_free($parser_resource);
1471                return $r;
1472        }
1473
1474        // gotta get rid of headers here
1475        if (!$hdrfnd && ($brpos = strpos($data,"\r\n\r\n"))) {
1476            $XML_RPC_xh[$parser]['ha'] = substr($data, 0, $brpos);
1477            $data = substr($data, $brpos + 4);
1478            $hdrfnd = 1;
1479        }
1480
1481        /*
1482         * be tolerant of junk after methodResponse
1483         * (e.g. javascript automatically inserted by free hosts)
1484         * thanks to Luca Mariano <luca.mariano@email.it>
1485         */
1486        $data = substr($data, 0, strpos($data, "</methodResponse>") + 17);
1487        $this->response_payload = $data;
1488
1489        if (!xml_parse($parser_resource, $data, sizeof($data))) {
1490            // thanks to Peter Kocks <peter.kocks@baygate.com>
1491            if (xml_get_current_line_number($parser_resource) == 1) {
1492                $errstr = 'XML error at line 1, check URL';
1493            } else {
1494                $errstr = sprintf('XML error: %s at line %d',
1495                                  xml_error_string(xml_get_error_code($parser_resource)),
1496                                  xml_get_current_line_number($parser_resource));
1497            }
1498            error_log($errstr);
1499            $r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'],
1500                                      $XML_RPC_str['invalid_return']);
1501            xml_parser_free($parser_resource);
1502            return $r;
1503        }
1504
1505        xml_parser_free($parser_resource);
1506
1507        if ($this->debug) {
1508            print "\n<pre>---PARSED---\n";
1509            var_dump($XML_RPC_xh[$parser]['value']);
1510            print "---END---</pre>\n";
1511        }
1512
1513        if ($XML_RPC_xh[$parser]['isf'] > 1) {
1514            $r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'],
1515                                      $XML_RPC_str['invalid_return'].' '.$XML_RPC_xh[$parser]['isf_reason']);
1516        } elseif (!is_object($XML_RPC_xh[$parser]['value'])) {
1517            // then something odd has happened
1518            // and it's time to generate a client side error
1519            // indicating something odd went on
1520            $r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'],
1521                                      $XML_RPC_str['invalid_return']);
1522        } else {
1523            $v = $XML_RPC_xh[$parser]['value'];
1524            if ($XML_RPC_xh[$parser]['isf']) {
1525                $f = $v->structmem('faultCode');
1526                $fs = $v->structmem('faultString');
1527                $r = new XML_RPC_Response($v, $f->scalarval(),
1528                                          $fs->scalarval());
1529            } else {
1530                $r = new XML_RPC_Response($v);
1531            }
1532        }
1533        $r->hdrs = preg_split("@\r?\n@", $XML_RPC_xh[$parser]['ha']);
1534        return $r;
1535    }
1536}
1537
1538/**
1539 * The methods and properties that represent data in XML RPC format
1540 *
1541 * @category   Web Services
1542 * @package    XML_RPC
1543 * @author     Edd Dumbill <edd@usefulinc.com>
1544 * @author     Stig Bakken <stig@php.net>
1545 * @author     Martin Jansen <mj@php.net>
1546 * @author     Daniel Convissor <danielc@php.net>
1547 * @copyright  1999-2001 Edd Dumbill, 2001-2010 The PHP Group
1548 * @license    http://www.php.net/license/3_01.txt  PHP License
1549 * @version    Release: @package_version@
1550 * @link       http://pear.php.net/package/XML_RPC
1551 */
1552class XML_RPC_Value extends XML_RPC_Base
1553{
1554    var $me = array();
1555    var $mytype = 0;
1556
1557    /**
1558     * @return void
1559     */
1560    function XML_RPC_Value($val = -1, $type = '')
1561    {
1562        $this->me = array();
1563        $this->mytype = 0;
1564        if ($val != -1 || $type != '') {
1565            if ($type == '') {
1566                $type = 'string';
1567            }
1568            if (!array_key_exists($type, $GLOBALS['XML_RPC_Types'])) {
1569                // XXX
1570                // need some way to report this error
1571            } elseif ($GLOBALS['XML_RPC_Types'][$type] == 1) {
1572                $this->addScalar($val, $type);
1573            } elseif ($GLOBALS['XML_RPC_Types'][$type] == 2) {
1574                $this->addArray($val);
1575            } elseif ($GLOBALS['XML_RPC_Types'][$type] == 3) {
1576                $this->addStruct($val);
1577            }
1578        }
1579    }
1580
1581    /**
1582     * @return int  returns 1 if successful or 0 if there are problems
1583     */
1584    function addScalar($val, $type = 'string')
1585    {
1586        if ($this->mytype == 1) {
1587            $this->raiseError('Scalar can have only one value',
1588                              XML_RPC_ERROR_INVALID_TYPE);
1589            return 0;
1590        }
1591        $typeof = $GLOBALS['XML_RPC_Types'][$type];
1592        if ($typeof != 1) {
1593            $this->raiseError("Not a scalar type (${typeof})",
1594                              XML_RPC_ERROR_INVALID_TYPE);
1595            return 0;
1596        }
1597
1598        if ($type == $GLOBALS['XML_RPC_Boolean']) {
1599            if (strcasecmp($val, 'true') == 0
1600                || $val == 1
1601                || ($val == true && strcasecmp($val, 'false')))
1602            {
1603                $val = 1;
1604            } else {
1605                $val = 0;
1606            }
1607        }
1608
1609        if ($this->mytype == 2) {
1610            // we're adding to an array here
1611            $ar = $this->me['array'];
1612            $ar[] = new XML_RPC_Value($val, $type);
1613            $this->me['array'] = $ar;
1614        } else {
1615            // a scalar, so set the value and remember we're scalar
1616            $this->me[$type] = $val;
1617            $this->mytype = $typeof;
1618        }
1619        return 1;
1620    }
1621
1622    /**
1623     * @return int  returns 1 if successful or 0 if there are problems
1624     */
1625    function addArray($vals)
1626    {
1627        if ($this->mytype != 0) {
1628            $this->raiseError(
1629                    'Already initialized as a [' . $this->kindOf() . ']',
1630                    XML_RPC_ERROR_ALREADY_INITIALIZED);
1631            return 0;
1632        }
1633        $this->mytype = $GLOBALS['XML_RPC_Types']['array'];
1634        $this->me['array'] = $vals;
1635        return 1;
1636    }
1637
1638    /**
1639     * @return int  returns 1 if successful or 0 if there are problems
1640     */
1641    function addStruct($vals)
1642    {
1643        if ($this->mytype != 0) {
1644            $this->raiseError(
1645                    'Already initialized as a [' . $this->kindOf() . ']',
1646                    XML_RPC_ERROR_ALREADY_INITIALIZED);
1647            return 0;
1648        }
1649        $this->mytype = $GLOBALS['XML_RPC_Types']['struct'];
1650        $this->me['struct'] = $vals;
1651        return 1;
1652    }
1653
1654    /**
1655     * @return void
1656     */
1657    function dump($ar)
1658    {
1659        reset($ar);
1660        foreach ($ar as $key => $val) {
1661            echo "$key => $val<br />";
1662            if ($key == 'array') {
1663                foreach ($val as $key2 => $val2) {
1664                    echo "-- $key2 => $val2<br />";
1665                }
1666            }
1667        }
1668    }
1669
1670    /**
1671     * @return string  the data type of the current value
1672     */
1673    function kindOf()
1674    {
1675        switch ($this->mytype) {
1676        case 3:
1677            return 'struct';
1678
1679        case 2:
1680            return 'array';
1681
1682        case 1:
1683            return 'scalar';
1684
1685        default:
1686            return 'undef';
1687        }
1688    }
1689
1690    /**
1691     * @return string  the data in XML format
1692     */
1693    function serializedata($typ, $val)
1694    {
1695        $rs = '';
1696        if (!array_key_exists($typ, $GLOBALS['XML_RPC_Types'])) {
1697            // XXX
1698            // need some way to report this error
1699            return;
1700        }
1701        switch ($GLOBALS['XML_RPC_Types'][$typ]) {
1702        case 3:
1703            // struct
1704            $rs .= "<struct>\n";
1705            reset($val);
1706            foreach ($val as $key2 => $val2) {
1707                $rs .= "<member><name>" . htmlspecialchars($key2) . "</name>\n";
1708                $rs .= $this->serializeval($val2);
1709                $rs .= "</member>\n";
1710            }
1711            $rs .= '</struct>';
1712            break;
1713
1714        case 2:
1715            // array
1716            $rs .= "<array>\n<data>\n";
1717            foreach ($val as $value) {
1718                $rs .= $this->serializeval($value);
1719            }
1720            $rs .= "</data>\n</array>";
1721            break;
1722
1723        case 1:
1724            switch ($typ) {
1725            case $GLOBALS['XML_RPC_Base64']:
1726                $rs .= "<${typ}>" . base64_encode($val) . "</${typ}>";
1727                break;
1728            case $GLOBALS['XML_RPC_Boolean']:
1729                $rs .= "<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
1730                break;
1731            case $GLOBALS['XML_RPC_String']:
1732                $rs .= "<${typ}>" . htmlspecialchars($val). "</${typ}>";
1733                break;
1734            default:
1735                $rs .= "<${typ}>${val}</${typ}>";
1736            }
1737        }
1738        return $rs;
1739    }
1740
1741    /**
1742     * @return string  the data in XML format
1743     */
1744    function serialize()
1745    {
1746        return $this->serializeval($this);
1747    }
1748
1749    /**
1750     * @return string  the data in XML format
1751     */
1752    function serializeval($o)
1753    {
1754        if (!is_object($o) || empty($o->me) || !is_array($o->me)) {
1755            return '';
1756        }
1757        $ar = $o->me;
1758        reset($ar);
1759        list($typ, $val) = each($ar);
1760        return '<value>' .  $this->serializedata($typ, $val) .  "</value>\n";
1761    }
1762
1763    /**
1764     * @return mixed  the contents of the element requested
1765     */
1766    function structmem($m)
1767    {
1768        return $this->me['struct'][$m];
1769    }
1770
1771    /**
1772     * @return void
1773     */
1774    function structreset()
1775    {
1776        reset($this->me['struct']);
1777    }
1778
1779    /**
1780     * @return  the key/value pair of the struct's current element
1781     */
1782    function structeach()
1783    {
1784        return each($this->me['struct']);
1785    }
1786
1787    /**
1788     * @return mixed  the current value
1789     */
1790    function getval()
1791    {
1792        // UNSTABLE
1793
1794        reset($this->me);
1795        $b = current($this->me);
1796
1797        // contributed by I Sofer, 2001-03-24
1798        // add support for nested arrays to scalarval
1799        // i've created a new method here, so as to
1800        // preserve back compatibility
1801
1802        if (is_array($b)) {
1803            foreach ($b as $id => $cont) {
1804                $b[$id] = $cont->scalarval();
1805            }
1806        }
1807
1808        // add support for structures directly encoding php objects
1809        if (is_object($b)) {
1810            $t = get_object_vars($b);
1811            foreach ($t as $id => $cont) {
1812                $t[$id] = $cont->scalarval();
1813            }
1814            foreach ($t as $id => $cont) {
1815                $b->$id = $cont;
1816            }
1817        }
1818
1819        // end contrib
1820        return $b;
1821    }
1822
1823    /**
1824     * @return mixed  the current element's scalar value.  If the value is
1825     *                 not scalar, FALSE is returned.
1826     */
1827    function scalarval()
1828    {
1829        reset($this->me);
1830        $v = current($this->me);
1831        if (!is_scalar($v)) {
1832            $v = false;
1833        }
1834        return $v;
1835    }
1836
1837    /**
1838     * @return string
1839     */
1840    function scalartyp()
1841    {
1842        reset($this->me);
1843        $a = key($this->me);
1844        if ($a == $GLOBALS['XML_RPC_I4']) {
1845            $a = $GLOBALS['XML_RPC_Int'];
1846        }
1847        return $a;
1848    }
1849
1850    /**
1851     * @return mixed  the struct's current element
1852     */
1853    function arraymem($m)
1854    {
1855        return $this->me['array'][$m];
1856    }
1857
1858    /**
1859     * @return int  the number of elements in the array
1860     */
1861    function arraysize()
1862    {
1863        reset($this->me);
1864        list($a, $b) = each($this->me);
1865        return sizeof($b);
1866    }
1867
1868    /**
1869     * Determines if the item submitted is an XML_RPC_Value object
1870     *
1871     * @param mixed $val  the variable to be evaluated
1872     *
1873     * @return bool  TRUE if the item is an XML_RPC_Value object
1874     *
1875     * @static
1876     * @since Method available since Release 1.3.0
1877     */
1878    function isValue($val)
1879    {
1880        return (strtolower(get_class($val)) == 'xml_rpc_value');
1881    }
1882}
1883
1884/**
1885 * Return an ISO8601 encoded string
1886 *
1887 * While timezones ought to be supported, the XML-RPC spec says:
1888 *
1889 * "Don't assume a timezone. It should be specified by the server in its
1890 * documentation what assumptions it makes about timezones."
1891 *
1892 * This routine always assumes localtime unless $utc is set to 1, in which
1893 * case UTC is assumed and an adjustment for locale is made when encoding.
1894 *
1895 * @return string  the formatted date
1896 */
1897function XML_RPC_iso8601_encode($timet, $utc = 0)
1898{
1899    if (!$utc) {
1900        $t = strftime('%Y%m%dT%H:%M:%S', $timet);
1901    } else {
1902        if (function_exists('gmstrftime')) {
1903            // gmstrftime doesn't exist in some versions
1904            // of PHP
1905            $t = gmstrftime('%Y%m%dT%H:%M:%S', $timet);
1906        } else {
1907            $t = strftime('%Y%m%dT%H:%M:%S', $timet - date('Z'));
1908        }
1909    }
1910    return $t;
1911}
1912
1913/**
1914 * Convert a datetime string into a Unix timestamp
1915 *
1916 * While timezones ought to be supported, the XML-RPC spec says:
1917 *
1918 * "Don't assume a timezone. It should be specified by the server in its
1919 * documentation what assumptions it makes about timezones."
1920 *
1921 * This routine always assumes localtime unless $utc is set to 1, in which
1922 * case UTC is assumed and an adjustment for locale is made when encoding.
1923 *
1924 * @return int  the unix timestamp of the date submitted
1925 */
1926function XML_RPC_iso8601_decode($idate, $utc = 0)
1927{
1928    $t = 0;
1929    if (preg_match('@([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})@', $idate, $regs)) {
1930        if ($utc) {
1931            $t = gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
1932        } else {
1933            $t = mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
1934        }
1935    }
1936    return $t;
1937}
1938
1939/**
1940 * Converts an XML_RPC_Value object into native PHP types
1941 *
1942 * @param object $XML_RPC_val  the XML_RPC_Value object to decode
1943 *
1944 * @return mixed  the PHP values
1945 */
1946function XML_RPC_decode($XML_RPC_val)
1947{
1948    $kind = $XML_RPC_val->kindOf();
1949
1950    if ($kind == 'scalar') {
1951        return $XML_RPC_val->scalarval();
1952
1953    } elseif ($kind == 'array') {
1954        $size = $XML_RPC_val->arraysize();
1955        $arr = array();
1956        for ($i = 0; $i < $size; $i++) {
1957            $arr[] = XML_RPC_decode($XML_RPC_val->arraymem($i));
1958        }
1959        return $arr;
1960
1961    } elseif ($kind == 'struct') {
1962        $XML_RPC_val->structreset();
1963        $arr = array();
1964        while (list($key, $value) = $XML_RPC_val->structeach()) {
1965            $arr[$key] = XML_RPC_decode($value);
1966        }
1967        return $arr;
1968    }
1969}
1970
1971/**
1972 * Converts native PHP types into an XML_RPC_Value object
1973 *
1974 * @param mixed $php_val  the PHP value or variable you want encoded
1975 *
1976 * @return object  the XML_RPC_Value object
1977 */
1978function XML_RPC_encode($php_val)
1979{
1980    $type = gettype($php_val);
1981    $XML_RPC_val = new XML_RPC_Value;
1982
1983    switch ($type) {
1984    case 'array':
1985        if (empty($php_val)) {
1986            $XML_RPC_val->addArray($php_val);
1987            break;
1988        }
1989        $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
1990        if (empty($tmp)) {
1991           $arr = array();
1992           foreach ($php_val as $k => $v) {
1993               $arr[$k] = XML_RPC_encode($v);
1994           }
1995           $XML_RPC_val->addArray($arr);
1996           break;
1997        }
1998        // fall though if it's not an enumerated array
1999
2000    case 'object':
2001        $arr = array();
2002        foreach ($php_val as $k => $v) {
2003            $arr[$k] = XML_RPC_encode($v);
2004        }
2005        $XML_RPC_val->addStruct($arr);
2006        break;
2007
2008    case 'integer':
2009        $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Int']);
2010        break;
2011
2012    case 'double':
2013        $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Double']);
2014        break;
2015
2016    case 'string':
2017    case 'NULL':
2018        if (preg_match('@^[0-9]{8}\T{1}[0-9]{2}\:[0-9]{2}\:[0-9]{2}$@', $php_val)) {
2019            $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_DateTime']);
2020        } elseif ($GLOBALS['XML_RPC_auto_base64']
2021                  && preg_match("@[^ -~\t\r\n]@", $php_val))
2022        {
2023            // Characters other than alpha-numeric, punctuation, SP, TAB,
2024            // LF and CR break the XML parser, encode value via Base 64.
2025            $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Base64']);
2026        } else {
2027            $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_String']);
2028        }
2029        break;
2030
2031    case 'boolean':
2032        // Add support for encoding/decoding of booleans, since they
2033        // are supported in PHP
2034        // by <G_Giunta_2001-02-29>
2035        $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Boolean']);
2036        break;
2037
2038    case 'unknown type':
2039    default:
2040        $XML_RPC_val = false;
2041    }
2042    return $XML_RPC_val;
2043}
2044
2045/*
2046 * Local variables:
2047 * tab-width: 4
2048 * c-basic-offset: 4
2049 * c-hanging-comment-ender-p: nil
2050 * End:
2051 */
2052
2053?>
2054