about summary refs log tree commit diff stats
path: root/central/trunk/includes
diff options
context:
space:
mode:
Diffstat (limited to 'central/trunk/includes')
-rw-r--r--central/trunk/includes/class.phpmailer.php1897
-rw-r--r--central/trunk/includes/class.smtp.php1113
-rw-r--r--central/trunk/includes/db.php28
-rw-r--r--central/trunk/includes/instadisc.php400
-rw-r--r--central/trunk/includes/template.php160
-rw-r--r--central/trunk/includes/xmlrpc/array_key_exists.php55
-rw-r--r--central/trunk/includes/xmlrpc/is_a.php47
-rw-r--r--central/trunk/includes/xmlrpc/is_callable.php53
-rw-r--r--central/trunk/includes/xmlrpc/is_scalar.php38
-rw-r--r--central/trunk/includes/xmlrpc/var_export.php105
-rw-r--r--central/trunk/includes/xmlrpc/version_compare.php179
-rw-r--r--central/trunk/includes/xmlrpc/xmlrpc.inc3705
-rw-r--r--central/trunk/includes/xmlrpc/xmlrpc_wrappers.inc819
-rw-r--r--central/trunk/includes/xmlrpc/xmlrpcs.inc1193
14 files changed, 9792 insertions, 0 deletions
diff --git a/central/trunk/includes/class.phpmailer.php b/central/trunk/includes/class.phpmailer.php new file mode 100644 index 0000000..51a50e7 --- /dev/null +++ b/central/trunk/includes/class.phpmailer.php
@@ -0,0 +1,1897 @@
1<?php
2/*~ class.phpmailer.php
3.---------------------------------------------------------------------------.
4| Software: PHPMailer - PHP email class |
5| Version: 2.2.1 |
6| Contact: via sourceforge.net support pages (also www.codeworxtech.com) |
7| Info: http://phpmailer.sourceforge.net |
8| Support: http://sourceforge.net/projects/phpmailer/ |
9| ------------------------------------------------------------------------- |
10| Author: Andy Prevost (project admininistrator) |
11| Author: Brent R. Matzelle (original founder) |
12| Copyright (c) 2004-2007, Andy Prevost. All Rights Reserved. |
13| Copyright (c) 2001-2003, Brent R. Matzelle |
14| ------------------------------------------------------------------------- |
15| License: Distributed under the Lesser General Public License (LGPL) |
16| http://www.gnu.org/copyleft/lesser.html |
17| This program is distributed in the hope that it will be useful - WITHOUT |
18| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
19| FITNESS FOR A PARTICULAR PURPOSE. |
20| ------------------------------------------------------------------------- |
21| We offer a number of paid services (www.codeworxtech.com): |
22| - Web Hosting on highly optimized fast and secure servers |
23| - Technology Consulting |
24| - Oursourcing (highly qualified programmers and graphic designers) |
25'---------------------------------------------------------------------------'
26
27/**
28 * PHPMailer - PHP email transport class
29 * NOTE: Designed for use with PHP version 5 and up
30 * @package PHPMailer
31 * @author Andy Prevost
32 * @copyright 2004 - 2008 Andy Prevost
33 */
34
35class PHPMailer {
36
37 /////////////////////////////////////////////////
38 // PROPERTIES, PUBLIC
39 /////////////////////////////////////////////////
40
41 /**
42 * Email priority (1 = High, 3 = Normal, 5 = low).
43 * @var int
44 */
45 public $Priority = 3;
46
47 /**
48 * Sets the CharSet of the message.
49 * @var string
50 */
51 public $CharSet = 'iso-8859-1';
52
53 /**
54 * Sets the Content-type of the message.
55 * @var string
56 */
57 public $ContentType = 'text/plain';
58
59 /**
60 * Sets the Encoding of the message. Options for this are "8bit",
61 * "7bit", "binary", "base64", and "quoted-printable".
62 * @var string
63 */
64 public $Encoding = '8bit';
65
66 /**
67 * Holds the most recent mailer error message.
68 * @var string
69 */
70 public $ErrorInfo = '';
71
72 /**
73 * Sets the From email address for the message.
74 * @var string
75 */
76 public $From = 'root@localhost';
77
78 /**
79 * Sets the From name of the message.
80 * @var string
81 */
82 public $FromName = 'Root User';
83
84 /**
85 * Sets the Sender email (Return-Path) of the message. If not empty,
86 * will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
87 * @var string
88 */
89 public $Sender = '';
90
91 /**
92 * Sets the Subject of the message.
93 * @var string
94 */
95 public $Subject = '';
96
97 /**
98 * Sets the Body of the message. This can be either an HTML or text body.
99 * If HTML then run IsHTML(true).
100 * @var string
101 */
102 public $Body = '';
103
104 /**
105 * Sets the text-only body of the message. This automatically sets the
106 * email to multipart/alternative. This body can be read by mail
107 * clients that do not have HTML email capability such as mutt. Clients
108 * that can read HTML will view the normal Body.
109 * @var string
110 */
111 public $AltBody = '';
112
113 /**
114 * Sets word wrapping on the body of the message to a given number of
115 * characters.
116 * @var int
117 */
118 public $WordWrap = 0;
119
120 /**
121 * Method to send mail: ("mail", "sendmail", or "smtp").
122 * @var string
123 */
124 public $Mailer = 'mail';
125
126 /**
127 * Sets the path of the sendmail program.
128 * @var string
129 */
130 public $Sendmail = '/usr/sbin/sendmail';
131
132 /**
133 * Path to PHPMailer plugins. This is now only useful if the SMTP class
134 * is in a different directory than the PHP include path.
135 * @var string
136 */
137 public $PluginDir = '';
138
139 /**
140 * Holds PHPMailer version.
141 * @var string
142 */
143 public $Version = "2.2";
144
145 /**
146 * Sets the email address that a reading confirmation will be sent.
147 * @var string
148 */
149 public $ConfirmReadingTo = '';
150
151 /**
152 * Sets the hostname to use in Message-Id and Received headers
153 * and as default HELO string. If empty, the value returned
154 * by SERVER_NAME is used or 'localhost.localdomain'.
155 * @var string
156 */
157 public $Hostname = '';
158
159 /**
160 * Sets the message ID to be used in the Message-Id header.
161 * If empty, a unique id will be generated.
162 * @var string
163 */
164 public $MessageID = '';
165
166 /////////////////////////////////////////////////
167 // PROPERTIES FOR SMTP
168 /////////////////////////////////////////////////
169
170 /**
171 * Sets the SMTP hosts. All hosts must be separated by a
172 * semicolon. You can also specify a different port
173 * for each host by using this format: [hostname:port]
174 * (e.g. "smtp1.example.com:25;smtp2.example.com").
175 * Hosts will be tried in order.
176 * @var string
177 */
178 public $Host = 'localhost';
179
180 /**
181 * Sets the default SMTP server port.
182 * @var int
183 */
184 public $Port = 25;
185
186 /**
187 * Sets the SMTP HELO of the message (Default is $Hostname).
188 * @var string
189 */
190 public $Helo = '';
191
192 /**
193 * Sets connection prefix.
194 * Options are "", "ssl" or "tls"
195 * @var string
196 */
197 public $SMTPSecure = "";
198
199 /**
200 * Sets SMTP authentication. Utilizes the Username and Password variables.
201 * @var bool
202 */
203 public $SMTPAuth = false;
204
205 /**
206 * Sets SMTP username.
207 * @var string
208 */
209 public $Username = '';
210
211 /**
212 * Sets SMTP password.
213 * @var string
214 */
215 public $Password = '';
216
217 /**
218 * Sets the SMTP server timeout in seconds. This function will not
219 * work with the win32 version.
220 * @var int
221 */
222 public $Timeout = 10;
223
224 /**
225 * Sets SMTP class debugging on or off.
226 * @var bool
227 */
228 public $SMTPDebug = false;
229
230 /**
231 * Prevents the SMTP connection from being closed after each mail
232 * sending. If this is set to true then to close the connection
233 * requires an explicit call to SmtpClose().
234 * @var bool
235 */
236 public $SMTPKeepAlive = false;
237
238 /**
239 * Provides the ability to have the TO field process individual
240 * emails, instead of sending to entire TO addresses
241 * @var bool
242 */
243 public $SingleTo = false;
244
245 /////////////////////////////////////////////////
246 // PROPERTIES, PRIVATE
247 /////////////////////////////////////////////////
248
249 private $smtp = NULL;
250 private $to = array();
251 private $cc = array();
252 private $bcc = array();
253 private $ReplyTo = array();
254 private $attachment = array();
255 private $CustomHeader = array();
256 private $message_type = '';
257 private $boundary = array();
258 private $language = array();
259 private $error_count = 0;
260 private $LE = "\n";
261 private $sign_cert_file = "";
262 private $sign_key_file = "";
263 private $sign_key_pass = "";
264
265 /////////////////////////////////////////////////
266 // METHODS, VARIABLES
267 /////////////////////////////////////////////////
268
269 /**
270 * Sets message type to HTML.
271 * @param bool $bool
272 * @return void
273 */
274 public function IsHTML($bool) {
275 if($bool == true) {
276 $this->ContentType = 'text/html';
277 } else {
278 $this->ContentType = 'text/plain';
279 }
280 }
281
282 /**
283 * Sets Mailer to send message using SMTP.
284 * @return void
285 */
286 public function IsSMTP() {
287 $this->Mailer = 'smtp';
288 }
289
290 /**
291 * Sets Mailer to send message using PHP mail() function.
292 * @return void
293 */
294 public function IsMail() {
295 $this->Mailer = 'mail';
296 }
297
298 /**
299 * Sets Mailer to send message using the $Sendmail program.
300 * @return void
301 */
302 public function IsSendmail() {
303 $this->Mailer = 'sendmail';
304 }
305
306 /**
307 * Sets Mailer to send message using the qmail MTA.
308 * @return void
309 */
310 public function IsQmail() {
311 $this->Sendmail = '/var/qmail/bin/sendmail';
312 $this->Mailer = 'sendmail';
313 }
314
315 /////////////////////////////////////////////////
316 // METHODS, RECIPIENTS
317 /////////////////////////////////////////////////
318
319 /**
320 * Adds a "To" address.
321 * @param string $address
322 * @param string $name
323 * @return void
324 */
325 public function AddAddress($address, $name = '') {
326 $cur = count($this->to);
327 $this->to[$cur][0] = trim($address);
328 $this->to[$cur][1] = $name;
329 }
330
331 /**
332 * Adds a "Cc" address. Note: this function works
333 * with the SMTP mailer on win32, not with the "mail"
334 * mailer.
335 * @param string $address
336 * @param string $name
337 * @return void
338 */
339 public function AddCC($address, $name = '') {
340 $cur = count($this->cc);
341 $this->cc[$cur][0] = trim($address);
342 $this->cc[$cur][1] = $name;
343 }
344
345 /**
346 * Adds a "Bcc" address. Note: this function works
347 * with the SMTP mailer on win32, not with the "mail"
348 * mailer.
349 * @param string $address
350 * @param string $name
351 * @return void
352 */
353 public function AddBCC($address, $name = '') {
354 $cur = count($this->bcc);
355 $this->bcc[$cur][0] = trim($address);
356 $this->bcc[$cur][1] = $name;
357 }
358
359 /**
360 * Adds a "Reply-to" address.
361 * @param string $address
362 * @param string $name
363 * @return void
364 */
365 public function AddReplyTo($address, $name = '') {
366 $cur = count($this->ReplyTo);
367 $this->ReplyTo[$cur][0] = trim($address);
368 $this->ReplyTo[$cur][1] = $name;
369 }
370
371 /////////////////////////////////////////////////
372 // METHODS, MAIL SENDING
373 /////////////////////////////////////////////////
374
375 /**
376 * Creates message and assigns Mailer. If the message is
377 * not sent successfully then it returns false. Use the ErrorInfo
378 * variable to view description of the error.
379 * @return bool
380 */
381 public function Send() {
382 $header = '';
383 $body = '';
384 $result = true;
385
386 if((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
387 $this->SetError($this->Lang('provide_address'));
388 return false;
389 }
390
391 /* Set whether the message is multipart/alternative */
392 if(!empty($this->AltBody)) {
393 $this->ContentType = 'multipart/alternative';
394 }
395
396 $this->error_count = 0; // reset errors
397 $this->SetMessageType();
398 $header .= $this->CreateHeader();
399 $body = $this->CreateBody();
400
401 if($body == '') {
402 return false;
403 }
404
405 /* Choose the mailer */
406 switch($this->Mailer) {
407 case 'sendmail':
408 $result = $this->SendmailSend($header, $body);
409 break;
410 case 'smtp':
411 $result = $this->SmtpSend($header, $body);
412 break;
413 case 'mail':
414 $result = $this->MailSend($header, $body);
415 break;
416 default:
417 $result = $this->MailSend($header, $body);
418 break;
419 //$this->SetError($this->Mailer . $this->Lang('mailer_not_supported'));
420 //$result = false;
421 //break;
422 }
423
424 return $result;
425 }
426
427 /**
428 * Sends mail using the $Sendmail program.
429 * @access public
430 * @return bool
431 */
432 public function SendmailSend($header, $body) {
433 if ($this->Sender != '') {
434 $sendmail = sprintf("%s -oi -f %s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
435 } else {
436 $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail));
437 }
438
439 if(!@$mail = popen($sendmail, 'w')) {
440 $this->SetError($this->Lang('execute') . $this->Sendmail);
441 return false;
442 }
443
444 fputs($mail, $header);
445 fputs($mail, $body);
446
447 $result = pclose($mail);
448 if (version_compare(phpversion(), '4.2.3') == -1) {
449 $result = $result >> 8 & 0xFF;
450 }
451 if($result != 0) {
452 $this->SetError($this->Lang('execute') . $this->Sendmail);
453 return false;
454 }
455
456 return true;
457 }
458
459 /**
460 * Sends mail using the PHP mail() function.
461 * @access public
462 * @return bool
463 */
464 public function MailSend($header, $body) {
465
466 $to = '';
467 for($i = 0; $i < count($this->to); $i++) {
468 if($i != 0) { $to .= ', '; }
469 $to .= $this->AddrFormat($this->to[$i]);
470 }
471
472 $toArr = split(',', $to);
473
474 $params = sprintf("-oi -f %s", $this->Sender);
475 if ($this->Sender != '' && strlen(ini_get('safe_mode'))< 1) {
476 $old_from = ini_get('sendmail_from');
477 ini_set('sendmail_from', $this->Sender);
478 if ($this->SingleTo === true && count($toArr) > 1) {
479 foreach ($toArr as $key => $val) {
480 $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params);
481 }
482 } else {
483 $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params);
484 }
485 } else {
486 if ($this->SingleTo === true && count($toArr) > 1) {
487 foreach ($toArr as $key => $val) {
488 $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params);
489 }
490 } else {
491 $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header);
492 }
493 }
494
495 if (isset($old_from)) {
496 ini_set('sendmail_from', $old_from);
497 }
498
499 if(!$rt) {
500 $this->SetError($this->Lang('instantiate'));
501 return false;
502 }
503
504 return true;
505 }
506
507 /**
508 * Sends mail via SMTP using PhpSMTP (Author:
509 * Chris Ryan). Returns bool. Returns false if there is a
510 * bad MAIL FROM, RCPT, or DATA input.
511 * @access public
512 * @return bool
513 */
514 public function SmtpSend($header, $body) {
515 include_once($this->PluginDir . 'class.smtp.php');
516 $error = '';
517 $bad_rcpt = array();
518
519 if(!$this->SmtpConnect()) {
520 return false;
521 }
522
523 $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender;
524 if(!$this->smtp->Mail($smtp_from)) {
525 $error = $this->Lang('from_failed') . $smtp_from;
526 $this->SetError($error);
527 $this->smtp->Reset();
528 return false;
529 }
530
531 /* Attempt to send attach all recipients */
532 for($i = 0; $i < count($this->to); $i++) {
533 if(!$this->smtp->Recipient($this->to[$i][0])) {
534 $bad_rcpt[] = $this->to[$i][0];
535 }
536 }
537 for($i = 0; $i < count($this->cc); $i++) {
538 if(!$this->smtp->Recipient($this->cc[$i][0])) {
539 $bad_rcpt[] = $this->cc[$i][0];
540 }
541 }
542 for($i = 0; $i < count($this->bcc); $i++) {
543 if(!$this->smtp->Recipient($this->bcc[$i][0])) {
544 $bad_rcpt[] = $this->bcc[$i][0];
545 }
546 }
547
548 if(count($bad_rcpt) > 0) { // Create error message
549 for($i = 0; $i < count($bad_rcpt); $i++) {
550 if($i != 0) {
551 $error .= ', ';
552 }
553 $error .= $bad_rcpt[$i];
554 }
555 $error = $this->Lang('recipients_failed') . $error;
556 $this->SetError($error);
557 $this->smtp->Reset();
558 return false;
559 }
560
561 if(!$this->smtp->Data($header . $body)) {
562 $this->SetError($this->Lang('data_not_accepted'));
563 $this->smtp->Reset();
564 return false;
565 }
566 if($this->SMTPKeepAlive == true) {
567 $this->smtp->Reset();
568 } else {
569 $this->SmtpClose();
570 }
571
572 return true;
573 }
574
575 /**
576 * Initiates a connection to an SMTP server. Returns false if the
577 * operation failed.
578 * @access public
579 * @return bool
580 */
581 public function SmtpConnect() {
582 if($this->smtp == NULL) {
583 $this->smtp = new SMTP();
584 }
585
586 $this->smtp->do_debug = $this->SMTPDebug;
587 $hosts = explode(';', $this->Host);
588 $index = 0;
589 $connection = ($this->smtp->Connected());
590
591 /* Retry while there is no connection */
592 while($index < count($hosts) && $connection == false) {
593 $hostinfo = array();
594 if(eregi('^(.+):([0-9]+)$', $hosts[$index], $hostinfo)) {
595 $host = $hostinfo[1];
596 $port = $hostinfo[2];
597 } else {
598 $host = $hosts[$index];
599 $port = $this->Port;
600 }
601
602 $tls = ($this->SMTPSecure == 'tls');
603 $ssl = ($this->SMTPSecure == 'ssl');
604
605 if($this->smtp->Connect(($ssl ? 'ssl://':'').$host, $port, $this->Timeout)) {
606
607 $hello = ($this->Helo != '' ? $this->Hello : $this->ServerHostname());
608 $this->smtp->Hello($hello);
609
610 if($tls) {
611 if(!$this->smtp->StartTLS()) {
612 $this->SetError($this->Lang("tls"));
613 $this->smtp->Reset();
614 $connection = false;
615 }
616
617 //We must resend HELLO after tls negociation
618 $this->smtp->Hello($hello);
619 }
620
621 $connection = true;
622 if($this->SMTPAuth) {
623 if(!$this->smtp->Authenticate($this->Username, $this->Password)) {
624 $this->SetError($this->Lang('authenticate'));
625 $this->smtp->Reset();
626 $connection = false;
627 }
628 }
629 }
630 $index++;
631 }
632 if(!$connection) {
633 $this->SetError($this->Lang('connect_host'));
634 }
635
636 return $connection;
637 }
638
639 /**
640 * Closes the active SMTP session if one exists.
641 * @return void
642 */
643 public function SmtpClose() {
644 if($this->smtp != NULL) {
645 if($this->smtp->Connected()) {
646 $this->smtp->Quit();
647 $this->smtp->Close();
648 }
649 }
650 }
651
652 /**
653 * Sets the language for all class error messages. Returns false
654 * if it cannot load the language file. The default language type
655 * is English.
656 * @param string $lang_type Type of language (e.g. Portuguese: "br")
657 * @param string $lang_path Path to the language file directory
658 * @access public
659 * @return bool
660 */
661 function SetLanguage($lang_type = 'en', $lang_path = 'language/') {
662 if( !(@include $lang_path.'phpmailer.lang-'.$lang_type.'.php') ) {
663 $this->SetError('Could not load language file');
664 return false;
665 }
666 $this->language = $PHPMAILER_LANG;
667 return true;
668 }
669
670 /////////////////////////////////////////////////
671 // METHODS, MESSAGE CREATION
672 /////////////////////////////////////////////////
673
674 /**
675 * Creates recipient headers.
676 * @access public
677 * @return string
678 */
679 public function AddrAppend($type, $addr) {
680 $addr_str = $type . ': ';
681 $addr_str .= $this->AddrFormat($addr[0]);
682 if(count($addr) > 1) {
683 for($i = 1; $i < count($addr); $i++) {
684 $addr_str .= ', ' . $this->AddrFormat($addr[$i]);
685 }
686 }
687 $addr_str .= $this->LE;
688
689 return $addr_str;
690 }
691
692 /**
693 * Formats an address correctly.
694 * @access public
695 * @return string
696 */
697 public function AddrFormat($addr) {
698 if(empty($addr[1])) {
699 $formatted = $this->SecureHeader($addr[0]);
700 } else {
701 $formatted = $this->EncodeHeader($this->SecureHeader($addr[1]), 'phrase') . " <" . $this->SecureHeader($addr[0]) . ">";
702 }
703
704 return $formatted;
705 }
706
707 /**
708 * Wraps message for use with mailers that do not
709 * automatically perform wrapping and for quoted-printable.
710 * Original written by philippe.
711 * @access public
712 * @return string
713 */
714 public function WrapText($message, $length, $qp_mode = false) {
715 $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE;
716 // If utf-8 encoding is used, we will need to make sure we don't
717 // split multibyte characters when we wrap
718 $is_utf8 = (strtolower($this->CharSet) == "utf-8");
719
720 $message = $this->FixEOL($message);
721 if (substr($message, -1) == $this->LE) {
722 $message = substr($message, 0, -1);
723 }
724
725 $line = explode($this->LE, $message);
726 $message = '';
727 for ($i=0 ;$i < count($line); $i++) {
728 $line_part = explode(' ', $line[$i]);
729 $buf = '';
730 for ($e = 0; $e<count($line_part); $e++) {
731 $word = $line_part[$e];
732 if ($qp_mode and (strlen($word) > $length)) {
733 $space_left = $length - strlen($buf) - 1;
734 if ($e != 0) {
735 if ($space_left > 20) {
736 $len = $space_left;
737 if ($is_utf8) {
738 $len = $this->UTF8CharBoundary($word, $len);
739 } elseif (substr($word, $len - 1, 1) == "=") {
740 $len--;
741 } elseif (substr($word, $len - 2, 1) == "=") {
742 $len -= 2;
743 }
744 $part = substr($word, 0, $len);
745 $word = substr($word, $len);
746 $buf .= ' ' . $part;
747 $message .= $buf . sprintf("=%s", $this->LE);
748 } else {
749 $message .= $buf . $soft_break;
750 }
751 $buf = '';
752 }
753 while (strlen($word) > 0) {
754 $len = $length;
755 if ($is_utf8) {
756 $len = $this->UTF8CharBoundary($word, $len);
757 } elseif (substr($word, $len - 1, 1) == "=") {
758 $len--;
759 } elseif (substr($word, $len - 2, 1) == "=") {
760 $len -= 2;
761 }
762 $part = substr($word, 0, $len);
763 $word = substr($word, $len);
764
765 if (strlen($word) > 0) {
766 $message .= $part . sprintf("=%s", $this->LE);
767 } else {
768 $buf = $part;
769 }
770 }
771 } else {
772 $buf_o = $buf;
773 $buf .= ($e == 0) ? $word : (' ' . $word);
774
775 if (strlen($buf) > $length and $buf_o != '') {
776 $message .= $buf_o . $soft_break;
777 $buf = $word;
778 }
779 }
780 }
781 $message .= $buf . $this->LE;
782 }
783
784 return $message;
785 }
786
787 /**
788 * Finds last character boundary prior to maxLength in a utf-8
789 * quoted (printable) encoded string.
790 * Original written by Colin Brown.
791 * @access public
792 * @param string $encodedText utf-8 QP text
793 * @param int $maxLength find last character boundary prior to this length
794 * @return int
795 */
796 public function UTF8CharBoundary($encodedText, $maxLength) {
797 $foundSplitPos = false;
798 $lookBack = 3;
799 while (!$foundSplitPos) {
800 $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
801 $encodedCharPos = strpos($lastChunk, "=");
802 if ($encodedCharPos !== false) {
803 // Found start of encoded character byte within $lookBack block.
804 // Check the encoded byte value (the 2 chars after the '=')
805 $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
806 $dec = hexdec($hex);
807 if ($dec < 128) { // Single byte character.
808 // If the encoded char was found at pos 0, it will fit
809 // otherwise reduce maxLength to start of the encoded char
810 $maxLength = ($encodedCharPos == 0) ? $maxLength :
811 $maxLength - ($lookBack - $encodedCharPos);
812 $foundSplitPos = true;
813 } elseif ($dec >= 192) { // First byte of a multi byte character
814 // Reduce maxLength to split at start of character
815 $maxLength = $maxLength - ($lookBack - $encodedCharPos);
816 $foundSplitPos = true;
817 } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back
818 $lookBack += 3;
819 }
820 } else {
821 // No encoded character found
822 $foundSplitPos = true;
823 }
824 }
825 return $maxLength;
826 }
827
828
829 /**
830 * Set the body wrapping.
831 * @access public
832 * @return void
833 */
834 public function SetWordWrap() {
835 if($this->WordWrap < 1) {
836 return;
837 }
838
839 switch($this->message_type) {
840 case 'alt':
841 /* fall through */
842 case 'alt_attachments':
843 $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap);
844 break;
845 default:
846 $this->Body = $this->WrapText($this->Body, $this->WordWrap);
847 break;
848 }
849 }
850
851 /**
852 * Assembles message header.
853 * @access public
854 * @return string
855 */
856 public function CreateHeader() {
857 $result = '';
858
859 /* Set the boundaries */
860 $uniq_id = md5(uniqid(time()));
861 $this->boundary[1] = 'b1_' . $uniq_id;
862 $this->boundary[2] = 'b2_' . $uniq_id;
863
864 $result .= $this->HeaderLine('Date', $this->RFCDate());
865 if($this->Sender == '') {
866 $result .= $this->HeaderLine('Return-Path', trim($this->From));
867 } else {
868 $result .= $this->HeaderLine('Return-Path', trim($this->Sender));
869 }
870
871 /* To be created automatically by mail() */
872 if($this->Mailer != 'mail') {
873 if(count($this->to) > 0) {
874 $result .= $this->AddrAppend('To', $this->to);
875 } elseif (count($this->cc) == 0) {
876 $result .= $this->HeaderLine('To', 'undisclosed-recipients:;');
877 }
878 if(count($this->cc) > 0) {
879 $result .= $this->AddrAppend('Cc', $this->cc);
880 }
881 }
882
883 $from = array();
884 $from[0][0] = trim($this->From);
885 $from[0][1] = $this->FromName;
886 $result .= $this->AddrAppend('From', $from);
887
888 /* sendmail and mail() extract Cc from the header before sending */
889 if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->cc) > 0)) {
890 $result .= $this->AddrAppend('Cc', $this->cc);
891 }
892
893 /* sendmail and mail() extract Bcc from the header before sending */
894 if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) {
895 $result .= $this->AddrAppend('Bcc', $this->bcc);
896 }
897
898 if(count($this->ReplyTo) > 0) {
899 $result .= $this->AddrAppend('Reply-to', $this->ReplyTo);
900 }
901
902 /* mail() sets the subject itself */
903 if($this->Mailer != 'mail') {
904 $result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject)));
905 }
906
907 if($this->MessageID != '') {
908 $result .= $this->HeaderLine('Message-ID',$this->MessageID);
909 } else {
910 $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE);
911 }
912 $result .= $this->HeaderLine('X-Priority', $this->Priority);
913 $result .= $this->HeaderLine('X-Mailer', 'PHPMailer (phpmailer.codeworxtech.com) [version ' . $this->Version . ']');
914
915 if($this->ConfirmReadingTo != '') {
916 $result .= $this->HeaderLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>');
917 }
918
919 // Add custom headers
920 for($index = 0; $index < count($this->CustomHeader); $index++) {
921 $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]), $this->EncodeHeader(trim($this->CustomHeader[$index][1])));
922 }
923 if (!$this->sign_key_file) {
924 $result .= $this->HeaderLine('MIME-Version', '1.0');
925 $result .= $this->GetMailMIME();
926 }
927
928 return $result;
929 }
930
931 /**
932 * Returns the message MIME.
933 * @access public
934 * @return string
935 */
936 public function GetMailMIME() {
937 $result = '';
938 switch($this->message_type) {
939 case 'plain':
940 $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding);
941 $result .= sprintf("Content-Type: %s; charset=\"%s\"", $this->ContentType, $this->CharSet);
942 break;
943 case 'attachments':
944 /* fall through */
945 case 'alt_attachments':
946 if($this->InlineImageExists()){
947 $result .= sprintf("Content-Type: %s;%s\ttype=\"text/html\";%s\tboundary=\"%s\"%s", 'multipart/related', $this->LE, $this->LE, $this->boundary[1], $this->LE);
948 } else {
949 $result .= $this->HeaderLine('Content-Type', 'multipart/mixed;');
950 $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"');
951 }
952 break;
953 case 'alt':
954 $result .= $this->HeaderLine('Content-Type', 'multipart/alternative;');
955 $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"');
956 break;
957 }
958
959 if($this->Mailer != 'mail') {
960 $result .= $this->LE.$this->LE;
961 }
962
963 return $result;
964 }
965
966 /**
967 * Assembles the message body. Returns an empty string on failure.
968 * @access public
969 * @return string
970 */
971 public function CreateBody() {
972 $result = '';
973
974 if ($this->sign_key_file) {
975 $result .= $this->GetMailMIME();
976 }
977
978 $this->SetWordWrap();
979
980 switch($this->message_type) {
981 case 'alt':
982 $result .= $this->GetBoundary($this->boundary[1], '', 'text/plain', '');
983 $result .= $this->EncodeString($this->AltBody, $this->Encoding);
984 $result .= $this->LE.$this->LE;
985 $result .= $this->GetBoundary($this->boundary[1], '', 'text/html', '');
986 $result .= $this->EncodeString($this->Body, $this->Encoding);
987 $result .= $this->LE.$this->LE;
988 $result .= $this->EndBoundary($this->boundary[1]);
989 break;
990 case 'plain':
991 $result .= $this->EncodeString($this->Body, $this->Encoding);
992 break;
993 case 'attachments':
994 $result .= $this->GetBoundary($this->boundary[1], '', '', '');
995 $result .= $this->EncodeString($this->Body, $this->Encoding);
996 $result .= $this->LE;
997 $result .= $this->AttachAll();
998 break;
999 case 'alt_attachments':
1000 $result .= sprintf("--%s%s", $this->boundary[1], $this->LE);
1001 $result .= sprintf("Content-Type: %s;%s" . "\tboundary=\"%s\"%s", 'multipart/alternative', $this->LE, $this->boundary[2], $this->LE.$this->LE);
1002 $result .= $this->GetBoundary($this->boundary[2], '', 'text/plain', '') . $this->LE; // Create text body
1003 $result .= $this->EncodeString($this->AltBody, $this->Encoding);
1004 $result .= $this->LE.$this->LE;
1005 $result .= $this->GetBoundary($this->boundary[2], '', 'text/html', '') . $this->LE; // Create the HTML body
1006 $result .= $this->EncodeString($this->Body, $this->Encoding);
1007 $result .= $this->LE.$this->LE;
1008 $result .= $this->EndBoundary($this->boundary[2]);
1009 $result .= $this->AttachAll();
1010 break;
1011 }
1012
1013 if($this->IsError()) {
1014 $result = '';
1015 } else if ($this->sign_key_file) {
1016 $file = tempnam("", "mail");
1017 $fp = fopen($file, "w");
1018 fwrite($fp, $result);
1019 fclose($fp);
1020 $signed = tempnam("", "signed");
1021
1022 if (@openssl_pkcs7_sign($file, $signed, "file://".$this->sign_cert_file, array("file://".$this->sign_key_file, $this->sign_key_pass), null)) {
1023 $fp = fopen($signed, "r");
1024 $result = '';
1025 while(!feof($fp)){
1026 $result = $result . fread($fp, 1024);
1027 }
1028 fclose($fp);
1029 } else {
1030 $this->SetError($this->Lang("signing").openssl_error_string());
1031 $result = '';
1032 }
1033
1034 unlink($file);
1035 unlink($signed);
1036 }
1037
1038 return $result;
1039 }
1040
1041 /**
1042 * Returns the start of a message boundary.
1043 * @access public
1044 */
1045 public function GetBoundary($boundary, $charSet, $contentType, $encoding) {
1046 $result = '';
1047 if($charSet == '') {
1048 $charSet = $this->CharSet;
1049 }
1050 if($contentType == '') {
1051 $contentType = $this->ContentType;
1052 }
1053 if($encoding == '') {
1054 $encoding = $this->Encoding;
1055 }
1056 $result .= $this->TextLine('--' . $boundary);
1057 $result .= sprintf("Content-Type: %s; charset = \"%s\"", $contentType, $charSet);
1058 $result .= $this->LE;
1059 $result .= $this->HeaderLine('Content-Transfer-Encoding', $encoding);
1060 $result .= $this->LE;
1061
1062 return $result;
1063 }
1064
1065 /**
1066 * Returns the end of a message boundary.
1067 * @access public
1068 */
1069 public function EndBoundary($boundary) {
1070 return $this->LE . '--' . $boundary . '--' . $this->LE;
1071 }
1072
1073 /**
1074 * Sets the message type.
1075 * @access public
1076 * @return void
1077 */
1078 public function SetMessageType() {
1079 if(count($this->attachment) < 1 && strlen($this->AltBody) < 1) {
1080 $this->message_type = 'plain';
1081 } else {
1082 if(count($this->attachment) > 0) {
1083 $this->message_type = 'attachments';
1084 }
1085 if(strlen($this->AltBody) > 0 && count($this->attachment) < 1) {
1086 $this->message_type = 'alt';
1087 }
1088 if(strlen($this->AltBody) > 0 && count($this->attachment) > 0) {
1089 $this->message_type = 'alt_attachments';
1090 }
1091 }
1092 }
1093
1094 /* Returns a formatted header line.
1095 * @access public
1096 * @return string
1097 */
1098 public function HeaderLine($name, $value) {
1099 return $name . ': ' . $value . $this->LE;
1100 }
1101
1102 /**
1103 * Returns a formatted mail line.
1104 * @access public
1105 * @return string
1106 */
1107 public function TextLine($value) {
1108 return $value . $this->LE;
1109 }
1110
1111 /////////////////////////////////////////////////
1112 // CLASS METHODS, ATTACHMENTS
1113 /////////////////////////////////////////////////
1114
1115 /**
1116 * Adds an attachment from a path on the filesystem.
1117 * Returns false if the file could not be found
1118 * or accessed.
1119 * @param string $path Path to the attachment.
1120 * @param string $name Overrides the attachment name.
1121 * @param string $encoding File encoding (see $Encoding).
1122 * @param string $type File extension (MIME) type.
1123 * @return bool
1124 */
1125 public function AddAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream') {
1126 if(!@is_file($path)) {
1127 $this->SetError($this->Lang('file_access') . $path);
1128 return false;
1129 }
1130
1131 $filename = basename($path);
1132 if($name == '') {
1133 $name = $filename;
1134 }
1135
1136 $cur = count($this->attachment);
1137 $this->attachment[$cur][0] = $path;
1138 $this->attachment[$cur][1] = $filename;
1139 $this->attachment[$cur][2] = $name;
1140 $this->attachment[$cur][3] = $encoding;
1141 $this->attachment[$cur][4] = $type;
1142 $this->attachment[$cur][5] = false; // isStringAttachment
1143 $this->attachment[$cur][6] = 'attachment';
1144 $this->attachment[$cur][7] = 0;
1145
1146 return true;
1147 }
1148
1149 /**
1150 * Attaches all fs, string, and binary attachments to the message.
1151 * Returns an empty string on failure.
1152 * @access public
1153 * @return string
1154 */
1155 public function AttachAll() {
1156 /* Return text of body */
1157 $mime = array();
1158
1159 /* Add all attachments */
1160 for($i = 0; $i < count($this->attachment); $i++) {
1161 /* Check for string attachment */
1162 $bString = $this->attachment[$i][5];
1163 if ($bString) {
1164 $string = $this->attachment[$i][0];
1165 } else {
1166 $path = $this->attachment[$i][0];
1167 }
1168
1169 $filename = $this->attachment[$i][1];
1170 $name = $this->attachment[$i][2];
1171 $encoding = $this->attachment[$i][3];
1172 $type = $this->attachment[$i][4];
1173 $disposition = $this->attachment[$i][6];
1174 $cid = $this->attachment[$i][7];
1175
1176 $mime[] = sprintf("--%s%s", $this->boundary[1], $this->LE);
1177 //$mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $name, $this->LE);
1178 $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $this->EncodeHeader($this->SecureHeader($name)), $this->LE);
1179 $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE);
1180
1181 if($disposition == 'inline') {
1182 $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE);
1183 }
1184
1185 //$mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $name, $this->LE.$this->LE);
1186 $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE);
1187
1188 /* Encode as string attachment */
1189 if($bString) {
1190 $mime[] = $this->EncodeString($string, $encoding);
1191 if($this->IsError()) {
1192 return '';
1193 }
1194 $mime[] = $this->LE.$this->LE;
1195 } else {
1196 $mime[] = $this->EncodeFile($path, $encoding);
1197 if($this->IsError()) {
1198 return '';
1199 }
1200 $mime[] = $this->LE.$this->LE;
1201 }
1202 }
1203
1204 $mime[] = sprintf("--%s--%s", $this->boundary[1], $this->LE);
1205
1206 return join('', $mime);
1207 }
1208
1209 /**
1210 * Encodes attachment in requested format. Returns an
1211 * empty string on failure.
1212 * @access public
1213 * @return string
1214 */
1215 public function EncodeFile ($path, $encoding = 'base64') {
1216 if(!@$fd = fopen($path, 'rb')) {
1217 $this->SetError($this->Lang('file_open') . $path);
1218 return '';
1219 }
1220 if (function_exists('get_magic_quotes')) {
1221 function get_magic_quotes() {
1222 return false;
1223 }
1224}
1225 if (PHP_VERSION < 6) {
1226 $magic_quotes = get_magic_quotes_runtime();
1227 set_magic_quotes_runtime(0);
1228 }
1229 $file_buffer = file_get_contents($path);
1230 $file_buffer = $this->EncodeString($file_buffer, $encoding);
1231 fclose($fd);
1232 if (PHP_VERSION < 6) { set_magic_quotes_runtime($magic_quotes); }
1233 return $file_buffer;
1234 }
1235
1236 /**
1237 * Encodes string to requested format. Returns an
1238 * empty string on failure.
1239 * @access public
1240 * @return string
1241 */
1242 public function EncodeString ($str, $encoding = 'base64') {
1243 $encoded = '';
1244 switch(strtolower($encoding)) {
1245 case 'base64':
1246 $encoded = chunk_split(base64_encode($str), 76, $this->LE);
1247 break;
1248 case '7bit':
1249 case '8bit':
1250 $encoded = $this->FixEOL($str);
1251 if (substr($encoded, -(strlen($this->LE))) != $this->LE)
1252 $encoded .= $this->LE;
1253 break;
1254 case 'binary':
1255 $encoded = $str;
1256 break;
1257 case 'quoted-printable':
1258 $encoded = $this->EncodeQP($str);
1259 break;
1260 default:
1261 $this->SetError($this->Lang('encoding') . $encoding);
1262 break;
1263 }
1264 return $encoded;
1265 }
1266
1267 /**
1268 * Encode a header string to best of Q, B, quoted or none.
1269 * @access public
1270 * @return string
1271 */
1272 public function EncodeHeader ($str, $position = 'text') {
1273 $x = 0;
1274
1275 switch (strtolower($position)) {
1276 case 'phrase':
1277 if (!preg_match('/[\200-\377]/', $str)) {
1278 /* Can't use addslashes as we don't know what value has magic_quotes_sybase. */
1279 $encoded = addcslashes($str, "\0..\37\177\\\"");
1280 if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
1281 return ($encoded);
1282 } else {
1283 return ("\"$encoded\"");
1284 }
1285 }
1286 $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
1287 break;
1288 case 'comment':
1289 $x = preg_match_all('/[()"]/', $str, $matches);
1290 /* Fall-through */
1291 case 'text':
1292 default:
1293 $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
1294 break;
1295 }
1296
1297 if ($x == 0) {
1298 return ($str);
1299 }
1300
1301 $maxlen = 75 - 7 - strlen($this->CharSet);
1302 /* Try to select the encoding which should produce the shortest output */
1303 if (strlen($str)/3 < $x) {
1304 $encoding = 'B';
1305 if (function_exists('mb_strlen') && $this->HasMultiBytes($str)) {
1306 // Use a custom function which correctly encodes and wraps long
1307 // multibyte strings without breaking lines within a character
1308 $encoded = $this->Base64EncodeWrapMB($str);
1309 } else {
1310 $encoded = base64_encode($str);
1311 $maxlen -= $maxlen % 4;
1312 $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
1313 }
1314 } else {
1315 $encoding = 'Q';
1316 $encoded = $this->EncodeQ($str, $position);
1317 $encoded = $this->WrapText($encoded, $maxlen, true);
1318 $encoded = str_replace('='.$this->LE, "\n", trim($encoded));
1319 }
1320
1321 $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded);
1322 $encoded = trim(str_replace("\n", $this->LE, $encoded));
1323
1324 return $encoded;
1325 }
1326
1327 /**
1328 * Checks if a string contains multibyte characters.
1329 * @access public
1330 * @param string $str multi-byte text to wrap encode
1331 * @return bool
1332 */
1333 public function HasMultiBytes($str) {
1334 if (function_exists('mb_strlen')) {
1335 return (strlen($str) > mb_strlen($str, $this->CharSet));
1336 } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
1337 return False;
1338 }
1339 }
1340
1341 /**
1342 * Correctly encodes and wraps long multibyte strings for mail headers
1343 * without breaking lines within a character.
1344 * Adapted from a function by paravoid at http://uk.php.net/manual/en/function.mb-encode-mimeheader.php
1345 * @access public
1346 * @param string $str multi-byte text to wrap encode
1347 * @return string
1348 */
1349 public function Base64EncodeWrapMB($str) {
1350 $start = "=?".$this->CharSet."?B?";
1351 $end = "?=";
1352 $encoded = "";
1353
1354 $mb_length = mb_strlen($str, $this->CharSet);
1355 // Each line must have length <= 75, including $start and $end
1356 $length = 75 - strlen($start) - strlen($end);
1357 // Average multi-byte ratio
1358 $ratio = $mb_length / strlen($str);
1359 // Base64 has a 4:3 ratio
1360 $offset = $avgLength = floor($length * $ratio * .75);
1361
1362 for ($i = 0; $i < $mb_length; $i += $offset) {
1363 $lookBack = 0;
1364
1365 do {
1366 $offset = $avgLength - $lookBack;
1367 $chunk = mb_substr($str, $i, $offset, $this->CharSet);
1368 $chunk = base64_encode($chunk);
1369 $lookBack++;
1370 }
1371 while (strlen($chunk) > $length);
1372
1373 $encoded .= $chunk . $this->LE;
1374 }
1375
1376 // Chomp the last linefeed
1377 $encoded = substr($encoded, 0, -strlen($this->LE));
1378 return $encoded;
1379 }
1380
1381 /**
1382 * Encode string to quoted-printable.
1383 * @access public
1384 * @param string $string the text to encode
1385 * @param integer $line_max Number of chars allowed on a line before wrapping
1386 * @return string
1387 */
1388 public function EncodeQP( $input = '', $line_max = 76, $space_conv = false ) {
1389 $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
1390 $lines = preg_split('/(?:\r\n|\r|\n)/', $input);
1391 $eol = "\r\n";
1392 $escape = '=';
1393 $output = '';
1394 while( list(, $line) = each($lines) ) {
1395 $linlen = strlen($line);
1396 $newline = '';
1397 for($i = 0; $i < $linlen; $i++) {
1398 $c = substr( $line, $i, 1 );
1399 $dec = ord( $c );
1400 if ( ( $i == 0 ) && ( $dec == 46 ) ) { // convert first point in the line into =2E
1401 $c = '=2E';
1402 }
1403 if ( $dec == 32 ) {
1404 if ( $i == ( $linlen - 1 ) ) { // convert space at eol only
1405 $c = '=20';
1406 } else if ( $space_conv ) {
1407 $c = '=20';
1408 }
1409 } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required
1410 $h2 = floor($dec/16);
1411 $h1 = floor($dec%16);
1412 $c = $escape.$hex[$h2].$hex[$h1];
1413 }
1414 if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted
1415 $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
1416 $newline = '';
1417 // check if newline first character will be point or not
1418 if ( $dec == 46 ) {
1419 $c = '=2E';
1420 }
1421 }
1422 $newline .= $c;
1423 } // end of for
1424 $output .= $newline.$eol;
1425 } // end of while
1426 return trim($output);
1427 }
1428
1429 /**
1430 * Encode string to q encoding.
1431 * @access public
1432 * @return string
1433 */
1434 public function EncodeQ ($str, $position = 'text') {
1435 /* There should not be any EOL in the string */
1436 $encoded = preg_replace("[\r\n]", '', $str);
1437
1438 switch (strtolower($position)) {
1439 case 'phrase':
1440 $encoded = preg_replace("/([^A-Za-z0-9!*+\/ -])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded);
1441 break;
1442 case 'comment':
1443 $encoded = preg_replace("/([\(\)\"])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded);
1444 case 'text':
1445 default:
1446 /* Replace every high ascii, control =, ? and _ characters */
1447 $encoded = preg_replace('/([\000-\011\013\014\016-\037\075\077\137\177-\377])/e',
1448 "'='.sprintf('%02X', ord('\\1'))", $encoded);
1449 break;
1450 }
1451
1452 /* Replace every spaces to _ (more readable than =20) */
1453 $encoded = str_replace(' ', '_', $encoded);
1454
1455 return $encoded;
1456 }
1457
1458 /**
1459 * Adds a string or binary attachment (non-filesystem) to the list.
1460 * This method can be used to attach ascii or binary data,
1461 * such as a BLOB record from a database.
1462 * @param string $string String attachment data.
1463 * @param string $filename Name of the attachment.
1464 * @param string $encoding File encoding (see $Encoding).
1465 * @param string $type File extension (MIME) type.
1466 * @return void
1467 */
1468 public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream') {
1469 /* Append to $attachment array */
1470 $cur = count($this->attachment);
1471 $this->attachment[$cur][0] = $string;
1472 $this->attachment[$cur][1] = $filename;
1473 $this->attachment[$cur][2] = $filename;
1474 $this->attachment[$cur][3] = $encoding;
1475 $this->attachment[$cur][4] = $type;
1476 $this->attachment[$cur][5] = true; // isString
1477 $this->attachment[$cur][6] = 'attachment';
1478 $this->attachment[$cur][7] = 0;
1479 }
1480
1481 /**
1482 * Adds an embedded attachment. This can include images, sounds, and
1483 * just about any other document. Make sure to set the $type to an
1484 * image type. For JPEG images use "image/jpeg" and for GIF images
1485 * use "image/gif".
1486 * @param string $path Path to the attachment.
1487 * @param string $cid Content ID of the attachment. Use this to identify
1488 * the Id for accessing the image in an HTML form.
1489 * @param string $name Overrides the attachment name.
1490 * @param string $encoding File encoding (see $Encoding).
1491 * @param string $type File extension (MIME) type.
1492 * @return bool
1493 */
1494 public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') {
1495
1496 if(!@is_file($path)) {
1497 $this->SetError($this->Lang('file_access') . $path);
1498 return false;
1499 }
1500
1501 $filename = basename($path);
1502 if($name == '') {
1503 $name = $filename;
1504 }
1505
1506 /* Append to $attachment array */
1507 $cur = count($this->attachment);
1508 $this->attachment[$cur][0] = $path;
1509 $this->attachment[$cur][1] = $filename;
1510 $this->attachment[$cur][2] = $name;
1511 $this->attachment[$cur][3] = $encoding;
1512 $this->attachment[$cur][4] = $type;
1513 $this->attachment[$cur][5] = false;
1514 $this->attachment[$cur][6] = 'inline';
1515 $this->attachment[$cur][7] = $cid;
1516
1517 return true;
1518 }
1519
1520 /**
1521 * Returns true if an inline attachment is present.
1522 * @access public
1523 * @return bool
1524 */
1525 public function InlineImageExists() {
1526 $result = false;
1527 for($i = 0; $i < count($this->attachment); $i++) {
1528 if($this->attachment[$i][6] == 'inline') {
1529 $result = true;
1530 break;
1531 }
1532 }
1533
1534 return $result;
1535 }
1536
1537 /////////////////////////////////////////////////
1538 // CLASS METHODS, MESSAGE RESET
1539 /////////////////////////////////////////////////
1540
1541 /**
1542 * Clears all recipients assigned in the TO array. Returns void.
1543 * @return void
1544 */
1545 public function ClearAddresses() {
1546 $this->to = array();
1547 }
1548
1549 /**
1550 * Clears all recipients assigned in the CC array. Returns void.
1551 * @return void
1552 */
1553 public function ClearCCs() {
1554 $this->cc = array();
1555 }
1556
1557 /**
1558 * Clears all recipients assigned in the BCC array. Returns void.
1559 * @return void
1560 */
1561 public function ClearBCCs() {
1562 $this->bcc = array();
1563 }
1564
1565 /**
1566 * Clears all recipients assigned in the ReplyTo array. Returns void.
1567 * @return void
1568 */
1569 public function ClearReplyTos() {
1570 $this->ReplyTo = array();
1571 }
1572
1573 /**
1574 * Clears all recipients assigned in the TO, CC and BCC
1575 * array. Returns void.
1576 * @return void
1577 */
1578 public function ClearAllRecipients() {
1579 $this->to = array();
1580 $this->cc = array();
1581 $this->bcc = array();
1582 }
1583
1584 /**
1585 * Clears all previously set filesystem, string, and binary
1586 * attachments. Returns void.
1587 * @return void
1588 */
1589 public function ClearAttachments() {
1590 $this->attachment = array();
1591 }
1592
1593 /**
1594 * Clears all custom headers. Returns void.
1595 * @return void
1596 */
1597 public function ClearCustomHeaders() {
1598 $this->CustomHeader = array();
1599 }
1600
1601 /////////////////////////////////////////////////
1602 // CLASS METHODS, MISCELLANEOUS
1603 /////////////////////////////////////////////////
1604
1605 /**
1606 * Adds the error message to the error container.
1607 * Returns void.
1608 * @access private
1609 * @return void
1610 */
1611 private function SetError($msg) {
1612 $this->error_count++;
1613 $this->ErrorInfo = $msg;
1614 }
1615
1616 /**
1617 * Returns the proper RFC 822 formatted date.
1618 * @access private
1619 * @return string
1620 */
1621 private static function RFCDate() {
1622 $tz = date('Z');
1623 $tzs = ($tz < 0) ? '-' : '+';
1624 $tz = abs($tz);
1625 $tz = (int)($tz/3600)*100 + ($tz%3600)/60;
1626 $result = sprintf("%s %s%04d", date('D, j M Y H:i:s'), $tzs, $tz);
1627
1628 return $result;
1629 }
1630
1631 /**
1632 * Returns the server hostname or 'localhost.localdomain' if unknown.
1633 * @access private
1634 * @return string
1635 */
1636 private function ServerHostname() {
1637 if (!empty($this->Hostname)) {
1638 $result = $this->Hostname;
1639 } elseif (isset($_SERVER['SERVER_NAME'])) {
1640 $result = $_SERVER['SERVER_NAME'];
1641 } else {
1642 $result = "localhost.localdomain";
1643 }
1644
1645 return $result;
1646 }
1647
1648 /**
1649 * Returns a message in the appropriate language.
1650 * @access private
1651 * @return string
1652 */
1653 private function Lang($key) {
1654 if(count($this->language) < 1) {
1655 $this->SetLanguage('en'); // set the default language
1656 }
1657
1658 if(isset($this->language[$key])) {
1659 return $this->language[$key];
1660 } else {
1661 return 'Language string failed to load: ' . $key;
1662 }
1663 }
1664
1665 /**
1666 * Returns true if an error occurred.
1667 * @access public
1668 * @return bool
1669 */
1670 public function IsError() {
1671 return ($this->error_count > 0);
1672 }
1673
1674 /**
1675 * Changes every end of line from CR or LF to CRLF.
1676 * @access private
1677 * @return string
1678 */
1679 private function FixEOL($str) {
1680 $str = str_replace("\r\n", "\n", $str);
1681 $str = str_replace("\r", "\n", $str);
1682 $str = str_replace("\n", $this->LE, $str);
1683 return $str;
1684 }
1685
1686 /**
1687 * Adds a custom header.
1688 * @access public
1689 * @return void
1690 */
1691 public function AddCustomHeader($custom_header) {
1692 $this->CustomHeader[] = explode(':', $custom_header, 2);
1693 }
1694
1695 /**
1696 * Evaluates the message and returns modifications for inline images and backgrounds
1697 * @access public
1698 * @return $message
1699 */
1700 public function MsgHTML($message,$basedir='') {
1701 preg_match_all("/(src|background)=\"(.*)\"/Ui", $message, $images);
1702 if(isset($images[2])) {
1703 foreach($images[2] as $i => $url) {
1704 // do not change urls for absolute images (thanks to corvuscorax)
1705 if (!preg_match('/^[A-z][A-z]*:\/\//',$url)) {
1706 $filename = basename($url);
1707 $directory = dirname($url);
1708 ($directory == '.')?$directory='':'';
1709 $cid = 'cid:' . md5($filename);
1710 $fileParts = split("\.", $filename);
1711 $ext = $fileParts[1];
1712 $mimeType = $this->_mime_types($ext);
1713 if ( strlen($basedir) > 1 && substr($basedir,-1) != '/') { $basedir .= '/'; }
1714 if ( strlen($directory) > 1 && substr($basedir,-1) != '/') { $directory .= '/'; }
1715 $this->AddEmbeddedImage($basedir.$directory.$filename, md5($filename), $filename, 'base64', $mimeType);
1716 if ( $this->AddEmbeddedImage($basedir.$directory.$filename, md5($filename), $filename, 'base64',$mimeType) ) {
1717 $message = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $message);
1718 }
1719 }
1720 }
1721 }
1722 $this->IsHTML(true);
1723 $this->Body = $message;
1724 $textMsg = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s','',$message)));
1725 if ( !empty($textMsg) && empty($this->AltBody) ) {
1726 $this->AltBody = $textMsg;
1727 }
1728 if ( empty($this->AltBody) ) {
1729 $this->AltBody = 'To view this email message, open the email in with HTML compatibility!' . "\n\n";
1730 }
1731 }
1732
1733 /**
1734 * Gets the mime type of the embedded or inline image
1735 * @access public
1736 * @return mime type of ext
1737 */
1738 public function _mime_types($ext = '') {
1739 $mimes = array(
1740 'hqx' => 'application/mac-binhex40',
1741 'cpt' => 'application/mac-compactpro',
1742 'doc' => 'application/msword',
1743 'bin' => 'application/macbinary',
1744 'dms' => 'application/octet-stream',
1745 'lha' => 'application/octet-stream',
1746 'lzh' => 'application/octet-stream',
1747 'exe' => 'application/octet-stream',
1748 'class' => 'application/octet-stream',
1749 'psd' => 'application/octet-stream',
1750 'so' => 'application/octet-stream',
1751 'sea' => 'application/octet-stream',
1752 'dll' => 'application/octet-stream',
1753 'oda' => 'application/oda',
1754 'pdf' => 'application/pdf',
1755 'ai' => 'application/postscript',
1756 'eps' => 'application/postscript',
1757 'ps' => 'application/postscript',
1758 'smi' => 'application/smil',
1759 'smil' => 'application/smil',
1760 'mif' => 'application/vnd.mif',
1761 'xls' => 'application/vnd.ms-excel',
1762 'ppt' => 'application/vnd.ms-powerpoint',
1763 'wbxml' => 'application/vnd.wap.wbxml',
1764 'wmlc' => 'application/vnd.wap.wmlc',
1765 'dcr' => 'application/x-director',
1766 'dir' => 'application/x-director',
1767 'dxr' => 'application/x-director',
1768 'dvi' => 'application/x-dvi',
1769 'gtar' => 'application/x-gtar',
1770 'php' => 'application/x-httpd-php',
1771 'php4' => 'application/x-httpd-php',
1772 'php3' => 'application/x-httpd-php',
1773 'phtml' => 'application/x-httpd-php',
1774 'phps' => 'application/x-httpd-php-source',
1775 'js' => 'application/x-javascript',
1776 'swf' => 'application/x-shockwave-flash',
1777 'sit' => 'application/x-stuffit',
1778 'tar' => 'application/x-tar',
1779 'tgz' => 'application/x-tar',
1780 'xhtml' => 'application/xhtml+xml',
1781 'xht' => 'application/xhtml+xml',
1782 'zip' => 'application/zip',
1783 'mid' => 'audio/midi',
1784 'midi' => 'audio/midi',
1785 'mpga' => 'audio/mpeg',
1786 'mp2' => 'audio/mpeg',
1787 'mp3' => 'audio/mpeg',
1788 'aif' => 'audio/x-aiff',
1789 'aiff' => 'audio/x-aiff',
1790 'aifc' => 'audio/x-aiff',
1791 'ram' => 'audio/x-pn-realaudio',
1792 'rm' => 'audio/x-pn-realaudio',
1793 'rpm' => 'audio/x-pn-realaudio-plugin',
1794 'ra' => 'audio/x-realaudio',
1795 'rv' => 'video/vnd.rn-realvideo',
1796 'wav' => 'audio/x-wav',
1797 'bmp' => 'image/bmp',
1798 'gif' => 'image/gif',
1799 'jpeg' => 'image/jpeg',
1800 'jpg' => 'image/jpeg',
1801 'jpe' => 'image/jpeg',
1802 'png' => 'image/png',
1803 'tiff' => 'image/tiff',
1804 'tif' => 'image/tiff',
1805 'css' => 'text/css',
1806 'html' => 'text/html',
1807 'htm' => 'text/html',
1808 'shtml' => 'text/html',
1809 'txt' => 'text/plain',
1810 'text' => 'text/plain',
1811 'log' => 'text/plain',
1812 'rtx' => 'text/richtext',
1813 'rtf' => 'text/rtf',
1814 'xml' => 'text/xml',
1815 'xsl' => 'text/xml',
1816 'mpeg' => 'video/mpeg',
1817 'mpg' => 'video/mpeg',
1818 'mpe' => 'video/mpeg',
1819 'qt' => 'video/quicktime',
1820 'mov' => 'video/quicktime',
1821 'avi' => 'video/x-msvideo',
1822 'movie' => 'video/x-sgi-movie',
1823 'doc' => 'application/msword',
1824 'word' => 'application/msword',
1825 'xl' => 'application/excel',
1826 'eml' => 'message/rfc822'
1827 );
1828 return ( ! isset($mimes[strtolower($ext)])) ? 'application/octet-stream' : $mimes[strtolower($ext)];
1829 }
1830
1831 /**
1832 * Set (or reset) Class Objects (variables)
1833 *
1834 * Usage Example:
1835 * $page->set('X-Priority', '3');
1836 *
1837 * @access public
1838 * @param string $name Parameter Name
1839 * @param mixed $value Parameter Value
1840 * NOTE: will not work with arrays, there are no arrays to set/reset
1841 */
1842 public function set ( $name, $value = '' ) {
1843 if ( isset($this->$name) ) {
1844 $this->$name = $value;
1845 } else {
1846 $this->SetError('Cannot set or reset variable ' . $name);
1847 return false;
1848 }
1849 }
1850
1851 /**
1852 * Read a file from a supplied filename and return it.
1853 *
1854 * @access public
1855 * @param string $filename Parameter File Name
1856 */
1857 public function getFile($filename) {
1858 $return = '';
1859 if ($fp = fopen($filename, 'rb')) {
1860 while (!feof($fp)) {
1861 $return .= fread($fp, 1024);
1862 }
1863 fclose($fp);
1864 return $return;
1865 } else {
1866 return false;
1867 }
1868 }
1869
1870 /**
1871 * Strips newlines to prevent header injection.
1872 * @access public
1873 * @param string $str String
1874 * @return string
1875 */
1876 public function SecureHeader($str) {
1877 $str = trim($str);
1878 $str = str_replace("\r", "", $str);
1879 $str = str_replace("\n", "", $str);
1880 return $str;
1881 }
1882
1883 /**
1884 * Set the private key file and password to sign the message.
1885 *
1886 * @access public
1887 * @param string $key_filename Parameter File Name
1888 * @param string $key_pass Password for private key
1889 */
1890 public function Sign($cert_filename, $key_filename, $key_pass) {
1891 $this->sign_cert_file = $cert_filename;
1892 $this->sign_key_file = $key_filename;
1893 $this->sign_key_pass = $key_pass;
1894 }
1895}
1896
1897?>
diff --git a/central/trunk/includes/class.smtp.php b/central/trunk/includes/class.smtp.php new file mode 100644 index 0000000..f1c9cfe --- /dev/null +++ b/central/trunk/includes/class.smtp.php
@@ -0,0 +1,1113 @@
1<?php
2/*~ class.smtp.php
3.---------------------------------------------------------------------------.
4| Software: PHPMailer - PHP email class |
5| Version: 2.2.1 |
6| Contact: via sourceforge.net support pages (also www.codeworxtech.com) |
7| Info: http://phpmailer.sourceforge.net |
8| Support: http://sourceforge.net/projects/phpmailer/ |
9| ------------------------------------------------------------------------- |
10| Author: Andy Prevost (project admininistrator) |
11| Author: Brent R. Matzelle (original founder) |
12| Copyright (c) 2004-2007, Andy Prevost. All Rights Reserved. |
13| Copyright (c) 2001-2003, Brent R. Matzelle |
14| ------------------------------------------------------------------------- |
15| License: Distributed under the Lesser General Public License (LGPL) |
16| http://www.gnu.org/copyleft/lesser.html |
17| This program is distributed in the hope that it will be useful - WITHOUT |
18| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
19| FITNESS FOR A PARTICULAR PURPOSE. |
20| ------------------------------------------------------------------------- |
21| We offer a number of paid services (www.codeworxtech.com): |
22| - Web Hosting on highly optimized fast and secure servers |
23| - Technology Consulting |
24| - Oursourcing (highly qualified programmers and graphic designers) |
25'---------------------------------------------------------------------------'
26
27/**
28 * SMTP is rfc 821 compliant and implements all the rfc 821 SMTP
29 * commands except TURN which will always return a not implemented
30 * error. SMTP also provides some utility methods for sending mail
31 * to an SMTP server.
32 * @package PHPMailer
33 * @author Chris Ryan
34 */
35
36class SMTP {
37 /**
38 * SMTP server port
39 * @var int
40 */
41 public $SMTP_PORT = 25;
42
43 /**
44 * SMTP reply line ending
45 * @var string
46 */
47 public $CRLF = "\r\n";
48
49 /**
50 * Sets whether debugging is turned on
51 * @var bool
52 */
53 public $do_debug; // the level of debug to perform
54
55 /**
56 * Sets VERP use on/off (default is off)
57 * @var bool
58 */
59 public $do_verp = false;
60
61 /**#@+
62 * @access private
63 */
64 private $smtp_conn; // the socket to the server
65 private $error; // error if any on the last call
66 private $helo_rply; // the reply the server sent to us for HELO
67 /**#@-*/
68
69 /**
70 * Initialize the class so that the data is in a known state.
71 * @access public
72 * @return void
73 */
74 public function __construct() {
75 $this->smtp_conn = 0;
76 $this->error = null;
77 $this->helo_rply = null;
78
79 $this->do_debug = 0;
80 }
81
82 /*************************************************************
83 * CONNECTION FUNCTIONS *
84 ***********************************************************/
85
86 /**
87 * Connect to the server specified on the port specified.
88 * If the port is not specified use the default SMTP_PORT.
89 * If tval is specified then a connection will try and be
90 * established with the server for that number of seconds.
91 * If tval is not specified the default is 30 seconds to
92 * try on the connection.
93 *
94 * SMTP CODE SUCCESS: 220
95 * SMTP CODE FAILURE: 421
96 * @access public
97 * @return bool
98 */
99 public function Connect($host,$port=0,$tval=30) {
100 /* set the error val to null so there is no confusion */
101 $this->error = null;
102
103 /* make sure we are __not__ connected */
104 if($this->connected()) {
105 /* ok we are connected! what should we do?
106 * for now we will just give an error saying we
107 * are already connected
108 */
109 $this->error = array("error" => "Already connected to a server");
110 return false;
111 }
112
113 if(empty($port)) {
114 $port = $this->SMTP_PORT;
115 }
116
117 /* connect to the smtp server */
118 $this->smtp_conn = fsockopen($host, // the host of the server
119 $port, // the port to use
120 $errno, // error number if any
121 $errstr, // error message if any
122 $tval); // give up after ? secs
123 /* verify we connected properly */
124 if(empty($this->smtp_conn)) {
125 $this->error = array("error" => "Failed to connect to server",
126 "errno" => $errno,
127 "errstr" => $errstr);
128 if($this->do_debug >= 1) {
129 echo "SMTP -> ERROR: " . $this->error["error"] .
130 ": $errstr ($errno)" . $this->CRLF;
131 }
132 return false;
133 }
134
135 /* sometimes the SMTP server takes a little longer to respond
136 * so we will give it a longer timeout for the first read
137 * - Windows still does not have support for this timeout function
138 */
139 if(substr(PHP_OS, 0, 3) != "WIN")
140 socket_set_timeout($this->smtp_conn, $tval, 0);
141
142 /* get any announcement stuff */
143 $announce = $this->get_lines();
144
145 /* set the timeout of any socket functions at 1/10 of a second */
146 //if(function_exists("socket_set_timeout"))
147 // socket_set_timeout($this->smtp_conn, 0, 100000);
148
149 if($this->do_debug >= 2) {
150 echo "SMTP -> FROM SERVER:" . $this->CRLF . $announce;
151 }
152
153 return true;
154 }
155
156 /**
157 * Initiate a TSL communication with the server.
158 *
159 * SMTP CODE 220 Ready to start TLS
160 * SMTP CODE 501 Syntax error (no parameters allowed)
161 * SMTP CODE 454 TLS not available due to temporary reason
162 * @access public
163 * @return bool success
164 */
165 public function StartTLS() {
166 $this->error = null; # to avoid confusion
167
168 if(!$this->connected()) {
169 $this->error = array("error" => "Called StartTLS() without being connected");
170 return false;
171 }
172
173 fputs($this->smtp_conn,"STARTTLS" . $extra . $this->CRLF);
174
175 $rply = $this->get_lines();
176 $code = substr($rply,0,3);
177
178 if($this->do_debug >= 2) {
179 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
180 }
181
182 if($code != 220) {
183 $this->error =
184 array("error" => "STARTTLS not accepted from server",
185 "smtp_code" => $code,
186 "smtp_msg" => substr($rply,4));
187 if($this->do_debug >= 1) {
188 echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF;
189 }
190 return false;
191 }
192
193 //Begin encrypted connection
194 if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
195 return false;
196 }
197
198 return true;
199 }
200
201 /**
202 * Performs SMTP authentication. Must be run after running the
203 * Hello() method. Returns true if successfully authenticated.
204 * @access public
205 * @return bool
206 */
207 public function Authenticate($username, $password) {
208 // Start authentication
209 fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF);
210
211 $rply = $this->get_lines();
212 $code = substr($rply,0,3);
213
214 if($code != 334) {
215 $this->error =
216 array("error" => "AUTH not accepted from server",
217 "smtp_code" => $code,
218 "smtp_msg" => substr($rply,4));
219 if($this->do_debug >= 1) {
220 echo "SMTP -> ERROR: " . $this->error["error"] .
221 ": " . $rply . $this->CRLF;
222 }
223 return false;
224 }
225
226 // Send encoded username
227 fputs($this->smtp_conn, base64_encode($username) . $this->CRLF);
228
229 $rply = $this->get_lines();
230 $code = substr($rply,0,3);
231
232 if($code != 334) {
233 $this->error =
234 array("error" => "Username not accepted from server",
235 "smtp_code" => $code,
236 "smtp_msg" => substr($rply,4));
237 if($this->do_debug >= 1) {
238 echo "SMTP -> ERROR: " . $this->error["error"] .
239 ": " . $rply . $this->CRLF;
240 }
241 return false;
242 }
243
244 // Send encoded password
245 fputs($this->smtp_conn, base64_encode($password) . $this->CRLF);
246
247 $rply = $this->get_lines();
248 $code = substr($rply,0,3);
249
250 if($code != 235) {
251 $this->error =
252 array("error" => "Password not accepted from server",
253 "smtp_code" => $code,
254 "smtp_msg" => substr($rply,4));
255 if($this->do_debug >= 1) {
256 echo "SMTP -> ERROR: " . $this->error["error"] .
257 ": " . $rply . $this->CRLF;
258 }
259 return false;
260 }
261
262 return true;
263 }
264
265 /**
266 * Returns true if connected to a server otherwise false
267 * @access public
268 * @return bool
269 */
270 public function Connected() {
271 if(!empty($this->smtp_conn)) {
272 $sock_status = socket_get_status($this->smtp_conn);
273 if($sock_status["eof"]) {
274 // hmm this is an odd situation... the socket is
275 // valid but we are not connected anymore
276 if($this->do_debug >= 1) {
277 echo "SMTP -> NOTICE:" . $this->CRLF .
278 "EOF caught while checking if connected";
279 }
280 $this->Close();
281 return false;
282 }
283 return true; // everything looks good
284 }
285 return false;
286 }
287
288 /**
289 * Closes the socket and cleans up the state of the class.
290 * It is not considered good to use this function without
291 * first trying to use QUIT.
292 * @access public
293 * @return void
294 */
295 public function Close() {
296 $this->error = null; // so there is no confusion
297 $this->helo_rply = null;
298 if(!empty($this->smtp_conn)) {
299 // close the connection and cleanup
300 fclose($this->smtp_conn);
301 $this->smtp_conn = 0;
302 }
303 }
304
305 /***************************************************************
306 * SMTP COMMANDS *
307 *************************************************************/
308
309 /**
310 * Issues a data command and sends the msg_data to the server
311 * finializing the mail transaction. $msg_data is the message
312 * that is to be send with the headers. Each header needs to be
313 * on a single line followed by a <CRLF> with the message headers
314 * and the message body being seperated by and additional <CRLF>.
315 *
316 * Implements rfc 821: DATA <CRLF>
317 *
318 * SMTP CODE INTERMEDIATE: 354
319 * [data]
320 * <CRLF>.<CRLF>
321 * SMTP CODE SUCCESS: 250
322 * SMTP CODE FAILURE: 552,554,451,452
323 * SMTP CODE FAILURE: 451,554
324 * SMTP CODE ERROR : 500,501,503,421
325 * @access public
326 * @return bool
327 */
328 public function Data($msg_data) {
329 $this->error = null; // so no confusion is caused
330
331 if(!$this->connected()) {
332 $this->error = array(
333 "error" => "Called Data() without being connected");
334 return false;
335 }
336
337 fputs($this->smtp_conn,"DATA" . $this->CRLF);
338
339 $rply = $this->get_lines();
340 $code = substr($rply,0,3);
341
342 if($this->do_debug >= 2) {
343 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
344 }
345
346 if($code != 354) {
347 $this->error =
348 array("error" => "DATA command not accepted from server",
349 "smtp_code" => $code,
350 "smtp_msg" => substr($rply,4));
351 if($this->do_debug >= 1) {
352 echo "SMTP -> ERROR: " . $this->error["error"] .
353 ": " . $rply . $this->CRLF;
354 }
355 return false;
356 }
357
358 /* the server is ready to accept data!
359 * according to rfc 821 we should not send more than 1000
360 * including the CRLF
361 * characters on a single line so we will break the data up
362 * into lines by \r and/or \n then if needed we will break
363 * each of those into smaller lines to fit within the limit.
364 * in addition we will be looking for lines that start with
365 * a period '.' and append and additional period '.' to that
366 * line. NOTE: this does not count towards are limit.
367 */
368
369 // normalize the line breaks so we know the explode works
370 $msg_data = str_replace("\r\n","\n",$msg_data);
371 $msg_data = str_replace("\r","\n",$msg_data);
372 $lines = explode("\n",$msg_data);
373
374 /* we need to find a good way to determine is headers are
375 * in the msg_data or if it is a straight msg body
376 * currently I am assuming rfc 822 definitions of msg headers
377 * and if the first field of the first line (':' sperated)
378 * does not contain a space then it _should_ be a header
379 * and we can process all lines before a blank "" line as
380 * headers.
381 */
382 $field = substr($lines[0],0,strpos($lines[0],":"));
383 $in_headers = false;
384 if(!empty($field) && !strstr($field," ")) {
385 $in_headers = true;
386 }
387
388 $max_line_length = 998; // used below; set here for ease in change
389
390 while(list(,$line) = @each($lines)) {
391 $lines_out = null;
392 if($line == "" && $in_headers) {
393 $in_headers = false;
394 }
395 // ok we need to break this line up into several smaller lines
396 while(strlen($line) > $max_line_length) {
397 $pos = strrpos(substr($line,0,$max_line_length)," ");
398
399 // Patch to fix DOS attack
400 if(!$pos) {
401 $pos = $max_line_length - 1;
402 $lines_out[] = substr($line,0,$pos);
403 $line = substr($line,$pos);
404 } else {
405 $lines_out[] = substr($line,0,$pos);
406 $line = substr($line,$pos + 1);
407 }
408
409 /* if we are processing headers we need to
410 * add a LWSP-char to the front of the new line
411 * rfc 822 on long msg headers
412 */
413 if($in_headers) {
414 $line = "\t" . $line;
415 }
416 }
417 $lines_out[] = $line;
418
419 // now send the lines to the server
420 while(list(,$line_out) = @each($lines_out)) {
421 if(strlen($line_out) > 0)
422 {
423 if(substr($line_out, 0, 1) == ".") {
424 $line_out = "." . $line_out;
425 }
426 }
427 fputs($this->smtp_conn,$line_out . $this->CRLF);
428 }
429 }
430
431 // ok all the message data has been sent so lets get this
432 // over with aleady
433 fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF);
434
435 $rply = $this->get_lines();
436 $code = substr($rply,0,3);
437
438 if($this->do_debug >= 2) {
439 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
440 }
441
442 if($code != 250) {
443 $this->error =
444 array("error" => "DATA not accepted from server",
445 "smtp_code" => $code,
446 "smtp_msg" => substr($rply,4));
447 if($this->do_debug >= 1) {
448 echo "SMTP -> ERROR: " . $this->error["error"] .
449 ": " . $rply . $this->CRLF;
450 }
451 return false;
452 }
453 return true;
454 }
455
456 /**
457 * Expand takes the name and asks the server to list all the
458 * people who are members of the _list_. Expand will return
459 * back and array of the result or false if an error occurs.
460 * Each value in the array returned has the format of:
461 * [ <full-name> <sp> ] <path>
462 * The definition of <path> is defined in rfc 821
463 *
464 * Implements rfc 821: EXPN <SP> <string> <CRLF>
465 *
466 * SMTP CODE SUCCESS: 250
467 * SMTP CODE FAILURE: 550
468 * SMTP CODE ERROR : 500,501,502,504,421
469 * @access public
470 * @return string array
471 */
472 public function Expand($name) {
473 $this->error = null; // so no confusion is caused
474
475 if(!$this->connected()) {
476 $this->error = array(
477 "error" => "Called Expand() without being connected");
478 return false;
479 }
480
481 fputs($this->smtp_conn,"EXPN " . $name . $this->CRLF);
482
483 $rply = $this->get_lines();
484 $code = substr($rply,0,3);
485
486 if($this->do_debug >= 2) {
487 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
488 }
489
490 if($code != 250) {
491 $this->error =
492 array("error" => "EXPN not accepted from server",
493 "smtp_code" => $code,
494 "smtp_msg" => substr($rply,4));
495 if($this->do_debug >= 1) {
496 echo "SMTP -> ERROR: " . $this->error["error"] .
497 ": " . $rply . $this->CRLF;
498 }
499 return false;
500 }
501
502 // parse the reply and place in our array to return to user
503 $entries = explode($this->CRLF,$rply);
504 while(list(,$l) = @each($entries)) {
505 $list[] = substr($l,4);
506 }
507
508 return $list;
509 }
510
511 /**
512 * Sends the HELO command to the smtp server.
513 * This makes sure that we and the server are in
514 * the same known state.
515 *
516 * Implements from rfc 821: HELO <SP> <domain> <CRLF>
517 *
518 * SMTP CODE SUCCESS: 250
519 * SMTP CODE ERROR : 500, 501, 504, 421
520 * @access public
521 * @return bool
522 */
523 public function Hello($host="") {
524 $this->error = null; // so no confusion is caused
525
526 if(!$this->connected()) {
527 $this->error = array(
528 "error" => "Called Hello() without being connected");
529 return false;
530 }
531
532 // if a hostname for the HELO was not specified determine
533 //a suitable one to send
534 if(empty($host)) {
535 // we need to determine some sort of appopiate default
536 // to send to the server
537 $host = "localhost";
538 }
539
540 // Send extended hello first (RFC 2821)
541 if(!$this->SendHello("EHLO", $host))
542 {
543 if(!$this->SendHello("HELO", $host))
544 return false;
545 }
546
547 return true;
548 }
549
550 /**
551 * Sends a HELO/EHLO command.
552 * @access private
553 * @return bool
554 */
555 private function SendHello($hello, $host) {
556 fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF);
557
558 $rply = $this->get_lines();
559 $code = substr($rply,0,3);
560
561 if($this->do_debug >= 2) {
562 echo "SMTP -> FROM SERVER: " . $this->CRLF . $rply;
563 }
564
565 if($code != 250) {
566 $this->error =
567 array("error" => $hello . " not accepted from server",
568 "smtp_code" => $code,
569 "smtp_msg" => substr($rply,4));
570 if($this->do_debug >= 1) {
571 echo "SMTP -> ERROR: " . $this->error["error"] .
572 ": " . $rply . $this->CRLF;
573 }
574 return false;
575 }
576
577 $this->helo_rply = $rply;
578
579 return true;
580 }
581
582 /**
583 * Gets help information on the keyword specified. If the keyword
584 * is not specified then returns generic help, ussually contianing
585 * A list of keywords that help is available on. This function
586 * returns the results back to the user. It is up to the user to
587 * handle the returned data. If an error occurs then false is
588 * returned with $this->error set appropiately.
589 *
590 * Implements rfc 821: HELP [ <SP> <string> ] <CRLF>
591 *
592 * SMTP CODE SUCCESS: 211,214
593 * SMTP CODE ERROR : 500,501,502,504,421
594 * @access public
595 * @return string
596 */
597 public function Help($keyword="") {
598 $this->error = null; // to avoid confusion
599
600 if(!$this->connected()) {
601 $this->error = array(
602 "error" => "Called Help() without being connected");
603 return false;
604 }
605
606 $extra = "";
607 if(!empty($keyword)) {
608 $extra = " " . $keyword;
609 }
610
611 fputs($this->smtp_conn,"HELP" . $extra . $this->CRLF);
612
613 $rply = $this->get_lines();
614 $code = substr($rply,0,3);
615
616 if($this->do_debug >= 2) {
617 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
618 }
619
620 if($code != 211 && $code != 214) {
621 $this->error =
622 array("error" => "HELP not accepted from server",
623 "smtp_code" => $code,
624 "smtp_msg" => substr($rply,4));
625 if($this->do_debug >= 1) {
626 echo "SMTP -> ERROR: " . $this->error["error"] .
627 ": " . $rply . $this->CRLF;
628 }
629 return false;
630 }
631
632 return $rply;
633 }
634
635 /**
636 * Starts a mail transaction from the email address specified in
637 * $from. Returns true if successful or false otherwise. If True
638 * the mail transaction is started and then one or more Recipient
639 * commands may be called followed by a Data command.
640 *
641 * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
642 *
643 * SMTP CODE SUCCESS: 250
644 * SMTP CODE SUCCESS: 552,451,452
645 * SMTP CODE SUCCESS: 500,501,421
646 * @access public
647 * @return bool
648 */
649 public function Mail($from) {
650 $this->error = null; // so no confusion is caused
651
652 if(!$this->connected()) {
653 $this->error = array(
654 "error" => "Called Mail() without being connected");
655 return false;
656 }
657
658 $useVerp = ($this->do_verp ? "XVERP" : "");
659 fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF);
660
661 $rply = $this->get_lines();
662 $code = substr($rply,0,3);
663
664 if($this->do_debug >= 2) {
665 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
666 }
667
668 if($code != 250) {
669 $this->error =
670 array("error" => "MAIL not accepted from server",
671 "smtp_code" => $code,
672 "smtp_msg" => substr($rply,4));
673 if($this->do_debug >= 1) {
674 echo "SMTP -> ERROR: " . $this->error["error"] .
675 ": " . $rply . $this->CRLF;
676 }
677 return false;
678 }
679 return true;
680 }
681
682 /**
683 * Sends the command NOOP to the SMTP server.
684 *
685 * Implements from rfc 821: NOOP <CRLF>
686 *
687 * SMTP CODE SUCCESS: 250
688 * SMTP CODE ERROR : 500, 421
689 * @access public
690 * @return bool
691 */
692 public function Noop() {
693 $this->error = null; // so no confusion is caused
694
695 if(!$this->connected()) {
696 $this->error = array(
697 "error" => "Called Noop() without being connected");
698 return false;
699 }
700
701 fputs($this->smtp_conn,"NOOP" . $this->CRLF);
702
703 $rply = $this->get_lines();
704 $code = substr($rply,0,3);
705
706 if($this->do_debug >= 2) {
707 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
708 }
709
710 if($code != 250) {
711 $this->error =
712 array("error" => "NOOP not accepted from server",
713 "smtp_code" => $code,
714 "smtp_msg" => substr($rply,4));
715 if($this->do_debug >= 1) {
716 echo "SMTP -> ERROR: " . $this->error["error"] .
717 ": " . $rply . $this->CRLF;
718 }
719 return false;
720 }
721 return true;
722 }
723
724 /**
725 * Sends the quit command to the server and then closes the socket
726 * if there is no error or the $close_on_error argument is true.
727 *
728 * Implements from rfc 821: QUIT <CRLF>
729 *
730 * SMTP CODE SUCCESS: 221
731 * SMTP CODE ERROR : 500
732 * @access public
733 * @return bool
734 */
735 public function Quit($close_on_error=true) {
736 $this->error = null; // so there is no confusion
737
738 if(!$this->connected()) {
739 $this->error = array(
740 "error" => "Called Quit() without being connected");
741 return false;
742 }
743
744 // send the quit command to the server
745 fputs($this->smtp_conn,"quit" . $this->CRLF);
746
747 // get any good-bye messages
748 $byemsg = $this->get_lines();
749
750 if($this->do_debug >= 2) {
751 echo "SMTP -> FROM SERVER:" . $this->CRLF . $byemsg;
752 }
753
754 $rval = true;
755 $e = null;
756
757 $code = substr($byemsg,0,3);
758 if($code != 221) {
759 // use e as a tmp var cause Close will overwrite $this->error
760 $e = array("error" => "SMTP server rejected quit command",
761 "smtp_code" => $code,
762 "smtp_rply" => substr($byemsg,4));
763 $rval = false;
764 if($this->do_debug >= 1) {
765 echo "SMTP -> ERROR: " . $e["error"] . ": " .
766 $byemsg . $this->CRLF;
767 }
768 }
769
770 if(empty($e) || $close_on_error) {
771 $this->Close();
772 }
773
774 return $rval;
775 }
776
777 /**
778 * Sends the command RCPT to the SMTP server with the TO: argument of $to.
779 * Returns true if the recipient was accepted false if it was rejected.
780 *
781 * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
782 *
783 * SMTP CODE SUCCESS: 250,251
784 * SMTP CODE FAILURE: 550,551,552,553,450,451,452
785 * SMTP CODE ERROR : 500,501,503,421
786 * @access public
787 * @return bool
788 */
789 public function Recipient($to) {
790 $this->error = null; // so no confusion is caused
791
792 if(!$this->connected()) {
793 $this->error = array(
794 "error" => "Called Recipient() without being connected");
795 return false;
796 }
797
798 fputs($this->smtp_conn,"RCPT TO:<" . $to . ">" . $this->CRLF);
799
800 $rply = $this->get_lines();
801 $code = substr($rply,0,3);
802
803 if($this->do_debug >= 2) {
804 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
805 }
806
807 if($code != 250 && $code != 251) {
808 $this->error =
809 array("error" => "RCPT not accepted from server",
810 "smtp_code" => $code,
811 "smtp_msg" => substr($rply,4));
812 if($this->do_debug >= 1) {
813 echo "SMTP -> ERROR: " . $this->error["error"] .
814 ": " . $rply . $this->CRLF;
815 }
816 return false;
817 }
818 return true;
819 }
820
821 /**
822 * Sends the RSET command to abort and transaction that is
823 * currently in progress. Returns true if successful false
824 * otherwise.
825 *
826 * Implements rfc 821: RSET <CRLF>
827 *
828 * SMTP CODE SUCCESS: 250
829 * SMTP CODE ERROR : 500,501,504,421
830 * @access public
831 * @return bool
832 */
833 public function Reset() {
834 $this->error = null; // so no confusion is caused
835
836 if(!$this->connected()) {
837 $this->error = array(
838 "error" => "Called Reset() without being connected");
839 return false;
840 }
841
842 fputs($this->smtp_conn,"RSET" . $this->CRLF);
843
844 $rply = $this->get_lines();
845 $code = substr($rply,0,3);
846
847 if($this->do_debug >= 2) {
848 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
849 }
850
851 if($code != 250) {
852 $this->error =
853 array("error" => "RSET failed",
854 "smtp_code" => $code,
855 "smtp_msg" => substr($rply,4));
856 if($this->do_debug >= 1) {
857 echo "SMTP -> ERROR: " . $this->error["error"] .
858 ": " . $rply . $this->CRLF;
859 }
860 return false;
861 }
862
863 return true;
864 }
865
866 /**
867 * Starts a mail transaction from the email address specified in
868 * $from. Returns true if successful or false otherwise. If True
869 * the mail transaction is started and then one or more Recipient
870 * commands may be called followed by a Data command. This command
871 * will send the message to the users terminal if they are logged
872 * in.
873 *
874 * Implements rfc 821: SEND <SP> FROM:<reverse-path> <CRLF>
875 *
876 * SMTP CODE SUCCESS: 250
877 * SMTP CODE SUCCESS: 552,451,452
878 * SMTP CODE SUCCESS: 500,501,502,421
879 * @access public
880 * @return bool
881 */
882 public function Send($from) {
883 $this->error = null; // so no confusion is caused
884
885 if(!$this->connected()) {
886 $this->error = array(
887 "error" => "Called Send() without being connected");
888 return false;
889 }
890
891 fputs($this->smtp_conn,"SEND FROM:" . $from . $this->CRLF);
892
893 $rply = $this->get_lines();
894 $code = substr($rply,0,3);
895
896 if($this->do_debug >= 2) {
897 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
898 }
899
900 if($code != 250) {
901 $this->error =
902 array("error" => "SEND not accepted from server",
903 "smtp_code" => $code,
904 "smtp_msg" => substr($rply,4));
905 if($this->do_debug >= 1) {
906 echo "SMTP -> ERROR: " . $this->error["error"] .
907 ": " . $rply . $this->CRLF;
908 }
909 return false;
910 }
911 return true;
912 }
913
914 /**
915 * Starts a mail transaction from the email address specified in
916 * $from. Returns true if successful or false otherwise. If True
917 * the mail transaction is started and then one or more Recipient
918 * commands may be called followed by a Data command. This command
919 * will send the message to the users terminal if they are logged
920 * in and send them an email.
921 *
922 * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
923 *
924 * SMTP CODE SUCCESS: 250
925 * SMTP CODE SUCCESS: 552,451,452
926 * SMTP CODE SUCCESS: 500,501,502,421
927 * @access public
928 * @return bool
929 */
930 public function SendAndMail($from) {
931 $this->error = null; // so no confusion is caused
932
933 if(!$this->connected()) {
934 $this->error = array(
935 "error" => "Called SendAndMail() without being connected");
936 return false;
937 }
938
939 fputs($this->smtp_conn,"SAML FROM:" . $from . $this->CRLF);
940
941 $rply = $this->get_lines();
942 $code = substr($rply,0,3);
943
944 if($this->do_debug >= 2) {
945 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
946 }
947
948 if($code != 250) {
949 $this->error =
950 array("error" => "SAML not accepted from server",
951 "smtp_code" => $code,
952 "smtp_msg" => substr($rply,4));
953 if($this->do_debug >= 1) {
954 echo "SMTP -> ERROR: " . $this->error["error"] .
955 ": " . $rply . $this->CRLF;
956 }
957 return false;
958 }
959 return true;
960 }
961
962 /**
963 * Starts a mail transaction from the email address specified in
964 * $from. Returns true if successful or false otherwise. If True
965 * the mail transaction is started and then one or more Recipient
966 * commands may be called followed by a Data command. This command
967 * will send the message to the users terminal if they are logged
968 * in or mail it to them if they are not.
969 *
970 * Implements rfc 821: SOML <SP> FROM:<reverse-path> <CRLF>
971 *
972 * SMTP CODE SUCCESS: 250
973 * SMTP CODE SUCCESS: 552,451,452
974 * SMTP CODE SUCCESS: 500,501,502,421
975 * @access public
976 * @return bool
977 */
978 public function SendOrMail($from) {
979 $this->error = null; // so no confusion is caused
980
981 if(!$this->connected()) {
982 $this->error = array(
983 "error" => "Called SendOrMail() without being connected");
984 return false;
985 }
986
987 fputs($this->smtp_conn,"SOML FROM:" . $from . $this->CRLF);
988
989 $rply = $this->get_lines();
990 $code = substr($rply,0,3);
991
992 if($this->do_debug >= 2) {
993 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
994 }
995
996 if($code != 250) {
997 $this->error =
998 array("error" => "SOML not accepted from server",
999 "smtp_code" => $code,
1000 "smtp_msg" => substr($rply,4));
1001 if($this->do_debug >= 1) {
1002 echo "SMTP -> ERROR: " . $this->error["error"] .
1003 ": " . $rply . $this->CRLF;
1004 }
1005 return false;
1006 }
1007 return true;
1008 }
1009
1010 /**
1011 * This is an optional command for SMTP that this class does not
1012 * support. This method is here to make the RFC821 Definition
1013 * complete for this class and __may__ be implimented in the future
1014 *
1015 * Implements from rfc 821: TURN <CRLF>
1016 *
1017 * SMTP CODE SUCCESS: 250
1018 * SMTP CODE FAILURE: 502
1019 * SMTP CODE ERROR : 500, 503
1020 * @access public
1021 * @return bool
1022 */
1023 public function Turn() {
1024 $this->error = array("error" => "This method, TURN, of the SMTP ".
1025 "is not implemented");
1026 if($this->do_debug >= 1) {
1027 echo "SMTP -> NOTICE: " . $this->error["error"] . $this->CRLF;
1028 }
1029 return false;
1030 }
1031
1032 /**
1033 * Verifies that the name is recognized by the server.
1034 * Returns false if the name could not be verified otherwise
1035 * the response from the server is returned.
1036 *
1037 * Implements rfc 821: VRFY <SP> <string> <CRLF>
1038 *
1039 * SMTP CODE SUCCESS: 250,251
1040 * SMTP CODE FAILURE: 550,551,553
1041 * SMTP CODE ERROR : 500,501,502,421
1042 * @access public
1043 * @return int
1044 */
1045 public function Verify($name) {
1046 $this->error = null; // so no confusion is caused
1047
1048 if(!$this->connected()) {
1049 $this->error = array(
1050 "error" => "Called Verify() without being connected");
1051 return false;
1052 }
1053
1054 fputs($this->smtp_conn,"VRFY " . $name . $this->CRLF);
1055
1056 $rply = $this->get_lines();
1057 $code = substr($rply,0,3);
1058
1059 if($this->do_debug >= 2) {
1060 echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
1061 }
1062
1063 if($code != 250 && $code != 251) {
1064 $this->error =
1065 array("error" => "VRFY failed on name '$name'",
1066 "smtp_code" => $code,
1067 "smtp_msg" => substr($rply,4));
1068 if($this->do_debug >= 1) {
1069 echo "SMTP -> ERROR: " . $this->error["error"] .
1070 ": " . $rply . $this->CRLF;
1071 }
1072 return false;
1073 }
1074 return $rply;
1075 }
1076
1077 /*******************************************************************
1078 * INTERNAL FUNCTIONS *
1079 ******************************************************************/
1080
1081 /**
1082 * Read in as many lines as possible
1083 * either before eof or socket timeout occurs on the operation.
1084 * With SMTP we can tell if we have more lines to read if the
1085 * 4th character is '-' symbol. If it is a space then we don't
1086 * need to read anything else.
1087 * @access private
1088 * @return string
1089 */
1090 private function get_lines() {
1091 $data = "";
1092 while($str = @fgets($this->smtp_conn,515)) {
1093 if($this->do_debug >= 4) {
1094 echo "SMTP -> get_lines(): \$data was \"$data\"" .
1095 $this->CRLF;
1096 echo "SMTP -> get_lines(): \$str is \"$str\"" .
1097 $this->CRLF;
1098 }
1099 $data .= $str;
1100 if($this->do_debug >= 4) {
1101 echo "SMTP -> get_lines(): \$data is \"$data\"" . $this->CRLF;
1102 }
1103 // if the 4th character is a space then we are done reading
1104 // so just break the loop
1105 if(substr($str,3,1) == " ") { break; }
1106 }
1107 return $data;
1108 }
1109
1110}
1111
1112
1113 ?>
diff --git a/central/trunk/includes/db.php b/central/trunk/includes/db.php new file mode 100644 index 0000000..b9f275b --- /dev/null +++ b/central/trunk/includes/db.php
@@ -0,0 +1,28 @@
1<?php
2
3/* InstaDisc Server - A Four Island Project */
4
5if (!extension_loaded('curl'))
6{
7 die('Sorry, but the PHP Extension "curl" is required for InstaDisc Server and you don\'t have it installed.');
8}
9
10if (!file_exists('config.php'))
11{
12 header('Location: install.php');
13 exit;
14}
15
16if (file_exists('install.php'))
17{
18 die('Excuse me, but you need to delete install.php before you can use this as leaving install.php there is a biiiig security hole.');
19}
20
21session_start();
22
23include('includes/config.php');
24
25mysql_connect($dbhost, $dbuser, $dbpass);
26mysql_select_db($dbname);
27
28?>
diff --git a/central/trunk/includes/instadisc.php b/central/trunk/includes/instadisc.php new file mode 100644 index 0000000..96f4940 --- /dev/null +++ b/central/trunk/includes/instadisc.php
@@ -0,0 +1,400 @@
1<?php
2
3/* InstaDisc Server - A Four Island Project */
4
5include_once('includes/db.php');
6include_once('includes/class.phpmailer.php');
7
8function instaDisc_checkVerification($username, $verification, $verificationID, $table, $nameField, $passField)
9{
10 $getverid = "SELECT * FROM oldVerID WHERE username = \"" . mysql_real_escape_string($username) . "\" AND verID = " . $verificationID;
11 $getverid2 = mysql_query($getverid);
12 $getverid3 = mysql_fetch_array($getverid2);
13 if ($getverid3['id'] != $verificationID)
14 {
15 $getitem = "SELECT * FROM " . $table . " WHERE " . $nameField . " = \"" . mysql_real_escape_string($username) . "\"";
16 $getitem2 = mysql_query($getitem);
17 $getitem3 = mysql_fetch_array($getitem2);
18 if ($getitem3[$nameField] == $username)
19 {
20 $test = $username . ':' . $getitem3[$passField] . ':' . $verificationID;
21
22 if (md5($test) == $verification)
23 {
24 $cntverid = "SELECT COUNT(*) FROM oldVerID WHERE username = \"" . mysql_real_escape_string($username) . "\"";
25 $cntverid2 = mysql_query($cntverid);
26 $cntverid3 = mysql_fetch_array($cntverid2);
27 if ($cntverid3[0] >= intval(instaDisc_getConfig('verIDBufferSize')))
28 {
29 $delverid = "DELETE FROM oldVerID WHERE username = \"" . mysql_real_escape_string($username) . "\"";
30 $delverid2 = mysql_query($delverid);
31 }
32
33 $insverid = "INSERT INTO oldVerID (username, verID) VALUES (\"" . mysql_real_escape_string($username) . "\", " . $verificationID . ")";
34 $insverid2 = mysql_query($insverid);
35
36 if (($table == 'users') && ($getitem3['ip'] != $_SERVER['REMOTE_ADDR']))
37 {
38 $setuser = "UPDATE users SET ip = \"" . $_SERVER['REMOTE_ADDR'] . "\" WHERE id = " . $getitem3['id'];
39 $setuser2 = mysql_query($setuser);
40 }
41
42 return true;
43 }
44 }
45 }
46
47 return false;
48}
49
50function instaDisc_sendItem($username, $id)
51{
52 $getitem = "SELECT * FROM inbox WHERE username = \"" . mysql_real_escape_string($username) . "\" AND itemID = " . $id;
53 $getitem2 = mysql_query($getitem);
54 $getitem3 = mysql_fetch_array($getitem2);
55 if ($getitem3['username'] == $username)
56 {
57 $getuser = "SELECT * FROM users WHERE username = \"" . mysql_real_escape_string($username) . "\"";
58 $getuser2 = mysql_query($getuser);
59 $getuser3 = mysql_fetch_array($getuser2);
60
61 $fp = @fsockopen($getuser3['ip'], 1204, $errno, $errstr);
62 if ($fp)
63 {
64 $verID = rand(1,65536);
65
66 $title = str_replace(': ', '__INSTADISC__', $getitem3['title']);
67
68 $out = 'ID: ' . $id . "\r\n";
69 $out .= 'Verification: ' . md5($username . ':' . $getuser3['password'] . ':' . $verID) . "\r\n";
70 $out .= 'Verification-ID: ' . $verID . "\r\n";
71 $out .= 'Subscription: ' . $getitem3['subscription'] . "\r\n";
72 $out .= 'Title: ' . $title . "\r\n";
73 $out .= 'Author: ' . $getitem3['author'] . "\r\n";
74 $out .= 'URL: ' . $getitem3['url'] . "\r\n";
75
76 $semantics = unserialize($getitem3['semantics']);
77 foreach ($semantics as $name => $value)
78 {
79 $value = str_replace(': ', '__INSTADISC__', $value);
80 $out .= $name . ': ' . $value . "\r\n";
81 }
82
83 if ($getitem3['encryptionID'] != 0)
84 {
85 $out .= 'Encryption-ID: ' . $getitem3['encryptionID'] . "\r\n";
86 }
87
88 $out .= "\r\n\r\n";
89
90 fwrite($fp, $out);
91 fclose($fp);
92
93 return true;
94 } else {
95 return false;
96 }
97 }
98}
99
100function instaDisc_sendUpdateNotice($softwareVersion)
101{
102 $username = instaDisc_getConfig('owner');
103 $subscription = 'http://fourisland.com/' . $_SERVER['SERVER_NAME'] . '/';
104 $title = 'Update your software to ' . $software;
105 $author = 'Hatkirby';
106 $url = 'http://fourisland.com/projects/instadisc/wiki/CentralSoftwareUpdate';
107 $semantics = array();
108
109 instaDisc_addItem($username, $subscription, $title, $author, $url, $semantics);
110}
111
112function instaDisc_sendDatabase($cserver)
113{
114 $getdb = "SELECT * FROM centralServers";
115 $getdb2 = mysql_query($getdb);
116 $i=0;
117 while ($getdb3[$i] = mysql_fetch_array($getdb2))
118 {
119 $db[$getdb3[$i]['url']]['code'] = $getdb3[$i]['code'];
120 $db[$getdb3[$i]['url']]['xmlrpc'] = $getdb3[$i]['xmlrpc'];
121 $i++;
122 }
123
124 $cserver2 = $_SERVER['HTTP_HOST'];
125 $getuk = "SELECT * FROM centralServers WHERE url = \"" . mysql_real_escape_string($cserver2) . "\"";
126 $getuk2 = mysql_query($getuk);
127 $getuk3 = mysql_fetch_array($getuk2);
128
129 $verID = rand(1,65536);
130
131 $client = new xmlrpc_client($cserver);
132 $msg = new xmlrpcmsg("InstaDisc.sendDatabase", array( new xmlrpcval($cserver2, 'string'),
133 new xmlrpcval(md5($cserver2 . ":" . $getuk3['code'] . ":" . $verID), 'string'),
134 new xmlrpcval($verID, 'int'),
135 new xmlrpcval(serialize($db), 'string'),
136 new xmlrpcval(instaDisc_getConfig('databaseVersion'), 'string')));
137 $client->send($msg);
138}
139
140function instaDisc_addItem($username, $subscription, $title, $author, $url, $semantics, $encryptionID)
141{
142 $getuser = "SELECT * FROM users WHERE username = \"" . mysql_real_escape_string($username) . "\"";
143 $getuser2 = mysql_query($getuser);
144 $getuser3 = mysql_fetch_array($getuser2);
145 if ($getuser3['username'] == $username)
146 {
147 $itemID = $getuser3['nextItemID'];
148 $setuser = "UPDATE users SET nextItemID = nextItemID+1 WHERE username = \"" . mysql_real_escape_string($username) . "\"";
149 $setuser2 = mysql_query($setuser);
150
151 $insitem = "INSERT INTO inbox (username, itemID, subscription, title, author, url, semantics, encryptionID) VALUES (\"" . mysql_real_escape_string($username) . "\", " . $itemID . ", \"" . mysql_real_escape_string($subscription) . "\", \"" . mysql_real_escape_string($title) . "\", \"" . mysql_real_escape_string($author) . "\", \"" . mysql_real_escape_string($url) . "\", \"" . mysql_real_escape_string($semantics) . "\"," . $encryptionID . ")";
152 $insitem2 = mysql_query($insitem);
153
154 instaDisc_sendItem($username, $itemID);
155 }
156}
157
158function instaDisc_phpMailer()
159{
160 $mail = new PHPMailer();
161 $mail->IsSMTP();
162 $mail->From = 'instadisc@' . instaDisc_getConfig('mailDomain');
163 $mail->FromName = 'InstaDisc';
164 $mail->Host = instaDisc_getConfig('smtpHost');
165 if (instaDisc_getConfig('smtpAuth') == 'true')
166 {
167 $mail->SMTPAuth = true;
168 $mail->Username = instaDisc_getConfig('smtpUser');
169 $mail->Password = instaDisc_getConfig('smtpPass');
170 }
171 $mail->Helo = $_SERVER['SERVER_NAME'];
172 $mail->ClearAddresses();
173
174 return $mail;
175}
176
177function instaDisc_sendActivationEmail($username, $password, $email)
178{
179 $penKey = md5(rand(1,65536));
180
181 $inspending = "INSERT INTO pending (username, password, email, code) VALUES (\"" . mysql_real_escape_string($username) . "\", \"" . mysql_real_escape_string(md5($password)) . "\", \"" . mysql_real_escape_string($email) . "\", \"" . mysql_real_escape_string($penKey) . "\")";
182 $inspending2 = mysql_query($inspending);
183
184 $mail = instaDisc_phpMailer();
185 $mail->AddAddress($email, $username);
186 $mail->Subject = 'InstaDisc Account Verification';
187 $mail->Body = "Hello, someone has recently registered an account at " . $_SERVER['HTTP_HOST'] . " with your email address. If that was you, and your chosen username IS " . $username . ", then copy the account verification code below to our Account Verification page, enter your username and press Activate!\r\n\r\n" . $penKey . "\r\n\r\nIf that was not you, copy the above code to our Account Verification page, enter the above username, and click Delete.";
188 $mail->Send();
189
190 return ($mail->IsError() ? $mail->ErrorInfo : true);
191}
192
193function instaDisc_activateAccount($username, $penKey)
194{
195 $getuser = "SELECT * FROM pending WHERE username = \"" . mysql_real_escape_string($username) . "\" AND code = \"" . mysql_real_escape_string($penKey) . "\"";
196 $getuser2 = mysql_query($getuser);
197 $getuser3 = mysql_fetch_array($getuser2);
198 if ($getuser3['username'] == $username)
199 {
200 $insuser = "INSERT INTO users (username, password, email) VALUES (\"" . mysql_real_escape_string($username) . "\", \"" . mysql_real_escape_string($getuser3['password']) . "\", \"" . mysql_real_escape_string($getuser3['email']) . "\")";
201 $insuser2 = mysql_query($insuser);
202
203 $delpending = "DELETE FROM pending WHERE username = \"" . mysql_real_escape_string($username) . "\"";
204 $delpending2 = mysql_query($delpending);
205
206 $mail = instaDisc_phpMailer();
207 $mail->AddAddress($getuser3['email'], $username);
208 $mail->Subject = 'Welcome to InstaDisc!';
209 $mail->Body = "Welcome to InstaDisc! Thank you for registering at " . instaDisc_getConfig('siteName') . " Central Server, we hope you enjoy our service! Now, when you download an InstaDisc Client, it will ask you for the following information which you will need to enter into it for it to work:\r\n\r\nUsername: " . $username . "\r\nPassword: (you should know this, it's not displayed here for security reasons)\r\nCentral Server URL: " . instaDisc_getConfig("xmlrpcURL") . "\r\n\r\nOnce again, thank you for choosing " . instaDisc_getConfig("siteName") . "!";
210 $mail->Send();
211
212 return ($mail->IsError() ? $mail->ErrorInfo : true);
213 } else {
214 return false;
215 }
216}
217
218function instaDisc_deactivateAccount($username, $penKey)
219{
220 $getuser = "SELECT * FROM pending WHERE username = \"" . mysql_real_escape_string($username) . "\" AND code = \"" . mysql_real_escape_string($penKey) . "\"";
221 $getuser2 = mysql_query($getuser);
222 $getuser3 = mysql_fetch_array($getuser2);
223 if ($getuser3['username'] == $username)
224 {
225 $delpending = "DELETE FROM pending WHERE username = \"" . mysql_real_escape_string($username) . "\"";
226 $delpending2 = mysql_query($delpending);
227
228 return true;
229 } else {
230 return false;
231 }
232}
233
234function instaDisc_verifyUser($username, $password)
235{
236 return instaDisc_checkVerification($username, md5($username . ':' . md5($password) . ':0'), 0, 'users', 'username', 'password');
237}
238
239function instaDisc_deleteAccount($username)
240{
241 $getuser = "SELECT * FROM users WHERE username = \"" . mysql_real_escape_string($username) . "\"";
242 $getuser2 = mysql_query($getuser);
243 $getuser3 = mysql_fetch_array($getuser2);
244 if ($getuser3['username'] == $username)
245 {
246 $deluser = "DELETE FROM users WHERE username = \"" . mysql_real_escape_string($username) . "\"";
247 $deluser2 = mysql_query($deluser);
248
249 $delsubs = "DELETE FROM subscriptions WHERE username = \"" . mysql_real_escape_string($username) . "\"";
250 $delsubs2 = mysql_query($delsubs);
251
252 $delitems = "DELETE FROM inbox WHERE username = \"" . mysql_real_escape_string($username) . "\"";
253 $delitems2 = mysql_query($delitems);
254
255 return true;
256 }
257
258 return false;
259}
260
261function instaDisc_getConfig($key)
262{
263 $getconfig = "SELECT * FROM config WHERE name = \"" . mysql_real_escape_string($key) . "\"";
264 $getconfig2 = mysql_query($getconfig);
265 $getconfig3 = mysql_fetch_array($getconfig2);
266
267 return $getconfig3['value'];
268}
269
270function instaDisc_listSubscriptions($username)
271{
272 $getsubs = "SELECT * FROM subscriptions WHERE username = \"" . mysql_real_escape_string($username) . "\" AND owner = \"true\"";
273 $getsubs2 = mysql_query($getsubs);
274 $i=0;
275 while ($getsubs3[$i] = mysql_fetch_array($getsubs2))
276 {
277 $subs[$i] = $getsubs3[$i]['url'];
278
279 $i++;
280 }
281
282 $subs['size'] = $i;
283 return $subs;
284}
285
286function instaDisc_addSubscription($username, $url)
287{
288 $getcode = "SELECT * FROM pending2 WHERE username = \"" . mysql_real_escape_string($username) . "\" AND url = \"" . mysql_real_escape_string($url) . "\"";
289 $getcode2 = mysql_query($getcode);
290 $getcode3 = mysql_fetch_array($getcode2);
291 if ($getcode3['username'] == $username)
292 {
293 $delcode = "DELETE FROM pending2 WHERE username = \"" . mysql_real_escape_string($username) . "\" AND url = \"" . mysql_real_escape_string($url) . "\"";
294 $delcode2 = mysql_query($delcode);
295
296 $c = curl_init();
297 curl_setopt($c, CURLOPT_URL, $url);
298 curl_setopt($c, CURLOPT_HEADER, false);
299 curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
300 $page_data = curl_exec($c);
301 curl_close($c);
302
303 $headers = split("\n", $page_date);
304 foreach ($headers as $name => $value)
305 {
306 $header = split(": ", $value);
307 $headerMap[$header[0]] = $header[1];
308 }
309
310 if (isset($header['Subscription']))
311 {
312 if (isset($header['Title']))
313 {
314 if (isset($header['Category']))
315 {
316 if (isset($header['Key']))
317 {
318 if ($header['Key'] == $getcode3['code'])
319 {
320 $inssub = "INSERT INTO subscriptions (username,url,owner,category) VALUES (\"" . mysql_real_escape_string($username) . "\", \"" . mysql_real_escape_string($header['Subscription']) . "\", \"true\", \"" . mysql_real_escape_string($header['Category']) . "\")";
321 $inssub2 = mysql_query($inssub);
322
323 return true;
324 }
325 }
326 }
327 }
328 }
329 }
330
331 return false;
332}
333
334function instaDisc_listPendingSubscriptions($username)
335{
336 $getsubs = "SELECT * FROM pending2 WHERE username = \"" . mysql_real_escape_string($username) . "\"";
337 $getsubs2 = mysql_query($getsubs);
338 $i=0;
339 while ($getsubs3[$i] = mysql_fetch_array($getsubs2))
340 {
341 $subs[$i] = array('url' => $getsubs3[$i]['url'], 'code' => $getsubs3[$i]['code']);
342
343 $i++;
344 }
345
346 $subs['size'] = $i;
347 return $subs;
348}
349
350function instaDisc_generateSubscriptionActivation($username, $url)
351{
352 $getuser = "SELECT * FROM users WHERE username = \"" . mysql_real_escape_string($username) . "\"";
353 $getuser2 = mysql_query($getuser);
354 $getuser3 = mysql_fetch_array($getuser2);
355 if ($getuser3['username'] == $username)
356 {
357 $key = md5(rand(1,65536));
358
359 $inspending = "INSERT INTO pending2 (username, url, code) VALUES (\"" . mysql_real_escape_string($username) . "\", \"" . mysql_real_escape_string($url) . "\", \"" . mysql_real_escape_string($key) . "\")";
360 $inspending2 = mysql_query($inspending);
361
362 return $key;
363 }
364
365 return false;
366}
367
368function instaDisc_deleteSubscription($username, $url)
369{
370 $getsub = "SELECT * FROM subscriptions WHERE username = \"" . mysql_real_escape_string($username) . "\" AND url = \"" . mysql_real_escape_string($url) . "\")";
371 $getsub2 = mysql_query($getsub);
372 $getsub3 = mysql_fetch_array($getsub2);
373 if ($getsub3['username'] == $username)
374 {
375 $delsub = "DELETE FROM subscriptions WHERE username = \"" . mysql_real_escape_string($username) . "\" AND url = \"" . mysql_real_escape_string($url) . "\")";
376 $delsub2 = mysql_query($delsub);
377
378 return true;
379 }
380
381 return false;
382}
383
384function instaDisc_cancelSubscription($username, $url)
385{
386 $getsub = "SELECT * FROM pending2 WHERE username = \"" . mysql_real_escape_string($username) . "\" AND url = \"" . mysql_real_escape_string($url) . "\")";
387 $getsub2 = mysql_query($getsub);
388 $getsub3 = mysql_fetch_array($getsub2);
389 if ($getsub3['username'] == $username)
390 {
391 $delsub = "DELETE FROM pending2 WHERE username = \"" . mysql_real_escape_string($username) . "\" AND url = \"" . mysql_real_escape_string($url) . "\")";
392 $delsub2 = mysql_query($delsub);
393
394 return true;
395 }
396
397 return false;
398}
399
400?>
diff --git a/central/trunk/includes/template.php b/central/trunk/includes/template.php new file mode 100644 index 0000000..b4ec16b --- /dev/null +++ b/central/trunk/includes/template.php
@@ -0,0 +1,160 @@
1<?php
2/*
3 444444444
4 4::::::::4
5 4:::::::::4
6 4::::44::::4
7 4::::4 4::::4 Four Island
8 4::::4 4::::4
9 4::::4 4::::4 Written and maintained by Starla Insigna
104::::444444::::444
114::::::::::::::::4 includes/template.php
124444444444:::::444
13 4::::4 Please do not use, reproduce or steal the
14 4::::4 contents of this file without explicit
15 4::::4 permission from Hatkirby.
16 44::::::44
17 4::::::::4
18 4444444444
19*/
20
21class FITemplate
22{
23
24 var $file;
25 var $tags;
26 var $blocks;
27 var $refs;
28
29 function FITemplate($filename)
30 {
31 $tfn = 'theme/' . $filename . '.tpl';
32 if (file_exists($tfn))
33 {
34 $this->file = $tfn;
35 } else {
36 throw new Exception($tfn . ' does not exist');
37 }
38 }
39
40 function add($name, $value)
41 {
42 $this->tags[$name] = $value;
43 }
44
45 function adds($tagarr)
46 {
47 foreach ($tagarr as $name => $value)
48 {
49 $this->add($name,$value);
50 }
51 }
52
53 function adds_block($block, $tagarr)
54 {
55 if (!isset($this->blocks[$block]))
56 {
57 $this->blocks[$block] = array('count' => 1);
58 }
59 foreach ($tagarr as $name => $value)
60 {
61 $this->blocks[$block][$this->blocks[$block]['count']][$name] = $value;
62 }
63 $this->blocks[$block]['count']++;
64 }
65
66 function add_ref($id, $block, $tagarr)
67 {
68 $this->adds_block($block,$tagarr);
69 $this->refs[$id] = &$this->blocks[$block][$this->blocks[$block]['count']-1];//'$this->blocks[\'' . $block . '\'][' . ($this->blocks[$block]['count']-1) . ']';
70 }
71
72 function adds_ref($id, $tagarr)
73 {
74 foreach ($tagarr as $name => $value)
75 {
76 $this->refs[$id][$name] = $value;
77 }
78 }
79
80 function adds_ref_sub($id, $block, $tagarr)
81 {
82 if (!isset($this->refs[$id][$block]))
83 {
84 $this->refs[$id][$block] = array('count' => 1);
85 }
86 foreach ($tagarr as $name => $value)
87 {
88 $this->refs[$id][$block][$this->refs[$id][$block]['count']][$name] = $value;
89 }
90 $this->refs[$id][$block]['count']++;
91 }
92
93 function display()
94 {
95 $template = file_get_contents($this->file);
96 while (preg_match('/<!--INCLUDE ([^>]*)-->/',$template) == 1)
97 {
98 preg_match('/<!--INCLUDE ([^>]*)-->/',$template,$mat);
99 $fname = $mat[1];
100 $itmp = new FITemplate($fname);
101 $template = str_replace('<!--INCLUDE ' . $fname . '-->',file_get_contents($itmp->file),$template);
102 }
103 if (isset($this->tags))
104 {
105 foreach ($this->tags as $name => $value)
106 {
107 $template = str_replace('<!--' . $name . '-->',$value,$template);
108 }
109 }
110 if (isset($this->blocks))
111 {
112 foreach ($this->blocks as $bname => $block)
113 {
114 $this->parseBlock($template, $bname, $block);
115 }
116 }
117 while (preg_match('/<!--BEGIN ([^>]*)-->/',$template) == 1)
118 {
119 preg_match('/<!--BEGIN ([^>]*)-->/',$template,$mat);
120 $bname = $mat[1];
121 $start = strpos($template,'<!--BEGIN ' . $bname . '-->');
122 $end = strpos($template,'<!--END ' . $bname . '-->');
123 $template = str_replace(substr($template,$start,$end-$start+strlen($bname)+11),'',$template);
124 }
125 $template = preg_replace('/<!--([^>]*)-->/','',$template);
126 echo($template);
127 }
128
129 function parseBlock(&$template, $bname, $block)
130 {
131 while (strpos($template,'<!--BEGIN ' . $bname . '-->') !== FALSE)
132 {
133 $start = strpos($template,'<!--BEGIN ' . $bname . '-->');
134 $end = strpos($template,'<!--END ' . $bname . '-->');
135 $gencont = substr($template,$start+strlen($bname)+13,$end-$start-strlen($bname)-13);
136 $blockcont = '';
137 foreach ($block as $lname => $blocktags)
138 {
139 if ($lname != 'count')
140 {
141 $scrcont = $gencont;
142 foreach ($blocktags as $name => $value)
143 {
144 if (!is_array($value))
145 {
146 $scrcont = str_replace('<!--' . $bname . '.' . $name . '-->',$value,$scrcont);
147 } else {
148 $this->parseBlock($scrcont, $bname . '.' . $name, $value);
149 }
150 }
151 $blockcont .= $scrcont;
152 }
153 }
154 $template = str_replace(substr($template,$start,$end-$start+strlen($bname)+11),$blockcont,$template);
155 }
156 }
157
158}
159
160?>
diff --git a/central/trunk/includes/xmlrpc/array_key_exists.php b/central/trunk/includes/xmlrpc/array_key_exists.php new file mode 100644 index 0000000..c5ae519 --- /dev/null +++ b/central/trunk/includes/xmlrpc/array_key_exists.php
@@ -0,0 +1,55 @@
1<?php
2// +----------------------------------------------------------------------+
3// | PHP Version 4 |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 1997-2004 The PHP Group |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 3.0 of the PHP license, |
8// | that is bundled with this package in the file LICENSE, and is |
9// | available at through the world-wide-web at |
10// | http://www.php.net/license/3_0.txt. |
11// | If you did not receive a copy of the PHP license and are unable to |
12// | obtain it through the world-wide-web, please send a note to |
13// | license@php.net so we can mail you a copy immediately. |
14// +----------------------------------------------------------------------+
15// | Authors: Aidan Lister <aidan@php.net> |
16// +----------------------------------------------------------------------+
17//
18// $Id: array_key_exists.php,v 1.1 2005/07/11 16:34:35 ggiunta Exp $
19
20
21/**
22 * Replace array_key_exists()
23 *
24 * @category PHP
25 * @package PHP_Compat
26 * @link http://php.net/function.array_key_exists
27 * @author Aidan Lister <aidan@php.net>
28 * @version $Revision: 1.1 $
29 * @since PHP 4.1.0
30 * @require PHP 4.0.0 (user_error)
31 */
32if (!function_exists('array_key_exists')) {
33 function array_key_exists($key, $search)
34 {
35 if (!is_scalar($key)) {
36 user_error('array_key_exists() The first argument should be either a string or an integer',
37 E_USER_WARNING);
38 return false;
39 }
40
41 if (is_object($search)) {
42 $search = get_object_vars($search);
43 }
44
45 if (!is_array($search)) {
46 user_error('array_key_exists() The second argument should be either an array or an object',
47 E_USER_WARNING);
48 return false;
49 }
50
51 return in_array($key, array_keys($search));
52 }
53}
54
55?> \ No newline at end of file
diff --git a/central/trunk/includes/xmlrpc/is_a.php b/central/trunk/includes/xmlrpc/is_a.php new file mode 100644 index 0000000..d98db1f --- /dev/null +++ b/central/trunk/includes/xmlrpc/is_a.php
@@ -0,0 +1,47 @@
1<?php
2// +----------------------------------------------------------------------+
3// | PHP Version 4 |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 1997-2004 The PHP Group |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 3.0 of the PHP license, |
8// | that is bundled with this package in the file LICENSE, and is |
9// | available at through the world-wide-web at |
10// | http://www.php.net/license/3_0.txt. |
11// | If you did not receive a copy of the PHP license and are unable to |
12// | obtain it through the world-wide-web, please send a note to |
13// | license@php.net so we can mail you a copy immediately. |
14// +----------------------------------------------------------------------+
15// | Authors: Aidan Lister <aidan@php.net> |
16// +----------------------------------------------------------------------+
17//
18// $Id: is_a.php,v 1.2 2005/11/21 10:57:23 ggiunta Exp $
19
20
21/**
22 * Replace function is_a()
23 *
24 * @category PHP
25 * @package PHP_Compat
26 * @link http://php.net/function.is_a
27 * @author Aidan Lister <aidan@php.net>
28 * @version $Revision: 1.2 $
29 * @since PHP 4.2.0
30 * @require PHP 4.0.0 (user_error) (is_subclass_of)
31 */
32if (!function_exists('is_a')) {
33 function is_a($object, $class)
34 {
35 if (!is_object($object)) {
36 return false;
37 }
38
39 if (get_class($object) == strtolower($class)) {
40 return true;
41 } else {
42 return is_subclass_of($object, $class);
43 }
44 }
45}
46
47?> \ No newline at end of file
diff --git a/central/trunk/includes/xmlrpc/is_callable.php b/central/trunk/includes/xmlrpc/is_callable.php new file mode 100644 index 0000000..b769c41 --- /dev/null +++ b/central/trunk/includes/xmlrpc/is_callable.php
@@ -0,0 +1,53 @@
1<?php
2/**
3 * Replace function is_callable()
4 *
5 * @category PHP
6 * @package PHP_Compat
7 * @link http://php.net/function.is_callable
8 * @author Gaetano Giunta <giunta.gaetano@sea-aeroportimilano.it>
9 * @version $Id: is_callable.php,v 1.3 2006/08/21 14:03:15 ggiunta Exp $
10 * @since PHP 4.0.6
11 * @require PHP 4.0.0 (true, false, etc...)
12 * @todo add the 3rd parameter syntax...
13 */
14if (!function_exists('is_callable')) {
15 function is_callable($var, $syntax_only=false)
16 {
17 if ($syntax_only)
18 {
19 /* from The Manual:
20 * If the syntax_only argument is TRUE the function only verifies
21 * that var might be a function or method. It will only reject simple
22 * variables that are not strings, or an array that does not have a
23 * valid structure to be used as a callback. The valid ones are
24 * supposed to have only 2 entries, the first of which is an object
25 * or a string, and the second a string
26 */
27 return (is_string($var) || (is_array($var) && count($var) == 2 && is_string(end($var)) && (is_string(reset($var)) || is_object(reset($var)))));
28 }
29 else
30 {
31 if (is_string($var))
32 {
33 return function_exists($var);
34 }
35 else if (is_array($var) && count($var) == 2 && is_string($method = end($var)))
36 {
37 $obj = reset($var);
38 if (is_string($obj))
39 {
40 $methods = get_class_methods($obj);
41 return (bool)(is_array($methods) && in_array(strtolower($method), $methods));
42 }
43 else if (is_object($obj))
44 {
45 return method_exists($obj, $method);
46 }
47 }
48 return false;
49 }
50 }
51}
52
53?> \ No newline at end of file
diff --git a/central/trunk/includes/xmlrpc/is_scalar.php b/central/trunk/includes/xmlrpc/is_scalar.php new file mode 100644 index 0000000..c8f2bfc --- /dev/null +++ b/central/trunk/includes/xmlrpc/is_scalar.php
@@ -0,0 +1,38 @@
1<?php
2// +----------------------------------------------------------------------+
3// | PHP Version 4 |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 1997-2004 The PHP Group |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 3.0 of the PHP license, |
8// | that is bundled with this package in the file LICENSE, and is |
9// | available at through the world-wide-web at |
10// | http://www.php.net/license/3_0.txt. |
11// | If you did not receive a copy of the PHP license and are unable to |
12// | obtain it through the world-wide-web, please send a note to |
13// | license@php.net so we can mail you a copy immediately. |
14// +----------------------------------------------------------------------+
15//
16// $Id: is_scalar.php,v 1.2 2005/11/21 10:57:23 ggiunta Exp $
17
18
19/**
20 * Replace is_scalar()
21 *
22 * @category PHP
23 * @package PHP_Compat
24 * @link http://php.net/function.is_scalar
25 * @author Gaetano Giunta
26 * @version $Revision: 1.2 $
27 * @since PHP 4.0.5
28 * @require PHP 4 (is_bool)
29 */
30if (!function_exists('is_scalar')) {
31 function is_scalar($val)
32 {
33 // Check input
34 return (is_bool($val) || is_int($val) || is_float($val) || is_string($val));
35 }
36}
37
38?> \ No newline at end of file
diff --git a/central/trunk/includes/xmlrpc/var_export.php b/central/trunk/includes/xmlrpc/var_export.php new file mode 100644 index 0000000..3a5ac3f --- /dev/null +++ b/central/trunk/includes/xmlrpc/var_export.php
@@ -0,0 +1,105 @@
1<?php
2// +----------------------------------------------------------------------+
3// | PHP Version 4 |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 1997-2004 The PHP Group |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 3.0 of the PHP license, |
8// | that is bundled with this package in the file LICENSE, and is |
9// | available at through the world-wide-web at |
10// | http://www.php.net/license/3_0.txt. |
11// | If you did not receive a copy of the PHP license and are unable to |
12// | obtain it through the world-wide-web, please send a note to |
13// | license@php.net so we can mail you a copy immediately. |
14// +----------------------------------------------------------------------+
15// | Authors: Aidan Lister <aidan@php.net> |
16// +----------------------------------------------------------------------+
17//
18// $Id: var_export.php,v 1.2 2005/11/21 10:57:23 ggiunta Exp $
19
20
21/**
22 * Replace var_export()
23 *
24 * @category PHP
25 * @package PHP_Compat
26 * @link http://php.net/function.var_export
27 * @author Aidan Lister <aidan@php.net>
28 * @version $Revision: 1.2 $
29 * @since PHP 4.2.0
30 * @require PHP 4.0.0 (user_error)
31 */
32if (!function_exists('var_export')) {
33 function var_export($array, $return = false, $lvl=0)
34 {
35 // Common output variables
36 $indent = ' ';
37 $doublearrow = ' => ';
38 $lineend = ",\n";
39 $stringdelim = '\'';
40
41 // Check the export isn't a simple string / int
42 if (is_string($array)) {
43 $out = $stringdelim . str_replace('\'', '\\\'', str_replace('\\', '\\\\', $array)) . $stringdelim;
44 } elseif (is_int($array) || is_float($array)) {
45 $out = (string)$array;
46 } elseif (is_bool($array)) {
47 $out = $array ? 'true' : 'false';
48 } elseif (is_null($array)) {
49 $out = 'NULL';
50 } elseif (is_resource($array)) {
51 $out = 'resource';
52 } else {
53 // Begin the array export
54 // Start the string
55 $out = "array (\n";
56
57 // Loop through each value in array
58 foreach ($array as $key => $value) {
59 // If the key is a string, delimit it
60 if (is_string($key)) {
61 $key = str_replace('\'', '\\\'', str_replace('\\', '\\\\', $key));
62 $key = $stringdelim . $key . $stringdelim;
63 }
64
65 $val = var_export($value, true, $lvl+1);
66 // Delimit value
67 /*if (is_array($value)) {
68 // We have an array, so do some recursion
69 // Do some basic recursion while increasing the indent
70 $recur_array = explode($newline, var_export($value, true));
71 $temp_array = array();
72 foreach ($recur_array as $recur_line) {
73 $temp_array[] = $indent . $recur_line;
74 }
75 $recur_array = implode($newline, $temp_array);
76 $value = $newline . $recur_array;
77 } elseif (is_null($value)) {
78 $value = 'NULL';
79 } else {
80 $value = str_replace($find, $replace, $value);
81 $value = $stringdelim . $value . $stringdelim;
82 }*/
83
84 // Piece together the line
85 for ($i = 0; $i < $lvl; $i++)
86 $out .= $indent;
87 $out .= $key . $doublearrow . $val . $lineend;
88 }
89
90 // End our string
91 for ($i = 0; $i < $lvl; $i++)
92 $out .= $indent;
93 $out .= ")";
94 }
95
96 // Decide method of output
97 if ($return === true) {
98 return $out;
99 } else {
100 echo $out;
101 return;
102 }
103 }
104}
105?> \ No newline at end of file
diff --git a/central/trunk/includes/xmlrpc/version_compare.php b/central/trunk/includes/xmlrpc/version_compare.php new file mode 100644 index 0000000..fc3abac --- /dev/null +++ b/central/trunk/includes/xmlrpc/version_compare.php
@@ -0,0 +1,179 @@
1<?php
2// +----------------------------------------------------------------------+
3// | PHP Version 4 |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 1997-2004 The PHP Group |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 3.0 of the PHP license, |
8// | that is bundled with this package in the file LICENSE, and is |
9// | available at through the world-wide-web at |
10// | http://www.php.net/license/3_0.txt. |
11// | If you did not receive a copy of the PHP license and are unable to |
12// | obtain it through the world-wide-web, please send a note to |
13// | license@php.net so we can mail you a copy immediately. |
14// +----------------------------------------------------------------------+
15// | Authors: Philippe Jausions <Philippe.Jausions@11abacus.com> |
16// | Aidan Lister <aidan@php.net> |
17// +----------------------------------------------------------------------+
18//
19// $Id: version_compare.php,v 1.1 2005/07/11 16:34:36 ggiunta Exp $
20
21
22/**
23 * Replace version_compare()
24 *
25 * @category PHP
26 * @package PHP_Compat
27 * @link http://php.net/function.version_compare
28 * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
29 * @author Aidan Lister <aidan@php.net>
30 * @version $Revision: 1.1 $
31 * @since PHP 4.1.0
32 * @require PHP 4.0.0 (user_error)
33 */
34if (!function_exists('version_compare')) {
35 function version_compare($version1, $version2, $operator = '<')
36 {
37 // Check input
38 if (!is_scalar($version1)) {
39 user_error('version_compare() expects parameter 1 to be string, ' .
40 gettype($version1) . ' given', E_USER_WARNING);
41 return;
42 }
43
44 if (!is_scalar($version2)) {
45 user_error('version_compare() expects parameter 2 to be string, ' .
46 gettype($version2) . ' given', E_USER_WARNING);
47 return;
48 }
49
50 if (!is_scalar($operator)) {
51 user_error('version_compare() expects parameter 3 to be string, ' .
52 gettype($operator) . ' given', E_USER_WARNING);
53 return;
54 }
55
56 // Standardise versions
57 $v1 = explode('.',
58 str_replace('..', '.',
59 preg_replace('/([^0-9\.]+)/', '.$1.',
60 str_replace(array('-', '_', '+'), '.',
61 trim($version1)))));
62
63 $v2 = explode('.',
64 str_replace('..', '.',
65 preg_replace('/([^0-9\.]+)/', '.$1.',
66 str_replace(array('-', '_', '+'), '.',
67 trim($version2)))));
68
69 // Replace empty entries at the start of the array
70 while (empty($v1[0]) && array_shift($v1)) {}
71 while (empty($v2[0]) && array_shift($v2)) {}
72
73 // Release state order
74 // '#' stands for any number
75 $versions = array(
76 'dev' => 0,
77 'alpha' => 1,
78 'a' => 1,
79 'beta' => 2,
80 'b' => 2,
81 'RC' => 3,
82 '#' => 4,
83 'p' => 5,
84 'pl' => 5);
85
86 // Loop through each segment in the version string
87 $compare = 0;
88 for ($i = 0, $x = min(count($v1), count($v2)); $i < $x; $i++) {
89 if ($v1[$i] == $v2[$i]) {
90 continue;
91 }
92 $i1 = $v1[$i];
93 $i2 = $v2[$i];
94 if (is_numeric($i1) && is_numeric($i2)) {
95 $compare = ($i1 < $i2) ? -1 : 1;
96 break;
97 }
98 // We use the position of '#' in the versions list
99 // for numbers... (so take care of # in original string)
100 if ($i1 == '#') {
101 $i1 = '';
102 } elseif (is_numeric($i1)) {
103 $i1 = '#';
104 }
105 if ($i2 == '#') {
106 $i2 = '';
107 } elseif (is_numeric($i2)) {
108 $i2 = '#';
109 }
110 if (isset($versions[$i1]) && isset($versions[$i2])) {
111 $compare = ($versions[$i1] < $versions[$i2]) ? -1 : 1;
112 } elseif (isset($versions[$i1])) {
113 $compare = 1;
114 } elseif (isset($versions[$i2])) {
115 $compare = -1;
116 } else {
117 $compare = 0;
118 }
119
120 break;
121 }
122
123 // If previous loop didn't find anything, compare the "extra" segments
124 if ($compare == 0) {
125 if (count($v2) > count($v1)) {
126 if (isset($versions[$v2[$i]])) {
127 $compare = ($versions[$v2[$i]] < 4) ? 1 : -1;
128 } else {
129 $compare = -1;
130 }
131 } elseif (count($v2) < count($v1)) {
132 if (isset($versions[$v1[$i]])) {
133 $compare = ($versions[$v1[$i]] < 4) ? -1 : 1;
134 } else {
135 $compare = 1;
136 }
137 }
138 }
139
140 // Compare the versions
141 if (func_num_args() > 2) {
142 switch ($operator) {
143 case '>':
144 case 'gt':
145 return (bool) ($compare > 0);
146 break;
147 case '>=':
148 case 'ge':
149 return (bool) ($compare >= 0);
150 break;
151 case '<=':
152 case 'le':
153 return (bool) ($compare <= 0);
154 break;
155 case '==':
156 case '=':
157 case 'eq':
158 return (bool) ($compare == 0);
159 break;
160 case '<>':
161 case '!=':
162 case 'ne':
163 return (bool) ($compare != 0);
164 break;
165 case '':
166 case '<':
167 case 'lt':
168 return (bool) ($compare < 0);
169 break;
170 default:
171 return;
172 }
173 }
174
175 return $compare;
176 }
177}
178
179?> \ No newline at end of file
diff --git a/central/trunk/includes/xmlrpc/xmlrpc.inc b/central/trunk/includes/xmlrpc/xmlrpc.inc new file mode 100644 index 0000000..06c6e44 --- /dev/null +++ b/central/trunk/includes/xmlrpc/xmlrpc.inc
@@ -0,0 +1,3705 @@
1<?php
2// by Edd Dumbill (C) 1999-2002
3// <edd@usefulinc.com>
4// $Id: xmlrpc.inc,v 1.169 2008/03/06 18:47:24 ggiunta Exp $
5
6// Copyright (c) 1999,2000,2002 Edd Dumbill.
7// All rights reserved.
8//
9// Redistribution and use in source and binary forms, with or without
10// modification, are permitted provided that the following conditions
11// are met:
12//
13// * Redistributions of source code must retain the above copyright
14// notice, this list of conditions and the following disclaimer.
15//
16// * Redistributions in binary form must reproduce the above
17// copyright notice, this list of conditions and the following
18// disclaimer in the documentation and/or other materials provided
19// with the distribution.
20//
21// * Neither the name of the "XML-RPC for PHP" nor the names of its
22// contributors may be used to endorse or promote products derived
23// from this software without specific prior written permission.
24//
25// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36// OF THE POSSIBILITY OF SUCH DAMAGE.
37
38 if(!function_exists('xml_parser_create'))
39 {
40 // For PHP 4 onward, XML functionality is always compiled-in on windows:
41 // no more need to dl-open it. It might have been compiled out on *nix...
42 if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN'))
43 {
44 dl('xml.so');
45 }
46 }
47
48 // Try to be backward compat with php < 4.2 (are we not being nice ?)
49 $phpversion = phpversion();
50 if($phpversion[0] == '4' && $phpversion[2] < 2)
51 {
52 // give an opportunity to user to specify where to include other files from
53 if(!defined('PHP_XMLRPC_COMPAT_DIR'))
54 {
55 define('PHP_XMLRPC_COMPAT_DIR',dirname(__FILE__).'/compat/');
56 }
57 if($phpversion[2] == '0')
58 {
59 if($phpversion[4] < 6)
60 {
61 include(PHP_XMLRPC_COMPAT_DIR.'is_callable.php');
62 }
63 include(PHP_XMLRPC_COMPAT_DIR.'is_scalar.php');
64 include(PHP_XMLRPC_COMPAT_DIR.'array_key_exists.php');
65 include(PHP_XMLRPC_COMPAT_DIR.'version_compare.php');
66 }
67 include(PHP_XMLRPC_COMPAT_DIR.'var_export.php');
68 include(PHP_XMLRPC_COMPAT_DIR.'is_a.php');
69 }
70
71 // G. Giunta 2005/01/29: declare global these variables,
72 // so that xmlrpc.inc will work even if included from within a function
73 // Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used.
74 $GLOBALS['xmlrpcI4']='i4';
75 $GLOBALS['xmlrpcInt']='int';
76 $GLOBALS['xmlrpcBoolean']='boolean';
77 $GLOBALS['xmlrpcDouble']='double';
78 $GLOBALS['xmlrpcString']='string';
79 $GLOBALS['xmlrpcDateTime']='dateTime.iso8601';
80 $GLOBALS['xmlrpcBase64']='base64';
81 $GLOBALS['xmlrpcArray']='array';
82 $GLOBALS['xmlrpcStruct']='struct';
83 $GLOBALS['xmlrpcValue']='undefined';
84
85 $GLOBALS['xmlrpcTypes']=array(
86 $GLOBALS['xmlrpcI4'] => 1,
87 $GLOBALS['xmlrpcInt'] => 1,
88 $GLOBALS['xmlrpcBoolean'] => 1,
89 $GLOBALS['xmlrpcString'] => 1,
90 $GLOBALS['xmlrpcDouble'] => 1,
91 $GLOBALS['xmlrpcDateTime'] => 1,
92 $GLOBALS['xmlrpcBase64'] => 1,
93 $GLOBALS['xmlrpcArray'] => 2,
94 $GLOBALS['xmlrpcStruct'] => 3
95 );
96
97 $GLOBALS['xmlrpc_valid_parents'] = array(
98 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
99 'BOOLEAN' => array('VALUE'),
100 'I4' => array('VALUE'),
101 'INT' => array('VALUE'),
102 'STRING' => array('VALUE'),
103 'DOUBLE' => array('VALUE'),
104 'DATETIME.ISO8601' => array('VALUE'),
105 'BASE64' => array('VALUE'),
106 'MEMBER' => array('STRUCT'),
107 'NAME' => array('MEMBER'),
108 'DATA' => array('ARRAY'),
109 'ARRAY' => array('VALUE'),
110 'STRUCT' => array('VALUE'),
111 'PARAM' => array('PARAMS'),
112 'METHODNAME' => array('METHODCALL'),
113 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
114 'FAULT' => array('METHODRESPONSE'),
115 'NIL' => array('VALUE') // only used when extension activated
116 );
117
118 // define extra types for supporting NULL (useful for json or <NIL/>)
119 $GLOBALS['xmlrpcNull']='null';
120 $GLOBALS['xmlrpcTypes']['null']=1;
121
122 // Not in use anymore since 2.0. Shall we remove it?
123 /// @deprecated
124 $GLOBALS['xmlEntities']=array(
125 'amp' => '&',
126 'quot' => '"',
127 'lt' => '<',
128 'gt' => '>',
129 'apos' => "'"
130 );
131
132 // tables used for transcoding different charsets into us-ascii xml
133
134 $GLOBALS['xml_iso88591_Entities']=array();
135 $GLOBALS['xml_iso88591_Entities']['in'] = array();
136 $GLOBALS['xml_iso88591_Entities']['out'] = array();
137 for ($i = 0; $i < 32; $i++)
138 {
139 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
140 $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
141 }
142 for ($i = 160; $i < 256; $i++)
143 {
144 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
145 $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
146 }
147
148 /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159?
149 /// These will NOT be present in true ISO-8859-1, but will save the unwary
150 /// windows user from sending junk (though no luck when reciving them...)
151 /*
152 $GLOBALS['xml_cp1252_Entities']=array();
153 for ($i = 128; $i < 160; $i++)
154 {
155 $GLOBALS['xml_cp1252_Entities']['in'][] = chr($i);
156 }
157 $GLOBALS['xml_cp1252_Entities']['out'] = array(
158 '&#x20AC;', '?', '&#x201A;', '&#x0192;',
159 '&#x201E;', '&#x2026;', '&#x2020;', '&#x2021;',
160 '&#x02C6;', '&#x2030;', '&#x0160;', '&#x2039;',
161 '&#x0152;', '?', '&#x017D;', '?',
162 '?', '&#x2018;', '&#x2019;', '&#x201C;',
163 '&#x201D;', '&#x2022;', '&#x2013;', '&#x2014;',
164 '&#x02DC;', '&#x2122;', '&#x0161;', '&#x203A;',
165 '&#x0153;', '?', '&#x017E;', '&#x0178;'
166 );
167 */
168
169 $GLOBALS['xmlrpcerr'] = array(
170 'unknown_method'=>1,
171 'invalid_return'=>2,
172 'incorrect_params'=>3,
173 'introspect_unknown'=>4,
174 'http_error'=>5,
175 'no_data'=>6,
176 'no_ssl'=>7,
177 'curl_fail'=>8,
178 'invalid_request'=>15,
179 'no_curl'=>16,
180 'server_error'=>17,
181 'multicall_error'=>18,
182 'multicall_notstruct'=>9,
183 'multicall_nomethod'=>10,
184 'multicall_notstring'=>11,
185 'multicall_recursion'=>12,
186 'multicall_noparams'=>13,
187 'multicall_notarray'=>14,
188
189 'cannot_decompress'=>103,
190 'decompress_fail'=>104,
191 'dechunk_fail'=>105,
192 'server_cannot_decompress'=>106,
193 'server_decompress_fail'=>107
194 );
195
196 $GLOBALS['xmlrpcstr'] = array(
197 'unknown_method'=>'Unknown method',
198 'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload',
199 'incorrect_params'=>'Incorrect parameters passed to method',
200 'introspect_unknown'=>"Can't introspect: method unknown",
201 'http_error'=>"Didn't receive 200 OK from remote server.",
202 'no_data'=>'No data received from server.',
203 'no_ssl'=>'No SSL support compiled in.',
204 'curl_fail'=>'CURL error',
205 'invalid_request'=>'Invalid request payload',
206 'no_curl'=>'No CURL support compiled in.',
207 'server_error'=>'Internal server error',
208 'multicall_error'=>'Received from server invalid multicall response',
209 'multicall_notstruct'=>'system.multicall expected struct',
210 'multicall_nomethod'=>'missing methodName',
211 'multicall_notstring'=>'methodName is not a string',
212 'multicall_recursion'=>'recursive system.multicall forbidden',
213 'multicall_noparams'=>'missing params',
214 'multicall_notarray'=>'params is not an array',
215
216 'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress',
217 'decompress_fail'=>'Received from server invalid compressed HTTP',
218 'dechunk_fail'=>'Received from server invalid chunked HTTP',
219 'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress',
220 'server_decompress_fail'=>'Received from client invalid compressed HTTP request'
221 );
222
223 // The charset encoding used by the server for received messages and
224 // by the client for received responses when received charset cannot be determined
225 // or is not supported
226 $GLOBALS['xmlrpc_defencoding']='UTF-8';
227
228 // The encoding used internally by PHP.
229 // String values received as xml will be converted to this, and php strings will be converted to xml
230 // as if having been coded with this
231 $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1';
232
233 $GLOBALS['xmlrpcName']='XML-RPC for PHP';
234 $GLOBALS['xmlrpcVersion']='2.2.1';
235
236 // let user errors start at 800
237 $GLOBALS['xmlrpcerruser']=800;
238 // let XML parse errors start at 100
239 $GLOBALS['xmlrpcerrxml']=100;
240
241 // formulate backslashes for escaping regexp
242 // Not in use anymore since 2.0. Shall we remove it?
243 /// @deprecated
244 $GLOBALS['xmlrpc_backslash']=chr(92).chr(92);
245
246 // set to TRUE to enable correct decoding of <NIL/> values
247 $GLOBALS['xmlrpc_null_extension']=false;
248
249 // used to store state during parsing
250 // quick explanation of components:
251 // ac - used to accumulate values
252 // isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1)
253 // isf_reason - used for storing xmlrpcresp fault string
254 // lv - used to indicate "looking for a value": implements
255 // the logic to allow values with no types to be strings
256 // params - used to store parameters in method calls
257 // method - used to store method name
258 // stack - array with genealogy of xml elements names:
259 // used to validate nesting of xmlrpc elements
260 $GLOBALS['_xh']=null;
261
262 /**
263 * Convert a string to the correct XML representation in a target charset
264 * To help correct communication of non-ascii chars inside strings, regardless
265 * of the charset used when sending requests, parsing them, sending responses
266 * and parsing responses, an option is to convert all non-ascii chars present in the message
267 * into their equivalent 'charset entity'. Charset entities enumerated this way
268 * are independent of the charset encoding used to transmit them, and all XML
269 * parsers are bound to understand them.
270 * Note that in the std case we are not sending a charset encoding mime type
271 * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
272 *
273 * @todo do a bit of basic benchmarking (strtr vs. str_replace)
274 * @todo make usage of iconv() or recode_string() or mb_string() where available
275 */
276 function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')
277 {
278 if ($src_encoding == '')
279 {
280 // lame, but we know no better...
281 $src_encoding = $GLOBALS['xmlrpc_internalencoding'];
282 }
283
284 switch(strtoupper($src_encoding.'_'.$dest_encoding))
285 {
286 case 'ISO-8859-1_':
287 case 'ISO-8859-1_US-ASCII':
288 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
289 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
290 break;
291 case 'ISO-8859-1_UTF-8':
292 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
293 $escaped_data = utf8_encode($escaped_data);
294 break;
295 case 'ISO-8859-1_ISO-8859-1':
296 case 'US-ASCII_US-ASCII':
297 case 'US-ASCII_UTF-8':
298 case 'US-ASCII_':
299 case 'US-ASCII_ISO-8859-1':
300 case 'UTF-8_UTF-8':
301 //case 'CP1252_CP1252':
302 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
303 break;
304 case 'UTF-8_':
305 case 'UTF-8_US-ASCII':
306 case 'UTF-8_ISO-8859-1':
307 // NB: this will choke on invalid UTF-8, going most likely beyond EOF
308 $escaped_data = '';
309 // be kind to users creating string xmlrpcvals out of different php types
310 $data = (string) $data;
311 $ns = strlen ($data);
312 for ($nn = 0; $nn < $ns; $nn++)
313 {
314 $ch = $data[$nn];
315 $ii = ord($ch);
316 //1 7 0bbbbbbb (127)
317 if ($ii < 128)
318 {
319 /// @todo shall we replace this with a (supposedly) faster str_replace?
320 switch($ii){
321 case 34:
322 $escaped_data .= '&quot;';
323 break;
324 case 38:
325 $escaped_data .= '&amp;';
326 break;
327 case 39:
328 $escaped_data .= '&apos;';
329 break;
330 case 60:
331 $escaped_data .= '&lt;';
332 break;
333 case 62:
334 $escaped_data .= '&gt;';
335 break;
336 default:
337 $escaped_data .= $ch;
338 } // switch
339 }
340 //2 11 110bbbbb 10bbbbbb (2047)
341 else if ($ii>>5 == 6)
342 {
343 $b1 = ($ii & 31);
344 $ii = ord($data[$nn+1]);
345 $b2 = ($ii & 63);
346 $ii = ($b1 * 64) + $b2;
347 $ent = sprintf ('&#%d;', $ii);
348 $escaped_data .= $ent;
349 $nn += 1;
350 }
351 //3 16 1110bbbb 10bbbbbb 10bbbbbb
352 else if ($ii>>4 == 14)
353 {
354 $b1 = ($ii & 31);
355 $ii = ord($data[$nn+1]);
356 $b2 = ($ii & 63);
357 $ii = ord($data[$nn+2]);
358 $b3 = ($ii & 63);
359 $ii = ((($b1 * 64) + $b2) * 64) + $b3;
360 $ent = sprintf ('&#%d;', $ii);
361 $escaped_data .= $ent;
362 $nn += 2;
363 }
364 //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
365 else if ($ii>>3 == 30)
366 {
367 $b1 = ($ii & 31);
368 $ii = ord($data[$nn+1]);
369 $b2 = ($ii & 63);
370 $ii = ord($data[$nn+2]);
371 $b3 = ($ii & 63);
372 $ii = ord($data[$nn+3]);
373 $b4 = ($ii & 63);
374 $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
375 $ent = sprintf ('&#%d;', $ii);
376 $escaped_data .= $ent;
377 $nn += 3;
378 }
379 }
380 break;
381/*
382 case 'CP1252_':
383 case 'CP1252_US-ASCII':
384 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
385 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
386 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
387 break;
388 case 'CP1252_UTF-8':
389 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
390 /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them)
391 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
392 $escaped_data = utf8_encode($escaped_data);
393 break;
394 case 'CP1252_ISO-8859-1':
395 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
396 // we might as well replave all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities...
397 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
398 break;
399*/
400 default:
401 $escaped_data = '';
402 error_log("Converting from $src_encoding to $dest_encoding: not supported...");
403 }
404 return $escaped_data;
405 }
406
407 /// xml parser handler function for opening element tags
408 function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false)
409 {
410 // if invalid xmlrpc already detected, skip all processing
411 if ($GLOBALS['_xh']['isf'] < 2)
412 {
413 // check for correct element nesting
414 // top level element can only be of 2 types
415 /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
416 /// there is only a single top level element in xml anyway
417 if (count($GLOBALS['_xh']['stack']) == 0)
418 {
419 if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (
420 $name != 'VALUE' && !$accept_single_vals))
421 {
422 $GLOBALS['_xh']['isf'] = 2;
423 $GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element';
424 return;
425 }
426 else
427 {
428 $GLOBALS['_xh']['rt'] = strtolower($name);
429 }
430 }
431 else
432 {
433 // not top level element: see if parent is OK
434 $parent = end($GLOBALS['_xh']['stack']);
435 if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name]))
436 {
437 $GLOBALS['_xh']['isf'] = 2;
438 $GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
439 return;
440 }
441 }
442
443 switch($name)
444 {
445 // optimize for speed switch cases: most common cases first
446 case 'VALUE':
447 /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
448 $GLOBALS['_xh']['vt']='value'; // indicator: no value found yet
449 $GLOBALS['_xh']['ac']='';
450 $GLOBALS['_xh']['lv']=1;
451 $GLOBALS['_xh']['php_class']=null;
452 break;
453 case 'I4':
454 case 'INT':
455 case 'STRING':
456 case 'BOOLEAN':
457 case 'DOUBLE':
458 case 'DATETIME.ISO8601':
459 case 'BASE64':
460 if ($GLOBALS['_xh']['vt']!='value')
461 {
462 //two data elements inside a value: an error occurred!
463 $GLOBALS['_xh']['isf'] = 2;
464 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
465 return;
466 }
467 $GLOBALS['_xh']['ac']=''; // reset the accumulator
468 break;
469 case 'STRUCT':
470 case 'ARRAY':
471 if ($GLOBALS['_xh']['vt']!='value')
472 {
473 //two data elements inside a value: an error occurred!
474 $GLOBALS['_xh']['isf'] = 2;
475 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
476 return;
477 }
478 // create an empty array to hold child values, and push it onto appropriate stack
479 $cur_val = array();
480 $cur_val['values'] = array();
481 $cur_val['type'] = $name;
482 // check for out-of-band information to rebuild php objs
483 // and in case it is found, save it
484 if (@isset($attrs['PHP_CLASS']))
485 {
486 $cur_val['php_class'] = $attrs['PHP_CLASS'];
487 }
488 $GLOBALS['_xh']['valuestack'][] = $cur_val;
489 $GLOBALS['_xh']['vt']='data'; // be prepared for a data element next
490 break;
491 case 'DATA':
492 if ($GLOBALS['_xh']['vt']!='data')
493 {
494 //two data elements inside a value: an error occurred!
495 $GLOBALS['_xh']['isf'] = 2;
496 $GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element";
497 return;
498 }
499 case 'METHODCALL':
500 case 'METHODRESPONSE':
501 case 'PARAMS':
502 // valid elements that add little to processing
503 break;
504 case 'METHODNAME':
505 case 'NAME':
506 /// @todo we could check for 2 NAME elements inside a MEMBER element
507 $GLOBALS['_xh']['ac']='';
508 break;
509 case 'FAULT':
510 $GLOBALS['_xh']['isf']=1;
511 break;
512 case 'MEMBER':
513 $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on
514 //$GLOBALS['_xh']['ac']='';
515 // Drop trough intentionally
516 case 'PARAM':
517 // clear value type, so we can check later if no value has been passed for this param/member
518 $GLOBALS['_xh']['vt']=null;
519 break;
520 case 'NIL':
521 if ($GLOBALS['xmlrpc_null_extension'])
522 {
523 if ($GLOBALS['_xh']['vt']!='value')
524 {
525 //two data elements inside a value: an error occurred!
526 $GLOBALS['_xh']['isf'] = 2;
527 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
528 return;
529 }
530 $GLOBALS['_xh']['ac']=''; // reset the accumulator
531 break;
532 }
533 // we do not support the <NIL/> extension, so
534 // drop through intentionally
535 default:
536 /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
537 $GLOBALS['_xh']['isf'] = 2;
538 $GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name";
539 break;
540 }
541
542 // Save current element name to stack, to validate nesting
543 $GLOBALS['_xh']['stack'][] = $name;
544
545 /// @todo optimization creep: move this inside the big switch() above
546 if($name!='VALUE')
547 {
548 $GLOBALS['_xh']['lv']=0;
549 }
550 }
551 }
552
553 /// Used in decoding xml chunks that might represent single xmlrpc values
554 function xmlrpc_se_any($parser, $name, $attrs)
555 {
556 xmlrpc_se($parser, $name, $attrs, true);
557 }
558
559 /// xml parser handler function for close element tags
560 function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)
561 {
562 if ($GLOBALS['_xh']['isf'] < 2)
563 {
564 // push this element name from stack
565 // NB: if XML validates, correct opening/closing is guaranteed and
566 // we do not have to check for $name == $curr_elem.
567 // we also checked for proper nesting at start of elements...
568 $curr_elem = array_pop($GLOBALS['_xh']['stack']);
569
570 switch($name)
571 {
572 case 'VALUE':
573 // This if() detects if no scalar was inside <VALUE></VALUE>
574 if ($GLOBALS['_xh']['vt']=='value')
575 {
576 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
577 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString'];
578 }
579
580 if ($rebuild_xmlrpcvals)
581 {
582 // build the xmlrpc val out of the data received, and substitute it
583 $temp =& new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']);
584 // in case we got info about underlying php class, save it
585 // in the object we're rebuilding
586 if (isset($GLOBALS['_xh']['php_class']))
587 $temp->_php_class = $GLOBALS['_xh']['php_class'];
588 // check if we are inside an array or struct:
589 // if value just built is inside an array, let's move it into array on the stack
590 $vscount = count($GLOBALS['_xh']['valuestack']);
591 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
592 {
593 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp;
594 }
595 else
596 {
597 $GLOBALS['_xh']['value'] = $temp;
598 }
599 }
600 else
601 {
602 /// @todo this needs to treat correctly php-serialized objects,
603 /// since std deserializing is done by php_xmlrpc_decode,
604 /// which we will not be calling...
605 if (isset($GLOBALS['_xh']['php_class']))
606 {
607 }
608
609 // check if we are inside an array or struct:
610 // if value just built is inside an array, let's move it into array on the stack
611 $vscount = count($GLOBALS['_xh']['valuestack']);
612 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
613 {
614 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value'];
615 }
616 }
617 break;
618 case 'BOOLEAN':
619 case 'I4':
620 case 'INT':
621 case 'STRING':
622 case 'DOUBLE':
623 case 'DATETIME.ISO8601':
624 case 'BASE64':
625 $GLOBALS['_xh']['vt']=strtolower($name);
626 /// @todo: optimization creep - remove the if/elseif cycle below
627 /// since the case() in which we are already did that
628 if ($name=='STRING')
629 {
630 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
631 }
632 elseif ($name=='DATETIME.ISO8601')
633 {
634 if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $GLOBALS['_xh']['ac']))
635 {
636 error_log('XML-RPC: invalid value received in DATETIME: '.$GLOBALS['_xh']['ac']);
637 }
638 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime'];
639 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
640 }
641 elseif ($name=='BASE64')
642 {
643 /// @todo check for failure of base64 decoding / catch warnings
644 $GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']);
645 }
646 elseif ($name=='BOOLEAN')
647 {
648 // special case here: we translate boolean 1 or 0 into PHP
649 // constants true or false.
650 // Strings 'true' and 'false' are accepted, even though the
651 // spec never mentions them (see eg. Blogger api docs)
652 // NB: this simple checks helps a lot sanitizing input, ie no
653 // security problems around here
654 if ($GLOBALS['_xh']['ac']=='1' || strcasecmp($GLOBALS['_xh']['ac'], 'true') == 0)
655 {
656 $GLOBALS['_xh']['value']=true;
657 }
658 else
659 {
660 // log if receiveing something strange, even though we set the value to false anyway
661 if ($GLOBALS['_xh']['ac']!='0' && strcasecmp($_xh[$parser]['ac'], 'false') != 0)
662 error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']);
663 $GLOBALS['_xh']['value']=false;
664 }
665 }
666 elseif ($name=='DOUBLE')
667 {
668 // we have a DOUBLE
669 // we must check that only 0123456789-.<space> are characters here
670 // NOTE: regexp could be much stricter than this...
671 if (!preg_match('/^[+-eE0123456789 \t.]+$/', $GLOBALS['_xh']['ac']))
672 {
673 /// @todo: find a better way of throwing an error than this!
674 error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']);
675 $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
676 }
677 else
678 {
679 // it's ok, add it on
680 $GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac'];
681 }
682 }
683 else
684 {
685 // we have an I4/INT
686 // we must check that only 0123456789-<space> are characters here
687 if (!preg_match('/^[+-]?[0123456789 \t]+$/', $GLOBALS['_xh']['ac']))
688 {
689 /// @todo find a better way of throwing an error than this!
690 error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']);
691 $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
692 }
693 else
694 {
695 // it's ok, add it on
696 $GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac'];
697 }
698 }
699 //$GLOBALS['_xh']['ac']=''; // is this necessary?
700 $GLOBALS['_xh']['lv']=3; // indicate we've found a value
701 break;
702 case 'NAME':
703 $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac'];
704 break;
705 case 'MEMBER':
706 //$GLOBALS['_xh']['ac']=''; // is this necessary?
707 // add to array in the stack the last element built,
708 // unless no VALUE was found
709 if ($GLOBALS['_xh']['vt'])
710 {
711 $vscount = count($GLOBALS['_xh']['valuestack']);
712 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value'];
713 } else
714 error_log('XML-RPC: missing VALUE inside STRUCT in received xml');
715 break;
716 case 'DATA':
717 //$GLOBALS['_xh']['ac']=''; // is this necessary?
718 $GLOBALS['_xh']['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty
719 break;
720 case 'STRUCT':
721 case 'ARRAY':
722 // fetch out of stack array of values, and promote it to current value
723 $curr_val = array_pop($GLOBALS['_xh']['valuestack']);
724 $GLOBALS['_xh']['value'] = $curr_val['values'];
725 $GLOBALS['_xh']['vt']=strtolower($name);
726 if (isset($curr_val['php_class']))
727 {
728 $GLOBALS['_xh']['php_class'] = $curr_val['php_class'];
729 }
730 break;
731 case 'PARAM':
732 // add to array of params the current value,
733 // unless no VALUE was found
734 if ($GLOBALS['_xh']['vt'])
735 {
736 $GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value'];
737 $GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt'];
738 }
739 else
740 error_log('XML-RPC: missing VALUE inside PARAM in received xml');
741 break;
742 case 'METHODNAME':
743 $GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', $GLOBALS['_xh']['ac']);
744 break;
745 case 'NIL':
746 if ($GLOBALS['xmlrpc_null_extension'])
747 {
748 $GLOBALS['_xh']['vt']='null';
749 $GLOBALS['_xh']['value']=null;
750 $GLOBALS['_xh']['lv']=3;
751 break;
752 }
753 // drop through intentionally if nil extension not enabled
754 case 'PARAMS':
755 case 'FAULT':
756 case 'METHODCALL':
757 case 'METHORESPONSE':
758 break;
759 default:
760 // End of INVALID ELEMENT!
761 // shall we add an assert here for unreachable code???
762 break;
763 }
764 }
765 }
766
767 /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values
768 function xmlrpc_ee_fast($parser, $name)
769 {
770 xmlrpc_ee($parser, $name, false);
771 }
772
773 /// xml parser handler function for character data
774 function xmlrpc_cd($parser, $data)
775 {
776 // skip processing if xml fault already detected
777 if ($GLOBALS['_xh']['isf'] < 2)
778 {
779 // "lookforvalue==3" means that we've found an entire value
780 // and should discard any further character data
781 if($GLOBALS['_xh']['lv']!=3)
782 {
783 // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2
784 //if($GLOBALS['_xh']['lv']==1)
785 //{
786 // if we've found text and we're just in a <value> then
787 // say we've found a value
788 //$GLOBALS['_xh']['lv']=2;
789 //}
790 // we always initialize the accumulator before starting parsing, anyway...
791 //if(!@isset($GLOBALS['_xh']['ac']))
792 //{
793 // $GLOBALS['_xh']['ac'] = '';
794 //}
795 $GLOBALS['_xh']['ac'].=$data;
796 }
797 }
798 }
799
800 /// xml parser handler function for 'other stuff', ie. not char data or
801 /// element start/end tag. In fact it only gets called on unknown entities...
802 function xmlrpc_dh($parser, $data)
803 {
804 // skip processing if xml fault already detected
805 if ($GLOBALS['_xh']['isf'] < 2)
806 {
807 if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
808 {
809 // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2
810 //if($GLOBALS['_xh']['lv']==1)
811 //{
812 // $GLOBALS['_xh']['lv']=2;
813 //}
814 $GLOBALS['_xh']['ac'].=$data;
815 }
816 }
817 return true;
818 }
819
820 class xmlrpc_client
821 {
822 var $path;
823 var $server;
824 var $port=0;
825 var $method='http';
826 var $errno;
827 var $errstr;
828 var $debug=0;
829 var $username='';
830 var $password='';
831 var $authtype=1;
832 var $cert='';
833 var $certpass='';
834 var $cacert='';
835 var $cacertdir='';
836 var $key='';
837 var $keypass='';
838 var $verifypeer=true;
839 var $verifyhost=1;
840 var $no_multicall=false;
841 var $proxy='';
842 var $proxyport=0;
843 var $proxy_user='';
844 var $proxy_pass='';
845 var $proxy_authtype=1;
846 var $cookies=array();
847 /**
848 * List of http compression methods accepted by the client for responses.
849 * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
850 *
851 * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
852 * in those cases it will be up to CURL to decide the compression methods
853 * it supports. You might check for the presence of 'zlib' in the output of
854 * curl_version() to determine wheter compression is supported or not
855 */
856 var $accepted_compression = array();
857 /**
858 * Name of compression scheme to be used for sending requests.
859 * Either null, gzip or deflate
860 */
861 var $request_compression = '';
862 /**
863 * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
864 * http://curl.haxx.se/docs/faq.html#7.3)
865 */
866 var $xmlrpc_curl_handle = null;
867 /// Wheter to use persistent connections for http 1.1 and https
868 var $keepalive = false;
869 /// Charset encodings that can be decoded without problems by the client
870 var $accepted_charset_encodings = array();
871 /// Charset encoding to be used in serializing request. NULL = use ASCII
872 var $request_charset_encoding = '';
873 /**
874 * Decides the content of xmlrpcresp objects returned by calls to send()
875 * valid strings are 'xmlrpcvals', 'phpvals' or 'xml'
876 */
877 var $return_type = 'xmlrpcvals';
878
879 /**
880 * @param string $path either the complete server URL or the PATH part of the xmlrc server URL, e.g. /xmlrpc/server.php
881 * @param string $server the server name / ip address
882 * @param integer $port the port the server is listening on, defaults to 80 or 443 depending on protocol used
883 * @param string $method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed
884 */
885 function xmlrpc_client($path, $server='', $port='', $method='')
886 {
887 // allow user to specify all params in $path
888 if($server == '' and $port == '' and $method == '')
889 {
890 $parts = parse_url($path);
891 $server = $parts['host'];
892 $path = isset($parts['path']) ? $parts['path'] : '';
893 if(isset($parts['query']))
894 {
895 $path .= '?'.$parts['query'];
896 }
897 if(isset($parts['fragment']))
898 {
899 $path .= '#'.$parts['fragment'];
900 }
901 if(isset($parts['port']))
902 {
903 $port = $parts['port'];
904 }
905 if(isset($parts['scheme']))
906 {
907 $method = $parts['scheme'];
908 }
909 if(isset($parts['user']))
910 {
911 $this->username = $parts['user'];
912 }
913 if(isset($parts['pass']))
914 {
915 $this->password = $parts['pass'];
916 }
917 }
918 if($path == '' || $path[0] != '/')
919 {
920 $this->path='/'.$path;
921 }
922 else
923 {
924 $this->path=$path;
925 }
926 $this->server=$server;
927 if($port != '')
928 {
929 $this->port=$port;
930 }
931 if($method != '')
932 {
933 $this->method=$method;
934 }
935
936 // if ZLIB is enabled, let the client by default accept compressed responses
937 if(function_exists('gzinflate') || (
938 function_exists('curl_init') && (($info = curl_version()) &&
939 ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
940 ))
941 {
942 $this->accepted_compression = array('gzip', 'deflate');
943 }
944
945 // keepalives: enabled by default ONLY for PHP >= 4.3.8
946 // (see http://curl.haxx.se/docs/faq.html#7.3)
947 if(version_compare(phpversion(), '4.3.8') >= 0)
948 {
949 $this->keepalive = true;
950 }
951
952 // by default the xml parser can support these 3 charset encodings
953 $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
954 }
955
956 /**
957 * Enables/disables the echoing to screen of the xmlrpc responses received
958 * @param integer $debug values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
959 * @access public
960 */
961 function setDebug($in)
962 {
963 $this->debug=$in;
964 }
965
966 /**
967 * Add some http BASIC AUTH credentials, used by the client to authenticate
968 * @param string $u username
969 * @param string $p password
970 * @param integer $t auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC (basic auth)
971 * @access public
972 */
973 function setCredentials($u, $p, $t=1)
974 {
975 $this->username=$u;
976 $this->password=$p;
977 $this->authtype=$t;
978 }
979
980 /**
981 * Add a client-side https certificate
982 * @param string $cert
983 * @param string $certpass
984 * @access public
985 */
986 function setCertificate($cert, $certpass)
987 {
988 $this->cert = $cert;
989 $this->certpass = $certpass;
990 }
991
992 /**
993 * Add a CA certificate to verify server with (see man page about
994 * CURLOPT_CAINFO for more details
995 * @param string $cacert certificate file name (or dir holding certificates)
996 * @param bool $is_dir set to true to indicate cacert is a dir. defaults to false
997 * @access public
998 */
999 function setCaCertificate($cacert, $is_dir=false)
1000 {
1001 if ($is_dir)
1002 {
1003 $this->cacertdir = $cacert;
1004 }
1005 else
1006 {
1007 $this->cacert = $cacert;
1008 }
1009 }
1010
1011 /**
1012 * Set attributes for SSL communication: private SSL key
1013 * NB: does not work in older php/curl installs
1014 * Thanks to Daniel Convissor
1015 * @param string $key The name of a file containing a private SSL key
1016 * @param string $keypass The secret password needed to use the private SSL key
1017 * @access public
1018 */
1019 function setKey($key, $keypass)
1020 {
1021 $this->key = $key;
1022 $this->keypass = $keypass;
1023 }
1024
1025 /**
1026 * Set attributes for SSL communication: verify server certificate
1027 * @param bool $i enable/disable verification of peer certificate
1028 * @access public
1029 */
1030 function setSSLVerifyPeer($i)
1031 {
1032 $this->verifypeer = $i;
1033 }
1034
1035 /**
1036 * Set attributes for SSL communication: verify match of server cert w. hostname
1037 * @param int $i
1038 * @access public
1039 */
1040 function setSSLVerifyHost($i)
1041 {
1042 $this->verifyhost = $i;
1043 }
1044
1045 /**
1046 * Set proxy info
1047 * @param string $proxyhost
1048 * @param string $proxyport Defaults to 8080 for HTTP and 443 for HTTPS
1049 * @param string $proxyusername Leave blank if proxy has public access
1050 * @param string $proxypassword Leave blank if proxy has public access
1051 * @param int $proxyauthtype set to constant CURLAUTH_NTLM to use NTLM auth with proxy
1052 * @access public
1053 */
1054 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 1)
1055 {
1056 $this->proxy = $proxyhost;
1057 $this->proxyport = $proxyport;
1058 $this->proxy_user = $proxyusername;
1059 $this->proxy_pass = $proxypassword;
1060 $this->proxy_authtype = $proxyauthtype;
1061 }
1062
1063 /**
1064 * Enables/disables reception of compressed xmlrpc responses.
1065 * Note that enabling reception of compressed responses merely adds some standard
1066 * http headers to xmlrpc requests. It is up to the xmlrpc server to return
1067 * compressed responses when receiving such requests.
1068 * @param string $compmethod either 'gzip', 'deflate', 'any' or ''
1069 * @access public
1070 */
1071 function setAcceptedCompression($compmethod)
1072 {
1073 if ($compmethod == 'any')
1074 $this->accepted_compression = array('gzip', 'deflate');
1075 else
1076 $this->accepted_compression = array($compmethod);
1077 }
1078
1079 /**
1080 * Enables/disables http compression of xmlrpc request.
1081 * Take care when sending compressed requests: servers might not support them
1082 * (and automatic fallback to uncompressed requests is not yet implemented)
1083 * @param string $compmethod either 'gzip', 'deflate' or ''
1084 * @access public
1085 */
1086 function setRequestCompression($compmethod)
1087 {
1088 $this->request_compression = $compmethod;
1089 }
1090
1091 /**
1092 * Adds a cookie to list of cookies that will be sent to server.
1093 * NB: setting any param but name and value will turn the cookie into a 'version 1' cookie:
1094 * do not do it unless you know what you are doing
1095 * @param string $name
1096 * @param string $value
1097 * @param string $path
1098 * @param string $domain
1099 * @param int $port
1100 * @access public
1101 *
1102 * @todo check correctness of urlencoding cookie value (copied from php way of doing it...)
1103 */
1104 function setCookie($name, $value='', $path='', $domain='', $port=null)
1105 {
1106 $this->cookies[$name]['value'] = urlencode($value);
1107 if ($path || $domain || $port)
1108 {
1109 $this->cookies[$name]['path'] = $path;
1110 $this->cookies[$name]['domain'] = $domain;
1111 $this->cookies[$name]['port'] = $port;
1112 $this->cookies[$name]['version'] = 1;
1113 }
1114 else
1115 {
1116 $this->cookies[$name]['version'] = 0;
1117 }
1118 }
1119
1120 /**
1121 * Send an xmlrpc request
1122 * @param mixed $msg The message object, or an array of messages for using multicall, or the complete xml representation of a request
1123 * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply
1124 * @param string $method if left unspecified, the http protocol chosen during creation of the object will be used
1125 * @return xmlrpcresp
1126 * @access public
1127 */
1128 function& send($msg, $timeout=0, $method='')
1129 {
1130 // if user deos not specify http protocol, use native method of this client
1131 // (i.e. method set during call to constructor)
1132 if($method == '')
1133 {
1134 $method = $this->method;
1135 }
1136
1137 if(is_array($msg))
1138 {
1139 // $msg is an array of xmlrpcmsg's
1140 $r = $this->multicall($msg, $timeout, $method);
1141 return $r;
1142 }
1143 elseif(is_string($msg))
1144 {
1145 $n =& new xmlrpcmsg('');
1146 $n->payload = $msg;
1147 $msg = $n;
1148 }
1149
1150 // where msg is an xmlrpcmsg
1151 $msg->debug=$this->debug;
1152
1153 if($method == 'https')
1154 {
1155 $r =& $this->sendPayloadHTTPS(
1156 $msg,
1157 $this->server,
1158 $this->port,
1159 $timeout,
1160 $this->username,
1161 $this->password,
1162 $this->authtype,
1163 $this->cert,
1164 $this->certpass,
1165 $this->cacert,
1166 $this->cacertdir,
1167 $this->proxy,
1168 $this->proxyport,
1169 $this->proxy_user,
1170 $this->proxy_pass,
1171 $this->proxy_authtype,
1172 $this->keepalive,
1173 $this->key,
1174 $this->keypass
1175 );
1176 }
1177 elseif($method == 'http11')
1178 {
1179 $r =& $this->sendPayloadCURL(
1180 $msg,
1181 $this->server,
1182 $this->port,
1183 $timeout,
1184 $this->username,
1185 $this->password,
1186 $this->authtype,
1187 null,
1188 null,
1189 null,
1190 null,
1191 $this->proxy,
1192 $this->proxyport,
1193 $this->proxy_user,
1194 $this->proxy_pass,
1195 $this->proxy_authtype,
1196 'http',
1197 $this->keepalive
1198 );
1199 }
1200 else
1201 {
1202 $r =& $this->sendPayloadHTTP10(
1203 $msg,
1204 $this->server,
1205 $this->port,
1206 $timeout,
1207 $this->username,
1208 $this->password,
1209 $this->authtype,
1210 $this->proxy,
1211 $this->proxyport,
1212 $this->proxy_user,
1213 $this->proxy_pass,
1214 $this->proxy_authtype
1215 );
1216 }
1217
1218 return $r;
1219 }
1220
1221 /**
1222 * @access private
1223 */
1224 function &sendPayloadHTTP10($msg, $server, $port, $timeout=0,
1225 $username='', $password='', $authtype=1, $proxyhost='',
1226 $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1)
1227 {
1228 if($port==0)
1229 {
1230 $port=80;
1231 }
1232
1233 // Only create the payload if it was not created previously
1234 if(empty($msg->payload))
1235 {
1236 $msg->createPayload($this->request_charset_encoding);
1237 }
1238
1239 $payload = $msg->payload;
1240 // Deflate request body and set appropriate request headers
1241 if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1242 {
1243 if($this->request_compression == 'gzip')
1244 {
1245 $a = @gzencode($payload);
1246 if($a)
1247 {
1248 $payload = $a;
1249 $encoding_hdr = "Content-Encoding: gzip\r\n";
1250 }
1251 }
1252 else
1253 {
1254 $a = @gzcompress($payload);
1255 if($a)
1256 {
1257 $payload = $a;
1258 $encoding_hdr = "Content-Encoding: deflate\r\n";
1259 }
1260 }
1261 }
1262 else
1263 {
1264 $encoding_hdr = '';
1265 }
1266
1267 // thanks to Grant Rauscher <grant7@firstworld.net> for this
1268 $credentials='';
1269 if($username!='')
1270 {
1271 $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
1272 if ($authtype != 1)
1273 {
1274 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported with HTTP 1.0');
1275 }
1276 }
1277
1278 $accepted_encoding = '';
1279 if(is_array($this->accepted_compression) && count($this->accepted_compression))
1280 {
1281 $accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
1282 }
1283
1284 $proxy_credentials = '';
1285 if($proxyhost)
1286 {
1287 if($proxyport == 0)
1288 {
1289 $proxyport = 8080;
1290 }
1291 $connectserver = $proxyhost;
1292 $connectport = $proxyport;
1293 $uri = 'http://'.$server.':'.$port.$this->path;
1294 if($proxyusername != '')
1295 {
1296 if ($proxyauthtype != 1)
1297 {
1298 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported with HTTP 1.0');
1299 }
1300 $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n";
1301 }
1302 }
1303 else
1304 {
1305 $connectserver = $server;
1306 $connectport = $port;
1307 $uri = $this->path;
1308 }
1309
1310 // Cookie generation, as per rfc2965 (version 1 cookies) or
1311 // netscape's rules (version 0 cookies)
1312 $cookieheader='';
1313 if (count($this->cookies))
1314 {
1315 $version = '';
1316 foreach ($this->cookies as $name => $cookie)
1317 {
1318 if ($cookie['version'])
1319 {
1320 $version = ' $Version="' . $cookie['version'] . '";';
1321 $cookieheader .= ' ' . $name . '="' . $cookie['value'] . '";';
1322 if ($cookie['path'])
1323 $cookieheader .= ' $Path="' . $cookie['path'] . '";';
1324 if ($cookie['domain'])
1325 $cookieheader .= ' $Domain="' . $cookie['domain'] . '";';
1326 if ($cookie['port'])
1327 $cookieheader .= ' $Port="' . $cookie['port'] . '";';
1328 }
1329 else
1330 {
1331 $cookieheader .= ' ' . $name . '=' . $cookie['value'] . ";";
1332 }
1333 }
1334 $cookieheader = 'Cookie:' . $version . substr($cookieheader, 0, -1) . "\r\n";
1335 }
1336
1337 $op= 'POST ' . $uri. " HTTP/1.0\r\n" .
1338 'User-Agent: ' . $GLOBALS['xmlrpcName'] . ' ' . $GLOBALS['xmlrpcVersion'] . "\r\n" .
1339 'Host: '. $server . ':' . $port . "\r\n" .
1340 $credentials .
1341 $proxy_credentials .
1342 $accepted_encoding .
1343 $encoding_hdr .
1344 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
1345 $cookieheader .
1346 'Content-Type: ' . $msg->content_type . "\r\nContent-Length: " .
1347 strlen($payload) . "\r\n\r\n" .
1348 $payload;
1349
1350 if($this->debug > 1)
1351 {
1352 print "<PRE>\n---SENDING---\n" . htmlentities($op) . "\n---END---\n</PRE>";
1353 // let the client see this now in case http times out...
1354 flush();
1355 }
1356
1357 if($timeout>0)
1358 {
1359 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout);
1360 }
1361 else
1362 {
1363 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr);
1364 }
1365 if($fp)
1366 {
1367 if($timeout>0 && function_exists('stream_set_timeout'))
1368 {
1369 stream_set_timeout($fp, $timeout);
1370 }
1371 }
1372 else
1373 {
1374 $this->errstr='Connect error: '.$this->errstr;
1375 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')');
1376 return $r;
1377 }
1378
1379 if(!fputs($fp, $op, strlen($op)))
1380 {
1381 $this->errstr='Write error';
1382 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr);
1383 return $r;
1384 }
1385 else
1386 {
1387 // reset errno and errstr on succesful socket connection
1388 $this->errstr = '';
1389 }
1390 // G. Giunta 2005/10/24: close socket before parsing.
1391 // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1392 $ipd='';
1393 while($data=fread($fp, 32768))
1394 {
1395 // shall we check for $data === FALSE?
1396 // as per the manual, it signals an error
1397 $ipd.=$data;
1398 }
1399 fclose($fp);
1400 $r =& $msg->parseResponse($ipd, false, $this->return_type);
1401 return $r;
1402
1403 }
1404
1405 /**
1406 * @access private
1407 */
1408 function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='',
1409 $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='',
1410 $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1,
1411 $keepalive=false, $key='', $keypass='')
1412 {
1413 $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username,
1414 $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport,
1415 $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass);
1416 return $r;
1417 }
1418
1419 /**
1420 * Contributed by Justin Miller <justin@voxel.net>
1421 * Requires curl to be built into PHP
1422 * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1423 * @access private
1424 */
1425 function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='',
1426 $password='', $authtype=1, $cert='', $certpass='', $cacert='', $cacertdir='',
1427 $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $method='https',
1428 $keepalive=false, $key='', $keypass='')
1429 {
1430 if(!function_exists('curl_init'))
1431 {
1432 $this->errstr='CURL unavailable on this install';
1433 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']);
1434 return $r;
1435 }
1436 if($method == 'https')
1437 {
1438 if(($info = curl_version()) &&
1439 ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version']))))
1440 {
1441 $this->errstr='SSL unavailable on this install';
1442 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']);
1443 return $r;
1444 }
1445 }
1446
1447 if($port == 0)
1448 {
1449 if($method == 'http')
1450 {
1451 $port = 80;
1452 }
1453 else
1454 {
1455 $port = 443;
1456 }
1457 }
1458
1459 // Only create the payload if it was not created previously
1460 if(empty($msg->payload))
1461 {
1462 $msg->createPayload($this->request_charset_encoding);
1463 }
1464
1465 // Deflate request body and set appropriate request headers
1466 $payload = $msg->payload;
1467 if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1468 {
1469 if($this->request_compression == 'gzip')
1470 {
1471 $a = @gzencode($payload);
1472 if($a)
1473 {
1474 $payload = $a;
1475 $encoding_hdr = 'Content-Encoding: gzip';
1476 }
1477 }
1478 else
1479 {
1480 $a = @gzcompress($payload);
1481 if($a)
1482 {
1483 $payload = $a;
1484 $encoding_hdr = 'Content-Encoding: deflate';
1485 }
1486 }
1487 }
1488 else
1489 {
1490 $encoding_hdr = '';
1491 }
1492
1493 if($this->debug > 1)
1494 {
1495 print "<PRE>\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n</PRE>";
1496 // let the client see this now in case http times out...
1497 flush();
1498 }
1499
1500 if(!$keepalive || !$this->xmlrpc_curl_handle)
1501 {
1502 $curl = curl_init($method . '://' . $server . ':' . $port . $this->path);
1503 if($keepalive)
1504 {
1505 $this->xmlrpc_curl_handle = $curl;
1506 }
1507 }
1508 else
1509 {
1510 $curl = $this->xmlrpc_curl_handle;
1511 }
1512
1513 // results into variable
1514 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
1515
1516 if($this->debug)
1517 {
1518 curl_setopt($curl, CURLOPT_VERBOSE, 1);
1519 }
1520 curl_setopt($curl, CURLOPT_USERAGENT, $GLOBALS['xmlrpcName'].' '.$GLOBALS['xmlrpcVersion']);
1521 // required for XMLRPC: post the data
1522 curl_setopt($curl, CURLOPT_POST, 1);
1523 // the data
1524 curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1525
1526 // return the header too
1527 curl_setopt($curl, CURLOPT_HEADER, 1);
1528
1529 // will only work with PHP >= 5.0
1530 // NB: if we set an empty string, CURL will add http header indicating
1531 // ALL methods it is supporting. This is possibly a better option than
1532 // letting the user tell what curl can / cannot do...
1533 if(is_array($this->accepted_compression) && count($this->accepted_compression))
1534 {
1535 //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
1536 // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1537 if (count($this->accepted_compression) == 1)
1538 {
1539 curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
1540 }
1541 else
1542 curl_setopt($curl, CURLOPT_ENCODING, '');
1543 }
1544 // extra headers
1545 $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
1546 // if no keepalive is wanted, let the server know it in advance
1547 if(!$keepalive)
1548 {
1549 $headers[] = 'Connection: close';
1550 }
1551 // request compression header
1552 if($encoding_hdr)
1553 {
1554 $headers[] = $encoding_hdr;
1555 }
1556
1557 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1558 // timeout is borked
1559 if($timeout)
1560 {
1561 curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
1562 }
1563
1564 if($username && $password)
1565 {
1566 curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password);
1567 if (defined('CURLOPT_HTTPAUTH'))
1568 {
1569 curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype);
1570 }
1571 else if ($authtype != 1)
1572 {
1573 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported by the current PHP/curl install');
1574 }
1575 }
1576
1577 if($method == 'https')
1578 {
1579 // set cert file
1580 if($cert)
1581 {
1582 curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1583 }
1584 // set cert password
1585 if($certpass)
1586 {
1587 curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass);
1588 }
1589 // whether to verify remote host's cert
1590 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1591 // set ca certificates file/dir
1592 if($cacert)
1593 {
1594 curl_setopt($curl, CURLOPT_CAINFO, $cacert);
1595 }
1596 if($cacertdir)
1597 {
1598 curl_setopt($curl, CURLOPT_CAPATH, $cacertdir);
1599 }
1600 // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1601 if($key)
1602 {
1603 curl_setopt($curl, CURLOPT_SSLKEY, $key);
1604 }
1605 // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1606 if($keypass)
1607 {
1608 curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass);
1609 }
1610 // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used
1611 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1612 }
1613
1614 // proxy info
1615 if($proxyhost)
1616 {
1617 if($proxyport == 0)
1618 {
1619 $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080
1620 }
1621 curl_setopt($curl, CURLOPT_PROXY, $proxyhost.':'.$proxyport);
1622 //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport);
1623 if($proxyusername)
1624 {
1625 curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword);
1626 if (defined('CURLOPT_PROXYAUTH'))
1627 {
1628 curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype);
1629 }
1630 else if ($proxyauthtype != 1)
1631 {
1632 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1633 }
1634 }
1635 }
1636
1637 // NB: should we build cookie http headers by hand rather than let CURL do it?
1638 // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
1639 // set to client obj the the user...
1640 if (count($this->cookies))
1641 {
1642 $cookieheader = '';
1643 foreach ($this->cookies as $name => $cookie)
1644 {
1645 $cookieheader .= $name . '=' . $cookie['value'] . '; ';
1646 }
1647 curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2));
1648 }
1649
1650 $result = curl_exec($curl);
1651
1652 if ($this->debug > 1)
1653 {
1654 print "<PRE>\n---CURL INFO---\n";
1655 foreach(curl_getinfo($curl) as $name => $val)
1656 print $name . ': ' . htmlentities($val). "\n";
1657 print "---END---\n</PRE>";
1658 }
1659
1660 if(!$result) /// @todo we should use a better check here - what if we get back '' or '0'?
1661 {
1662 $this->errstr='no response';
1663 $resp=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl));
1664 curl_close($curl);
1665 if($keepalive)
1666 {
1667 $this->xmlrpc_curl_handle = null;
1668 }
1669 }
1670 else
1671 {
1672 if(!$keepalive)
1673 {
1674 curl_close($curl);
1675 }
1676 $resp =& $msg->parseResponse($result, true, $this->return_type);
1677 }
1678 return $resp;
1679 }
1680
1681 /**
1682 * Send an array of request messages and return an array of responses.
1683 * Unless $this->no_multicall has been set to true, it will try first
1684 * to use one single xmlrpc call to server method system.multicall, and
1685 * revert to sending many successive calls in case of failure.
1686 * This failure is also stored in $this->no_multicall for subsequent calls.
1687 * Unfortunately, there is no server error code universally used to denote
1688 * the fact that multicall is unsupported, so there is no way to reliably
1689 * distinguish between that and a temporary failure.
1690 * If you are sure that server supports multicall and do not want to
1691 * fallback to using many single calls, set the fourth parameter to FALSE.
1692 *
1693 * NB: trying to shoehorn extra functionality into existing syntax has resulted
1694 * in pretty much convoluted code...
1695 *
1696 * @param array $msgs an array of xmlrpcmsg objects
1697 * @param integer $timeout connection timeout (in seconds)
1698 * @param string $method the http protocol variant to be used
1699 * @param boolean fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted
1700 * @return array
1701 * @access public
1702 */
1703 function multicall($msgs, $timeout=0, $method='', $fallback=true)
1704 {
1705 if ($method == '')
1706 {
1707 $method = $this->method;
1708 }
1709 if(!$this->no_multicall)
1710 {
1711 $results = $this->_try_multicall($msgs, $timeout, $method);
1712 if(is_array($results))
1713 {
1714 // System.multicall succeeded
1715 return $results;
1716 }
1717 else
1718 {
1719 // either system.multicall is unsupported by server,
1720 // or call failed for some other reason.
1721 if ($fallback)
1722 {
1723 // Don't try it next time...
1724 $this->no_multicall = true;
1725 }
1726 else
1727 {
1728 if (is_a($results, 'xmlrpcresp'))
1729 {
1730 $result = $results;
1731 }
1732 else
1733 {
1734 $result =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']);
1735 }
1736 }
1737 }
1738 }
1739 else
1740 {
1741 // override fallback, in case careless user tries to do two
1742 // opposite things at the same time
1743 $fallback = true;
1744 }
1745
1746 $results = array();
1747 if ($fallback)
1748 {
1749 // system.multicall is (probably) unsupported by server:
1750 // emulate multicall via multiple requests
1751 foreach($msgs as $msg)
1752 {
1753 $results[] =& $this->send($msg, $timeout, $method);
1754 }
1755 }
1756 else
1757 {
1758 // user does NOT want to fallback on many single calls:
1759 // since we should always return an array of responses,
1760 // return an array with the same error repeated n times
1761 foreach($msgs as $msg)
1762 {
1763 $results[] = $result;
1764 }
1765 }
1766 return $results;
1767 }
1768
1769 /**
1770 * Attempt to boxcar $msgs via system.multicall.
1771 * Returns either an array of xmlrpcreponses, an xmlrpc error response
1772 * or false (when received response does not respect valid multicall syntax)
1773 * @access private
1774 */
1775 function _try_multicall($msgs, $timeout, $method)
1776 {
1777 // Construct multicall message
1778 $calls = array();
1779 foreach($msgs as $msg)
1780 {
1781 $call['methodName'] =& new xmlrpcval($msg->method(),'string');
1782 $numParams = $msg->getNumParams();
1783 $params = array();
1784 for($i = 0; $i < $numParams; $i++)
1785 {
1786 $params[$i] = $msg->getParam($i);
1787 }
1788 $call['params'] =& new xmlrpcval($params, 'array');
1789 $calls[] =& new xmlrpcval($call, 'struct');
1790 }
1791 $multicall =& new xmlrpcmsg('system.multicall');
1792 $multicall->addParam(new xmlrpcval($calls, 'array'));
1793
1794 // Attempt RPC call
1795 $result =& $this->send($multicall, $timeout, $method);
1796
1797 if($result->faultCode() != 0)
1798 {
1799 // call to system.multicall failed
1800 return $result;
1801 }
1802
1803 // Unpack responses.
1804 $rets = $result->value();
1805
1806 if ($this->return_type == 'xml')
1807 {
1808 return $rets;
1809 }
1810 else if ($this->return_type == 'phpvals')
1811 {
1812 ///@todo test this code branch...
1813 $rets = $result->value();
1814 if(!is_array($rets))
1815 {
1816 return false; // bad return type from system.multicall
1817 }
1818 $numRets = count($rets);
1819 if($numRets != count($msgs))
1820 {
1821 return false; // wrong number of return values.
1822 }
1823
1824 $response = array();
1825 for($i = 0; $i < $numRets; $i++)
1826 {
1827 $val = $rets[$i];
1828 if (!is_array($val)) {
1829 return false;
1830 }
1831 switch(count($val))
1832 {
1833 case 1:
1834 if(!isset($val[0]))
1835 {
1836 return false; // Bad value
1837 }
1838 // Normal return value
1839 $response[$i] =& new xmlrpcresp($val[0], 0, '', 'phpvals');
1840 break;
1841 case 2:
1842 /// @todo remove usage of @: it is apparently quite slow
1843 $code = @$val['faultCode'];
1844 if(!is_int($code))
1845 {
1846 return false;
1847 }
1848 $str = @$val['faultString'];
1849 if(!is_string($str))
1850 {
1851 return false;
1852 }
1853 $response[$i] =& new xmlrpcresp(0, $code, $str);
1854 break;
1855 default:
1856 return false;
1857 }
1858 }
1859 return $response;
1860 }
1861 else // return type == 'xmlrpcvals'
1862 {
1863 $rets = $result->value();
1864 if($rets->kindOf() != 'array')
1865 {
1866 return false; // bad return type from system.multicall
1867 }
1868 $numRets = $rets->arraysize();
1869 if($numRets != count($msgs))
1870 {
1871 return false; // wrong number of return values.
1872 }
1873
1874 $response = array();
1875 for($i = 0; $i < $numRets; $i++)
1876 {
1877 $val = $rets->arraymem($i);
1878 switch($val->kindOf())
1879 {
1880 case 'array':
1881 if($val->arraysize() != 1)
1882 {
1883 return false; // Bad value
1884 }
1885 // Normal return value
1886 $response[$i] =& new xmlrpcresp($val->arraymem(0));
1887 break;
1888 case 'struct':
1889 $code = $val->structmem('faultCode');
1890 if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int')
1891 {
1892 return false;
1893 }
1894 $str = $val->structmem('faultString');
1895 if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string')
1896 {
1897 return false;
1898 }
1899 $response[$i] =& new xmlrpcresp(0, $code->scalarval(), $str->scalarval());
1900 break;
1901 default:
1902 return false;
1903 }
1904 }
1905 return $response;
1906 }
1907 }
1908 } // end class xmlrpc_client
1909
1910 class xmlrpcresp
1911 {
1912 var $val = 0;
1913 var $valtyp;
1914 var $errno = 0;
1915 var $errstr = '';
1916 var $payload;
1917 var $hdrs = array();
1918 var $_cookies = array();
1919 var $content_type = 'text/xml';
1920 var $raw_data = '';
1921
1922 /**
1923 * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string)
1924 * @param integer $fcode set it to anything but 0 to create an error response
1925 * @param string $fstr the error string, in case of an error response
1926 * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml'
1927 *
1928 * @todo add check that $val / $fcode / $fstr is of correct type???
1929 * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain
1930 * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called...
1931 */
1932 function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='')
1933 {
1934 if($fcode != 0)
1935 {
1936 // error response
1937 $this->errno = $fcode;
1938 $this->errstr = $fstr;
1939 //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later.
1940 }
1941 else
1942 {
1943 // successful response
1944 $this->val = $val;
1945 if ($valtyp == '')
1946 {
1947 // user did not declare type of response value: try to guess it
1948 if (is_object($this->val) && is_a($this->val, 'xmlrpcval'))
1949 {
1950 $this->valtyp = 'xmlrpcvals';
1951 }
1952 else if (is_string($this->val))
1953 {
1954 $this->valtyp = 'xml';
1955
1956 }
1957 else
1958 {
1959 $this->valtyp = 'phpvals';
1960 }
1961 }
1962 else
1963 {
1964 // user declares type of resp value: believe him
1965 $this->valtyp = $valtyp;
1966 }
1967 }
1968 }
1969
1970 /**
1971 * Returns the error code of the response.
1972 * @return integer the error code of this response (0 for not-error responses)
1973 * @access public
1974 */
1975 function faultCode()
1976 {
1977 return $this->errno;
1978 }
1979
1980 /**
1981 * Returns the error code of the response.
1982 * @return string the error string of this response ('' for not-error responses)
1983 * @access public
1984 */
1985 function faultString()
1986 {
1987 return $this->errstr;
1988 }
1989
1990 /**
1991 * Returns the value received by the server.
1992 * @return mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects
1993 * @access public
1994 */
1995 function value()
1996 {
1997 return $this->val;
1998 }
1999
2000 /**
2001 * Returns an array with the cookies received from the server.
2002 * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...)
2003 * with attributes being e.g. 'expires', 'path', domain'.
2004 * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past)
2005 * are still present in the array. It is up to the user-defined code to decide
2006 * how to use the received cookies, and wheter they have to be sent back with the next
2007 * request to the server (using xmlrpc_client::setCookie) or not
2008 * @return array array of cookies received from the server
2009 * @access public
2010 */
2011 function cookies()
2012 {
2013 return $this->_cookies;
2014 }
2015
2016 /**
2017 * Returns xml representation of the response. XML prologue not included
2018 * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
2019 * @return string the xml representation of the response
2020 * @access public
2021 */
2022 function serialize($charset_encoding='')
2023 {
2024 if ($charset_encoding != '')
2025 $this->content_type = 'text/xml; charset=' . $charset_encoding;
2026 else
2027 $this->content_type = 'text/xml';
2028 $result = "<methodResponse>\n";
2029 if($this->errno)
2030 {
2031 // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients
2032 // by xml-encoding non ascii chars
2033 $result .= "<fault>\n" .
2034"<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
2035"</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
2036xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "</string></value>\n</member>\n" .
2037"</struct>\n</value>\n</fault>";
2038 }
2039 else
2040 {
2041 if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval'))
2042 {
2043 if (is_string($this->val) && $this->valtyp == 'xml')
2044 {
2045 $result .= "<params>\n<param>\n" .
2046 $this->val .
2047 "</param>\n</params>";
2048 }
2049 else
2050 {
2051 /// @todo try to build something serializable?
2052 die('cannot serialize xmlrpcresp objects whose content is native php values');
2053 }
2054 }
2055 else
2056 {
2057 $result .= "<params>\n<param>\n" .
2058 $this->val->serialize($charset_encoding) .
2059 "</param>\n</params>";
2060 }
2061 }
2062 $result .= "\n</methodResponse>";
2063 $this->payload = $result;
2064 return $result;
2065 }
2066 }
2067
2068 class xmlrpcmsg
2069 {
2070 var $payload;
2071 var $methodname;
2072 var $params=array();
2073 var $debug=0;
2074 var $content_type = 'text/xml';
2075
2076 /**
2077 * @param string $meth the name of the method to invoke
2078 * @param array $pars array of parameters to be paased to the method (xmlrpcval objects)
2079 */
2080 function xmlrpcmsg($meth, $pars=0)
2081 {
2082 $this->methodname=$meth;
2083 if(is_array($pars) && count($pars)>0)
2084 {
2085 for($i=0; $i<count($pars); $i++)
2086 {
2087 $this->addParam($pars[$i]);
2088 }
2089 }
2090 }
2091
2092 /**
2093 * @access private
2094 */
2095 function xml_header($charset_encoding='')
2096 {
2097 if ($charset_encoding != '')
2098 {
2099 return "<?xml version=\"1.0\" encoding=\"$charset_encoding\" ?" . ">\n<methodCall>\n";
2100 }
2101 else
2102 {
2103 return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
2104 }
2105 }
2106
2107 /**
2108 * @access private
2109 */
2110 function xml_footer()
2111 {
2112 return '</methodCall>';
2113 }
2114
2115 /**
2116 * @access private
2117 */
2118 function kindOf()
2119 {
2120 return 'msg';
2121 }
2122
2123 /**
2124 * @access private
2125 */
2126 function createPayload($charset_encoding='')
2127 {
2128 if ($charset_encoding != '')
2129 $this->content_type = 'text/xml; charset=' . $charset_encoding;
2130 else
2131 $this->content_type = 'text/xml';
2132 $this->payload=$this->xml_header($charset_encoding);
2133 $this->payload.='<methodName>' . $this->methodname . "</methodName>\n";
2134 $this->payload.="<params>\n";
2135 for($i=0; $i<count($this->params); $i++)
2136 {
2137 $p=$this->params[$i];
2138 $this->payload.="<param>\n" . $p->serialize($charset_encoding) .
2139 "</param>\n";
2140 }
2141 $this->payload.="</params>\n";
2142 $this->payload.=$this->xml_footer();
2143 }
2144
2145 /**
2146 * Gets/sets the xmlrpc method to be invoked
2147 * @param string $meth the method to be set (leave empty not to set it)
2148 * @return string the method that will be invoked
2149 * @access public
2150 */
2151 function method($meth='')
2152 {
2153 if($meth!='')
2154 {
2155 $this->methodname=$meth;
2156 }
2157 return $this->methodname;
2158 }
2159
2160 /**
2161 * Returns xml representation of the message. XML prologue included
2162 * @return string the xml representation of the message, xml prologue included
2163 * @access public
2164 */
2165 function serialize($charset_encoding='')
2166 {
2167 $this->createPayload($charset_encoding);
2168 return $this->payload;
2169 }
2170
2171 /**
2172 * Add a parameter to the list of parameters to be used upon method invocation
2173 * @param xmlrpcval $par
2174 * @return boolean false on failure
2175 * @access public
2176 */
2177 function addParam($par)
2178 {
2179 // add check: do not add to self params which are not xmlrpcvals
2180 if(is_object($par) && is_a($par, 'xmlrpcval'))
2181 {
2182 $this->params[]=$par;
2183 return true;
2184 }
2185 else
2186 {
2187 return false;
2188 }
2189 }
2190
2191 /**
2192 * Returns the nth parameter in the message. The index zero-based.
2193 * @param integer $i the index of the parameter to fetch (zero based)
2194 * @return xmlrpcval the i-th parameter
2195 * @access public
2196 */
2197 function getParam($i) { return $this->params[$i]; }
2198
2199 /**
2200 * Returns the number of parameters in the messge.
2201 * @return integer the number of parameters currently set
2202 * @access public
2203 */
2204 function getNumParams() { return count($this->params); }
2205
2206 /**
2207 * Given an open file handle, read all data available and parse it as axmlrpc response.
2208 * NB: the file handle is not closed by this function.
2209 * @access public
2210 * @return xmlrpcresp
2211 * @todo add 2nd & 3rd param to be passed to ParseResponse() ???
2212 */
2213 function &parseResponseFile($fp)
2214 {
2215 $ipd='';
2216 while($data=fread($fp, 32768))
2217 {
2218 $ipd.=$data;
2219 }
2220 //fclose($fp);
2221 $r =& $this->parseResponse($ipd);
2222 return $r;
2223 }
2224
2225 /**
2226 * Parses HTTP headers and separates them from data.
2227 * @access private
2228 */
2229 function &parseResponseHeaders(&$data, $headers_processed=false)
2230 {
2231 // Support "web-proxy-tunelling" connections for https through proxies
2232 if(preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data))
2233 {
2234 // Look for CR/LF or simple LF as line separator,
2235 // (even though it is not valid http)
2236 $pos = strpos($data,"\r\n\r\n");
2237 if($pos || is_int($pos))
2238 {
2239 $bd = $pos+4;
2240 }
2241 else
2242 {
2243 $pos = strpos($data,"\n\n");
2244 if($pos || is_int($pos))
2245 {
2246 $bd = $pos+2;
2247 }
2248 else
2249 {
2250 // No separation between response headers and body: fault?
2251 $bd = 0;
2252 }
2253 }
2254 if ($bd)
2255 {
2256 // this filters out all http headers from proxy.
2257 // maybe we could take them into account, too?
2258 $data = substr($data, $bd);
2259 }
2260 else
2261 {
2262 error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTPS via proxy error, tunnel connection possibly failed');
2263 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)');
2264 return $r;
2265 }
2266 }
2267
2268 // Strip HTTP 1.1 100 Continue header if present
2269 while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data))
2270 {
2271 $pos = strpos($data, 'HTTP', 12);
2272 // server sent a Continue header without any (valid) content following...
2273 // give the client a chance to know it
2274 if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
2275 {
2276 break;
2277 }
2278 $data = substr($data, $pos);
2279 }
2280 if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data))
2281 {
2282 $errstr= substr($data, 0, strpos($data, "\n")-1);
2283 error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .$errstr);
2284 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
2285 return $r;
2286 }
2287
2288 $GLOBALS['_xh']['headers'] = array();
2289 $GLOBALS['_xh']['cookies'] = array();
2290
2291 // be tolerant to usage of \n instead of \r\n to separate headers and data
2292 // (even though it is not valid http)
2293 $pos = strpos($data,"\r\n\r\n");
2294 if($pos || is_int($pos))
2295 {
2296 $bd = $pos+4;
2297 }
2298 else
2299 {
2300 $pos = strpos($data,"\n\n");
2301 if($pos || is_int($pos))
2302 {
2303 $bd = $pos+2;
2304 }
2305 else
2306 {
2307 // No separation between response headers and body: fault?
2308 // we could take some action here instead of going on...
2309 $bd = 0;
2310 }
2311 }
2312 // be tolerant to line endings, and extra empty lines
2313 $ar = split("\r?\n", trim(substr($data, 0, $pos)));
2314 while(list(,$line) = @each($ar))
2315 {
2316 // take care of multi-line headers and cookies
2317 $arr = explode(':',$line,2);
2318 if(count($arr) > 1)
2319 {
2320 $header_name = strtolower(trim($arr[0]));
2321 /// @todo some other headers (the ones that allow a CSV list of values)
2322 /// do allow many values to be passed using multiple header lines.
2323 /// We should add content to $GLOBALS['_xh']['headers'][$header_name]
2324 /// instead of replacing it for those...
2325 if ($header_name == 'set-cookie' || $header_name == 'set-cookie2')
2326 {
2327 if ($header_name == 'set-cookie2')
2328 {
2329 // version 2 cookies:
2330 // there could be many cookies on one line, comma separated
2331 $cookies = explode(',', $arr[1]);
2332 }
2333 else
2334 {
2335 $cookies = array($arr[1]);
2336 }
2337 foreach ($cookies as $cookie)
2338 {
2339 // glue together all received cookies, using a comma to separate them
2340 // (same as php does with getallheaders())
2341 if (isset($GLOBALS['_xh']['headers'][$header_name]))
2342 $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie);
2343 else
2344 $GLOBALS['_xh']['headers'][$header_name] = trim($cookie);
2345 // parse cookie attributes, in case user wants to correctly honour them
2346 // feature creep: only allow rfc-compliant cookie attributes?
2347 // @todo support for server sending multiple time cookie with same name, but using different PATHs
2348 $cookie = explode(';', $cookie);
2349 foreach ($cookie as $pos => $val)
2350 {
2351 $val = explode('=', $val, 2);
2352 $tag = trim($val[0]);
2353 $val = trim(@$val[1]);
2354 /// @todo with version 1 cookies, we should strip leading and trailing " chars
2355 if ($pos == 0)
2356 {
2357 $cookiename = $tag;
2358 $GLOBALS['_xh']['cookies'][$tag] = array();
2359 $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val);
2360 }
2361 else
2362 {
2363 if ($tag != 'value')
2364 {
2365 $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val;
2366 }
2367 }
2368 }
2369 }
2370 }
2371 else
2372 {
2373 $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]);
2374 }
2375 }
2376 elseif(isset($header_name))
2377 {
2378 /// @todo version1 cookies might span multiple lines, thus breaking the parsing above
2379 $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line);
2380 }
2381 }
2382
2383 $data = substr($data, $bd);
2384
2385 if($this->debug && count($GLOBALS['_xh']['headers']))
2386 {
2387 print '<PRE>';
2388 foreach($GLOBALS['_xh']['headers'] as $header => $value)
2389 {
2390 print htmlentities("HEADER: $header: $value\n");
2391 }
2392 foreach($GLOBALS['_xh']['cookies'] as $header => $value)
2393 {
2394 print htmlentities("COOKIE: $header={$value['value']}\n");
2395 }
2396 print "</PRE>\n";
2397 }
2398
2399 // if CURL was used for the call, http headers have been processed,
2400 // and dechunking + reinflating have been carried out
2401 if(!$headers_processed)
2402 {
2403 // Decode chunked encoding sent by http 1.1 servers
2404 if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
2405 {
2406 if(!$data = decode_chunked($data))
2407 {
2408 error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server');
2409 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
2410 return $r;
2411 }
2412 }
2413
2414 // Decode gzip-compressed stuff
2415 // code shamelessly inspired from nusoap library by Dietrich Ayala
2416 if(isset($GLOBALS['_xh']['headers']['content-encoding']))
2417 {
2418 $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']);
2419 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
2420 {
2421 // if decoding works, use it. else assume data wasn't gzencoded
2422 if(function_exists('gzinflate'))
2423 {
2424 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data))
2425 {
2426 $data = $degzdata;
2427 if($this->debug)
2428 print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2429 }
2430 elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
2431 {
2432 $data = $degzdata;
2433 if($this->debug)
2434 print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2435 }
2436 else
2437 {
2438 error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server');
2439 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
2440 return $r;
2441 }
2442 }
2443 else
2444 {
2445 error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
2446 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
2447 return $r;
2448 }
2449 }
2450 }
2451 } // end of 'if needed, de-chunk, re-inflate response'
2452
2453 // real stupid hack to avoid PHP 4 complaining about returning NULL by ref
2454 $r = null;
2455 $r =& $r;
2456 return $r;
2457 }
2458
2459 /**
2460 * Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object.
2461 * @param string $data the xmlrpc response, eventually including http headers
2462 * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding
2463 * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
2464 * @return xmlrpcresp
2465 * @access public
2466 */
2467 function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals')
2468 {
2469 if($this->debug)
2470 {
2471 //by maHo, replaced htmlspecialchars with htmlentities
2472 print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
2473 }
2474
2475 if($data == '')
2476 {
2477 error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.');
2478 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
2479 return $r;
2480 }
2481
2482 $GLOBALS['_xh']=array();
2483
2484 $raw_data = $data;
2485 // parse the HTTP headers of the response, if present, and separate them from data
2486 if(substr($data, 0, 4) == 'HTTP')
2487 {
2488 $r =& $this->parseResponseHeaders($data, $headers_processed);
2489 if ($r)
2490 {
2491 // failed processing of HTTP response headers
2492 // save into response obj the full payload received, for debugging
2493 $r->raw_data = $data;
2494 return $r;
2495 }
2496 }
2497 else
2498 {
2499 $GLOBALS['_xh']['headers'] = array();
2500 $GLOBALS['_xh']['cookies'] = array();
2501 }
2502
2503 if($this->debug)
2504 {
2505 $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2506 if ($start)
2507 {
2508 $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2509 $end = strpos($data, '-->', $start);
2510 $comments = substr($data, $start, $end-$start);
2511 print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n</PRE>";
2512 }
2513 }
2514
2515 // be tolerant of extra whitespace in response body
2516 $data = trim($data);
2517
2518 /// @todo return an error msg if $data=='' ?
2519
2520 // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
2521 // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib
2522 $bd = false;
2523 // Poor man's version of strrpos for php 4...
2524 $pos = strpos($data, '</methodResponse>');
2525 while($pos || is_int($pos))
2526 {
2527 $bd = $pos+17;
2528 $pos = strpos($data, '</methodResponse>', $bd);
2529 }
2530 if($bd)
2531 {
2532 $data = substr($data, 0, $bd);
2533 }
2534
2535 // if user wants back raw xml, give it to him
2536 if ($return_type == 'xml')
2537 {
2538 $r =& new xmlrpcresp($data, 0, '', 'xml');
2539 $r->hdrs = $GLOBALS['_xh']['headers'];
2540 $r->_cookies = $GLOBALS['_xh']['cookies'];
2541 $r->raw_data = $raw_data;
2542 return $r;
2543 }
2544
2545 // try to 'guestimate' the character encoding of the received response
2546 $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data);
2547
2548 $GLOBALS['_xh']['ac']='';
2549 //$GLOBALS['_xh']['qt']=''; //unused...
2550 $GLOBALS['_xh']['stack'] = array();
2551 $GLOBALS['_xh']['valuestack'] = array();
2552 $GLOBALS['_xh']['isf']=0; // 0 = OK, 1 for xmlrpc fault responses, 2 = invalid xmlrpc
2553 $GLOBALS['_xh']['isf_reason']='';
2554 $GLOBALS['_xh']['rt']=''; // 'methodcall or 'methodresponse'
2555
2556 // if response charset encoding is not known / supported, try to use
2557 // the default encoding and parse the xml anyway, but log a warning...
2558 if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2559 // the following code might be better for mb_string enabled installs, but
2560 // makes the lib about 200% slower...
2561 //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2562 {
2563 error_log('XML-RPC: xmlrpcmsg::parseResponse: invalid charset encoding of received response: '.$resp_encoding);
2564 $resp_encoding = $GLOBALS['xmlrpc_defencoding'];
2565 }
2566 $parser = xml_parser_create($resp_encoding);
2567 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
2568 // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
2569 // the xml parser to give us back data in the expected charset.
2570 // What if internal encoding is not in one of the 3 allowed?
2571 // we use the broadest one, ie. utf8
2572 // This allows to send data which is native in various charset,
2573 // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding
2574 if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2575 {
2576 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
2577 }
2578 else
2579 {
2580 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
2581 }
2582
2583 if ($return_type == 'phpvals')
2584 {
2585 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
2586 }
2587 else
2588 {
2589 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
2590 }
2591
2592 xml_set_character_data_handler($parser, 'xmlrpc_cd');
2593 xml_set_default_handler($parser, 'xmlrpc_dh');
2594
2595 // first error check: xml not well formed
2596 if(!xml_parse($parser, $data, count($data)))
2597 {
2598 // thanks to Peter Kocks <peter.kocks@baygate.com>
2599 if((xml_get_current_line_number($parser)) == 1)
2600 {
2601 $errstr = 'XML error at line 1, check URL';
2602 }
2603 else
2604 {
2605 $errstr = sprintf('XML error: %s at line %d, column %d',
2606 xml_error_string(xml_get_error_code($parser)),
2607 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
2608 }
2609 error_log($errstr);
2610 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
2611 xml_parser_free($parser);
2612 if($this->debug)
2613 {
2614 print $errstr;
2615 }
2616 $r->hdrs = $GLOBALS['_xh']['headers'];
2617 $r->_cookies = $GLOBALS['_xh']['cookies'];
2618 $r->raw_data = $raw_data;
2619 return $r;
2620 }
2621 xml_parser_free($parser);
2622 // second error check: xml well formed but not xml-rpc compliant
2623 if ($GLOBALS['_xh']['isf'] > 1)
2624 {
2625 if ($this->debug)
2626 {
2627 /// @todo echo something for user?
2628 }
2629
2630 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2631 $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
2632 }
2633 // third error check: parsing of the response has somehow gone boink.
2634 // NB: shall we omit this check, since we trust the parsing code?
2635 elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value']))
2636 {
2637 // something odd has happened
2638 // and it's time to generate a client side error
2639 // indicating something odd went on
2640 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2641 $GLOBALS['xmlrpcstr']['invalid_return']);
2642 }
2643 else
2644 {
2645 if ($this->debug)
2646 {
2647 print "<PRE>---PARSED---\n";
2648 // somehow htmlentities chokes on var_export, and some full html string...
2649 //print htmlentitites(var_export($GLOBALS['_xh']['value'], true));
2650 print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true));
2651 print "\n---END---</PRE>";
2652 }
2653
2654 // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object.
2655 $v =& $GLOBALS['_xh']['value'];
2656
2657 if($GLOBALS['_xh']['isf'])
2658 {
2659 /// @todo we should test here if server sent an int and a string,
2660 /// and/or coerce them into such...
2661 if ($return_type == 'xmlrpcvals')
2662 {
2663 $errno_v = $v->structmem('faultCode');
2664 $errstr_v = $v->structmem('faultString');
2665 $errno = $errno_v->scalarval();
2666 $errstr = $errstr_v->scalarval();
2667 }
2668 else
2669 {
2670 $errno = $v['faultCode'];
2671 $errstr = $v['faultString'];
2672 }
2673
2674 if($errno == 0)
2675 {
2676 // FAULT returned, errno needs to reflect that
2677 $errno = -1;
2678 }
2679
2680 $r =& new xmlrpcresp(0, $errno, $errstr);
2681 }
2682 else
2683 {
2684 $r=&new xmlrpcresp($v, 0, '', $return_type);
2685 }
2686 }
2687
2688 $r->hdrs = $GLOBALS['_xh']['headers'];
2689 $r->_cookies = $GLOBALS['_xh']['cookies'];
2690 $r->raw_data = $raw_data;
2691 return $r;
2692 }
2693 }
2694
2695 class xmlrpcval
2696 {
2697 var $me=array();
2698 var $mytype=0;
2699 var $_php_class=null;
2700
2701 /**
2702 * @param mixed $val
2703 * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed
2704 */
2705 function xmlrpcval($val=-1, $type='')
2706 {
2707 /// @todo: optimization creep - do not call addXX, do it all inline.
2708 /// downside: booleans will not be coerced anymore
2709 if($val!==-1 || $type!='')
2710 {
2711 // optimization creep: inlined all work done by constructor
2712 switch($type)
2713 {
2714 case '':
2715 $this->mytype=1;
2716 $this->me['string']=$val;
2717 break;
2718 case 'i4':
2719 case 'int':
2720 case 'double':
2721 case 'string':
2722 case 'boolean':
2723 case 'dateTime.iso8601':
2724 case 'base64':
2725 case 'null':
2726 $this->mytype=1;
2727 $this->me[$type]=$val;
2728 break;
2729 case 'array':
2730 $this->mytype=2;
2731 $this->me['array']=$val;
2732 break;
2733 case 'struct':
2734 $this->mytype=3;
2735 $this->me['struct']=$val;
2736 break;
2737 default:
2738 error_log("XML-RPC: xmlrpcval::xmlrpcval: not a known type ($type)");
2739 }
2740 /*if($type=='')
2741 {
2742 $type='string';
2743 }
2744 if($GLOBALS['xmlrpcTypes'][$type]==1)
2745 {
2746 $this->addScalar($val,$type);
2747 }
2748 elseif($GLOBALS['xmlrpcTypes'][$type]==2)
2749 {
2750 $this->addArray($val);
2751 }
2752 elseif($GLOBALS['xmlrpcTypes'][$type]==3)
2753 {
2754 $this->addStruct($val);
2755 }*/
2756 }
2757 }
2758
2759 /**
2760 * Add a single php value to an (unitialized) xmlrpcval
2761 * @param mixed $val
2762 * @param string $type
2763 * @return int 1 or 0 on failure
2764 */
2765 function addScalar($val, $type='string')
2766 {
2767 $typeof=@$GLOBALS['xmlrpcTypes'][$type];
2768 if($typeof!=1)
2769 {
2770 error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($type)");
2771 return 0;
2772 }
2773
2774 // coerce booleans into correct values
2775 // NB: we should iether do it for datetimes, integers and doubles, too,
2776 // or just plain remove this check, implemnted on booleans only...
2777 if($type==$GLOBALS['xmlrpcBoolean'])
2778 {
2779 if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
2780 {
2781 $val=true;
2782 }
2783 else
2784 {
2785 $val=false;
2786 }
2787 }
2788
2789 switch($this->mytype)
2790 {
2791 case 1:
2792 error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value');
2793 return 0;
2794 case 3:
2795 error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval');
2796 return 0;
2797 case 2:
2798 // we're adding a scalar value to an array here
2799 //$ar=$this->me['array'];
2800 //$ar[]=&new xmlrpcval($val, $type);
2801 //$this->me['array']=$ar;
2802 // Faster (?) avoid all the costly array-copy-by-val done here...
2803 $this->me['array'][]=&new xmlrpcval($val, $type);
2804 return 1;
2805 default:
2806 // a scalar, so set the value and remember we're scalar
2807 $this->me[$type]=$val;
2808 $this->mytype=$typeof;
2809 return 1;
2810 }
2811 }
2812
2813 /**
2814 * Add an array of xmlrpcval objects to an xmlrpcval
2815 * @param array $vals
2816 * @return int 1 or 0 on failure
2817 * @access public
2818 *
2819 * @todo add some checking for $vals to be an array of xmlrpcvals?
2820 */
2821 function addArray($vals)
2822 {
2823 if($this->mytype==0)
2824 {
2825 $this->mytype=$GLOBALS['xmlrpcTypes']['array'];
2826 $this->me['array']=$vals;
2827 return 1;
2828 }
2829 elseif($this->mytype==2)
2830 {
2831 // we're adding to an array here
2832 $this->me['array'] = array_merge($this->me['array'], $vals);
2833 return 1;
2834 }
2835 else
2836 {
2837 error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']');
2838 return 0;
2839 }
2840 }
2841
2842 /**
2843 * Add an array of named xmlrpcval objects to an xmlrpcval
2844 * @param array $vals
2845 * @return int 1 or 0 on failure
2846 * @access public
2847 *
2848 * @todo add some checking for $vals to be an array?
2849 */
2850 function addStruct($vals)
2851 {
2852 if($this->mytype==0)
2853 {
2854 $this->mytype=$GLOBALS['xmlrpcTypes']['struct'];
2855 $this->me['struct']=$vals;
2856 return 1;
2857 }
2858 elseif($this->mytype==3)
2859 {
2860 // we're adding to a struct here
2861 $this->me['struct'] = array_merge($this->me['struct'], $vals);
2862 return 1;
2863 }
2864 else
2865 {
2866 error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']');
2867 return 0;
2868 }
2869 }
2870
2871 // poor man's version of print_r ???
2872 // DEPRECATED!
2873 function dump($ar)
2874 {
2875 foreach($ar as $key => $val)
2876 {
2877 echo "$key => $val<br />";
2878 if($key == 'array')
2879 {
2880 while(list($key2, $val2) = each($val))
2881 {
2882 echo "-- $key2 => $val2<br />";
2883 }
2884 }
2885 }
2886 }
2887
2888 /**
2889 * Returns a string containing "struct", "array" or "scalar" describing the base type of the value
2890 * @return string
2891 * @access public
2892 */
2893 function kindOf()
2894 {
2895 switch($this->mytype)
2896 {
2897 case 3:
2898 return 'struct';
2899 break;
2900 case 2:
2901 return 'array';
2902 break;
2903 case 1:
2904 return 'scalar';
2905 break;
2906 default:
2907 return 'undef';
2908 }
2909 }
2910
2911 /**
2912 * @access private
2913 */
2914 function serializedata($typ, $val, $charset_encoding='')
2915 {
2916 $rs='';
2917 switch(@$GLOBALS['xmlrpcTypes'][$typ])
2918 {
2919 case 1:
2920 switch($typ)
2921 {
2922 case $GLOBALS['xmlrpcBase64']:
2923 $rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
2924 break;
2925 case $GLOBALS['xmlrpcBoolean']:
2926 $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
2927 break;
2928 case $GLOBALS['xmlrpcString']:
2929 // G. Giunta 2005/2/13: do NOT use htmlentities, since
2930 // it will produce named html entities, which are invalid xml
2931 $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "</${typ}>";
2932 break;
2933 case $GLOBALS['xmlrpcInt']:
2934 case $GLOBALS['xmlrpcI4']:
2935 $rs.="<${typ}>".(int)$val."</${typ}>";
2936 break;
2937 case $GLOBALS['xmlrpcDouble']:
2938 $rs.="<${typ}>".(double)$val."</${typ}>";
2939 break;
2940 case $GLOBALS['xmlrpcNull']:
2941 $rs.="<nil/>";
2942 break;
2943 default:
2944 // no standard type value should arrive here, but provide a possibility
2945 // for xmlrpcvals of unknown type...
2946 $rs.="<${typ}>${val}</${typ}>";
2947 }
2948 break;
2949 case 3:
2950 // struct
2951 if ($this->_php_class)
2952 {
2953 $rs.='<struct php_class="' . $this->_php_class . "\">\n";
2954 }
2955 else
2956 {
2957 $rs.="<struct>\n";
2958 }
2959 foreach($val as $key2 => $val2)
2960 {
2961 $rs.='<member><name>'.xmlrpc_encode_entitites($key2, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding)."</name>\n";
2962 //$rs.=$this->serializeval($val2);
2963 $rs.=$val2->serialize($charset_encoding);
2964 $rs.="</member>\n";
2965 }
2966 $rs.='</struct>';
2967 break;
2968 case 2:
2969 // array
2970 $rs.="<array>\n<data>\n";
2971 for($i=0; $i<count($val); $i++)
2972 {
2973 //$rs.=$this->serializeval($val[$i]);
2974 $rs.=$val[$i]->serialize($charset_encoding);
2975 }
2976 $rs.="</data>\n</array>";
2977 break;
2978 default:
2979 break;
2980 }
2981 return $rs;
2982 }
2983
2984 /**
2985 * Returns xml representation of the value. XML prologue not included
2986 * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
2987 * @return string
2988 * @access public
2989 */
2990 function serialize($charset_encoding='')
2991 {
2992 // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
2993 //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
2994 //{
2995 reset($this->me);
2996 list($typ, $val) = each($this->me);
2997 return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n";
2998 //}
2999 }
3000
3001 // DEPRECATED
3002 function serializeval($o)
3003 {
3004 // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
3005 //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
3006 //{
3007 $ar=$o->me;
3008 reset($ar);
3009 list($typ, $val) = each($ar);
3010 return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
3011 //}
3012 }
3013
3014 /**
3015 * Checks wheter a struct member with a given name is present.
3016 * Works only on xmlrpcvals of type struct.
3017 * @param string $m the name of the struct member to be looked up
3018 * @return boolean
3019 * @access public
3020 */
3021 function structmemexists($m)
3022 {
3023 return array_key_exists($m, $this->me['struct']);
3024 }
3025
3026 /**
3027 * Returns the value of a given struct member (an xmlrpcval object in itself).
3028 * Will raise a php warning if struct member of given name does not exist
3029 * @param string $m the name of the struct member to be looked up
3030 * @return xmlrpcval
3031 * @access public
3032 */
3033 function structmem($m)
3034 {
3035 return $this->me['struct'][$m];
3036 }
3037
3038 /**
3039 * Reset internal pointer for xmlrpcvals of type struct.
3040 * @access public
3041 */
3042 function structreset()
3043 {
3044 reset($this->me['struct']);
3045 }
3046
3047 /**
3048 * Return next member element for xmlrpcvals of type struct.
3049 * @return xmlrpcval
3050 * @access public
3051 */
3052 function structeach()
3053 {
3054 return each($this->me['struct']);
3055 }
3056
3057 // DEPRECATED! this code looks like it is very fragile and has not been fixed
3058 // for a long long time. Shall we remove it for 2.0?
3059 function getval()
3060 {
3061 // UNSTABLE
3062 reset($this->me);
3063 list($a,$b)=each($this->me);
3064 // contributed by I Sofer, 2001-03-24
3065 // add support for nested arrays to scalarval
3066 // i've created a new method here, so as to
3067 // preserve back compatibility
3068
3069 if(is_array($b))
3070 {
3071 @reset($b);
3072 while(list($id,$cont) = @each($b))
3073 {
3074 $b[$id] = $cont->scalarval();
3075 }
3076 }
3077
3078 // add support for structures directly encoding php objects
3079 if(is_object($b))
3080 {
3081 $t = get_object_vars($b);
3082 @reset($t);
3083 while(list($id,$cont) = @each($t))
3084 {
3085 $t[$id] = $cont->scalarval();
3086 }
3087 @reset($t);
3088 while(list($id,$cont) = @each($t))
3089 {
3090 @$b->$id = $cont;
3091 }
3092 }
3093 // end contrib
3094 return $b;
3095 }
3096
3097 /**
3098 * Returns the value of a scalar xmlrpcval
3099 * @return mixed
3100 * @access public
3101 */
3102 function scalarval()
3103 {
3104 reset($this->me);
3105 list(,$b)=each($this->me);
3106 return $b;
3107 }
3108
3109 /**
3110 * Returns the type of the xmlrpcval.
3111 * For integers, 'int' is always returned in place of 'i4'
3112 * @return string
3113 * @access public
3114 */
3115 function scalartyp()
3116 {
3117 reset($this->me);
3118 list($a,)=each($this->me);
3119 if($a==$GLOBALS['xmlrpcI4'])
3120 {
3121 $a=$GLOBALS['xmlrpcInt'];
3122 }
3123 return $a;
3124 }
3125
3126 /**
3127 * Returns the m-th member of an xmlrpcval of struct type
3128 * @param integer $m the index of the value to be retrieved (zero based)
3129 * @return xmlrpcval
3130 * @access public
3131 */
3132 function arraymem($m)
3133 {
3134 return $this->me['array'][$m];
3135 }
3136
3137 /**
3138 * Returns the number of members in an xmlrpcval of array type
3139 * @return integer
3140 * @access public
3141 */
3142 function arraysize()
3143 {
3144 return count($this->me['array']);
3145 }
3146
3147 /**
3148 * Returns the number of members in an xmlrpcval of struct type
3149 * @return integer
3150 * @access public
3151 */
3152 function structsize()
3153 {
3154 return count($this->me['struct']);
3155 }
3156 }
3157
3158
3159 // date helpers
3160
3161 /**
3162 * Given a timestamp, return the corresponding ISO8601 encoded string.
3163 *
3164 * Really, timezones ought to be supported
3165 * but the XML-RPC spec says:
3166 *
3167 * "Don't assume a timezone. It should be specified by the server in its
3168 * documentation what assumptions it makes about timezones."
3169 *
3170 * These routines always assume localtime unless
3171 * $utc is set to 1, in which case UTC is assumed
3172 * and an adjustment for locale is made when encoding
3173 *
3174 * @param int $timet (timestamp)
3175 * @param int $utc (0 or 1)
3176 * @return string
3177 */
3178 function iso8601_encode($timet, $utc=0)
3179 {
3180 if(!$utc)
3181 {
3182 $t=strftime("%Y%m%dT%H:%M:%S", $timet);
3183 }
3184 else
3185 {
3186 if(function_exists('gmstrftime'))
3187 {
3188 // gmstrftime doesn't exist in some versions
3189 // of PHP
3190 $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
3191 }
3192 else
3193 {
3194 $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
3195 }
3196 }
3197 return $t;
3198 }
3199
3200 /**
3201 * Given an ISO8601 date string, return a timet in the localtime, or UTC
3202 * @param string $idate
3203 * @param int $utc either 0 or 1
3204 * @return int (datetime)
3205 */
3206 function iso8601_decode($idate, $utc=0)
3207 {
3208 $t=0;
3209 if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs))
3210 {
3211 if($utc)
3212 {
3213 $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3214 }
3215 else
3216 {
3217 $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3218 }
3219 }
3220 return $t;
3221 }
3222
3223 /**
3224 * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types.
3225 *
3226 * Works with xmlrpc message objects as input, too.
3227 *
3228 * Given proper options parameter, can rebuild generic php object instances
3229 * (provided those have been encoded to xmlrpc format using a corresponding
3230 * option in php_xmlrpc_encode())
3231 * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
3232 * This means that the remote communication end can decide which php code will
3233 * get executed on your server, leaving the door possibly open to 'php-injection'
3234 * style of attacks (provided you have some classes defined on your server that
3235 * might wreak havoc if instances are built outside an appropriate context).
3236 * Make sure you trust the remote server/client before eanbling this!
3237 *
3238 * @author Dan Libby (dan@libby.com)
3239 *
3240 * @param xmlrpcval $xmlrpc_val
3241 * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects
3242 * @return mixed
3243 */
3244 function php_xmlrpc_decode($xmlrpc_val, $options=array())
3245 {
3246 switch($xmlrpc_val->kindOf())
3247 {
3248 case 'scalar':
3249 if (in_array('extension_api', $options))
3250 {
3251 reset($xmlrpc_val->me);
3252 list($typ,$val) = each($xmlrpc_val->me);
3253 switch ($typ)
3254 {
3255 case 'dateTime.iso8601':
3256 $xmlrpc_val->scalar = $val;
3257 $xmlrpc_val->xmlrpc_type = 'datetime';
3258 $xmlrpc_val->timestamp = iso8601_decode($val);
3259 return $xmlrpc_val;
3260 case 'base64':
3261 $xmlrpc_val->scalar = $val;
3262 $xmlrpc_val->type = $typ;
3263 return $xmlrpc_val;
3264 default:
3265 return $xmlrpc_val->scalarval();
3266 }
3267 }
3268 return $xmlrpc_val->scalarval();
3269 case 'array':
3270 $size = $xmlrpc_val->arraysize();
3271 $arr = array();
3272 for($i = 0; $i < $size; $i++)
3273 {
3274 $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
3275 }
3276 return $arr;
3277 case 'struct':
3278 $xmlrpc_val->structreset();
3279 // If user said so, try to rebuild php objects for specific struct vals.
3280 /// @todo should we raise a warning for class not found?
3281 // shall we check for proper subclass of xmlrpcval instead of
3282 // presence of _php_class to detect what we can do?
3283 if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
3284 && class_exists($xmlrpc_val->_php_class))
3285 {
3286 $obj = @new $xmlrpc_val->_php_class;
3287 while(list($key,$value)=$xmlrpc_val->structeach())
3288 {
3289 $obj->$key = php_xmlrpc_decode($value, $options);
3290 }
3291 return $obj;
3292 }
3293 else
3294 {
3295 $arr = array();
3296 while(list($key,$value)=$xmlrpc_val->structeach())
3297 {
3298 $arr[$key] = php_xmlrpc_decode($value, $options);
3299 }
3300 return $arr;
3301 }
3302 case 'msg':
3303 $paramcount = $xmlrpc_val->getNumParams();
3304 $arr = array();
3305 for($i = 0; $i < $paramcount; $i++)
3306 {
3307 $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
3308 }
3309 return $arr;
3310 }
3311 }
3312
3313 // This constant left here only for historical reasons...
3314 // it was used to decide if we have to define xmlrpc_encode on our own, but
3315 // we do not do it anymore
3316 if(function_exists('xmlrpc_decode'))
3317 {
3318 define('XMLRPC_EPI_ENABLED','1');
3319 }
3320 else
3321 {
3322 define('XMLRPC_EPI_ENABLED','0');
3323 }
3324
3325 /**
3326 * Takes native php types and encodes them into xmlrpc PHP object format.
3327 * It will not re-encode xmlrpcval objects.
3328 *
3329 * Feature creep -- could support more types via optional type argument
3330 * (string => datetime support has been added, ??? => base64 not yet)
3331 *
3332 * If given a proper options parameter, php object instances will be encoded
3333 * into 'special' xmlrpc values, that can later be decoded into php objects
3334 * by calling php_xmlrpc_decode() with a corresponding option
3335 *
3336 * @author Dan Libby (dan@libby.com)
3337 *
3338 * @param mixed $php_val the value to be converted into an xmlrpcval object
3339 * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
3340 * @return xmlrpcval
3341 */
3342 function &php_xmlrpc_encode($php_val, $options=array())
3343 {
3344 $type = gettype($php_val);
3345 switch($type)
3346 {
3347 case 'string':
3348 if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))
3349 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcDateTime']);
3350 else
3351 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcString']);
3352 break;
3353 case 'integer':
3354 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcInt']);
3355 break;
3356 case 'double':
3357 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcDouble']);
3358 break;
3359 // <G_Giunta_2001-02-29>
3360 // Add support for encoding/decoding of booleans, since they are supported in PHP
3361 case 'boolean':
3362 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcBoolean']);
3363 break;
3364 // </G_Giunta_2001-02-29>
3365 case 'array':
3366 // PHP arrays can be encoded to either xmlrpc structs or arrays,
3367 // depending on wheter they are hashes or plain 0..n integer indexed
3368 // A shorter one-liner would be
3369 // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
3370 // but execution time skyrockets!
3371 $j = 0;
3372 $arr = array();
3373 $ko = false;
3374 foreach($php_val as $key => $val)
3375 {
3376 $arr[$key] =& php_xmlrpc_encode($val, $options);
3377 if(!$ko && $key !== $j)
3378 {
3379 $ko = true;
3380 }
3381 $j++;
3382 }
3383 if($ko)
3384 {
3385 $xmlrpc_val =& new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3386 }
3387 else
3388 {
3389 $xmlrpc_val =& new xmlrpcval($arr, $GLOBALS['xmlrpcArray']);
3390 }
3391 break;
3392 case 'object':
3393 if(is_a($php_val, 'xmlrpcval'))
3394 {
3395 $xmlrpc_val = $php_val;
3396 }
3397 else
3398 {
3399 $arr = array();
3400 while(list($k,$v) = each($php_val))
3401 {
3402 $arr[$k] = php_xmlrpc_encode($v, $options);
3403 }
3404 $xmlrpc_val =& new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3405 if (in_array('encode_php_objs', $options))
3406 {
3407 // let's save original class name into xmlrpcval:
3408 // might be useful later on...
3409 $xmlrpc_val->_php_class = get_class($php_val);
3410 }
3411 }
3412 break;
3413 case 'NULL':
3414 if (in_array('extension_api', $options))
3415 {
3416 $xmlrpc_val =& new xmlrpcval('', $GLOBALS['xmlrpcString']);
3417 }
3418 if (in_array('null_extension', $options))
3419 {
3420 $xmlrpc_val =& new xmlrpcval('', $GLOBALS['xmlrpcNull']);
3421 }
3422 else
3423 {
3424 $xmlrpc_val =& new xmlrpcval();
3425 }
3426 break;
3427 case 'resource':
3428 if (in_array('extension_api', $options))
3429 {
3430 $xmlrpc_val =& new xmlrpcval((int)$php_val, $GLOBALS['xmlrpcInt']);
3431 }
3432 else
3433 {
3434 $xmlrpc_val =& new xmlrpcval();
3435 }
3436 // catch "user function", "unknown type"
3437 default:
3438 // giancarlo pinerolo <ping@alt.it>
3439 // it has to return
3440 // an empty object in case, not a boolean.
3441 $xmlrpc_val =& new xmlrpcval();
3442 break;
3443 }
3444 return $xmlrpc_val;
3445 }
3446
3447 /**
3448 * Convert the xml representation of a method response, method request or single
3449 * xmlrpc value into the appropriate object (a.k.a. deserialize)
3450 * @param string $xml_val
3451 * @param array $options
3452 * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp
3453 */
3454 function php_xmlrpc_decode_xml($xml_val, $options=array())
3455 {
3456 $GLOBALS['_xh'] = array();
3457 $GLOBALS['_xh']['ac'] = '';
3458 $GLOBALS['_xh']['stack'] = array();
3459 $GLOBALS['_xh']['valuestack'] = array();
3460 $GLOBALS['_xh']['params'] = array();
3461 $GLOBALS['_xh']['pt'] = array();
3462 $GLOBALS['_xh']['isf'] = 0;
3463 $GLOBALS['_xh']['isf_reason'] = '';
3464 $GLOBALS['_xh']['method'] = false;
3465 $GLOBALS['_xh']['rt'] = '';
3466 /// @todo 'guestimate' encoding
3467 $parser = xml_parser_create();
3468 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
3469 // What if internal encoding is not in one of the 3 allowed?
3470 // we use the broadest one, ie. utf8!
3471 if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
3472 {
3473 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
3474 }
3475 else
3476 {
3477 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
3478 }
3479 xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
3480 xml_set_character_data_handler($parser, 'xmlrpc_cd');
3481 xml_set_default_handler($parser, 'xmlrpc_dh');
3482 if(!xml_parse($parser, $xml_val, 1))
3483 {
3484 $errstr = sprintf('XML error: %s at line %d, column %d',
3485 xml_error_string(xml_get_error_code($parser)),
3486 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
3487 error_log($errstr);
3488 xml_parser_free($parser);
3489 return false;
3490 }
3491 xml_parser_free($parser);
3492 if ($GLOBALS['_xh']['isf'] > 1) // test that $GLOBALS['_xh']['value'] is an obj, too???
3493 {
3494 error_log($GLOBALS['_xh']['isf_reason']);
3495 return false;
3496 }
3497 switch ($GLOBALS['_xh']['rt'])
3498 {
3499 case 'methodresponse':
3500 $v =& $GLOBALS['_xh']['value'];
3501 if ($GLOBALS['_xh']['isf'] == 1)
3502 {
3503 $vc = $v->structmem('faultCode');
3504 $vs = $v->structmem('faultString');
3505 $r =& new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval());
3506 }
3507 else
3508 {
3509 $r =& new xmlrpcresp($v);
3510 }
3511 return $r;
3512 case 'methodcall':
3513 $m =& new xmlrpcmsg($GLOBALS['_xh']['method']);
3514 for($i=0; $i < count($GLOBALS['_xh']['params']); $i++)
3515 {
3516 $m->addParam($GLOBALS['_xh']['params'][$i]);
3517 }
3518 return $m;
3519 case 'value':
3520 return $GLOBALS['_xh']['value'];
3521 default:
3522 return false;
3523 }
3524 }
3525
3526 /**
3527 * decode a string that is encoded w/ "chunked" transfer encoding
3528 * as defined in rfc2068 par. 19.4.6
3529 * code shamelessly stolen from nusoap library by Dietrich Ayala
3530 *
3531 * @param string $buffer the string to be decoded
3532 * @return string
3533 */
3534 function decode_chunked($buffer)
3535 {
3536 // length := 0
3537 $length = 0;
3538 $new = '';
3539
3540 // read chunk-size, chunk-extension (if any) and crlf
3541 // get the position of the linebreak
3542 $chunkend = strpos($buffer,"\r\n") + 2;
3543 $temp = substr($buffer,0,$chunkend);
3544 $chunk_size = hexdec( trim($temp) );
3545 $chunkstart = $chunkend;
3546 while($chunk_size > 0)
3547 {
3548 $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
3549
3550 // just in case we got a broken connection
3551 if($chunkend == false)
3552 {
3553 $chunk = substr($buffer,$chunkstart);
3554 // append chunk-data to entity-body
3555 $new .= $chunk;
3556 $length += strlen($chunk);
3557 break;
3558 }
3559
3560 // read chunk-data and crlf
3561 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3562 // append chunk-data to entity-body
3563 $new .= $chunk;
3564 // length := length + chunk-size
3565 $length += strlen($chunk);
3566 // read chunk-size and crlf
3567 $chunkstart = $chunkend + 2;
3568
3569 $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
3570 if($chunkend == false)
3571 {
3572 break; //just in case we got a broken connection
3573 }
3574 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3575 $chunk_size = hexdec( trim($temp) );
3576 $chunkstart = $chunkend;
3577 }
3578 return $new;
3579 }
3580
3581 /**
3582 * xml charset encoding guessing helper function.
3583 * Tries to determine the charset encoding of an XML chunk
3584 * received over HTTP.
3585 * NB: according to the spec (RFC 3023, if text/xml content-type is received over HTTP without a content-type,
3586 * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
3587 * which will be most probably using UTF-8 anyway...
3588 *
3589 * @param string $httpheaders the http Content-type header
3590 * @param string $xmlchunk xml content buffer
3591 * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
3592 *
3593 * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
3594 */
3595 function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
3596 {
3597 // discussion: see http://www.yale.edu/pclt/encoding/
3598 // 1 - test if encoding is specified in HTTP HEADERS
3599
3600 //Details:
3601 // LWS: (\13\10)?( |\t)+
3602 // token: (any char but excluded stuff)+
3603 // header: Content-type = ...; charset=value(; ...)*
3604 // where value is of type token, no LWS allowed between 'charset' and value
3605 // Note: we do not check for invalid chars in VALUE:
3606 // this had better be done using pure ereg as below
3607
3608 /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
3609 $matches = array();
3610 if(preg_match('/;\s*charset=([^;]+)/i', $httpheader, $matches))
3611 {
3612 return strtoupper(trim($matches[1]));
3613 }
3614
3615 // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
3616 // (source: http://www.w3.org/TR/2000/REC-xml-20001006)
3617 // NOTE: actually, according to the spec, even if we find the BOM and determine
3618 // an encoding, we should check if there is an encoding specified
3619 // in the xml declaration, and verify if they match.
3620 /// @todo implement check as described above?
3621 /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
3622 if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))
3623 {
3624 return 'UCS-4';
3625 }
3626 elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))
3627 {
3628 return 'UTF-16';
3629 }
3630 elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))
3631 {
3632 return 'UTF-8';
3633 }
3634
3635 // 3 - test if encoding is specified in the xml declaration
3636 // Details:
3637 // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
3638 // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
3639 if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".
3640 '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
3641 $xmlchunk, $matches))
3642 {
3643 return strtoupper(substr($matches[2], 1, -1));
3644 }
3645
3646 // 4 - if mbstring is available, let it do the guesswork
3647 // NB: we favour finding an encoding that is compatible with what we can process
3648 if(extension_loaded('mbstring'))
3649 {
3650 if($encoding_prefs)
3651 {
3652 $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
3653 }
3654 else
3655 {
3656 $enc = mb_detect_encoding($xmlchunk);
3657 }
3658 // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
3659 // IANA also likes better US-ASCII, so go with it
3660 if($enc == 'ASCII')
3661 {
3662 $enc = 'US-'.$enc;
3663 }
3664 return $enc;
3665 }
3666 else
3667 {
3668 // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
3669 // Both RFC 2616 (HTTP 1.1) and 1945(http 1.0) clearly state that for text/xxx content types
3670 // this should be the standard. And we should be getting text/xml as request and response.
3671 // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
3672 return $GLOBALS['xmlrpc_defencoding'];
3673 }
3674 }
3675
3676 /**
3677 * Checks if a given charset encoding is present in a list of encodings or
3678 * if it is a valid subset of any encoding in the list
3679 * @param string $encoding charset to be tested
3680 * @param mixed $validlist comma separated list of valid charsets (or array of charsets)
3681 */
3682 function is_valid_charset($encoding, $validlist)
3683 {
3684 $charset_supersets = array(
3685 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
3686 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
3687 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
3688 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
3689 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
3690 );
3691 if (is_string($validlist))
3692 $validlist = explode(',', $validlist);
3693 if (@in_array(strtoupper($encoding), $validlist))
3694 return true;
3695 else
3696 {
3697 if (array_key_exists($encoding, $charset_supersets))
3698 foreach ($validlist as $allowed)
3699 if (in_array($allowed, $charset_supersets[$encoding]))
3700 return true;
3701 return false;
3702 }
3703 }
3704
3705?> \ No newline at end of file
diff --git a/central/trunk/includes/xmlrpc/xmlrpc_wrappers.inc b/central/trunk/includes/xmlrpc/xmlrpc_wrappers.inc new file mode 100644 index 0000000..cd0a56f --- /dev/null +++ b/central/trunk/includes/xmlrpc/xmlrpc_wrappers.inc
@@ -0,0 +1,819 @@
1<?php
2/**
3 * PHP-XMLRPC "wrapper" functions
4 * Generate stubs to transparently access xmlrpc methods as php functions and viceversa
5 *
6 * @version $Id: xmlrpc_wrappers.inc,v 1.12 2008/03/06 18:58:44 ggiunta Exp $
7 * @author Gaetano Giunta
8 * @copyright (C) 2006-2008 G. Giunta
9 * @license code licensed under the BSD License: http://phpxmlrpc.sourceforge.net/license.txt
10 *
11 * @todo separate introspection from code generation for func-2-method wrapping
12 * @todo use some better templating system from code generation?
13 * @todo implement method wrapping with preservation of php objs in calls
14 * @todo when wrapping methods without obj rebuilding, use return_type = 'phpvals' (faster)
15 * @todo implement self-parsing of php code for PHP <= 4
16 */
17
18 // requires: xmlrpc.inc
19
20 /**
21 * Given a string defining a php type or phpxmlrpc type (loosely defined: strings
22 * accepted come from javadoc blocks), return corresponding phpxmlrpc type.
23 * NB: for php 'resource' types returns empty string, since resources cannot be serialized;
24 * for php class names returns 'struct', since php objects can be serialized as xmlrpc structs
25 * @param string $phptype
26 * @return string
27 */
28 function php_2_xmlrpc_type($phptype)
29 {
30 switch(strtolower($phptype))
31 {
32 case 'string':
33 return $GLOBALS['xmlrpcString'];
34 case 'integer':
35 case $GLOBALS['xmlrpcInt']: // 'int'
36 case $GLOBALS['xmlrpcI4']:
37 return $GLOBALS['xmlrpcInt'];
38 case 'double':
39 return $GLOBALS['xmlrpcDouble'];
40 case 'boolean':
41 return $GLOBALS['xmlrpcBoolean'];
42 case 'array':
43 return $GLOBALS['xmlrpcArray'];
44 case 'object':
45 return $GLOBALS['xmlrpcStruct'];
46 case $GLOBALS['xmlrpcBase64']:
47 case $GLOBALS['xmlrpcStruct']:
48 return strtolower($phptype);
49 case 'resource':
50 return '';
51 default:
52 if(class_exists($phptype))
53 {
54 return $GLOBALS['xmlrpcStruct'];
55 }
56 else
57 {
58 // unknown: might be any 'extended' xmlrpc type
59 return $GLOBALS['xmlrpcValue'];
60 }
61 }
62 }
63
64 /**
65 * Given a string defining a phpxmlrpc type return corresponding php type.
66 * @param string $xmlrpctype
67 * @return string
68 */
69 function xmlrpc_2_php_type($xmlrpctype)
70 {
71 switch(strtolower($xmlrpctype))
72 {
73 case 'base64':
74 case 'datetime.iso8601':
75 case 'string':
76 return $GLOBALS['xmlrpcString'];
77 case 'int':
78 case 'i4':
79 return 'integer';
80 case 'struct':
81 case 'array':
82 return 'array';
83 case 'double':
84 return 'float';
85 case 'undefined':
86 return 'mixed';
87 case 'boolean':
88 case 'null':
89 default:
90 // unknown: might be any xmlrpc type
91 return strtolower($xmlrpctype);
92 }
93 }
94
95 /**
96 * Given a user-defined PHP function, create a PHP 'wrapper' function that can
97 * be exposed as xmlrpc method from an xmlrpc_server object and called from remote
98 * clients (as well as its corresponding signature info).
99 *
100 * Since php is a typeless language, to infer types of input and output parameters,
101 * it relies on parsing the javadoc-style comment block associated with the given
102 * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
103 * in the @param tag is also allowed, if you need the php function to receive/send
104 * data in that particular format (note that base64 encoding/decoding is transparently
105 * carried out by the lib, while datetime vals are passed around as strings)
106 *
107 * Known limitations:
108 * - requires PHP 5.0.3 +
109 * - only works for user-defined functions, not for PHP internal functions
110 * (reflection does not support retrieving number/type of params for those)
111 * - functions returning php objects will generate special xmlrpc responses:
112 * when the xmlrpc decoding of those responses is carried out by this same lib, using
113 * the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt.
114 * In short: php objects can be serialized, too (except for their resource members),
115 * using this function.
116 * Other libs might choke on the very same xml that will be generated in this case
117 * (i.e. it has a nonstandard attribute on struct element tags)
118 * - usage of javadoc @param tags using param names in a different order from the
119 * function prototype is not considered valid (to be fixed?)
120 *
121 * Note that since rel. 2.0RC3 the preferred method to have the server call 'standard'
122 * php functions (ie. functions not expecting a single xmlrpcmsg obj as parameter)
123 * is by making use of the functions_parameters_type class member.
124 *
125 * @param string $funcname the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') might be ok too, in the future...
126 * @param string $newfuncname (optional) name for function to be created
127 * @param array $extra_options (optional) array of options for conversion. valid values include:
128 * bool return_source when true, php code w. function definition will be returned, not evaluated
129 * bool encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects
130 * bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers ---
131 * bool suppress_warnings remove from produced xml any runtime warnings due to the php function being invoked
132 * @return false on error, or an array containing the name of the new php function,
133 * its signature and docs, to be used in the server dispatch map
134 *
135 * @todo decide how to deal with params passed by ref: bomb out or allow?
136 * @todo finish using javadoc info to build method sig if all params are named but out of order
137 * @todo add a check for params of 'resource' type
138 * @todo add some trigger_errors / error_log when returning false?
139 * @todo what to do when the PHP function returns NULL? we are currently an empty string value...
140 * @todo add an option to suppress php warnings in invocation of user function, similar to server debug level 3?
141 */
142 function wrap_php_function($funcname, $newfuncname='', $extra_options=array())
143 {
144 $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
145 $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
146 $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
147 $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
148 $catch_warnings = isset($extra_options['suppress_warnings']) && $extra_options['suppress_warnings'] ? '@' : '';
149
150 if(version_compare(phpversion(), '5.0.3') == -1)
151 {
152 // up to php 5.0.3 some useful reflection methods were missing
153 error_log('XML-RPC: cannot not wrap php functions unless running php version bigger than 5.0.3');
154 return false;
155 }
156 if((is_array($funcname) && !method_exists($funcname[0], $funcname[1])) || !function_exists($funcname))
157 {
158 error_log('XML-RPC: function to be wrapped is not defined: '.$funcname);
159 return false;
160 }
161 else
162 {
163 // determine name of new php function
164 if($newfuncname == '')
165 {
166 if(is_array($funcname))
167 {
168 $xmlrpcfuncname = "{$prefix}_".implode('_', $funcname);
169 }
170 else
171 {
172 $xmlrpcfuncname = "{$prefix}_$funcname";
173 }
174 }
175 else
176 {
177 $xmlrpcfuncname = $newfuncname;
178 }
179 while($buildit && function_exists($xmlrpcfuncname))
180 {
181 $xmlrpcfuncname .= 'x';
182 }
183
184 // start to introspect PHP code
185 $func =& new ReflectionFunction($funcname);
186 if($func->isInternal())
187 {
188 // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
189 // instead of getparameters to fully reflect internal php functions ?
190 error_log('XML-RPC: function to be wrapped is internal: '.$funcname);
191 return false;
192 }
193
194 // retrieve parameter names, types and description from javadoc comments
195
196 // function description
197 $desc = '';
198 // type of return val: by default 'any'
199 $returns = $GLOBALS['xmlrpcValue'];
200 // desc of return val
201 $returnsDocs = '';
202 // type + name of function parameters
203 $paramDocs = array();
204
205 $docs = $func->getDocComment();
206 if($docs != '')
207 {
208 $docs = explode("\n", $docs);
209 $i = 0;
210 foreach($docs as $doc)
211 {
212 $doc = trim($doc, " \r\t/*");
213 if(strlen($doc) && strpos($doc, '@') !== 0 && !$i)
214 {
215 if($desc)
216 {
217 $desc .= "\n";
218 }
219 $desc .= $doc;
220 }
221 elseif(strpos($doc, '@param') === 0)
222 {
223 // syntax: @param type [$name] desc
224 if(preg_match('/@param\s+(\S+)(\s+\$\S+)?\s+(.+)/', $doc, $matches))
225 {
226 if(strpos($matches[1], '|'))
227 {
228 //$paramDocs[$i]['type'] = explode('|', $matches[1]);
229 $paramDocs[$i]['type'] = 'mixed';
230 }
231 else
232 {
233 $paramDocs[$i]['type'] = $matches[1];
234 }
235 $paramDocs[$i]['name'] = trim($matches[2]);
236 $paramDocs[$i]['doc'] = $matches[3];
237 }
238 $i++;
239 }
240 elseif(strpos($doc, '@return') === 0)
241 {
242 // syntax: @return type desc
243 //$returns = preg_split('/\s+/', $doc);
244 if(preg_match('/@return\s+(\S+)\s+(.+)/', $doc, $matches))
245 {
246 $returns = php_2_xmlrpc_type($matches[1]);
247 if(isset($matches[2]))
248 {
249 $returnsDocs = $matches[2];
250 }
251 }
252 }
253 }
254 }
255
256 // execute introspection of actual function prototype
257 $params = array();
258 $i = 0;
259 foreach($func->getParameters() as $paramobj)
260 {
261 $params[$i] = array();
262 $params[$i]['name'] = '$'.$paramobj->getName();
263 $params[$i]['isoptional'] = $paramobj->isOptional();
264 $i++;
265 }
266
267
268 // start building of PHP code to be eval'd
269 $innercode = '';
270 $i = 0;
271 $parsvariations = array();
272 $pars = array();
273 $pnum = count($params);
274 foreach($params as $param)
275 {
276 if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != strtolower($param['name']))
277 {
278 // param name from phpdoc info does not match param definition!
279 $paramDocs[$i]['type'] = 'mixed';
280 }
281
282 if($param['isoptional'])
283 {
284 // this particular parameter is optional. save as valid previous list of parameters
285 $innercode .= "if (\$paramcount > $i) {\n";
286 $parsvariations[] = $pars;
287 }
288 $innercode .= "\$p$i = \$msg->getParam($i);\n";
289 if ($decode_php_objects)
290 {
291 $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i, array('decode_php_objs'));\n";
292 }
293 else
294 {
295 $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i);\n";
296 }
297
298 $pars[] = "\$p$i";
299 $i++;
300 if($param['isoptional'])
301 {
302 $innercode .= "}\n";
303 }
304 if($i == $pnum)
305 {
306 // last allowed parameters combination
307 $parsvariations[] = $pars;
308 }
309 }
310
311 $sigs = array();
312 $psigs = array();
313 if(count($parsvariations) == 0)
314 {
315 // only known good synopsis = no parameters
316 $parsvariations[] = array();
317 $minpars = 0;
318 }
319 else
320 {
321 $minpars = count($parsvariations[0]);
322 }
323
324 if($minpars)
325 {
326 // add to code the check for min params number
327 // NB: this check needs to be done BEFORE decoding param values
328 $innercode = "\$paramcount = \$msg->getNumParams();\n" .
329 "if (\$paramcount < $minpars) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode;
330 }
331 else
332 {
333 $innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode;
334 }
335
336 $innercode .= "\$np = false;\n";
337 foreach($parsvariations as $pars)
338 {
339 $innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = {$catch_warnings}$funcname(" . implode(',', $pars) . "); else\n";
340 // build a 'generic' signature (only use an appropriate return type)
341 $sig = array($returns);
342 $psig = array($returnsDocs);
343 for($i=0; $i < count($pars); $i++)
344 {
345 if (isset($paramDocs[$i]['type']))
346 {
347 $sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']);
348 }
349 else
350 {
351 $sig[] = $GLOBALS['xmlrpcValue'];
352 }
353 $psig[] = isset($paramDocs[$i]['doc']) ? $paramDocs[$i]['doc'] : '';
354 }
355 $sigs[] = $sig;
356 $psigs[] = $psig;
357 }
358 $innercode .= "\$np = true;\n";
359 $innercode .= "if (\$np) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else {\n";
360 //$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n";
361 $innercode .= "if (is_a(\$retval, '{$prefix}resp')) return \$retval; else\n";
362 if($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64'])
363 {
364 $innercode .= "return new {$prefix}resp(new {$prefix}val(\$retval, '$returns'));";
365 }
366 else
367 {
368 if ($encode_php_objects)
369 $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval, array('encode_php_objs')));\n";
370 else
371 $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval));\n";
372 }
373 // shall we exclude functions returning by ref?
374 // if($func->returnsReference())
375 // return false;
376 $code = "function $xmlrpcfuncname(\$msg) {\n" . $innercode . "}\n}";
377 //print_r($code);
378 if ($buildit)
379 {
380 $allOK = 0;
381 eval($code.'$allOK=1;');
382 // alternative
383 //$xmlrpcfuncname = create_function('$m', $innercode);
384
385 if(!$allOK)
386 {
387 error_log('XML-RPC: could not create function '.$xmlrpcfuncname.' to wrap php function '.$funcname);
388 return false;
389 }
390 }
391
392 /// @todo examine if $paramDocs matches $parsvariations and build array for
393 /// usage as method signature, plus put together a nice string for docs
394
395 $ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc, 'signature_docs' => $psigs, 'source' => $code);
396 return $ret;
397 }
398 }
399
400 /**
401 * Given an xmlrpc client and a method name, register a php wrapper function
402 * that will call it and return results using native php types for both
403 * params and results. The generated php function will return an xmlrpcresp
404 * oject for failed xmlrpc calls
405 *
406 * Known limitations:
407 * - server must support system.methodsignature for the wanted xmlrpc method
408 * - for methods that expose many signatures, only one can be picked (we
409 * could in priciple check if signatures differ only by number of params
410 * and not by type, but it would be more complication than we can spare time)
411 * - nested xmlrpc params: the caller of the generated php function has to
412 * encode on its own the params passed to the php function if these are structs
413 * or arrays whose (sub)members include values of type datetime or base64
414 *
415 * Notes: the connection properties of the given client will be copied
416 * and reused for the connection used during the call to the generated
417 * php function.
418 * Calling the generated php function 'might' be slow: a new xmlrpc client
419 * is created on every invocation and an xmlrpc-connection opened+closed.
420 * An extra 'debug' param is appended to param list of xmlrpc method, useful
421 * for debugging purposes.
422 *
423 * @param xmlrpc_client $client an xmlrpc client set up correctly to communicate with target server
424 * @param string $methodname the xmlrpc method to be mapped to a php function
425 * @param array $extra_options array of options that specify conversion details. valid ptions include
426 * integer signum the index of the method signature to use in mapping (if method exposes many sigs)
427 * integer timeout timeout (in secs) to be used when executing function/calling remote method
428 * string protocol 'http' (default), 'http11' or 'https'
429 * string new_function_name the name of php function to create. If unsepcified, lib will pick an appropriate name
430 * string return_source if true return php code w. function definition instead fo function name
431 * bool encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects
432 * bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers ---
433 * mixed return_on_fault a php value to be returned when the xmlrpc call fails/returns a fault response (by default the xmlrpcresp object is returned in this case). If a string is used, '%faultCode%' and '%faultString%' tokens will be substituted with actual error values
434 * bool debug set it to 1 or 2 to see debug results of querying server for method synopsis
435 * @return string the name of the generated php function (or false) - OR AN ARRAY...
436 */
437 function wrap_xmlrpc_method($client, $methodname, $extra_options=0, $timeout=0, $protocol='', $newfuncname='')
438 {
439 // mind numbing: let caller use sane calling convention (as per javadoc, 3 params),
440 // OR the 2.0 calling convention (no ptions) - we really love backward compat, don't we?
441 if (!is_array($extra_options))
442 {
443 $signum = $extra_options;
444 $extra_options = array();
445 }
446 else
447 {
448 $signum = isset($extra_options['signum']) ? (int)$extra_options['signum'] : 0;
449 $timeout = isset($extra_options['timeout']) ? (int)$extra_options['timeout'] : 0;
450 $protocol = isset($extra_options['protocol']) ? $extra_options['protocol'] : '';
451 $newfuncname = isset($extra_options['new_function_name']) ? $extra_options['new_function_name'] : '';
452 }
453 //$encode_php_objects = in_array('encode_php_objects', $extra_options);
454 //$verbatim_client_copy = in_array('simple_client_copy', $extra_options) ? 1 :
455 // in_array('build_class_code', $extra_options) ? 2 : 0;
456
457 $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
458 $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
459 $simple_client_copy = isset($extra_options['simple_client_copy']) ? (int)($extra_options['simple_client_copy']) : 0;
460 $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
461 $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
462 if (isset($extra_options['return_on_fault']))
463 {
464 $decode_fault = true;
465 $fault_response = $extra_options['return_on_fault'];
466 }
467 else
468 {
469 $decode_fault = false;
470 $fault_response = '';
471 }
472 $debug = isset($extra_options['debug']) ? ($extra_options['debug']) : 0;
473
474 $msgclass = $prefix.'msg';
475 $valclass = $prefix.'val';
476 $decodefunc = 'php_'.$prefix.'_decode';
477
478 $msg =& new $msgclass('system.methodSignature');
479 $msg->addparam(new $valclass($methodname));
480 $client->setDebug($debug);
481 $response =& $client->send($msg, $timeout, $protocol);
482 if($response->faultCode())
483 {
484 error_log('XML-RPC: could not retrieve method signature from remote server for method '.$methodname);
485 return false;
486 }
487 else
488 {
489 $msig = $response->value();
490 if ($client->return_type != 'phpvals')
491 {
492 $msig = $decodefunc($msig);
493 }
494 if(!is_array($msig) || count($msig) <= $signum)
495 {
496 error_log('XML-RPC: could not retrieve method signature nr.'.$signum.' from remote server for method '.$methodname);
497 return false;
498 }
499 else
500 {
501 // pick a suitable name for the new function, avoiding collisions
502 if($newfuncname != '')
503 {
504 $xmlrpcfuncname = $newfuncname;
505 }
506 else
507 {
508 // take care to insure that methodname is translated to valid
509 // php function name
510 $xmlrpcfuncname = $prefix.'_'.preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
511 array('_', ''), $methodname);
512 }
513 while($buildit && function_exists($xmlrpcfuncname))
514 {
515 $xmlrpcfuncname .= 'x';
516 }
517
518 $msig = $msig[$signum];
519 $mdesc = '';
520 // if in 'offline' mode, get method description too.
521 // in online mode, favour speed of operation
522 if(!$buildit)
523 {
524 $msg =& new $msgclass('system.methodHelp');
525 $msg->addparam(new $valclass($methodname));
526 $response =& $client->send($msg, $timeout, $protocol);
527 if (!$response->faultCode())
528 {
529 $mdesc = $response->value();
530 if ($client->return_type != 'phpvals')
531 {
532 $mdesc = $mdesc->scalarval();
533 }
534 }
535 }
536
537 $results = build_remote_method_wrapper_code($client, $methodname,
538 $xmlrpcfuncname, $msig, $mdesc, $timeout, $protocol, $simple_client_copy,
539 $prefix, $decode_php_objects, $encode_php_objects, $decode_fault,
540 $fault_response);
541
542 //print_r($code);
543 if ($buildit)
544 {
545 $allOK = 0;
546 eval($results['source'].'$allOK=1;');
547 // alternative
548 //$xmlrpcfuncname = create_function('$m', $innercode);
549 if($allOK)
550 {
551 return $xmlrpcfuncname;
552 }
553 else
554 {
555 error_log('XML-RPC: could not create function '.$xmlrpcfuncname.' to wrap remote method '.$methodname);
556 return false;
557 }
558 }
559 else
560 {
561 $results['function'] = $xmlrpcfuncname;
562 return $results;
563 }
564 }
565 }
566 }
567
568 /**
569 * Similar to wrap_xmlrpc_method, but will generate a php class that wraps
570 * all xmlrpc methods exposed by the remote server as own methods.
571 * For more details see wrap_xmlrpc_method.
572 * @param xmlrpc_client $client the client obj all set to query the desired server
573 * @param array $extra_options list of options for wrapped code
574 * @return mixed false on error, the name of the created class if all ok or an array with code, class name and comments (if the appropriatevoption is set in extra_options)
575 */
576 function wrap_xmlrpc_server($client, $extra_options=array())
577 {
578 $methodfilter = isset($extra_options['method_filter']) ? $extra_options['method_filter'] : '';
579 $signum = isset($extra_options['signum']) ? (int)$extra_options['signum'] : 0;
580 $timeout = isset($extra_options['timeout']) ? (int)$extra_options['timeout'] : 0;
581 $protocol = isset($extra_options['protocol']) ? $extra_options['protocol'] : '';
582 $newclassname = isset($extra_options['new_class_name']) ? $extra_options['new_class_name'] : '';
583 $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
584 $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
585 $verbatim_client_copy = isset($extra_options['simple_client_copy']) ? !($extra_options['simple_client_copy']) : true;
586 $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
587 $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
588
589 $msgclass = $prefix.'msg';
590 //$valclass = $prefix.'val';
591 $decodefunc = 'php_'.$prefix.'_decode';
592
593 $msg =& new $msgclass('system.listMethods');
594 $response =& $client->send($msg, $timeout, $protocol);
595 if($response->faultCode())
596 {
597 error_log('XML-RPC: could not retrieve method list from remote server');
598 return false;
599 }
600 else
601 {
602 $mlist = $response->value();
603 if ($client->return_type != 'phpvals')
604 {
605 $mlist = $decodefunc($mlist);
606 }
607 if(!is_array($mlist) || !count($mlist))
608 {
609 error_log('XML-RPC: could not retrieve meaningful method list from remote server');
610 return false;
611 }
612 else
613 {
614 // pick a suitable name for the new function, avoiding collisions
615 if($newclassname != '')
616 {
617 $xmlrpcclassname = $newclassname;
618 }
619 else
620 {
621 $xmlrpcclassname = $prefix.'_'.preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
622 array('_', ''), $client->server).'_client';
623 }
624 while($buildit && class_exists($xmlrpcclassname))
625 {
626 $xmlrpcclassname .= 'x';
627 }
628
629 /// @todo add function setdebug() to new class, to enable/disable debugging
630 $source = "class $xmlrpcclassname\n{\nvar \$client;\n\n";
631 $source .= "function $xmlrpcclassname()\n{\n";
632 $source .= build_client_wrapper_code($client, $verbatim_client_copy, $prefix);
633 $source .= "\$this->client =& \$client;\n}\n\n";
634 $opts = array('simple_client_copy' => 2, 'return_source' => true,
635 'timeout' => $timeout, 'protocol' => $protocol,
636 'encode_php_objs' => $encode_php_objects, 'prefix' => $prefix,
637 'decode_php_objs' => $decode_php_objects
638 );
639 /// @todo build javadoc for class definition, too
640 foreach($mlist as $mname)
641 {
642 if ($methodfilter == '' || preg_match($methodfilter, $mname))
643 {
644 $opts['new_function_name'] = preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
645 array('_', ''), $mname);
646 $methodwrap = wrap_xmlrpc_method($client, $mname, $opts);
647 if ($methodwrap)
648 {
649 if (!$buildit)
650 {
651 $source .= $methodwrap['docstring'];
652 }
653 $source .= $methodwrap['source']."\n";
654 }
655 else
656 {
657 error_log('XML-RPC: will not create class method to wrap remote method '.$mname);
658 }
659 }
660 }
661 $source .= "}\n";
662 if ($buildit)
663 {
664 $allOK = 0;
665 eval($source.'$allOK=1;');
666 // alternative
667 //$xmlrpcfuncname = create_function('$m', $innercode);
668 if($allOK)
669 {
670 return $xmlrpcclassname;
671 }
672 else
673 {
674 error_log('XML-RPC: could not create class '.$xmlrpcclassname.' to wrap remote server '.$client->server);
675 return false;
676 }
677 }
678 else
679 {
680 return array('class' => $xmlrpcclassname, 'code' => $source, 'docstring' => '');
681 }
682 }
683 }
684 }
685
686 /**
687 * Given the necessary info, build php code that creates a new function to
688 * invoke a remote xmlrpc method.
689 * Take care that no full checking of input parameters is done to ensure that
690 * valid php code is emitted.
691 * Note: real spaghetti code follows...
692 * @access private
693 */
694 function build_remote_method_wrapper_code($client, $methodname, $xmlrpcfuncname,
695 $msig, $mdesc='', $timeout=0, $protocol='', $client_copy_mode=0, $prefix='xmlrpc',
696 $decode_php_objects=false, $encode_php_objects=false, $decode_fault=false,
697 $fault_response='')
698 {
699 $code = "function $xmlrpcfuncname (";
700 if ($client_copy_mode < 2)
701 {
702 // client copy mode 0 or 1 == partial / full client copy in emitted code
703 $innercode = build_client_wrapper_code($client, $client_copy_mode, $prefix);
704 $innercode .= "\$client->setDebug(\$debug);\n";
705 $this_ = '';
706 }
707 else
708 {
709 // client copy mode 2 == no client copy in emitted code
710 $innercode = '';
711 $this_ = 'this->';
712 }
713 $innercode .= "\$msg =& new {$prefix}msg('$methodname');\n";
714
715 if ($mdesc != '')
716 {
717 // take care that PHP comment is not terminated unwillingly by method description
718 $mdesc = "/**\n* ".str_replace('*/', '* /', $mdesc)."\n";
719 }
720 else
721 {
722 $mdesc = "/**\nFunction $xmlrpcfuncname\n";
723 }
724
725 // param parsing
726 $plist = array();
727 $pcount = count($msig);
728 for($i = 1; $i < $pcount; $i++)
729 {
730 $plist[] = "\$p$i";
731 $ptype = $msig[$i];
732 if($ptype == 'i4' || $ptype == 'int' || $ptype == 'boolean' || $ptype == 'double' ||
733 $ptype == 'string' || $ptype == 'dateTime.iso8601' || $ptype == 'base64' || $ptype == 'null')
734 {
735 // only build directly xmlrpcvals when type is known and scalar
736 $innercode .= "\$p$i =& new {$prefix}val(\$p$i, '$ptype');\n";
737 }
738 else
739 {
740 if ($encode_php_objects)
741 {
742 $innercode .= "\$p$i =& php_{$prefix}_encode(\$p$i, array('encode_php_objs'));\n";
743 }
744 else
745 {
746 $innercode .= "\$p$i =& php_{$prefix}_encode(\$p$i);\n";
747 }
748 }
749 $innercode .= "\$msg->addparam(\$p$i);\n";
750 $mdesc .= '* @param '.xmlrpc_2_php_type($ptype)." \$p$i\n";
751 }
752 if ($client_copy_mode < 2)
753 {
754 $plist[] = '$debug=0';
755 $mdesc .= "* @param int \$debug when 1 (or 2) will enable debugging of the underlying {$prefix} call (defaults to 0)\n";
756 }
757 $plist = implode(', ', $plist);
758 $mdesc .= '* @return '.xmlrpc_2_php_type($msig[0])." (or an {$prefix}resp obj instance if call fails)\n*/\n";
759
760 $innercode .= "\$res =& \${$this_}client->send(\$msg, $timeout, '$protocol');\n";
761 if ($decode_fault)
762 {
763 if (is_string($fault_response) && ((strpos($fault_response, '%faultCode%') !== false) || (strpos($fault_response, '%faultString%') !== false)))
764 {
765 $respcode = "str_replace(array('%faultCode%', '%faultString%'), array(\$res->faultCode(), \$res->faultString()), '".str_replace("'", "''", $fault_response)."')";
766 }
767 else
768 {
769 $respcode = var_export($fault_response, true);
770 }
771 }
772 else
773 {
774 $respcode = '$res';
775 }
776 if ($decode_php_objects)
777 {
778 $innercode .= "if (\$res->faultcode()) return $respcode; else return php_{$prefix}_decode(\$res->value(), array('decode_php_objs'));";
779 }
780 else
781 {
782 $innercode .= "if (\$res->faultcode()) return $respcode; else return php_{$prefix}_decode(\$res->value());";
783 }
784
785 $code = $code . $plist. ") {\n" . $innercode . "\n}\n";
786
787 return array('source' => $code, 'docstring' => $mdesc);
788 }
789
790 /**
791 * Given necessary info, generate php code that will rebuild a client object
792 * Take care that no full checking of input parameters is done to ensure that
793 * valid php code is emitted.
794 * @access private
795 */
796 function build_client_wrapper_code($client, $verbatim_client_copy, $prefix='xmlrpc')
797 {
798 $code = "\$client =& new {$prefix}_client('".str_replace("'", "\'", $client->path).
799 "', '" . str_replace("'", "\'", $client->server) . "', $client->port);\n";
800
801 // copy all client fields to the client that will be generated runtime
802 // (this provides for future expansion or subclassing of client obj)
803 if ($verbatim_client_copy)
804 {
805 foreach($client as $fld => $val)
806 {
807 if($fld != 'debug' && $fld != 'return_type')
808 {
809 $val = var_export($val, true);
810 $code .= "\$client->$fld = $val;\n";
811 }
812 }
813 }
814 // only make sure that client always returns the correct data type
815 $code .= "\$client->return_type = '{$prefix}vals';\n";
816 //$code .= "\$client->setDebug(\$debug);\n";
817 return $code;
818 }
819?> \ No newline at end of file
diff --git a/central/trunk/includes/xmlrpc/xmlrpcs.inc b/central/trunk/includes/xmlrpc/xmlrpcs.inc new file mode 100644 index 0000000..9588919 --- /dev/null +++ b/central/trunk/includes/xmlrpc/xmlrpcs.inc
@@ -0,0 +1,1193 @@
1<?php
2// by Edd Dumbill (C) 1999-2002
3// <edd@usefulinc.com>
4// $Id: xmlrpcs.inc,v 1.69 2007/09/20 20:14:25 ggiunta Exp $
5
6// Copyright (c) 1999,2000,2002 Edd Dumbill.
7// All rights reserved.
8//
9// Redistribution and use in source and binary forms, with or without
10// modification, are permitted provided that the following conditions
11// are met:
12//
13// * Redistributions of source code must retain the above copyright
14// notice, this list of conditions and the following disclaimer.
15//
16// * Redistributions in binary form must reproduce the above
17// copyright notice, this list of conditions and the following
18// disclaimer in the documentation and/or other materials provided
19// with the distribution.
20//
21// * Neither the name of the "XML-RPC for PHP" nor the names of its
22// contributors may be used to endorse or promote products derived
23// from this software without specific prior written permission.
24//
25// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36// OF THE POSSIBILITY OF SUCH DAMAGE.
37
38 // XML RPC Server class
39 // requires: xmlrpc.inc
40
41 $GLOBALS['xmlrpcs_capabilities'] = array(
42 // xmlrpc spec: always supported
43 'xmlrpc' => new xmlrpcval(array(
44 'specUrl' => new xmlrpcval('http://www.xmlrpc.com/spec', 'string'),
45 'specVersion' => new xmlrpcval(1, 'int')
46 ), 'struct'),
47 // if we support system.xxx functions, we always support multicall, too...
48 // Note that, as of 2006/09/17, the following URL does not respond anymore
49 'system.multicall' => new xmlrpcval(array(
50 'specUrl' => new xmlrpcval('http://www.xmlrpc.com/discuss/msgReader$1208', 'string'),
51 'specVersion' => new xmlrpcval(1, 'int')
52 ), 'struct'),
53 // introspection: version 2! we support 'mixed', too
54 'introspection' => new xmlrpcval(array(
55 'specUrl' => new xmlrpcval('http://phpxmlrpc.sourceforge.net/doc-2/ch10.html', 'string'),
56 'specVersion' => new xmlrpcval(2, 'int')
57 ), 'struct')
58 );
59
60 /* Functions that implement system.XXX methods of xmlrpc servers */
61 $_xmlrpcs_getCapabilities_sig=array(array($GLOBALS['xmlrpcStruct']));
62 $_xmlrpcs_getCapabilities_doc='This method lists all the capabilites that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to';
63 $_xmlrpcs_getCapabilities_sdoc=array(array('list of capabilities, described as structs with a version number and url for the spec'));
64 function _xmlrpcs_getCapabilities($server, $m=null)
65 {
66 $outAr = $GLOBALS['xmlrpcs_capabilities'];
67 // NIL extension
68 if ($GLOBALS['xmlrpc_null_extension']) {
69 $outAr['nil'] = new xmlrpcval(array(
70 'specUrl' => new xmlrpcval('http://www.ontosys.com/xml-rpc/extensions.php', 'string'),
71 'specVersion' => new xmlrpcval(1, 'int')
72 ), 'struct');
73 }
74 return new xmlrpcresp(new xmlrpcval($outAr, 'struct'));
75 }
76
77 // listMethods: signature was either a string, or nothing.
78 // The useless string variant has been removed
79 $_xmlrpcs_listMethods_sig=array(array($GLOBALS['xmlrpcArray']));
80 $_xmlrpcs_listMethods_doc='This method lists all the methods that the XML-RPC server knows how to dispatch';
81 $_xmlrpcs_listMethods_sdoc=array(array('list of method names'));
82 function _xmlrpcs_listMethods($server, $m=null) // if called in plain php values mode, second param is missing
83 {
84
85 $outAr=array();
86 foreach($server->dmap as $key => $val)
87 {
88 $outAr[]=&new xmlrpcval($key, 'string');
89 }
90 if($server->allow_system_funcs)
91 {
92 foreach($GLOBALS['_xmlrpcs_dmap'] as $key => $val)
93 {
94 $outAr[]=&new xmlrpcval($key, 'string');
95 }
96 }
97 return new xmlrpcresp(new xmlrpcval($outAr, 'array'));
98 }
99
100 $_xmlrpcs_methodSignature_sig=array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcString']));
101 $_xmlrpcs_methodSignature_doc='Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)';
102 $_xmlrpcs_methodSignature_sdoc=array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described'));
103 function _xmlrpcs_methodSignature($server, $m)
104 {
105 // let accept as parameter both an xmlrpcval or string
106 if (is_object($m))
107 {
108 $methName=$m->getParam(0);
109 $methName=$methName->scalarval();
110 }
111 else
112 {
113 $methName=$m;
114 }
115 if(strpos($methName, "system.") === 0)
116 {
117 $dmap=$GLOBALS['_xmlrpcs_dmap']; $sysCall=1;
118 }
119 else
120 {
121 $dmap=$server->dmap; $sysCall=0;
122 }
123 if(isset($dmap[$methName]))
124 {
125 if(isset($dmap[$methName]['signature']))
126 {
127 $sigs=array();
128 foreach($dmap[$methName]['signature'] as $inSig)
129 {
130 $cursig=array();
131 foreach($inSig as $sig)
132 {
133 $cursig[]=&new xmlrpcval($sig, 'string');
134 }
135 $sigs[]=&new xmlrpcval($cursig, 'array');
136 }
137 $r=&new xmlrpcresp(new xmlrpcval($sigs, 'array'));
138 }
139 else
140 {
141 // NB: according to the official docs, we should be returning a
142 // "none-array" here, which means not-an-array
143 $r=&new xmlrpcresp(new xmlrpcval('undef', 'string'));
144 }
145 }
146 else
147 {
148 $r=&new xmlrpcresp(0,$GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']);
149 }
150 return $r;
151 }
152
153 $_xmlrpcs_methodHelp_sig=array(array($GLOBALS['xmlrpcString'], $GLOBALS['xmlrpcString']));
154 $_xmlrpcs_methodHelp_doc='Returns help text if defined for the method passed, otherwise returns an empty string';
155 $_xmlrpcs_methodHelp_sdoc=array(array('method description', 'name of the method to be described'));
156 function _xmlrpcs_methodHelp($server, $m)
157 {
158 // let accept as parameter both an xmlrpcval or string
159 if (is_object($m))
160 {
161 $methName=$m->getParam(0);
162 $methName=$methName->scalarval();
163 }
164 else
165 {
166 $methName=$m;
167 }
168 if(strpos($methName, "system.") === 0)
169 {
170 $dmap=$GLOBALS['_xmlrpcs_dmap']; $sysCall=1;
171 }
172 else
173 {
174 $dmap=$server->dmap; $sysCall=0;
175 }
176 if(isset($dmap[$methName]))
177 {
178 if(isset($dmap[$methName]['docstring']))
179 {
180 $r=&new xmlrpcresp(new xmlrpcval($dmap[$methName]['docstring']), 'string');
181 }
182 else
183 {
184 $r=&new xmlrpcresp(new xmlrpcval('', 'string'));
185 }
186 }
187 else
188 {
189 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']);
190 }
191 return $r;
192 }
193
194 $_xmlrpcs_multicall_sig = array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcArray']));
195 $_xmlrpcs_multicall_doc = 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details';
196 $_xmlrpcs_multicall_sdoc = array(array('list of response structs, where each struct has the usual members', 'list of calls, with each call being represented as a struct, with members "methodname" and "params"'));
197 function _xmlrpcs_multicall_error($err)
198 {
199 if(is_string($err))
200 {
201 $str = $GLOBALS['xmlrpcstr']["multicall_${err}"];
202 $code = $GLOBALS['xmlrpcerr']["multicall_${err}"];
203 }
204 else
205 {
206 $code = $err->faultCode();
207 $str = $err->faultString();
208 }
209 $struct = array();
210 $struct['faultCode'] =& new xmlrpcval($code, 'int');
211 $struct['faultString'] =& new xmlrpcval($str, 'string');
212 return new xmlrpcval($struct, 'struct');
213 }
214
215 function _xmlrpcs_multicall_do_call($server, $call)
216 {
217 if($call->kindOf() != 'struct')
218 {
219 return _xmlrpcs_multicall_error('notstruct');
220 }
221 $methName = @$call->structmem('methodName');
222 if(!$methName)
223 {
224 return _xmlrpcs_multicall_error('nomethod');
225 }
226 if($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string')
227 {
228 return _xmlrpcs_multicall_error('notstring');
229 }
230 if($methName->scalarval() == 'system.multicall')
231 {
232 return _xmlrpcs_multicall_error('recursion');
233 }
234
235 $params = @$call->structmem('params');
236 if(!$params)
237 {
238 return _xmlrpcs_multicall_error('noparams');
239 }
240 if($params->kindOf() != 'array')
241 {
242 return _xmlrpcs_multicall_error('notarray');
243 }
244 $numParams = $params->arraysize();
245
246 $msg =& new xmlrpcmsg($methName->scalarval());
247 for($i = 0; $i < $numParams; $i++)
248 {
249 if(!$msg->addParam($params->arraymem($i)))
250 {
251 $i++;
252 return _xmlrpcs_multicall_error(new xmlrpcresp(0,
253 $GLOBALS['xmlrpcerr']['incorrect_params'],
254 $GLOBALS['xmlrpcstr']['incorrect_params'] . ": probable xml error in param " . $i));
255 }
256 }
257
258 $result = $server->execute($msg);
259
260 if($result->faultCode() != 0)
261 {
262 return _xmlrpcs_multicall_error($result); // Method returned fault.
263 }
264
265 return new xmlrpcval(array($result->value()), 'array');
266 }
267
268 function _xmlrpcs_multicall_do_call_phpvals($server, $call)
269 {
270 if(!is_array($call))
271 {
272 return _xmlrpcs_multicall_error('notstruct');
273 }
274 if(!array_key_exists('methodName', $call))
275 {
276 return _xmlrpcs_multicall_error('nomethod');
277 }
278 if (!is_string($call['methodName']))
279 {
280 return _xmlrpcs_multicall_error('notstring');
281 }
282 if($call['methodName'] == 'system.multicall')
283 {
284 return _xmlrpcs_multicall_error('recursion');
285 }
286 if(!array_key_exists('params', $call))
287 {
288 return _xmlrpcs_multicall_error('noparams');
289 }
290 if(!is_array($call['params']))
291 {
292 return _xmlrpcs_multicall_error('notarray');
293 }
294
295 // this is a real dirty and simplistic hack, since we might have received a
296 // base64 or datetime values, but they will be listed as strings here...
297 $numParams = count($call['params']);
298 $pt = array();
299 foreach($call['params'] as $val)
300 $pt[] = php_2_xmlrpc_type(gettype($val));
301
302 $result = $server->execute($call['methodName'], $call['params'], $pt);
303
304 if($result->faultCode() != 0)
305 {
306 return _xmlrpcs_multicall_error($result); // Method returned fault.
307 }
308
309 return new xmlrpcval(array($result->value()), 'array');
310 }
311
312 function _xmlrpcs_multicall($server, $m)
313 {
314 $result = array();
315 // let accept a plain list of php parameters, beside a single xmlrpc msg object
316 if (is_object($m))
317 {
318 $calls = $m->getParam(0);
319 $numCalls = $calls->arraysize();
320 for($i = 0; $i < $numCalls; $i++)
321 {
322 $call = $calls->arraymem($i);
323 $result[$i] = _xmlrpcs_multicall_do_call($server, $call);
324 }
325 }
326 else
327 {
328 $numCalls=count($m);
329 for($i = 0; $i < $numCalls; $i++)
330 {
331 $result[$i] = _xmlrpcs_multicall_do_call_phpvals($server, $m[$i]);
332 }
333 }
334
335 return new xmlrpcresp(new xmlrpcval($result, 'array'));
336 }
337
338 $GLOBALS['_xmlrpcs_dmap']=array(
339 'system.listMethods' => array(
340 'function' => '_xmlrpcs_listMethods',
341 'signature' => $_xmlrpcs_listMethods_sig,
342 'docstring' => $_xmlrpcs_listMethods_doc,
343 'signature_docs' => $_xmlrpcs_listMethods_sdoc),
344 'system.methodHelp' => array(
345 'function' => '_xmlrpcs_methodHelp',
346 'signature' => $_xmlrpcs_methodHelp_sig,
347 'docstring' => $_xmlrpcs_methodHelp_doc,
348 'signature_docs' => $_xmlrpcs_methodHelp_sdoc),
349 'system.methodSignature' => array(
350 'function' => '_xmlrpcs_methodSignature',
351 'signature' => $_xmlrpcs_methodSignature_sig,
352 'docstring' => $_xmlrpcs_methodSignature_doc,
353 'signature_docs' => $_xmlrpcs_methodSignature_sdoc),
354 'system.multicall' => array(
355 'function' => '_xmlrpcs_multicall',
356 'signature' => $_xmlrpcs_multicall_sig,
357 'docstring' => $_xmlrpcs_multicall_doc,
358 'signature_docs' => $_xmlrpcs_multicall_sdoc),
359 'system.getCapabilities' => array(
360 'function' => '_xmlrpcs_getCapabilities',
361 'signature' => $_xmlrpcs_getCapabilities_sig,
362 'docstring' => $_xmlrpcs_getCapabilities_doc,
363 'signature_docs' => $_xmlrpcs_getCapabilities_sdoc)
364 );
365
366 $GLOBALS['_xmlrpcs_occurred_errors'] = '';
367 $GLOBALS['_xmlrpcs_prev_ehandler'] = '';
368 /**
369 * Error handler used to track errors that occur during server-side execution of PHP code.
370 * This allows to report back to the client whether an internal error has occurred or not
371 * using an xmlrpc response object, instead of letting the client deal with the html junk
372 * that a PHP execution error on the server generally entails.
373 *
374 * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
375 *
376 */
377 function _xmlrpcs_errorHandler($errcode, $errstring, $filename=null, $lineno=null, $context=null)
378 {
379 // obey the @ protocol
380 if (error_reporting() == 0)
381 return;
382
383 //if($errcode != E_NOTICE && $errcode != E_WARNING && $errcode != E_USER_NOTICE && $errcode != E_USER_WARNING)
384 if($errcode != 2048) // do not use E_STRICT by name, since on PHP 4 it will not be defined
385 {
386 $GLOBALS['_xmlrpcs_occurred_errors'] = $GLOBALS['_xmlrpcs_occurred_errors'] . $errstring . "\n";
387 }
388 // Try to avoid as much as possible disruption to the previous error handling
389 // mechanism in place
390 if($GLOBALS['_xmlrpcs_prev_ehandler'] == '')
391 {
392 // The previous error handler was the default: all we should do is log error
393 // to the default error log (if level high enough)
394 if(ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errcode))
395 {
396 error_log($errstring);
397 }
398 }
399 else
400 {
401 // Pass control on to previous error handler, trying to avoid loops...
402 if($GLOBALS['_xmlrpcs_prev_ehandler'] != '_xmlrpcs_errorHandler')
403 {
404 // NB: this code will NOT work on php < 4.0.2: only 2 params were used for error handlers
405 if(is_array($GLOBALS['_xmlrpcs_prev_ehandler']))
406 {
407 // the following works both with static class methods and plain object methods as error handler
408 call_user_func_array($GLOBALS['_xmlrpcs_prev_ehandler'], array($errcode, $errstring, $filename, $lineno, $context));
409 }
410 else
411 {
412 $GLOBALS['_xmlrpcs_prev_ehandler']($errcode, $errstring, $filename, $lineno, $context);
413 }
414 }
415 }
416 }
417
418 $GLOBALS['_xmlrpc_debuginfo']='';
419
420 /**
421 * Add a string to the debug info that can be later seralized by the server
422 * as part of the response message.
423 * Note that for best compatbility, the debug string should be encoded using
424 * the $GLOBALS['xmlrpc_internalencoding'] character set.
425 * @param string $m
426 * @access public
427 */
428 function xmlrpc_debugmsg($m)
429 {
430 $GLOBALS['_xmlrpc_debuginfo'] .= $m . "\n";
431 }
432
433 class xmlrpc_server
434 {
435 /// array defining php functions exposed as xmlrpc methods by this server
436 var $dmap=array();
437 /**
438 * Defines how functions in dmap will be invokde: either using an xmlrpc msg object
439 * or plain php values.
440 * valid strings are 'xmlrpcvals', 'phpvals' or 'epivals'
441 */
442 var $functions_parameters_type='xmlrpcvals';
443 /// controls wether the server is going to echo debugging messages back to the client as comments in response body. valid values: 0,1,2,3
444 var $debug = 1;
445 /**
446 * When set to true, it will enable HTTP compression of the response, in case
447 * the client has declared its support for compression in the request.
448 */
449 var $compress_response = false;
450 /**
451 * List of http compression methods accepted by the server for requests.
452 * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
453 */
454 var $accepted_compression = array();
455 /// shall we serve calls to system.* methods?
456 var $allow_system_funcs = true;
457 /// list of charset encodings natively accepted for requests
458 var $accepted_charset_encodings = array();
459 /**
460 * charset encoding to be used for response.
461 * NB: if we can, we will convert the generated response from internal_encoding to the intended one.
462 * can be: a supported xml encoding (only UTF-8 and ISO-8859-1 at present, unless mbstring is enabled),
463 * null (leave unspecified in response, convert output stream to US_ASCII),
464 * 'default' (use xmlrpc library default as specified in xmlrpc.inc, convert output stream if needed),
465 * or 'auto' (use client-specified charset encoding or same as request if request headers do not specify it (unless request is US-ASCII: then use library default anyway).
466 * NB: pretty dangerous if you accept every charset and do not have mbstring enabled)
467 */
468 var $response_charset_encoding = '';
469 /// storage for internal debug info
470 var $debug_info = '';
471 /// extra data passed at runtime to method handling functions. Used only by EPI layer
472 var $user_data = null;
473
474 /**
475 * @param array $dispmap the dispatch map withd efinition of exposed services
476 * @param boolean $servicenow set to false to prevent the server from runnung upon construction
477 */
478 function xmlrpc_server($dispMap=null, $serviceNow=true)
479 {
480 // if ZLIB is enabled, let the server by default accept compressed requests,
481 // and compress responses sent to clients that support them
482 if(function_exists('gzinflate'))
483 {
484 $this->accepted_compression = array('gzip', 'deflate');
485 $this->compress_response = true;
486 }
487
488 // by default the xml parser can support these 3 charset encodings
489 $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
490
491 // dispMap is a dispatch array of methods
492 // mapped to function names and signatures
493 // if a method
494 // doesn't appear in the map then an unknown
495 // method error is generated
496 /* milosch - changed to make passing dispMap optional.
497 * instead, you can use the class add_to_map() function
498 * to add functions manually (borrowed from SOAPX4)
499 */
500 if($dispMap)
501 {
502 $this->dmap = $dispMap;
503 if($serviceNow)
504 {
505 $this->service();
506 }
507 }
508 }
509
510 /**
511 * Set debug level of server.
512 * @param integer $in debug lvl: determines info added to xmlrpc responses (as xml comments)
513 * 0 = no debug info,
514 * 1 = msgs set from user with debugmsg(),
515 * 2 = add complete xmlrpc request (headers and body),
516 * 3 = add also all processing warnings happened during method processing
517 * (NB: this involves setting a custom error handler, and might interfere
518 * with the standard processing of the php function exposed as method. In
519 * particular, triggering an USER_ERROR level error will not halt script
520 * execution anymore, but just end up logged in the xmlrpc response)
521 * Note that info added at elevel 2 and 3 will be base64 encoded
522 * @access public
523 */
524 function setDebug($in)
525 {
526 $this->debug=$in;
527 }
528
529 /**
530 * Return a string with the serialized representation of all debug info
531 * @param string $charset_encoding the target charset encoding for the serialization
532 * @return string an XML comment (or two)
533 */
534 function serializeDebug($charset_encoding='')
535 {
536 // Tough encoding problem: which internal charset should we assume for debug info?
537 // It might contain a copy of raw data received from client, ie with unknown encoding,
538 // intermixed with php generated data and user generated data...
539 // so we split it: system debug is base 64 encoded,
540 // user debug info should be encoded by the end user using the INTERNAL_ENCODING
541 $out = '';
542 if ($this->debug_info != '')
543 {
544 $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n".base64_encode($this->debug_info)."\n-->\n";
545 }
546 if($GLOBALS['_xmlrpc_debuginfo']!='')
547 {
548
549 $out .= "<!-- DEBUG INFO:\n" . xmlrpc_encode_entitites(str_replace('--', '_-', $GLOBALS['_xmlrpc_debuginfo']), $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "\n-->\n";
550 // NB: a better solution MIGHT be to use CDATA, but we need to insert it
551 // into return payload AFTER the beginning tag
552 //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', $GLOBALS['_xmlrpc_debuginfo']) . "\n]]>\n";
553 }
554 return $out;
555 }
556
557 /**
558 * Execute the xmlrpc request, printing the response
559 * @param string $data the request body. If null, the http POST request will be examined
560 * @return xmlrpcresp the response object (usually not used by caller...)
561 * @access public
562 */
563 function service($data=null, $return_payload=false)
564 {
565 if ($data === null)
566 {
567 // workaround for a known bug in php ver. 5.2.2 that broke $HTTP_RAW_POST_DATA
568 $ver = phpversion();
569 if ($ver[0] >= 5)
570 {
571 $data = file_get_contents('php://input');
572 }
573 else
574 {
575 $data = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';
576 }
577 }
578 $raw_data = $data;
579
580 // reset internal debug info
581 $this->debug_info = '';
582
583 // Echo back what we received, before parsing it
584 if($this->debug > 1)
585 {
586 $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++");
587 }
588
589 $r = $this->parseRequestHeaders($data, $req_charset, $resp_charset, $resp_encoding);
590 if (!$r)
591 {
592 $r=$this->parseRequest($data, $req_charset);
593 }
594
595 // save full body of request into response, for more debugging usages
596 $r->raw_data = $raw_data;
597
598 if($this->debug > 2 && $GLOBALS['_xmlrpcs_occurred_errors'])
599 {
600 $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
601 $GLOBALS['_xmlrpcs_occurred_errors'] . "+++END+++");
602 }
603
604 $payload=$this->xml_header($resp_charset);
605 if($this->debug > 0)
606 {
607 $payload = $payload . $this->serializeDebug($resp_charset);
608 }
609
610 // G. Giunta 2006-01-27: do not create response serialization if it has
611 // already happened. Helps building json magic
612 if (empty($r->payload))
613 {
614 $r->serialize($resp_charset);
615 }
616 $payload = $payload . $r->payload;
617
618 if ($return_payload)
619 {
620 return $payload;
621 }
622
623 // if we get a warning/error that has output some text before here, then we cannot
624 // add a new header. We cannot say we are sending xml, either...
625 if(!headers_sent())
626 {
627 header('Content-Type: '.$r->content_type);
628 // we do not know if client actually told us an accepted charset, but if he did
629 // we have to tell him what we did
630 header("Vary: Accept-Charset");
631
632 // http compression of output: only
633 // if we can do it, and we want to do it, and client asked us to,
634 // and php ini settings do not force it already
635 $php_no_self_compress = ini_get('zlib.output_compression') == '' && (ini_get('output_handler') != 'ob_gzhandler');
636 if($this->compress_response && function_exists('gzencode') && $resp_encoding != ''
637 && $php_no_self_compress)
638 {
639 if(strpos($resp_encoding, 'gzip') !== false)
640 {
641 $payload = gzencode($payload);
642 header("Content-Encoding: gzip");
643 header("Vary: Accept-Encoding");
644 }
645 elseif (strpos($resp_encoding, 'deflate') !== false)
646 {
647 $payload = gzcompress($payload);
648 header("Content-Encoding: deflate");
649 header("Vary: Accept-Encoding");
650 }
651 }
652
653 // do not ouput content-length header if php is compressing output for us:
654 // it will mess up measurements
655 if($php_no_self_compress)
656 {
657 header('Content-Length: ' . (int)strlen($payload));
658 }
659 }
660 else
661 {
662 error_log('XML-RPC: xmlrpc_server::service: http headers already sent before response is fully generated. Check for php warning or error messages');
663 }
664
665 print $payload;
666
667 // return request, in case subclasses want it
668 return $r;
669 }
670
671 /**
672 * Add a method to the dispatch map
673 * @param string $methodname the name with which the method will be made available
674 * @param string $function the php function that will get invoked
675 * @param array $sig the array of valid method signatures
676 * @param string $doc method documentation
677 * @access public
678 */
679 function add_to_map($methodname,$function,$sig=null,$doc='')
680 {
681 $this->dmap[$methodname] = array(
682 'function' => $function,
683 'docstring' => $doc
684 );
685 if ($sig)
686 {
687 $this->dmap[$methodname]['signature'] = $sig;
688 }
689 }
690
691 /**
692 * Verify type and number of parameters received against a list of known signatures
693 * @param array $in array of either xmlrpcval objects or xmlrpc type definitions
694 * @param array $sig array of known signatures to match against
695 * @access private
696 */
697 function verifySignature($in, $sig)
698 {
699 // check each possible signature in turn
700 if (is_object($in))
701 {
702 $numParams = $in->getNumParams();
703 }
704 else
705 {
706 $numParams = count($in);
707 }
708 foreach($sig as $cursig)
709 {
710 if(count($cursig)==$numParams+1)
711 {
712 $itsOK=1;
713 for($n=0; $n<$numParams; $n++)
714 {
715 if (is_object($in))
716 {
717 $p=$in->getParam($n);
718 if($p->kindOf() == 'scalar')
719 {
720 $pt=$p->scalartyp();
721 }
722 else
723 {
724 $pt=$p->kindOf();
725 }
726 }
727 else
728 {
729 $pt= $in[$n] == 'i4' ? 'int' : $in[$n]; // dispatch maps never use i4...
730 }
731
732 // param index is $n+1, as first member of sig is return type
733 if($pt != $cursig[$n+1] && $cursig[$n+1] != $GLOBALS['xmlrpcValue'])
734 {
735 $itsOK=0;
736 $pno=$n+1;
737 $wanted=$cursig[$n+1];
738 $got=$pt;
739 break;
740 }
741 }
742 if($itsOK)
743 {
744 return array(1,'');
745 }
746 }
747 }
748 if(isset($wanted))
749 {
750 return array(0, "Wanted ${wanted}, got ${got} at param ${pno}");
751 }
752 else
753 {
754 return array(0, "No method signature matches number of parameters");
755 }
756 }
757
758 /**
759 * Parse http headers received along with xmlrpc request. If needed, inflate request
760 * @return null on success or an xmlrpcresp
761 * @access private
762 */
763 function parseRequestHeaders(&$data, &$req_encoding, &$resp_encoding, &$resp_compression)
764 {
765 // Play nice to PHP 4.0.x: superglobals were not yet invented...
766 if(!isset($_SERVER))
767 {
768 $_SERVER = $GLOBALS['HTTP_SERVER_VARS'];
769 }
770
771 if($this->debug > 1)
772 {
773 if(function_exists('getallheaders'))
774 {
775 $this->debugmsg(''); // empty line
776 foreach(getallheaders() as $name => $val)
777 {
778 $this->debugmsg("HEADER: $name: $val");
779 }
780 }
781
782 }
783
784 if(isset($_SERVER['HTTP_CONTENT_ENCODING']))
785 {
786 $content_encoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
787 }
788 else
789 {
790 $content_encoding = '';
791 }
792
793 // check if request body has been compressed and decompress it
794 if($content_encoding != '' && strlen($data))
795 {
796 if($content_encoding == 'deflate' || $content_encoding == 'gzip')
797 {
798 // if decoding works, use it. else assume data wasn't gzencoded
799 if(function_exists('gzinflate') && in_array($content_encoding, $this->accepted_compression))
800 {
801 if($content_encoding == 'deflate' && $degzdata = @gzuncompress($data))
802 {
803 $data = $degzdata;
804 if($this->debug > 1)
805 {
806 $this->debugmsg("\n+++INFLATED REQUEST+++[".strlen($data)." chars]+++\n" . $data . "\n+++END+++");
807 }
808 }
809 elseif($content_encoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
810 {
811 $data = $degzdata;
812 if($this->debug > 1)
813 $this->debugmsg("+++INFLATED REQUEST+++[".strlen($data)." chars]+++\n" . $data . "\n+++END+++");
814 }
815 else
816 {
817 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_decompress_fail'], $GLOBALS['xmlrpcstr']['server_decompress_fail']);
818 return $r;
819 }
820 }
821 else
822 {
823 //error_log('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
824 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_cannot_decompress'], $GLOBALS['xmlrpcstr']['server_cannot_decompress']);
825 return $r;
826 }
827 }
828 }
829
830 // check if client specified accepted charsets, and if we know how to fulfill
831 // the request
832 if ($this->response_charset_encoding == 'auto')
833 {
834 $resp_encoding = '';
835 if (isset($_SERVER['HTTP_ACCEPT_CHARSET']))
836 {
837 // here we should check if we can match the client-requested encoding
838 // with the encodings we know we can generate.
839 /// @todo we should parse q=0.x preferences instead of getting first charset specified...
840 $client_accepted_charsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
841 // Give preference to internal encoding
842 $known_charsets = array($GLOBALS['xmlrpc_internalencoding'], 'UTF-8', 'ISO-8859-1', 'US-ASCII');
843 foreach ($known_charsets as $charset)
844 {
845 foreach ($client_accepted_charsets as $accepted)
846 if (strpos($accepted, $charset) === 0)
847 {
848 $resp_encoding = $charset;
849 break;
850 }
851 if ($resp_encoding)
852 break;
853 }
854 }
855 }
856 else
857 {
858 $resp_encoding = $this->response_charset_encoding;
859 }
860
861 if (isset($_SERVER['HTTP_ACCEPT_ENCODING']))
862 {
863 $resp_compression = $_SERVER['HTTP_ACCEPT_ENCODING'];
864 }
865 else
866 {
867 $resp_compression = '';
868 }
869
870 // 'guestimate' request encoding
871 /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
872 $req_encoding = guess_encoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
873 $data);
874
875 return null;
876 }
877
878 /**
879 * Parse an xml chunk containing an xmlrpc request and execute the corresponding
880 * php function registered with the server
881 * @param string $data the xml request
882 * @param string $req_encoding (optional) the charset encoding of the xml request
883 * @return xmlrpcresp
884 * @access private
885 */
886 function parseRequest($data, $req_encoding='')
887 {
888 // 2005/05/07 commented and moved into caller function code
889 //if($data=='')
890 //{
891 // $data=$GLOBALS['HTTP_RAW_POST_DATA'];
892 //}
893
894 // G. Giunta 2005/02/13: we do NOT expect to receive html entities
895 // so we do not try to convert them into xml character entities
896 //$data = xmlrpc_html_entity_xlate($data);
897
898 $GLOBALS['_xh']=array();
899 $GLOBALS['_xh']['ac']='';
900 $GLOBALS['_xh']['stack']=array();
901 $GLOBALS['_xh']['valuestack'] = array();
902 $GLOBALS['_xh']['params']=array();
903 $GLOBALS['_xh']['pt']=array();
904 $GLOBALS['_xh']['isf']=0;
905 $GLOBALS['_xh']['isf_reason']='';
906 $GLOBALS['_xh']['method']=false; // so we can check later if we got a methodname or not
907 $GLOBALS['_xh']['rt']='';
908
909 // decompose incoming XML into request structure
910 if ($req_encoding != '')
911 {
912 if (!in_array($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
913 // the following code might be better for mb_string enabled installs, but
914 // makes the lib about 200% slower...
915 //if (!is_valid_charset($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
916 {
917 error_log('XML-RPC: xmlrpc_server::parseRequest: invalid charset encoding of received request: '.$req_encoding);
918 $req_encoding = $GLOBALS['xmlrpc_defencoding'];
919 }
920 /// @BUG this will fail on PHP 5 if charset is not specified in the xml prologue,
921 // the encoding is not UTF8 and there are non-ascii chars in the text...
922 /// @todo use an ampty string for php 5 ???
923 $parser = xml_parser_create($req_encoding);
924 }
925 else
926 {
927 $parser = xml_parser_create();
928 }
929
930 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
931 // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
932 // the xml parser to give us back data in the expected charset
933 // What if internal encoding is not in one of the 3 allowed?
934 // we use the broadest one, ie. utf8
935 // This allows to send data which is native in various charset,
936 // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding
937 if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
938 {
939 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
940 }
941 else
942 {
943 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
944 }
945
946 if ($this->functions_parameters_type != 'xmlrpcvals')
947 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
948 else
949 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
950 xml_set_character_data_handler($parser, 'xmlrpc_cd');
951 xml_set_default_handler($parser, 'xmlrpc_dh');
952 if(!xml_parse($parser, $data, 1))
953 {
954 // return XML error as a faultCode
955 $r=&new xmlrpcresp(0,
956 $GLOBALS['xmlrpcerrxml']+xml_get_error_code($parser),
957 sprintf('XML error: %s at line %d, column %d',
958 xml_error_string(xml_get_error_code($parser)),
959 xml_get_current_line_number($parser), xml_get_current_column_number($parser)));
960 xml_parser_free($parser);
961 }
962 elseif ($GLOBALS['_xh']['isf'])
963 {
964 xml_parser_free($parser);
965 $r=&new xmlrpcresp(0,
966 $GLOBALS['xmlrpcerr']['invalid_request'],
967 $GLOBALS['xmlrpcstr']['invalid_request'] . ' ' . $GLOBALS['_xh']['isf_reason']);
968 }
969 else
970 {
971 xml_parser_free($parser);
972 if ($this->functions_parameters_type != 'xmlrpcvals')
973 {
974 if($this->debug > 1)
975 {
976 $this->debugmsg("\n+++PARSED+++\n".var_export($GLOBALS['_xh']['params'], true)."\n+++END+++");
977 }
978 $r = $this->execute($GLOBALS['_xh']['method'], $GLOBALS['_xh']['params'], $GLOBALS['_xh']['pt']);
979 }
980 else
981 {
982 // build an xmlrpcmsg object with data parsed from xml
983 $m=&new xmlrpcmsg($GLOBALS['_xh']['method']);
984 // now add parameters in
985 for($i=0; $i<count($GLOBALS['_xh']['params']); $i++)
986 {
987 $m->addParam($GLOBALS['_xh']['params'][$i]);
988 }
989
990 if($this->debug > 1)
991 {
992 $this->debugmsg("\n+++PARSED+++\n".var_export($m, true)."\n+++END+++");
993 }
994 $r = $this->execute($m);
995 }
996 }
997 return $r;
998 }
999
1000 /**
1001 * Execute a method invoked by the client, checking parameters used
1002 * @param mixed $m either an xmlrpcmsg obj or a method name
1003 * @param array $params array with method parameters as php types (if m is method name only)
1004 * @param array $paramtypes array with xmlrpc types of method parameters (if m is method name only)
1005 * @return xmlrpcresp
1006 * @access private
1007 */
1008 function execute($m, $params=null, $paramtypes=null)
1009 {
1010 if (is_object($m))
1011 {
1012 $methName = $m->method();
1013 }
1014 else
1015 {
1016 $methName = $m;
1017 }
1018 $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0);
1019 $dmap = $sysCall ? $GLOBALS['_xmlrpcs_dmap'] : $this->dmap;
1020
1021 if(!isset($dmap[$methName]['function']))
1022 {
1023 // No such method
1024 return new xmlrpcresp(0,
1025 $GLOBALS['xmlrpcerr']['unknown_method'],
1026 $GLOBALS['xmlrpcstr']['unknown_method']);
1027 }
1028
1029 // Check signature
1030 if(isset($dmap[$methName]['signature']))
1031 {
1032 $sig = $dmap[$methName]['signature'];
1033 if (is_object($m))
1034 {
1035 list($ok, $errstr) = $this->verifySignature($m, $sig);
1036 }
1037 else
1038 {
1039 list($ok, $errstr) = $this->verifySignature($paramtypes, $sig);
1040 }
1041 if(!$ok)
1042 {
1043 // Didn't match.
1044 return new xmlrpcresp(
1045 0,
1046 $GLOBALS['xmlrpcerr']['incorrect_params'],
1047 $GLOBALS['xmlrpcstr']['incorrect_params'] . ": ${errstr}"
1048 );
1049 }
1050 }
1051
1052 $func = $dmap[$methName]['function'];
1053 // let the 'class::function' syntax be accepted in dispatch maps
1054 if(is_string($func) && strpos($func, '::'))
1055 {
1056 $func = explode('::', $func);
1057 }
1058 // verify that function to be invoked is in fact callable
1059 if(!is_callable($func))
1060 {
1061 error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler is not callable");
1062 return new xmlrpcresp(
1063 0,
1064 $GLOBALS['xmlrpcerr']['server_error'],
1065 $GLOBALS['xmlrpcstr']['server_error'] . ": no function matches method"
1066 );
1067 }
1068
1069 // If debug level is 3, we should catch all errors generated during
1070 // processing of user function, and log them as part of response
1071 if($this->debug > 2)
1072 {
1073 $GLOBALS['_xmlrpcs_prev_ehandler'] = set_error_handler('_xmlrpcs_errorHandler');
1074 }
1075 if (is_object($m))
1076 {
1077 if($sysCall)
1078 {
1079 $r = call_user_func($func, $this, $m);
1080 }
1081 else
1082 {
1083 $r = call_user_func($func, $m);
1084 }
1085 if (!is_a($r, 'xmlrpcresp'))
1086 {
1087 error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler does not return an xmlrpcresp object");
1088 if (is_a($r, 'xmlrpcval'))
1089 {
1090 $r =& new xmlrpcresp($r);
1091 }
1092 else
1093 {
1094 $r =& new xmlrpcresp(
1095 0,
1096 $GLOBALS['xmlrpcerr']['server_error'],
1097 $GLOBALS['xmlrpcstr']['server_error'] . ": function does not return xmlrpcresp object"
1098 );
1099 }
1100 }
1101 }
1102 else
1103 {
1104 // call a 'plain php' function
1105 if($sysCall)
1106 {
1107 array_unshift($params, $this);
1108 $r = call_user_func_array($func, $params);
1109 }
1110 else
1111 {
1112 // 3rd API convention for method-handling functions: EPI-style
1113 if ($this->functions_parameters_type == 'epivals')
1114 {
1115 $r = call_user_func_array($func, array($methName, $params, $this->user_data));
1116 // mimic EPI behaviour: if we get an array that looks like an error, make it
1117 // an eror response
1118 if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r))
1119 {
1120 $r =& new xmlrpcresp(0, (integer)$r['faultCode'], (string)$r['faultString']);
1121 }
1122 else
1123 {
1124 // functions using EPI api should NOT return resp objects,
1125 // so make sure we encode the return type correctly
1126 $r =& new xmlrpcresp(php_xmlrpc_encode($r, array('extension_api')));
1127 }
1128 }
1129 else
1130 {
1131 $r = call_user_func_array($func, $params);
1132 }
1133 }
1134 // the return type can be either an xmlrpcresp object or a plain php value...
1135 if (!is_a($r, 'xmlrpcresp'))
1136 {
1137 // what should we assume here about automatic encoding of datetimes
1138 // and php classes instances???
1139 $r =& new xmlrpcresp(php_xmlrpc_encode($r, array('auto_dates')));
1140 }
1141 }
1142 if($this->debug > 2)
1143 {
1144 // note: restore the error handler we found before calling the
1145 // user func, even if it has been changed inside the func itself
1146 if($GLOBALS['_xmlrpcs_prev_ehandler'])
1147 {
1148 set_error_handler($GLOBALS['_xmlrpcs_prev_ehandler']);
1149 }
1150 else
1151 {
1152 restore_error_handler();
1153 }
1154 }
1155 return $r;
1156 }
1157
1158 /**
1159 * add a string to the 'internal debug message' (separate from 'user debug message')
1160 * @param string $strings
1161 * @access private
1162 */
1163 function debugmsg($string)
1164 {
1165 $this->debug_info .= $string."\n";
1166 }
1167
1168 /**
1169 * @access private
1170 */
1171 function xml_header($charset_encoding='')
1172 {
1173 if ($charset_encoding != '')
1174 {
1175 return "<?xml version=\"1.0\" encoding=\"$charset_encoding\"?" . ">\n";
1176 }
1177 else
1178 {
1179 return "<?xml version=\"1.0\"?" . ">\n";
1180 }
1181 }
1182
1183 /**
1184 * A debugging routine: just echoes back the input packet as a string value
1185 * DEPRECATED!
1186 */
1187 function echoInput()
1188 {
1189 $r=&new xmlrpcresp(new xmlrpcval( "'Aha said I: '" . $GLOBALS['HTTP_RAW_POST_DATA'], 'string'));
1190 print $r->serialize();
1191 }
1192 }
1193?> \ No newline at end of file