Changeset 39730
- Timestamp:
- 01/06/2017 06:07:32 AM (8 years ago)
- Location:
- branches/3.8
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/3.8
- Property svn:mergeinfo changed
/trunk merged: 27385,29783,33124,33142,36083,39645
- Property svn:mergeinfo changed
-
branches/3.8/src/wp-includes/class-phpmailer.php
r25682 r39730 1 1 <?php 2 /*~ class.phpmailer.php3 .---------------------------------------------------------------------------.4 | Software: PHPMailer - PHP email class |5 | Version: 5.2.4 |6 | Site: https://code.google.com/a/apache-extras.org/p/phpmailer/ |7 | ------------------------------------------------------------------------- |8 | Admin: Jim Jagielski (project admininistrator) |9 | Authors: Andy Prevost (codeworxtech) codeworxtech@users.sourceforge.net |10 | : Marcus Bointon (coolbru) coolbru@users.sourceforge.net |11 | : Jim Jagielski (jimjag) jimjag@gmail.com |12 | Founder: Brent R. Matzelle (original founder) |13 | Copyright (c) 2010-2012, Jim Jagielski. All Rights Reserved. |14 | Copyright (c) 2004-2009, Andy Prevost. All Rights Reserved. |15 | Copyright (c) 2001-2003, Brent R. Matzelle |16 | ------------------------------------------------------------------------- |17 | License: Distributed under the Lesser General Public License (LGPL) |18 | http://www.gnu.org/copyleft/lesser.html |19 | This program is distributed in the hope that it will be useful - WITHOUT |20 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |21 | FITNESS FOR A PARTICULAR PURPOSE. |22 '---------------------------------------------------------------------------'23 */24 25 2 /** 26 * PHPMailer - PHP email creation and transport class 27 * NOTE: Requires PHP version 5 or later3 * PHPMailer - PHP email creation and transport class. 4 * PHP Version 5 28 5 * @package PHPMailer 29 * @author Andy Prevost 30 * @author Marcus Bointon 31 * @author Jim Jagielski 6 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project 7 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk> 8 * @author Jim Jagielski (jimjag) <jimjag@gmail.com> 9 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net> 10 * @author Brent R. Matzelle (original founder) 11 * @copyright 2012 - 2014 Marcus Bointon 32 12 * @copyright 2010 - 2012 Jim Jagielski 33 13 * @copyright 2004 - 2009 Andy Prevost 34 14 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 * @note This program is distributed in the hope that it will be useful - WITHOUT 16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 * FITNESS FOR A PARTICULAR PURPOSE. 35 18 */ 36 19 37 if (version_compare(PHP_VERSION, '5.0.0', '<') ) exit("Sorry, this version of PHPMailer will only run on PHP version 5 or greater!\n");38 39 20 /** 40 * PHP email creation and transport class 21 * PHPMailer - PHP email creation and transport class. 22 * @package PHPMailer 23 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk> 24 * @author Jim Jagielski (jimjag) <jimjag@gmail.com> 25 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net> 26 * @author Brent R. Matzelle (original founder) 27 */ 28 class PHPMailer 29 { 30 /** 31 * The PHPMailer Version number. 32 * @var string 33 */ 34 public $Version = '5.2.21'; 35 36 /** 37 * Email priority. 38 * Options: null (default), 1 = High, 3 = Normal, 5 = low. 39 * When null, the header is not set at all. 40 * @var integer 41 */ 42 public $Priority = null; 43 44 /** 45 * The character set of the message. 46 * @var string 47 */ 48 public $CharSet = 'iso-8859-1'; 49 50 /** 51 * The MIME Content-type of the message. 52 * @var string 53 */ 54 public $ContentType = 'text/plain'; 55 56 /** 57 * The message encoding. 58 * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". 59 * @var string 60 */ 61 public $Encoding = '8bit'; 62 63 /** 64 * Holds the most recent mailer error message. 65 * @var string 66 */ 67 public $ErrorInfo = ''; 68 69 /** 70 * The From email address for the message. 71 * @var string 72 */ 73 public $From = 'root@localhost'; 74 75 /** 76 * The From name of the message. 77 * @var string 78 */ 79 public $FromName = 'Root User'; 80 81 /** 82 * The Sender email (Return-Path) of the message. 83 * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. 84 * @var string 85 */ 86 public $Sender = ''; 87 88 /** 89 * The Return-Path of the message. 90 * If empty, it will be set to either From or Sender. 91 * @var string 92 * @deprecated Email senders should never set a return-path header; 93 * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything. 94 * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference 95 */ 96 public $ReturnPath = ''; 97 98 /** 99 * The Subject of the message. 100 * @var string 101 */ 102 public $Subject = ''; 103 104 /** 105 * An HTML or plain text message body. 106 * If HTML then call isHTML(true). 107 * @var string 108 */ 109 public $Body = ''; 110 111 /** 112 * The plain-text message body. 113 * This body can be read by mail clients that do not have HTML email 114 * capability such as mutt & Eudora. 115 * Clients that can read HTML will view the normal Body. 116 * @var string 117 */ 118 public $AltBody = ''; 119 120 /** 121 * An iCal message part body. 122 * Only supported in simple alt or alt_inline message types 123 * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator 124 * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ 125 * @link http://kigkonsult.se/iCalcreator/ 126 * @var string 127 */ 128 public $Ical = ''; 129 130 /** 131 * The complete compiled MIME message body. 132 * @access protected 133 * @var string 134 */ 135 protected $MIMEBody = ''; 136 137 /** 138 * The complete compiled MIME message headers. 139 * @var string 140 * @access protected 141 */ 142 protected $MIMEHeader = ''; 143 144 /** 145 * Extra headers that createHeader() doesn't fold in. 146 * @var string 147 * @access protected 148 */ 149 protected $mailHeader = ''; 150 151 /** 152 * Word-wrap the message body to this number of chars. 153 * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. 154 * @var integer 155 */ 156 public $WordWrap = 0; 157 158 /** 159 * Which method to use to send mail. 160 * Options: "mail", "sendmail", or "smtp". 161 * @var string 162 */ 163 public $Mailer = 'mail'; 164 165 /** 166 * The path to the sendmail program. 167 * @var string 168 */ 169 public $Sendmail = '/usr/sbin/sendmail'; 170 171 /** 172 * Whether mail() uses a fully sendmail-compatible MTA. 173 * One which supports sendmail's "-oi -f" options. 174 * @var boolean 175 */ 176 public $UseSendmailOptions = true; 177 178 /** 179 * Path to PHPMailer plugins. 180 * Useful if the SMTP class is not in the PHP include path. 181 * @var string 182 * @deprecated Should not be needed now there is an autoloader. 183 */ 184 public $PluginDir = ''; 185 186 /** 187 * The email address that a reading confirmation should be sent to, also known as read receipt. 188 * @var string 189 */ 190 public $ConfirmReadingTo = ''; 191 192 /** 193 * The hostname to use in the Message-ID header and as default HELO string. 194 * If empty, PHPMailer attempts to find one with, in order, 195 * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value 196 * 'localhost.localdomain'. 197 * @var string 198 */ 199 public $Hostname = ''; 200 201 /** 202 * An ID to be used in the Message-ID header. 203 * If empty, a unique id will be generated. 204 * You can set your own, but it must be in the format "<id@domain>", 205 * as defined in RFC5322 section 3.6.4 or it will be ignored. 206 * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 207 * @var string 208 */ 209 public $MessageID = ''; 210 211 /** 212 * The message Date to be used in the Date header. 213 * If empty, the current date will be added. 214 * @var string 215 */ 216 public $MessageDate = ''; 217 218 /** 219 * SMTP hosts. 220 * Either a single hostname or multiple semicolon-delimited hostnames. 221 * You can also specify a different port 222 * for each host by using this format: [hostname:port] 223 * (e.g. "smtp1.example.com:25;smtp2.example.com"). 224 * You can also specify encryption type, for example: 225 * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). 226 * Hosts will be tried in order. 227 * @var string 228 */ 229 public $Host = 'localhost'; 230 231 /** 232 * The default SMTP server port. 233 * @var integer 234 * @TODO Why is this needed when the SMTP class takes care of it? 235 */ 236 public $Port = 25; 237 238 /** 239 * The SMTP HELO of the message. 240 * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find 241 * one with the same method described above for $Hostname. 242 * @var string 243 * @see PHPMailer::$Hostname 244 */ 245 public $Helo = ''; 246 247 /** 248 * What kind of encryption to use on the SMTP connection. 249 * Options: '', 'ssl' or 'tls' 250 * @var string 251 */ 252 public $SMTPSecure = ''; 253 254 /** 255 * Whether to enable TLS encryption automatically if a server supports it, 256 * even if `SMTPSecure` is not set to 'tls'. 257 * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. 258 * @var boolean 259 */ 260 public $SMTPAutoTLS = true; 261 262 /** 263 * Whether to use SMTP authentication. 264 * Uses the Username and Password properties. 265 * @var boolean 266 * @see PHPMailer::$Username 267 * @see PHPMailer::$Password 268 */ 269 public $SMTPAuth = false; 270 271 /** 272 * Options array passed to stream_context_create when connecting via SMTP. 273 * @var array 274 */ 275 public $SMTPOptions = array(); 276 277 /** 278 * SMTP username. 279 * @var string 280 */ 281 public $Username = ''; 282 283 /** 284 * SMTP password. 285 * @var string 286 */ 287 public $Password = ''; 288 289 /** 290 * SMTP auth type. 291 * Options are CRAM-MD5, LOGIN, PLAIN, attempted in that order if not specified 292 * @var string 293 */ 294 public $AuthType = ''; 295 296 /** 297 * SMTP realm. 298 * Used for NTLM auth 299 * @var string 300 */ 301 public $Realm = ''; 302 303 /** 304 * SMTP workstation. 305 * Used for NTLM auth 306 * @var string 307 */ 308 public $Workstation = ''; 309 310 /** 311 * The SMTP server timeout in seconds. 312 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 313 * @var integer 314 */ 315 public $Timeout = 300; 316 317 /** 318 * SMTP class debug output mode. 319 * Debug output level. 320 * Options: 321 * * `0` No output 322 * * `1` Commands 323 * * `2` Data and commands 324 * * `3` As 2 plus connection status 325 * * `4` Low-level data output 326 * @var integer 327 * @see SMTP::$do_debug 328 */ 329 public $SMTPDebug = 0; 330 331 /** 332 * How to handle debug output. 333 * Options: 334 * * `echo` Output plain-text as-is, appropriate for CLI 335 * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output 336 * * `error_log` Output to error log as configured in php.ini 337 * 338 * Alternatively, you can provide a callable expecting two params: a message string and the debug level: 339 * <code> 340 * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; 341 * </code> 342 * @var string|callable 343 * @see SMTP::$Debugoutput 344 */ 345 public $Debugoutput = 'echo'; 346 347 /** 348 * Whether to keep SMTP connection open after each message. 349 * If this is set to true then to close the connection 350 * requires an explicit call to smtpClose(). 351 * @var boolean 352 */ 353 public $SMTPKeepAlive = false; 354 355 /** 356 * Whether to split multiple to addresses into multiple messages 357 * or send them all in one message. 358 * Only supported in `mail` and `sendmail` transports, not in SMTP. 359 * @var boolean 360 */ 361 public $SingleTo = false; 362 363 /** 364 * Storage for addresses when SingleTo is enabled. 365 * @var array 366 * @TODO This should really not be public 367 */ 368 public $SingleToArray = array(); 369 370 /** 371 * Whether to generate VERP addresses on send. 372 * Only applicable when sending via SMTP. 373 * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path 374 * @link http://www.postfix.org/VERP_README.html Postfix VERP info 375 * @var boolean 376 */ 377 public $do_verp = false; 378 379 /** 380 * Whether to allow sending messages with an empty body. 381 * @var boolean 382 */ 383 public $AllowEmpty = false; 384 385 /** 386 * The default line ending. 387 * @note The default remains "\n". We force CRLF where we know 388 * it must be used via self::CRLF. 389 * @var string 390 */ 391 public $LE = "\n"; 392 393 /** 394 * DKIM selector. 395 * @var string 396 */ 397 public $DKIM_selector = ''; 398 399 /** 400 * DKIM Identity. 401 * Usually the email address used as the source of the email. 402 * @var string 403 */ 404 public $DKIM_identity = ''; 405 406 /** 407 * DKIM passphrase. 408 * Used if your key is encrypted. 409 * @var string 410 */ 411 public $DKIM_passphrase = ''; 412 413 /** 414 * DKIM signing domain name. 415 * @example 'example.com' 416 * @var string 417 */ 418 public $DKIM_domain = ''; 419 420 /** 421 * DKIM private key file path. 422 * @var string 423 */ 424 public $DKIM_private = ''; 425 426 /** 427 * DKIM private key string. 428 * If set, takes precedence over `$DKIM_private`. 429 * @var string 430 */ 431 public $DKIM_private_string = ''; 432 433 /** 434 * Callback Action function name. 435 * 436 * The function that handles the result of the send email action. 437 * It is called out by send() for each email sent. 438 * 439 * Value can be any php callable: http://www.php.net/is_callable 440 * 441 * Parameters: 442 * boolean $result result of the send action 443 * string $to email address of the recipient 444 * string $cc cc email addresses 445 * string $bcc bcc email addresses 446 * string $subject the subject 447 * string $body the email body 448 * string $from email address of sender 449 * @var string 450 */ 451 public $action_function = ''; 452 453 /** 454 * What to put in the X-Mailer header. 455 * Options: An empty string for PHPMailer default, whitespace for none, or a string to use 456 * @var string 457 */ 458 public $XMailer = ''; 459 460 /** 461 * Which validator to use by default when validating email addresses. 462 * May be a callable to inject your own validator, but there are several built-in validators. 463 * @see PHPMailer::validateAddress() 464 * @var string|callable 465 * @static 466 */ 467 public static $validator = 'auto'; 468 469 /** 470 * An instance of the SMTP sender class. 471 * @var SMTP 472 * @access protected 473 */ 474 protected $smtp = null; 475 476 /** 477 * The array of 'to' names and addresses. 478 * @var array 479 * @access protected 480 */ 481 protected $to = array(); 482 483 /** 484 * The array of 'cc' names and addresses. 485 * @var array 486 * @access protected 487 */ 488 protected $cc = array(); 489 490 /** 491 * The array of 'bcc' names and addresses. 492 * @var array 493 * @access protected 494 */ 495 protected $bcc = array(); 496 497 /** 498 * The array of reply-to names and addresses. 499 * @var array 500 * @access protected 501 */ 502 protected $ReplyTo = array(); 503 504 /** 505 * An array of all kinds of addresses. 506 * Includes all of $to, $cc, $bcc 507 * @var array 508 * @access protected 509 * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc 510 */ 511 protected $all_recipients = array(); 512 513 /** 514 * An array of names and addresses queued for validation. 515 * In send(), valid and non duplicate entries are moved to $all_recipients 516 * and one of $to, $cc, or $bcc. 517 * This array is used only for addresses with IDN. 518 * @var array 519 * @access protected 520 * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc 521 * @see PHPMailer::$all_recipients 522 */ 523 protected $RecipientsQueue = array(); 524 525 /** 526 * An array of reply-to names and addresses queued for validation. 527 * In send(), valid and non duplicate entries are moved to $ReplyTo. 528 * This array is used only for addresses with IDN. 529 * @var array 530 * @access protected 531 * @see PHPMailer::$ReplyTo 532 */ 533 protected $ReplyToQueue = array(); 534 535 /** 536 * The array of attachments. 537 * @var array 538 * @access protected 539 */ 540 protected $attachment = array(); 541 542 /** 543 * The array of custom headers. 544 * @var array 545 * @access protected 546 */ 547 protected $CustomHeader = array(); 548 549 /** 550 * The most recent Message-ID (including angular brackets). 551 * @var string 552 * @access protected 553 */ 554 protected $lastMessageID = ''; 555 556 /** 557 * The message's MIME type. 558 * @var string 559 * @access protected 560 */ 561 protected $message_type = ''; 562 563 /** 564 * The array of MIME boundary strings. 565 * @var array 566 * @access protected 567 */ 568 protected $boundary = array(); 569 570 /** 571 * The array of available languages. 572 * @var array 573 * @access protected 574 */ 575 protected $language = array(); 576 577 /** 578 * The number of errors encountered. 579 * @var integer 580 * @access protected 581 */ 582 protected $error_count = 0; 583 584 /** 585 * The S/MIME certificate file path. 586 * @var string 587 * @access protected 588 */ 589 protected $sign_cert_file = ''; 590 591 /** 592 * The S/MIME key file path. 593 * @var string 594 * @access protected 595 */ 596 protected $sign_key_file = ''; 597 598 /** 599 * The optional S/MIME extra certificates ("CA Chain") file path. 600 * @var string 601 * @access protected 602 */ 603 protected $sign_extracerts_file = ''; 604 605 /** 606 * The S/MIME password for the key. 607 * Used only if the key is encrypted. 608 * @var string 609 * @access protected 610 */ 611 protected $sign_key_pass = ''; 612 613 /** 614 * Whether to throw exceptions for errors. 615 * @var boolean 616 * @access protected 617 */ 618 protected $exceptions = false; 619 620 /** 621 * Unique ID used for message ID and boundaries. 622 * @var string 623 * @access protected 624 */ 625 protected $uniqueid = ''; 626 627 /** 628 * Error severity: message only, continue processing. 629 */ 630 const STOP_MESSAGE = 0; 631 632 /** 633 * Error severity: message, likely ok to continue processing. 634 */ 635 const STOP_CONTINUE = 1; 636 637 /** 638 * Error severity: message, plus full stop, critical error reached. 639 */ 640 const STOP_CRITICAL = 2; 641 642 /** 643 * SMTP RFC standard line ending. 644 */ 645 const CRLF = "\r\n"; 646 647 /** 648 * The maximum line length allowed by RFC 2822 section 2.1.1 649 * @var integer 650 */ 651 const MAX_LINE_LENGTH = 998; 652 653 /** 654 * Constructor. 655 * @param boolean $exceptions Should we throw external exceptions? 656 */ 657 public function __construct($exceptions = null) 658 { 659 if ($exceptions !== null) { 660 $this->exceptions = (boolean)$exceptions; 661 } 662 } 663 664 /** 665 * Destructor. 666 */ 667 public function __destruct() 668 { 669 //Close any open SMTP connection nicely 670 $this->smtpClose(); 671 } 672 673 /** 674 * Call mail() in a safe_mode-aware fashion. 675 * Also, unless sendmail_path points to sendmail (or something that 676 * claims to be sendmail), don't pass params (not a perfect fix, 677 * but it will do) 678 * @param string $to To 679 * @param string $subject Subject 680 * @param string $body Message Body 681 * @param string $header Additional Header(s) 682 * @param string $params Params 683 * @access private 684 * @return boolean 685 */ 686 private function mailPassthru($to, $subject, $body, $header, $params) 687 { 688 //Check overloading of mail function to avoid double-encoding 689 if (ini_get('mbstring.func_overload') & 1) { 690 $subject = $this->secureHeader($subject); 691 } else { 692 $subject = $this->encodeHeader($this->secureHeader($subject)); 693 } 694 695 //Can't use additional_parameters in safe_mode, calling mail() with null params breaks 696 //@link http://php.net/manual/en/function.mail.php 697 if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) { 698 $result = @mail($to, $subject, $body, $header); 699 } else { 700 $result = @mail($to, $subject, $body, $header, $params); 701 } 702 return $result; 703 } 704 /** 705 * Output debugging info via user-defined method. 706 * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). 707 * @see PHPMailer::$Debugoutput 708 * @see PHPMailer::$SMTPDebug 709 * @param string $str 710 */ 711 protected function edebug($str) 712 { 713 if ($this->SMTPDebug <= 0) { 714 return; 715 } 716 //Avoid clash with built-in function names 717 if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { 718 call_user_func($this->Debugoutput, $str, $this->SMTPDebug); 719 return; 720 } 721 switch ($this->Debugoutput) { 722 case 'error_log': 723 //Don't output, just log 724 error_log($str); 725 break; 726 case 'html': 727 //Cleans up output a bit for a better looking, HTML-safe output 728 echo htmlentities( 729 preg_replace('/[\r\n]+/', '', $str), 730 ENT_QUOTES, 731 'UTF-8' 732 ) 733 . "<br>\n"; 734 break; 735 case 'echo': 736 default: 737 //Normalize line breaks 738 $str = preg_replace('/\r\n?/ms', "\n", $str); 739 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 740 "\n", 741 "\n \t ", 742 trim($str) 743 ) . "\n"; 744 } 745 } 746 747 /** 748 * Sets message type to HTML or plain. 749 * @param boolean $isHtml True for HTML mode. 750 * @return void 751 */ 752 public function isHTML($isHtml = true) 753 { 754 if ($isHtml) { 755 $this->ContentType = 'text/html'; 756 } else { 757 $this->ContentType = 'text/plain'; 758 } 759 } 760 761 /** 762 * Send messages using SMTP. 763 * @return void 764 */ 765 public function isSMTP() 766 { 767 $this->Mailer = 'smtp'; 768 } 769 770 /** 771 * Send messages using PHP's mail() function. 772 * @return void 773 */ 774 public function isMail() 775 { 776 $this->Mailer = 'mail'; 777 } 778 779 /** 780 * Send messages using $Sendmail. 781 * @return void 782 */ 783 public function isSendmail() 784 { 785 $ini_sendmail_path = ini_get('sendmail_path'); 786 787 if (!stristr($ini_sendmail_path, 'sendmail')) { 788 $this->Sendmail = '/usr/sbin/sendmail'; 789 } else { 790 $this->Sendmail = $ini_sendmail_path; 791 } 792 $this->Mailer = 'sendmail'; 793 } 794 795 /** 796 * Send messages using qmail. 797 * @return void 798 */ 799 public function isQmail() 800 { 801 $ini_sendmail_path = ini_get('sendmail_path'); 802 803 if (!stristr($ini_sendmail_path, 'qmail')) { 804 $this->Sendmail = '/var/qmail/bin/qmail-inject'; 805 } else { 806 $this->Sendmail = $ini_sendmail_path; 807 } 808 $this->Mailer = 'qmail'; 809 } 810 811 /** 812 * Add a "To" address. 813 * @param string $address The email address to send to 814 * @param string $name 815 * @return boolean true on success, false if address already used or invalid in some way 816 */ 817 public function addAddress($address, $name = '') 818 { 819 return $this->addOrEnqueueAnAddress('to', $address, $name); 820 } 821 822 /** 823 * Add a "CC" address. 824 * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. 825 * @param string $address The email address to send to 826 * @param string $name 827 * @return boolean true on success, false if address already used or invalid in some way 828 */ 829 public function addCC($address, $name = '') 830 { 831 return $this->addOrEnqueueAnAddress('cc', $address, $name); 832 } 833 834 /** 835 * Add a "BCC" address. 836 * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. 837 * @param string $address The email address to send to 838 * @param string $name 839 * @return boolean true on success, false if address already used or invalid in some way 840 */ 841 public function addBCC($address, $name = '') 842 { 843 return $this->addOrEnqueueAnAddress('bcc', $address, $name); 844 } 845 846 /** 847 * Add a "Reply-To" address. 848 * @param string $address The email address to reply to 849 * @param string $name 850 * @return boolean true on success, false if address already used or invalid in some way 851 */ 852 public function addReplyTo($address, $name = '') 853 { 854 return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); 855 } 856 857 /** 858 * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer 859 * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still 860 * be modified after calling this function), addition of such addresses is delayed until send(). 861 * Addresses that have been added already return false, but do not throw exceptions. 862 * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' 863 * @param string $address The email address to send, resp. to reply to 864 * @param string $name 865 * @throws phpmailerException 866 * @return boolean true on success, false if address already used or invalid in some way 867 * @access protected 868 */ 869 protected function addOrEnqueueAnAddress($kind, $address, $name) 870 { 871 $address = trim($address); 872 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim 873 if (($pos = strrpos($address, '@')) === false) { 874 // At-sign is misssing. 875 $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; 876 $this->setError($error_message); 877 $this->edebug($error_message); 878 if ($this->exceptions) { 879 throw new phpmailerException($error_message); 880 } 881 return false; 882 } 883 $params = array($kind, $address, $name); 884 // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. 885 if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) { 886 if ($kind != 'Reply-To') { 887 if (!array_key_exists($address, $this->RecipientsQueue)) { 888 $this->RecipientsQueue[$address] = $params; 889 return true; 890 } 891 } else { 892 if (!array_key_exists($address, $this->ReplyToQueue)) { 893 $this->ReplyToQueue[$address] = $params; 894 return true; 895 } 896 } 897 return false; 898 } 899 // Immediately add standard addresses without IDN. 900 return call_user_func_array(array($this, 'addAnAddress'), $params); 901 } 902 903 /** 904 * Add an address to one of the recipient arrays or to the ReplyTo array. 905 * Addresses that have been added already return false, but do not throw exceptions. 906 * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' 907 * @param string $address The email address to send, resp. to reply to 908 * @param string $name 909 * @throws phpmailerException 910 * @return boolean true on success, false if address already used or invalid in some way 911 * @access protected 912 */ 913 protected function addAnAddress($kind, $address, $name = '') 914 { 915 if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) { 916 $error_message = $this->lang('Invalid recipient kind: ') . $kind; 917 $this->setError($error_message); 918 $this->edebug($error_message); 919 if ($this->exceptions) { 920 throw new phpmailerException($error_message); 921 } 922 return false; 923 } 924 if (!$this->validateAddress($address)) { 925 $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; 926 $this->setError($error_message); 927 $this->edebug($error_message); 928 if ($this->exceptions) { 929 throw new phpmailerException($error_message); 930 } 931 return false; 932 } 933 if ($kind != 'Reply-To') { 934 if (!array_key_exists(strtolower($address), $this->all_recipients)) { 935 array_push($this->$kind, array($address, $name)); 936 $this->all_recipients[strtolower($address)] = true; 937 return true; 938 } 939 } else { 940 if (!array_key_exists(strtolower($address), $this->ReplyTo)) { 941 $this->ReplyTo[strtolower($address)] = array($address, $name); 942 return true; 943 } 944 } 945 return false; 946 } 947 948 /** 949 * Parse and validate a string containing one or more RFC822-style comma-separated email addresses 950 * of the form "display name <address>" into an array of name/address pairs. 951 * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. 952 * Note that quotes in the name part are removed. 953 * @param string $addrstr The address list string 954 * @param bool $useimap Whether to use the IMAP extension to parse the list 955 * @return array 956 * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation 957 */ 958 public function parseAddresses($addrstr, $useimap = true) 959 { 960 $addresses = array(); 961 if ($useimap and function_exists('imap_rfc822_parse_adrlist')) { 962 //Use this built-in parser if it's available 963 $list = imap_rfc822_parse_adrlist($addrstr, ''); 964 foreach ($list as $address) { 965 if ($address->host != '.SYNTAX-ERROR.') { 966 if ($this->validateAddress($address->mailbox . '@' . $address->host)) { 967 $addresses[] = array( 968 'name' => (property_exists($address, 'personal') ? $address->personal : ''), 969 'address' => $address->mailbox . '@' . $address->host 970 ); 971 } 972 } 973 } 974 } else { 975 //Use this simpler parser 976 $list = explode(',', $addrstr); 977 foreach ($list as $address) { 978 $address = trim($address); 979 //Is there a separate name part? 980 if (strpos($address, '<') === false) { 981 //No separate name, just use the whole thing 982 if ($this->validateAddress($address)) { 983 $addresses[] = array( 984 'name' => '', 985 'address' => $address 986 ); 987 } 988 } else { 989 list($name, $email) = explode('<', $address); 990 $email = trim(str_replace('>', '', $email)); 991 if ($this->validateAddress($email)) { 992 $addresses[] = array( 993 'name' => trim(str_replace(array('"', "'"), '', $name)), 994 'address' => $email 995 ); 996 } 997 } 998 } 999 } 1000 return $addresses; 1001 } 1002 1003 /** 1004 * Set the From and FromName properties. 1005 * @param string $address 1006 * @param string $name 1007 * @param boolean $auto Whether to also set the Sender address, defaults to true 1008 * @throws phpmailerException 1009 * @return boolean 1010 */ 1011 public function setFrom($address, $name = '', $auto = true) 1012 { 1013 $address = trim($address); 1014 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim 1015 // Don't validate now addresses with IDN. Will be done in send(). 1016 if (($pos = strrpos($address, '@')) === false or 1017 (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and 1018 !$this->validateAddress($address)) { 1019 $error_message = $this->lang('invalid_address') . " (setFrom) $address"; 1020 $this->setError($error_message); 1021 $this->edebug($error_message); 1022 if ($this->exceptions) { 1023 throw new phpmailerException($error_message); 1024 } 1025 return false; 1026 } 1027 $this->From = $address; 1028 $this->FromName = $name; 1029 if ($auto) { 1030 if (empty($this->Sender)) { 1031 $this->Sender = $address; 1032 } 1033 } 1034 return true; 1035 } 1036 1037 /** 1038 * Return the Message-ID header of the last email. 1039 * Technically this is the value from the last time the headers were created, 1040 * but it's also the message ID of the last sent message except in 1041 * pathological cases. 1042 * @return string 1043 */ 1044 public function getLastMessageID() 1045 { 1046 return $this->lastMessageID; 1047 } 1048 1049 /** 1050 * Check that a string looks like an email address. 1051 * @param string $address The email address to check 1052 * @param string|callable $patternselect A selector for the validation pattern to use : 1053 * * `auto` Pick best pattern automatically; 1054 * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; 1055 * * `pcre` Use old PCRE implementation; 1056 * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; 1057 * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. 1058 * * `noregex` Don't use a regex: super fast, really dumb. 1059 * Alternatively you may pass in a callable to inject your own validator, for example: 1060 * PHPMailer::validateAddress('user@example.com', function($address) { 1061 * return (strpos($address, '@') !== false); 1062 * }); 1063 * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. 1064 * @return boolean 1065 * @static 1066 * @access public 1067 */ 1068 public static function validateAddress($address, $patternselect = null) 1069 { 1070 if (is_null($patternselect)) { 1071 $patternselect = self::$validator; 1072 } 1073 if (is_callable($patternselect)) { 1074 return call_user_func($patternselect, $address); 1075 } 1076 //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 1077 if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) { 1078 return false; 1079 } 1080 if (!$patternselect or $patternselect == 'auto') { 1081 //Check this constant first so it works when extension_loaded() is disabled by safe mode 1082 //Constant was added in PHP 5.2.4 1083 if (defined('PCRE_VERSION')) { 1084 //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2 1085 if (version_compare(PCRE_VERSION, '8.0.3') >= 0) { 1086 $patternselect = 'pcre8'; 1087 } else { 1088 $patternselect = 'pcre'; 1089 } 1090 } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) { 1091 //Fall back to older PCRE 1092 $patternselect = 'pcre'; 1093 } else { 1094 //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension 1095 if (version_compare(PHP_VERSION, '5.2.0') >= 0) { 1096 $patternselect = 'php'; 1097 } else { 1098 $patternselect = 'noregex'; 1099 } 1100 } 1101 } 1102 switch ($patternselect) { 1103 case 'pcre8': 1104 /** 1105 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains. 1106 * @link http://squiloople.com/2009/12/20/email-address-validation/ 1107 * @copyright 2009-2010 Michael Rushton 1108 * Feel free to use and redistribute this code. But please keep this copyright notice. 1109 */ 1110 return (boolean)preg_match( 1111 '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . 1112 '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . 1113 '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . 1114 '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . 1115 '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . 1116 '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . 1117 '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . 1118 '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . 1119 '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', 1120 $address 1121 ); 1122 case 'pcre': 1123 //An older regex that doesn't need a recent PCRE 1124 return (boolean)preg_match( 1125 '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' . 1126 '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' . 1127 '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' . 1128 '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' . 1129 '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' . 1130 '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' . 1131 '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' . 1132 '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' . 1133 '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' . 1134 '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', 1135 $address 1136 ); 1137 case 'html5': 1138 /** 1139 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. 1140 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) 1141 */ 1142 return (boolean)preg_match( 1143 '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . 1144 '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', 1145 $address 1146 ); 1147 case 'noregex': 1148 //No PCRE! Do something _very_ approximate! 1149 //Check the address is 3 chars or longer and contains an @ that's not the first or last char 1150 return (strlen($address) >= 3 1151 and strpos($address, '@') >= 1 1152 and strpos($address, '@') != strlen($address) - 1); 1153 case 'php': 1154 default: 1155 return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL); 1156 } 1157 } 1158 1159 /** 1160 * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the 1161 * "intl" and "mbstring" PHP extensions. 1162 * @return bool "true" if required functions for IDN support are present 1163 */ 1164 public function idnSupported() 1165 { 1166 // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2. 1167 return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding'); 1168 } 1169 1170 /** 1171 * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. 1172 * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. 1173 * This function silently returns unmodified address if: 1174 * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) 1175 * - Conversion to punycode is impossible (e.g. required PHP functions are not available) 1176 * or fails for any reason (e.g. domain has characters not allowed in an IDN) 1177 * @see PHPMailer::$CharSet 1178 * @param string $address The email address to convert 1179 * @return string The encoded address in ASCII form 1180 */ 1181 public function punyencodeAddress($address) 1182 { 1183 // Verify we have required functions, CharSet, and at-sign. 1184 if ($this->idnSupported() and 1185 !empty($this->CharSet) and 1186 ($pos = strrpos($address, '@')) !== false) { 1187 $domain = substr($address, ++$pos); 1188 // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. 1189 if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) { 1190 $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); 1191 if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ? 1192 idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) : 1193 idn_to_ascii($domain)) !== false) { 1194 return substr($address, 0, $pos) . $punycode; 1195 } 1196 } 1197 } 1198 return $address; 1199 } 1200 1201 /** 1202 * Create a message and send it. 1203 * Uses the sending method specified by $Mailer. 1204 * @throws phpmailerException 1205 * @return boolean false on error - See the ErrorInfo property for details of the error. 1206 */ 1207 public function send() 1208 { 1209 try { 1210 if (!$this->preSend()) { 1211 return false; 1212 } 1213 return $this->postSend(); 1214 } catch (phpmailerException $exc) { 1215 $this->mailHeader = ''; 1216 $this->setError($exc->getMessage()); 1217 if ($this->exceptions) { 1218 throw $exc; 1219 } 1220 return false; 1221 } 1222 } 1223 1224 /** 1225 * Prepare a message for sending. 1226 * @throws phpmailerException 1227 * @return boolean 1228 */ 1229 public function preSend() 1230 { 1231 try { 1232 $this->error_count = 0; // Reset errors 1233 $this->mailHeader = ''; 1234 1235 // Dequeue recipient and Reply-To addresses with IDN 1236 foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { 1237 $params[1] = $this->punyencodeAddress($params[1]); 1238 call_user_func_array(array($this, 'addAnAddress'), $params); 1239 } 1240 if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { 1241 throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL); 1242 } 1243 1244 // Validate From, Sender, and ConfirmReadingTo addresses 1245 foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) { 1246 $this->$address_kind = trim($this->$address_kind); 1247 if (empty($this->$address_kind)) { 1248 continue; 1249 } 1250 $this->$address_kind = $this->punyencodeAddress($this->$address_kind); 1251 if (!$this->validateAddress($this->$address_kind)) { 1252 $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind; 1253 $this->setError($error_message); 1254 $this->edebug($error_message); 1255 if ($this->exceptions) { 1256 throw new phpmailerException($error_message); 1257 } 1258 return false; 1259 } 1260 } 1261 1262 // Set whether the message is multipart/alternative 1263 if ($this->alternativeExists()) { 1264 $this->ContentType = 'multipart/alternative'; 1265 } 1266 1267 $this->setMessageType(); 1268 // Refuse to send an empty message unless we are specifically allowing it 1269 if (!$this->AllowEmpty and empty($this->Body)) { 1270 throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL); 1271 } 1272 1273 // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) 1274 $this->MIMEHeader = ''; 1275 $this->MIMEBody = $this->createBody(); 1276 // createBody may have added some headers, so retain them 1277 $tempheaders = $this->MIMEHeader; 1278 $this->MIMEHeader = $this->createHeader(); 1279 $this->MIMEHeader .= $tempheaders; 1280 1281 // To capture the complete message when using mail(), create 1282 // an extra header list which createHeader() doesn't fold in 1283 if ($this->Mailer == 'mail') { 1284 if (count($this->to) > 0) { 1285 $this->mailHeader .= $this->addrAppend('To', $this->to); 1286 } else { 1287 $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); 1288 } 1289 $this->mailHeader .= $this->headerLine( 1290 'Subject', 1291 $this->encodeHeader($this->secureHeader(trim($this->Subject))) 1292 ); 1293 } 1294 1295 // Sign with DKIM if enabled 1296 if (!empty($this->DKIM_domain) 1297 && !empty($this->DKIM_selector) 1298 && (!empty($this->DKIM_private_string) 1299 || (!empty($this->DKIM_private) && file_exists($this->DKIM_private)) 1300 ) 1301 ) { 1302 $header_dkim = $this->DKIM_Add( 1303 $this->MIMEHeader . $this->mailHeader, 1304 $this->encodeHeader($this->secureHeader($this->Subject)), 1305 $this->MIMEBody 1306 ); 1307 $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF . 1308 str_replace("\r\n", "\n", $header_dkim) . self::CRLF; 1309 } 1310 return true; 1311 } catch (phpmailerException $exc) { 1312 $this->setError($exc->getMessage()); 1313 if ($this->exceptions) { 1314 throw $exc; 1315 } 1316 return false; 1317 } 1318 } 1319 1320 /** 1321 * Actually send a message. 1322 * Send the email via the selected mechanism 1323 * @throws phpmailerException 1324 * @return boolean 1325 */ 1326 public function postSend() 1327 { 1328 try { 1329 // Choose the mailer and send through it 1330 switch ($this->Mailer) { 1331 case 'sendmail': 1332 case 'qmail': 1333 return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); 1334 case 'smtp': 1335 return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); 1336 case 'mail': 1337 return $this->mailSend($this->MIMEHeader, $this->MIMEBody); 1338 default: 1339 $sendMethod = $this->Mailer.'Send'; 1340 if (method_exists($this, $sendMethod)) { 1341 return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); 1342 } 1343 1344 return $this->mailSend($this->MIMEHeader, $this->MIMEBody); 1345 } 1346 } catch (phpmailerException $exc) { 1347 $this->setError($exc->getMessage()); 1348 $this->edebug($exc->getMessage()); 1349 if ($this->exceptions) { 1350 throw $exc; 1351 } 1352 } 1353 return false; 1354 } 1355 1356 /** 1357 * Send mail using the $Sendmail program. 1358 * @param string $header The message headers 1359 * @param string $body The message body 1360 * @see PHPMailer::$Sendmail 1361 * @throws phpmailerException 1362 * @access protected 1363 * @return boolean 1364 */ 1365 protected function sendmailSend($header, $body) 1366 { 1367 // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. 1368 if (!empty($this->Sender) and self::isShellSafe($this->Sender)) { 1369 if ($this->Mailer == 'qmail') { 1370 $sendmailFmt = '%s -f%s'; 1371 } else { 1372 $sendmailFmt = '%s -oi -f%s -t'; 1373 } 1374 } else { 1375 if ($this->Mailer == 'qmail') { 1376 $sendmailFmt = '%s'; 1377 } else { 1378 $sendmailFmt = '%s -oi -t'; 1379 } 1380 } 1381 1382 // TODO: If possible, this should be changed to escapeshellarg. Needs thorough testing. 1383 $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); 1384 1385 if ($this->SingleTo) { 1386 foreach ($this->SingleToArray as $toAddr) { 1387 if (!@$mail = popen($sendmail, 'w')) { 1388 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1389 } 1390 fputs($mail, 'To: ' . $toAddr . "\n"); 1391 fputs($mail, $header); 1392 fputs($mail, $body); 1393 $result = pclose($mail); 1394 $this->doCallback( 1395 ($result == 0), 1396 array($toAddr), 1397 $this->cc, 1398 $this->bcc, 1399 $this->Subject, 1400 $body, 1401 $this->From 1402 ); 1403 if ($result != 0) { 1404 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1405 } 1406 } 1407 } else { 1408 if (!@$mail = popen($sendmail, 'w')) { 1409 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1410 } 1411 fputs($mail, $header); 1412 fputs($mail, $body); 1413 $result = pclose($mail); 1414 $this->doCallback( 1415 ($result == 0), 1416 $this->to, 1417 $this->cc, 1418 $this->bcc, 1419 $this->Subject, 1420 $body, 1421 $this->From 1422 ); 1423 if ($result != 0) { 1424 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1425 } 1426 } 1427 return true; 1428 } 1429 1430 /** 1431 * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. 1432 * 1433 * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. 1434 * @param string $string The string to be validated 1435 * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report 1436 * @access protected 1437 * @return boolean 1438 */ 1439 protected static function isShellSafe($string) 1440 { 1441 // Future-proof 1442 if (escapeshellcmd($string) !== $string 1443 or !in_array(escapeshellarg($string), array("'$string'", "\"$string\"")) 1444 ) { 1445 return false; 1446 } 1447 1448 $length = strlen($string); 1449 1450 for ($i = 0; $i < $length; $i++) { 1451 $c = $string[$i]; 1452 1453 // All other characters have a special meaning in at least one common shell, including = and +. 1454 // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. 1455 // Note that this does permit non-Latin alphanumeric characters based on the current locale. 1456 if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { 1457 return false; 1458 } 1459 } 1460 1461 return true; 1462 } 1463 1464 /** 1465 * Send mail using the PHP mail() function. 1466 * @param string $header The message headers 1467 * @param string $body The message body 1468 * @link http://www.php.net/manual/en/book.mail.php 1469 * @throws phpmailerException 1470 * @access protected 1471 * @return boolean 1472 */ 1473 protected function mailSend($header, $body) 1474 { 1475 $toArr = array(); 1476 foreach ($this->to as $toaddr) { 1477 $toArr[] = $this->addrFormat($toaddr); 1478 } 1479 $to = implode(', ', $toArr); 1480 1481 $params = null; 1482 //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver 1483 if (!empty($this->Sender) and $this->validateAddress($this->Sender)) { 1484 // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. 1485 if (self::isShellSafe($this->Sender)) { 1486 $params = sprintf('-f%s', $this->Sender); 1487 } 1488 } 1489 if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) { 1490 $old_from = ini_get('sendmail_from'); 1491 ini_set('sendmail_from', $this->Sender); 1492 } 1493 $result = false; 1494 if ($this->SingleTo and count($toArr) > 1) { 1495 foreach ($toArr as $toAddr) { 1496 $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); 1497 $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1498 } 1499 } else { 1500 $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); 1501 $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1502 } 1503 if (isset($old_from)) { 1504 ini_set('sendmail_from', $old_from); 1505 } 1506 if (!$result) { 1507 throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL); 1508 } 1509 return true; 1510 } 1511 1512 /** 1513 * Get an instance to use for SMTP operations. 1514 * Override this function to load your own SMTP implementation 1515 * @return SMTP 1516 */ 1517 public function getSMTPInstance() 1518 { 1519 if (!is_object($this->smtp)) { 1520 require_once( 'class-smtp.php' ); 1521 $this->smtp = new SMTP; 1522 } 1523 return $this->smtp; 1524 } 1525 1526 /** 1527 * Send mail via SMTP. 1528 * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. 1529 * Uses the PHPMailerSMTP class by default. 1530 * @see PHPMailer::getSMTPInstance() to use a different class. 1531 * @param string $header The message headers 1532 * @param string $body The message body 1533 * @throws phpmailerException 1534 * @uses SMTP 1535 * @access protected 1536 * @return boolean 1537 */ 1538 protected function smtpSend($header, $body) 1539 { 1540 $bad_rcpt = array(); 1541 if (!$this->smtpConnect($this->SMTPOptions)) { 1542 throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); 1543 } 1544 if (!empty($this->Sender) and $this->validateAddress($this->Sender)) { 1545 $smtp_from = $this->Sender; 1546 } else { 1547 $smtp_from = $this->From; 1548 } 1549 if (!$this->smtp->mail($smtp_from)) { 1550 $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); 1551 throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL); 1552 } 1553 1554 // Attempt to send to all recipients 1555 foreach (array($this->to, $this->cc, $this->bcc) as $togroup) { 1556 foreach ($togroup as $to) { 1557 if (!$this->smtp->recipient($to[0])) { 1558 $error = $this->smtp->getError(); 1559 $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']); 1560 $isSent = false; 1561 } else { 1562 $isSent = true; 1563 } 1564 $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From); 1565 } 1566 } 1567 1568 // Only send the DATA command if we have viable recipients 1569 if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) { 1570 throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL); 1571 } 1572 if ($this->SMTPKeepAlive) { 1573 $this->smtp->reset(); 1574 } else { 1575 $this->smtp->quit(); 1576 $this->smtp->close(); 1577 } 1578 //Create error message for any bad addresses 1579 if (count($bad_rcpt) > 0) { 1580 $errstr = ''; 1581 foreach ($bad_rcpt as $bad) { 1582 $errstr .= $bad['to'] . ': ' . $bad['error']; 1583 } 1584 throw new phpmailerException( 1585 $this->lang('recipients_failed') . $errstr, 1586 self::STOP_CONTINUE 1587 ); 1588 } 1589 return true; 1590 } 1591 1592 /** 1593 * Initiate a connection to an SMTP server. 1594 * Returns false if the operation failed. 1595 * @param array $options An array of options compatible with stream_context_create() 1596 * @uses SMTP 1597 * @access public 1598 * @throws phpmailerException 1599 * @return boolean 1600 */ 1601 public function smtpConnect($options = null) 1602 { 1603 if (is_null($this->smtp)) { 1604 $this->smtp = $this->getSMTPInstance(); 1605 } 1606 1607 //If no options are provided, use whatever is set in the instance 1608 if (is_null($options)) { 1609 $options = $this->SMTPOptions; 1610 } 1611 1612 // Already connected? 1613 if ($this->smtp->connected()) { 1614 return true; 1615 } 1616 1617 $this->smtp->setTimeout($this->Timeout); 1618 $this->smtp->setDebugLevel($this->SMTPDebug); 1619 $this->smtp->setDebugOutput($this->Debugoutput); 1620 $this->smtp->setVerp($this->do_verp); 1621 $hosts = explode(';', $this->Host); 1622 $lastexception = null; 1623 1624 foreach ($hosts as $hostentry) { 1625 $hostinfo = array(); 1626 if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { 1627 // Not a valid host entry 1628 continue; 1629 } 1630 // $hostinfo[2]: optional ssl or tls prefix 1631 // $hostinfo[3]: the hostname 1632 // $hostinfo[4]: optional port number 1633 // The host string prefix can temporarily override the current setting for SMTPSecure 1634 // If it's not specified, the default value is used 1635 $prefix = ''; 1636 $secure = $this->SMTPSecure; 1637 $tls = ($this->SMTPSecure == 'tls'); 1638 if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { 1639 $prefix = 'ssl://'; 1640 $tls = false; // Can't have SSL and TLS at the same time 1641 $secure = 'ssl'; 1642 } elseif ($hostinfo[2] == 'tls') { 1643 $tls = true; 1644 // tls doesn't use a prefix 1645 $secure = 'tls'; 1646 } 1647 //Do we need the OpenSSL extension? 1648 $sslext = defined('OPENSSL_ALGO_SHA1'); 1649 if ('tls' === $secure or 'ssl' === $secure) { 1650 //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled 1651 if (!$sslext) { 1652 throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL); 1653 } 1654 } 1655 $host = $hostinfo[3]; 1656 $port = $this->Port; 1657 $tport = (integer)$hostinfo[4]; 1658 if ($tport > 0 and $tport < 65536) { 1659 $port = $tport; 1660 } 1661 if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { 1662 try { 1663 if ($this->Helo) { 1664 $hello = $this->Helo; 1665 } else { 1666 $hello = $this->serverHostname(); 1667 } 1668 $this->smtp->hello($hello); 1669 //Automatically enable TLS encryption if: 1670 // * it's not disabled 1671 // * we have openssl extension 1672 // * we are not already using SSL 1673 // * the server offers STARTTLS 1674 if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) { 1675 $tls = true; 1676 } 1677 if ($tls) { 1678 if (!$this->smtp->startTLS()) { 1679 throw new phpmailerException($this->lang('connect_host')); 1680 } 1681 // We must resend EHLO after TLS negotiation 1682 $this->smtp->hello($hello); 1683 } 1684 if ($this->SMTPAuth) { 1685 if (!$this->smtp->authenticate( 1686 $this->Username, 1687 $this->Password, 1688 $this->AuthType, 1689 $this->Realm, 1690 $this->Workstation 1691 ) 1692 ) { 1693 throw new phpmailerException($this->lang('authenticate')); 1694 } 1695 } 1696 return true; 1697 } catch (phpmailerException $exc) { 1698 $lastexception = $exc; 1699 $this->edebug($exc->getMessage()); 1700 // We must have connected, but then failed TLS or Auth, so close connection nicely 1701 $this->smtp->quit(); 1702 } 1703 } 1704 } 1705 // If we get here, all connection attempts have failed, so close connection hard 1706 $this->smtp->close(); 1707 // As we've caught all exceptions, just report whatever the last one was 1708 if ($this->exceptions and !is_null($lastexception)) { 1709 throw $lastexception; 1710 } 1711 return false; 1712 } 1713 1714 /** 1715 * Close the active SMTP session if one exists. 1716 * @return void 1717 */ 1718 public function smtpClose() 1719 { 1720 if (is_a($this->smtp, 'SMTP')) { 1721 if ($this->smtp->connected()) { 1722 $this->smtp->quit(); 1723 $this->smtp->close(); 1724 } 1725 } 1726 } 1727 1728 /** 1729 * Set the language for error messages. 1730 * Returns false if it cannot load the language file. 1731 * The default language is English. 1732 * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") 1733 * @param string $lang_path Path to the language file directory, with trailing separator (slash) 1734 * @return boolean 1735 * @access public 1736 */ 1737 public function setLanguage($langcode = 'en', $lang_path = '') 1738 { 1739 // Backwards compatibility for renamed language codes 1740 $renamed_langcodes = array( 1741 'br' => 'pt_br', 1742 'cz' => 'cs', 1743 'dk' => 'da', 1744 'no' => 'nb', 1745 'se' => 'sv', 1746 ); 1747 1748 if (isset($renamed_langcodes[$langcode])) { 1749 $langcode = $renamed_langcodes[$langcode]; 1750 } 1751 1752 // Define full set of translatable strings in English 1753 $PHPMAILER_LANG = array( 1754 'authenticate' => 'SMTP Error: Could not authenticate.', 1755 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', 1756 'data_not_accepted' => 'SMTP Error: data not accepted.', 1757 'empty_message' => 'Message body empty', 1758 'encoding' => 'Unknown encoding: ', 1759 'execute' => 'Could not execute: ', 1760 'file_access' => 'Could not access file: ', 1761 'file_open' => 'File Error: Could not open file: ', 1762 'from_failed' => 'The following From address failed: ', 1763 'instantiate' => 'Could not instantiate mail function.', 1764 'invalid_address' => 'Invalid address: ', 1765 'mailer_not_supported' => ' mailer is not supported.', 1766 'provide_address' => 'You must provide at least one recipient email address.', 1767 'recipients_failed' => 'SMTP Error: The following recipients failed: ', 1768 'signing' => 'Signing Error: ', 1769 'smtp_connect_failed' => 'SMTP connect() failed.', 1770 'smtp_error' => 'SMTP server error: ', 1771 'variable_set' => 'Cannot set or reset variable: ', 1772 'extension_missing' => 'Extension missing: ' 1773 ); 1774 if (empty($lang_path)) { 1775 // Calculate an absolute path so it can work if CWD is not here 1776 $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR; 1777 } 1778 //Validate $langcode 1779 if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { 1780 $langcode = 'en'; 1781 } 1782 $foundlang = true; 1783 $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; 1784 // There is no English translation file 1785 if ($langcode != 'en') { 1786 // Make sure language file path is readable 1787 if (!is_readable($lang_file)) { 1788 $foundlang = false; 1789 } else { 1790 // Overwrite language-specific strings. 1791 // This way we'll never have missing translation keys. 1792 $foundlang = include $lang_file; 1793 } 1794 } 1795 $this->language = $PHPMAILER_LANG; 1796 return (boolean)$foundlang; // Returns false if language not found 1797 } 1798 1799 /** 1800 * Get the array of strings for the current language. 1801 * @return array 1802 */ 1803 public function getTranslations() 1804 { 1805 return $this->language; 1806 } 1807 1808 /** 1809 * Create recipient headers. 1810 * @access public 1811 * @param string $type 1812 * @param array $addr An array of recipient, 1813 * where each recipient is a 2-element indexed array with element 0 containing an address 1814 * and element 1 containing a name, like: 1815 * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User')) 1816 * @return string 1817 */ 1818 public function addrAppend($type, $addr) 1819 { 1820 $addresses = array(); 1821 foreach ($addr as $address) { 1822 $addresses[] = $this->addrFormat($address); 1823 } 1824 return $type . ': ' . implode(', ', $addresses) . $this->LE; 1825 } 1826 1827 /** 1828 * Format an address for use in a message header. 1829 * @access public 1830 * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name 1831 * like array('joe@example.com', 'Joe User') 1832 * @return string 1833 */ 1834 public function addrFormat($addr) 1835 { 1836 if (empty($addr[1])) { // No name provided 1837 return $this->secureHeader($addr[0]); 1838 } else { 1839 return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader( 1840 $addr[0] 1841 ) . '>'; 1842 } 1843 } 1844 1845 /** 1846 * Word-wrap message. 1847 * For use with mailers that do not automatically perform wrapping 1848 * and for quoted-printable encoded messages. 1849 * Original written by philippe. 1850 * @param string $message The message to wrap 1851 * @param integer $length The line length to wrap to 1852 * @param boolean $qp_mode Whether to run in Quoted-Printable mode 1853 * @access public 1854 * @return string 1855 */ 1856 public function wrapText($message, $length, $qp_mode = false) 1857 { 1858 if ($qp_mode) { 1859 $soft_break = sprintf(' =%s', $this->LE); 1860 } else { 1861 $soft_break = $this->LE; 1862 } 1863 // If utf-8 encoding is used, we will need to make sure we don't 1864 // split multibyte characters when we wrap 1865 $is_utf8 = (strtolower($this->CharSet) == 'utf-8'); 1866 $lelen = strlen($this->LE); 1867 $crlflen = strlen(self::CRLF); 1868 1869 $message = $this->fixEOL($message); 1870 //Remove a trailing line break 1871 if (substr($message, -$lelen) == $this->LE) { 1872 $message = substr($message, 0, -$lelen); 1873 } 1874 1875 //Split message into lines 1876 $lines = explode($this->LE, $message); 1877 //Message will be rebuilt in here 1878 $message = ''; 1879 foreach ($lines as $line) { 1880 $words = explode(' ', $line); 1881 $buf = ''; 1882 $firstword = true; 1883 foreach ($words as $word) { 1884 if ($qp_mode and (strlen($word) > $length)) { 1885 $space_left = $length - strlen($buf) - $crlflen; 1886 if (!$firstword) { 1887 if ($space_left > 20) { 1888 $len = $space_left; 1889 if ($is_utf8) { 1890 $len = $this->utf8CharBoundary($word, $len); 1891 } elseif (substr($word, $len - 1, 1) == '=') { 1892 $len--; 1893 } elseif (substr($word, $len - 2, 1) == '=') { 1894 $len -= 2; 1895 } 1896 $part = substr($word, 0, $len); 1897 $word = substr($word, $len); 1898 $buf .= ' ' . $part; 1899 $message .= $buf . sprintf('=%s', self::CRLF); 1900 } else { 1901 $message .= $buf . $soft_break; 1902 } 1903 $buf = ''; 1904 } 1905 while (strlen($word) > 0) { 1906 if ($length <= 0) { 1907 break; 1908 } 1909 $len = $length; 1910 if ($is_utf8) { 1911 $len = $this->utf8CharBoundary($word, $len); 1912 } elseif (substr($word, $len - 1, 1) == '=') { 1913 $len--; 1914 } elseif (substr($word, $len - 2, 1) == '=') { 1915 $len -= 2; 1916 } 1917 $part = substr($word, 0, $len); 1918 $word = substr($word, $len); 1919 1920 if (strlen($word) > 0) { 1921 $message .= $part . sprintf('=%s', self::CRLF); 1922 } else { 1923 $buf = $part; 1924 } 1925 } 1926 } else { 1927 $buf_o = $buf; 1928 if (!$firstword) { 1929 $buf .= ' '; 1930 } 1931 $buf .= $word; 1932 1933 if (strlen($buf) > $length and $buf_o != '') { 1934 $message .= $buf_o . $soft_break; 1935 $buf = $word; 1936 } 1937 } 1938 $firstword = false; 1939 } 1940 $message .= $buf . self::CRLF; 1941 } 1942 1943 return $message; 1944 } 1945 1946 /** 1947 * Find the last character boundary prior to $maxLength in a utf-8 1948 * quoted-printable encoded string. 1949 * Original written by Colin Brown. 1950 * @access public 1951 * @param string $encodedText utf-8 QP text 1952 * @param integer $maxLength Find the last character boundary prior to this length 1953 * @return integer 1954 */ 1955 public function utf8CharBoundary($encodedText, $maxLength) 1956 { 1957 $foundSplitPos = false; 1958 $lookBack = 3; 1959 while (!$foundSplitPos) { 1960 $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); 1961 $encodedCharPos = strpos($lastChunk, '='); 1962 if (false !== $encodedCharPos) { 1963 // Found start of encoded character byte within $lookBack block. 1964 // Check the encoded byte value (the 2 chars after the '=') 1965 $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); 1966 $dec = hexdec($hex); 1967 if ($dec < 128) { 1968 // Single byte character. 1969 // If the encoded char was found at pos 0, it will fit 1970 // otherwise reduce maxLength to start of the encoded char 1971 if ($encodedCharPos > 0) { 1972 $maxLength = $maxLength - ($lookBack - $encodedCharPos); 1973 } 1974 $foundSplitPos = true; 1975 } elseif ($dec >= 192) { 1976 // First byte of a multi byte character 1977 // Reduce maxLength to split at start of character 1978 $maxLength = $maxLength - ($lookBack - $encodedCharPos); 1979 $foundSplitPos = true; 1980 } elseif ($dec < 192) { 1981 // Middle byte of a multi byte character, look further back 1982 $lookBack += 3; 1983 } 1984 } else { 1985 // No encoded character found 1986 $foundSplitPos = true; 1987 } 1988 } 1989 return $maxLength; 1990 } 1991 1992 /** 1993 * Apply word wrapping to the message body. 1994 * Wraps the message body to the number of chars set in the WordWrap property. 1995 * You should only do this to plain-text bodies as wrapping HTML tags may break them. 1996 * This is called automatically by createBody(), so you don't need to call it yourself. 1997 * @access public 1998 * @return void 1999 */ 2000 public function setWordWrap() 2001 { 2002 if ($this->WordWrap < 1) { 2003 return; 2004 } 2005 2006 switch ($this->message_type) { 2007 case 'alt': 2008 case 'alt_inline': 2009 case 'alt_attach': 2010 case 'alt_inline_attach': 2011 $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); 2012 break; 2013 default: 2014 $this->Body = $this->wrapText($this->Body, $this->WordWrap); 2015 break; 2016 } 2017 } 2018 2019 /** 2020 * Assemble message headers. 2021 * @access public 2022 * @return string The assembled headers 2023 */ 2024 public function createHeader() 2025 { 2026 $result = ''; 2027 2028 if ($this->MessageDate == '') { 2029 $this->MessageDate = self::rfcDate(); 2030 } 2031 $result .= $this->headerLine('Date', $this->MessageDate); 2032 2033 // To be created automatically by mail() 2034 if ($this->SingleTo) { 2035 if ($this->Mailer != 'mail') { 2036 foreach ($this->to as $toaddr) { 2037 $this->SingleToArray[] = $this->addrFormat($toaddr); 2038 } 2039 } 2040 } else { 2041 if (count($this->to) > 0) { 2042 if ($this->Mailer != 'mail') { 2043 $result .= $this->addrAppend('To', $this->to); 2044 } 2045 } elseif (count($this->cc) == 0) { 2046 $result .= $this->headerLine('To', 'undisclosed-recipients:;'); 2047 } 2048 } 2049 2050 $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName))); 2051 2052 // sendmail and mail() extract Cc from the header before sending 2053 if (count($this->cc) > 0) { 2054 $result .= $this->addrAppend('Cc', $this->cc); 2055 } 2056 2057 // sendmail and mail() extract Bcc from the header before sending 2058 if (( 2059 $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail' 2060 ) 2061 and count($this->bcc) > 0 2062 ) { 2063 $result .= $this->addrAppend('Bcc', $this->bcc); 2064 } 2065 2066 if (count($this->ReplyTo) > 0) { 2067 $result .= $this->addrAppend('Reply-To', $this->ReplyTo); 2068 } 2069 2070 // mail() sets the subject itself 2071 if ($this->Mailer != 'mail') { 2072 $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); 2073 } 2074 2075 // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 2076 // https://tools.ietf.org/html/rfc5322#section-3.6.4 2077 if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) { 2078 $this->lastMessageID = $this->MessageID; 2079 } else { 2080 $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); 2081 } 2082 $result .= $this->headerLine('Message-ID', $this->lastMessageID); 2083 if (!is_null($this->Priority)) { 2084 $result .= $this->headerLine('X-Priority', $this->Priority); 2085 } 2086 if ($this->XMailer == '') { 2087 $result .= $this->headerLine( 2088 'X-Mailer', 2089 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)' 2090 ); 2091 } else { 2092 $myXmailer = trim($this->XMailer); 2093 if ($myXmailer) { 2094 $result .= $this->headerLine('X-Mailer', $myXmailer); 2095 } 2096 } 2097 2098 if ($this->ConfirmReadingTo != '') { 2099 $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); 2100 } 2101 2102 // Add custom headers 2103 foreach ($this->CustomHeader as $header) { 2104 $result .= $this->headerLine( 2105 trim($header[0]), 2106 $this->encodeHeader(trim($header[1])) 2107 ); 2108 } 2109 if (!$this->sign_key_file) { 2110 $result .= $this->headerLine('MIME-Version', '1.0'); 2111 $result .= $this->getMailMIME(); 2112 } 2113 2114 return $result; 2115 } 2116 2117 /** 2118 * Get the message MIME type headers. 2119 * @access public 2120 * @return string 2121 */ 2122 public function getMailMIME() 2123 { 2124 $result = ''; 2125 $ismultipart = true; 2126 switch ($this->message_type) { 2127 case 'inline': 2128 $result .= $this->headerLine('Content-Type', 'multipart/related;'); 2129 $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); 2130 break; 2131 case 'attach': 2132 case 'inline_attach': 2133 case 'alt_attach': 2134 case 'alt_inline_attach': 2135 $result .= $this->headerLine('Content-Type', 'multipart/mixed;'); 2136 $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); 2137 break; 2138 case 'alt': 2139 case 'alt_inline': 2140 $result .= $this->headerLine('Content-Type', 'multipart/alternative;'); 2141 $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); 2142 break; 2143 default: 2144 // Catches case 'plain': and case '': 2145 $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); 2146 $ismultipart = false; 2147 break; 2148 } 2149 // RFC1341 part 5 says 7bit is assumed if not specified 2150 if ($this->Encoding != '7bit') { 2151 // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE 2152 if ($ismultipart) { 2153 if ($this->Encoding == '8bit') { 2154 $result .= $this->headerLine('Content-Transfer-Encoding', '8bit'); 2155 } 2156 // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible 2157 } else { 2158 $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); 2159 } 2160 } 2161 2162 if ($this->Mailer != 'mail') { 2163 $result .= $this->LE; 2164 } 2165 2166 return $result; 2167 } 2168 2169 /** 2170 * Returns the whole MIME message. 2171 * Includes complete headers and body. 2172 * Only valid post preSend(). 2173 * @see PHPMailer::preSend() 2174 * @access public 2175 * @return string 2176 */ 2177 public function getSentMIMEMessage() 2178 { 2179 return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody; 2180 } 2181 2182 /** 2183 * Create unique ID 2184 * @return string 2185 */ 2186 protected function generateId() { 2187 return md5(uniqid(time())); 2188 } 2189 2190 /** 2191 * Assemble the message body. 2192 * Returns an empty string on failure. 2193 * @access public 2194 * @throws phpmailerException 2195 * @return string The assembled message body 2196 */ 2197 public function createBody() 2198 { 2199 $body = ''; 2200 //Create unique IDs and preset boundaries 2201 $this->uniqueid = $this->generateId(); 2202 $this->boundary[1] = 'b1_' . $this->uniqueid; 2203 $this->boundary[2] = 'b2_' . $this->uniqueid; 2204 $this->boundary[3] = 'b3_' . $this->uniqueid; 2205 2206 if ($this->sign_key_file) { 2207 $body .= $this->getMailMIME() . $this->LE; 2208 } 2209 2210 $this->setWordWrap(); 2211 2212 $bodyEncoding = $this->Encoding; 2213 $bodyCharSet = $this->CharSet; 2214 //Can we do a 7-bit downgrade? 2215 if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) { 2216 $bodyEncoding = '7bit'; 2217 //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit 2218 $bodyCharSet = 'us-ascii'; 2219 } 2220 //If lines are too long, and we're not already using an encoding that will shorten them, 2221 //change to quoted-printable transfer encoding for the body part only 2222 if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) { 2223 $bodyEncoding = 'quoted-printable'; 2224 } 2225 2226 $altBodyEncoding = $this->Encoding; 2227 $altBodyCharSet = $this->CharSet; 2228 //Can we do a 7-bit downgrade? 2229 if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) { 2230 $altBodyEncoding = '7bit'; 2231 //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit 2232 $altBodyCharSet = 'us-ascii'; 2233 } 2234 //If lines are too long, and we're not already using an encoding that will shorten them, 2235 //change to quoted-printable transfer encoding for the alt body part only 2236 if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) { 2237 $altBodyEncoding = 'quoted-printable'; 2238 } 2239 //Use this as a preamble in all multipart message types 2240 $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE; 2241 switch ($this->message_type) { 2242 case 'inline': 2243 $body .= $mimepre; 2244 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); 2245 $body .= $this->encodeString($this->Body, $bodyEncoding); 2246 $body .= $this->LE . $this->LE; 2247 $body .= $this->attachAll('inline', $this->boundary[1]); 2248 break; 2249 case 'attach': 2250 $body .= $mimepre; 2251 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); 2252 $body .= $this->encodeString($this->Body, $bodyEncoding); 2253 $body .= $this->LE . $this->LE; 2254 $body .= $this->attachAll('attachment', $this->boundary[1]); 2255 break; 2256 case 'inline_attach': 2257 $body .= $mimepre; 2258 $body .= $this->textLine('--' . $this->boundary[1]); 2259 $body .= $this->headerLine('Content-Type', 'multipart/related;'); 2260 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 2261 $body .= $this->LE; 2262 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); 2263 $body .= $this->encodeString($this->Body, $bodyEncoding); 2264 $body .= $this->LE . $this->LE; 2265 $body .= $this->attachAll('inline', $this->boundary[2]); 2266 $body .= $this->LE; 2267 $body .= $this->attachAll('attachment', $this->boundary[1]); 2268 break; 2269 case 'alt': 2270 $body .= $mimepre; 2271 $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); 2272 $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 2273 $body .= $this->LE . $this->LE; 2274 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding); 2275 $body .= $this->encodeString($this->Body, $bodyEncoding); 2276 $body .= $this->LE . $this->LE; 2277 if (!empty($this->Ical)) { 2278 $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', ''); 2279 $body .= $this->encodeString($this->Ical, $this->Encoding); 2280 $body .= $this->LE . $this->LE; 2281 } 2282 $body .= $this->endBoundary($this->boundary[1]); 2283 break; 2284 case 'alt_inline': 2285 $body .= $mimepre; 2286 $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); 2287 $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 2288 $body .= $this->LE . $this->LE; 2289 $body .= $this->textLine('--' . $this->boundary[1]); 2290 $body .= $this->headerLine('Content-Type', 'multipart/related;'); 2291 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 2292 $body .= $this->LE; 2293 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); 2294 $body .= $this->encodeString($this->Body, $bodyEncoding); 2295 $body .= $this->LE . $this->LE; 2296 $body .= $this->attachAll('inline', $this->boundary[2]); 2297 $body .= $this->LE; 2298 $body .= $this->endBoundary($this->boundary[1]); 2299 break; 2300 case 'alt_attach': 2301 $body .= $mimepre; 2302 $body .= $this->textLine('--' . $this->boundary[1]); 2303 $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); 2304 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 2305 $body .= $this->LE; 2306 $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); 2307 $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 2308 $body .= $this->LE . $this->LE; 2309 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); 2310 $body .= $this->encodeString($this->Body, $bodyEncoding); 2311 $body .= $this->LE . $this->LE; 2312 $body .= $this->endBoundary($this->boundary[2]); 2313 $body .= $this->LE; 2314 $body .= $this->attachAll('attachment', $this->boundary[1]); 2315 break; 2316 case 'alt_inline_attach': 2317 $body .= $mimepre; 2318 $body .= $this->textLine('--' . $this->boundary[1]); 2319 $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); 2320 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 2321 $body .= $this->LE; 2322 $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); 2323 $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 2324 $body .= $this->LE . $this->LE; 2325 $body .= $this->textLine('--' . $this->boundary[2]); 2326 $body .= $this->headerLine('Content-Type', 'multipart/related;'); 2327 $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"'); 2328 $body .= $this->LE; 2329 $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding); 2330 $body .= $this->encodeString($this->Body, $bodyEncoding); 2331 $body .= $this->LE . $this->LE; 2332 $body .= $this->attachAll('inline', $this->boundary[3]); 2333 $body .= $this->LE; 2334 $body .= $this->endBoundary($this->boundary[2]); 2335 $body .= $this->LE; 2336 $body .= $this->attachAll('attachment', $this->boundary[1]); 2337 break; 2338 default: 2339 // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types 2340 //Reset the `Encoding` property in case we changed it for line length reasons 2341 $this->Encoding = $bodyEncoding; 2342 $body .= $this->encodeString($this->Body, $this->Encoding); 2343 break; 2344 } 2345 2346 if ($this->isError()) { 2347 $body = ''; 2348 } elseif ($this->sign_key_file) { 2349 try { 2350 if (!defined('PKCS7_TEXT')) { 2351 throw new phpmailerException($this->lang('extension_missing') . 'openssl'); 2352 } 2353 // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1 2354 $file = tempnam(sys_get_temp_dir(), 'mail'); 2355 if (false === file_put_contents($file, $body)) { 2356 throw new phpmailerException($this->lang('signing') . ' Could not write temp file'); 2357 } 2358 $signed = tempnam(sys_get_temp_dir(), 'signed'); 2359 //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 2360 if (empty($this->sign_extracerts_file)) { 2361 $sign = @openssl_pkcs7_sign( 2362 $file, 2363 $signed, 2364 'file://' . realpath($this->sign_cert_file), 2365 array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), 2366 null 2367 ); 2368 } else { 2369 $sign = @openssl_pkcs7_sign( 2370 $file, 2371 $signed, 2372 'file://' . realpath($this->sign_cert_file), 2373 array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), 2374 null, 2375 PKCS7_DETACHED, 2376 $this->sign_extracerts_file 2377 ); 2378 } 2379 if ($sign) { 2380 @unlink($file); 2381 $body = file_get_contents($signed); 2382 @unlink($signed); 2383 //The message returned by openssl contains both headers and body, so need to split them up 2384 $parts = explode("\n\n", $body, 2); 2385 $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE; 2386 $body = $parts[1]; 2387 } else { 2388 @unlink($file); 2389 @unlink($signed); 2390 throw new phpmailerException($this->lang('signing') . openssl_error_string()); 2391 } 2392 } catch (phpmailerException $exc) { 2393 $body = ''; 2394 if ($this->exceptions) { 2395 throw $exc; 2396 } 2397 } 2398 } 2399 return $body; 2400 } 2401 2402 /** 2403 * Return the start of a message boundary. 2404 * @access protected 2405 * @param string $boundary 2406 * @param string $charSet 2407 * @param string $contentType 2408 * @param string $encoding 2409 * @return string 2410 */ 2411 protected function getBoundary($boundary, $charSet, $contentType, $encoding) 2412 { 2413 $result = ''; 2414 if ($charSet == '') { 2415 $charSet = $this->CharSet; 2416 } 2417 if ($contentType == '') { 2418 $contentType = $this->ContentType; 2419 } 2420 if ($encoding == '') { 2421 $encoding = $this->Encoding; 2422 } 2423 $result .= $this->textLine('--' . $boundary); 2424 $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); 2425 $result .= $this->LE; 2426 // RFC1341 part 5 says 7bit is assumed if not specified 2427 if ($encoding != '7bit') { 2428 $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); 2429 } 2430 $result .= $this->LE; 2431 2432 return $result; 2433 } 2434 2435 /** 2436 * Return the end of a message boundary. 2437 * @access protected 2438 * @param string $boundary 2439 * @return string 2440 */ 2441 protected function endBoundary($boundary) 2442 { 2443 return $this->LE . '--' . $boundary . '--' . $this->LE; 2444 } 2445 2446 /** 2447 * Set the message type. 2448 * PHPMailer only supports some preset message types, not arbitrary MIME structures. 2449 * @access protected 2450 * @return void 2451 */ 2452 protected function setMessageType() 2453 { 2454 $type = array(); 2455 if ($this->alternativeExists()) { 2456 $type[] = 'alt'; 2457 } 2458 if ($this->inlineImageExists()) { 2459 $type[] = 'inline'; 2460 } 2461 if ($this->attachmentExists()) { 2462 $type[] = 'attach'; 2463 } 2464 $this->message_type = implode('_', $type); 2465 if ($this->message_type == '') { 2466 //The 'plain' message_type refers to the message having a single body element, not that it is plain-text 2467 $this->message_type = 'plain'; 2468 } 2469 } 2470 2471 /** 2472 * Format a header line. 2473 * @access public 2474 * @param string $name 2475 * @param string $value 2476 * @return string 2477 */ 2478 public function headerLine($name, $value) 2479 { 2480 return $name . ': ' . $value . $this->LE; 2481 } 2482 2483 /** 2484 * Return a formatted mail line. 2485 * @access public 2486 * @param string $value 2487 * @return string 2488 */ 2489 public function textLine($value) 2490 { 2491 return $value . $this->LE; 2492 } 2493 2494 /** 2495 * Add an attachment from a path on the filesystem. 2496 * Returns false if the file could not be found or read. 2497 * @param string $path Path to the attachment. 2498 * @param string $name Overrides the attachment name. 2499 * @param string $encoding File encoding (see $Encoding). 2500 * @param string $type File extension (MIME) type. 2501 * @param string $disposition Disposition to use 2502 * @throws phpmailerException 2503 * @return boolean 2504 */ 2505 public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') 2506 { 2507 try { 2508 if (!@is_file($path)) { 2509 throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); 2510 } 2511 2512 // If a MIME type is not specified, try to work it out from the file name 2513 if ($type == '') { 2514 $type = self::filenameToType($path); 2515 } 2516 2517 $filename = basename($path); 2518 if ($name == '') { 2519 $name = $filename; 2520 } 2521 2522 $this->attachment[] = array( 2523 0 => $path, 2524 1 => $filename, 2525 2 => $name, 2526 3 => $encoding, 2527 4 => $type, 2528 5 => false, // isStringAttachment 2529 6 => $disposition, 2530 7 => 0 2531 ); 2532 2533 } catch (phpmailerException $exc) { 2534 $this->setError($exc->getMessage()); 2535 $this->edebug($exc->getMessage()); 2536 if ($this->exceptions) { 2537 throw $exc; 2538 } 2539 return false; 2540 } 2541 return true; 2542 } 2543 2544 /** 2545 * Return the array of attachments. 2546 * @return array 2547 */ 2548 public function getAttachments() 2549 { 2550 return $this->attachment; 2551 } 2552 2553 /** 2554 * Attach all file, string, and binary attachments to the message. 2555 * Returns an empty string on failure. 2556 * @access protected 2557 * @param string $disposition_type 2558 * @param string $boundary 2559 * @return string 2560 */ 2561 protected function attachAll($disposition_type, $boundary) 2562 { 2563 // Return text of body 2564 $mime = array(); 2565 $cidUniq = array(); 2566 $incl = array(); 2567 2568 // Add all attachments 2569 foreach ($this->attachment as $attachment) { 2570 // Check if it is a valid disposition_filter 2571 if ($attachment[6] == $disposition_type) { 2572 // Check for string attachment 2573 $string = ''; 2574 $path = ''; 2575 $bString = $attachment[5]; 2576 if ($bString) { 2577 $string = $attachment[0]; 2578 } else { 2579 $path = $attachment[0]; 2580 } 2581 2582 $inclhash = md5(serialize($attachment)); 2583 if (in_array($inclhash, $incl)) { 2584 continue; 2585 } 2586 $incl[] = $inclhash; 2587 $name = $attachment[2]; 2588 $encoding = $attachment[3]; 2589 $type = $attachment[4]; 2590 $disposition = $attachment[6]; 2591 $cid = $attachment[7]; 2592 if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) { 2593 continue; 2594 } 2595 $cidUniq[$cid] = true; 2596 2597 $mime[] = sprintf('--%s%s', $boundary, $this->LE); 2598 //Only include a filename property if we have one 2599 if (!empty($name)) { 2600 $mime[] = sprintf( 2601 'Content-Type: %s; name="%s"%s', 2602 $type, 2603 $this->encodeHeader($this->secureHeader($name)), 2604 $this->LE 2605 ); 2606 } else { 2607 $mime[] = sprintf( 2608 'Content-Type: %s%s', 2609 $type, 2610 $this->LE 2611 ); 2612 } 2613 // RFC1341 part 5 says 7bit is assumed if not specified 2614 if ($encoding != '7bit') { 2615 $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE); 2616 } 2617 2618 if ($disposition == 'inline') { 2619 $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE); 2620 } 2621 2622 // If a filename contains any of these chars, it should be quoted, 2623 // but not otherwise: RFC2183 & RFC2045 5.1 2624 // Fixes a warning in IETF's msglint MIME checker 2625 // Allow for bypassing the Content-Disposition header totally 2626 if (!(empty($disposition))) { 2627 $encoded_name = $this->encodeHeader($this->secureHeader($name)); 2628 if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) { 2629 $mime[] = sprintf( 2630 'Content-Disposition: %s; filename="%s"%s', 2631 $disposition, 2632 $encoded_name, 2633 $this->LE . $this->LE 2634 ); 2635 } else { 2636 if (!empty($encoded_name)) { 2637 $mime[] = sprintf( 2638 'Content-Disposition: %s; filename=%s%s', 2639 $disposition, 2640 $encoded_name, 2641 $this->LE . $this->LE 2642 ); 2643 } else { 2644 $mime[] = sprintf( 2645 'Content-Disposition: %s%s', 2646 $disposition, 2647 $this->LE . $this->LE 2648 ); 2649 } 2650 } 2651 } else { 2652 $mime[] = $this->LE; 2653 } 2654 2655 // Encode as string attachment 2656 if ($bString) { 2657 $mime[] = $this->encodeString($string, $encoding); 2658 if ($this->isError()) { 2659 return ''; 2660 } 2661 $mime[] = $this->LE . $this->LE; 2662 } else { 2663 $mime[] = $this->encodeFile($path, $encoding); 2664 if ($this->isError()) { 2665 return ''; 2666 } 2667 $mime[] = $this->LE . $this->LE; 2668 } 2669 } 2670 } 2671 2672 $mime[] = sprintf('--%s--%s', $boundary, $this->LE); 2673 2674 return implode('', $mime); 2675 } 2676 2677 /** 2678 * Encode a file attachment in requested format. 2679 * Returns an empty string on failure. 2680 * @param string $path The full path to the file 2681 * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' 2682 * @throws phpmailerException 2683 * @access protected 2684 * @return string 2685 */ 2686 protected function encodeFile($path, $encoding = 'base64') 2687 { 2688 try { 2689 if (!is_readable($path)) { 2690 throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE); 2691 } 2692 $magic_quotes = get_magic_quotes_runtime(); 2693 if ($magic_quotes) { 2694 if (version_compare(PHP_VERSION, '5.3.0', '<')) { 2695 set_magic_quotes_runtime(false); 2696 } else { 2697 //Doesn't exist in PHP 5.4, but we don't need to check because 2698 //get_magic_quotes_runtime always returns false in 5.4+ 2699 //so it will never get here 2700 ini_set('magic_quotes_runtime', false); 2701 } 2702 } 2703 $file_buffer = file_get_contents($path); 2704 $file_buffer = $this->encodeString($file_buffer, $encoding); 2705 if ($magic_quotes) { 2706 if (version_compare(PHP_VERSION, '5.3.0', '<')) { 2707 set_magic_quotes_runtime($magic_quotes); 2708 } else { 2709 ini_set('magic_quotes_runtime', $magic_quotes); 2710 } 2711 } 2712 return $file_buffer; 2713 } catch (Exception $exc) { 2714 $this->setError($exc->getMessage()); 2715 return ''; 2716 } 2717 } 2718 2719 /** 2720 * Encode a string in requested format. 2721 * Returns an empty string on failure. 2722 * @param string $str The text to encode 2723 * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' 2724 * @access public 2725 * @return string 2726 */ 2727 public function encodeString($str, $encoding = 'base64') 2728 { 2729 $encoded = ''; 2730 switch (strtolower($encoding)) { 2731 case 'base64': 2732 $encoded = chunk_split(base64_encode($str), 76, $this->LE); 2733 break; 2734 case '7bit': 2735 case '8bit': 2736 $encoded = $this->fixEOL($str); 2737 // Make sure it ends with a line break 2738 if (substr($encoded, -(strlen($this->LE))) != $this->LE) { 2739 $encoded .= $this->LE; 2740 } 2741 break; 2742 case 'binary': 2743 $encoded = $str; 2744 break; 2745 case 'quoted-printable': 2746 $encoded = $this->encodeQP($str); 2747 break; 2748 default: 2749 $this->setError($this->lang('encoding') . $encoding); 2750 break; 2751 } 2752 return $encoded; 2753 } 2754 2755 /** 2756 * Encode a header string optimally. 2757 * Picks shortest of Q, B, quoted-printable or none. 2758 * @access public 2759 * @param string $str 2760 * @param string $position 2761 * @return string 2762 */ 2763 public function encodeHeader($str, $position = 'text') 2764 { 2765 $matchcount = 0; 2766 switch (strtolower($position)) { 2767 case 'phrase': 2768 if (!preg_match('/[\200-\377]/', $str)) { 2769 // Can't use addslashes as we don't know the value of magic_quotes_sybase 2770 $encoded = addcslashes($str, "\0..\37\177\\\""); 2771 if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { 2772 return ($encoded); 2773 } else { 2774 return ("\"$encoded\""); 2775 } 2776 } 2777 $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); 2778 break; 2779 /** @noinspection PhpMissingBreakStatementInspection */ 2780 case 'comment': 2781 $matchcount = preg_match_all('/[()"]/', $str, $matches); 2782 // Intentional fall-through 2783 case 'text': 2784 default: 2785 $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); 2786 break; 2787 } 2788 2789 //There are no chars that need encoding 2790 if ($matchcount == 0) { 2791 return ($str); 2792 } 2793 2794 $maxlen = 75 - 7 - strlen($this->CharSet); 2795 // Try to select the encoding which should produce the shortest output 2796 if ($matchcount > strlen($str) / 3) { 2797 // More than a third of the content will need encoding, so B encoding will be most efficient 2798 $encoding = 'B'; 2799 if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) { 2800 // Use a custom function which correctly encodes and wraps long 2801 // multibyte strings without breaking lines within a character 2802 $encoded = $this->base64EncodeWrapMB($str, "\n"); 2803 } else { 2804 $encoded = base64_encode($str); 2805 $maxlen -= $maxlen % 4; 2806 $encoded = trim(chunk_split($encoded, $maxlen, "\n")); 2807 } 2808 } else { 2809 $encoding = 'Q'; 2810 $encoded = $this->encodeQ($str, $position); 2811 $encoded = $this->wrapText($encoded, $maxlen, true); 2812 $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded)); 2813 } 2814 2815 $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); 2816 $encoded = trim(str_replace("\n", $this->LE, $encoded)); 2817 2818 return $encoded; 2819 } 2820 2821 /** 2822 * Check if a string contains multi-byte characters. 2823 * @access public 2824 * @param string $str multi-byte text to wrap encode 2825 * @return boolean 2826 */ 2827 public function hasMultiBytes($str) 2828 { 2829 if (function_exists('mb_strlen')) { 2830 return (strlen($str) > mb_strlen($str, $this->CharSet)); 2831 } else { // Assume no multibytes (we can't handle without mbstring functions anyway) 2832 return false; 2833 } 2834 } 2835 2836 /** 2837 * Does a string contain any 8-bit chars (in any charset)? 2838 * @param string $text 2839 * @return boolean 2840 */ 2841 public function has8bitChars($text) 2842 { 2843 return (boolean)preg_match('/[\x80-\xFF]/', $text); 2844 } 2845 2846 /** 2847 * Encode and wrap long multibyte strings for mail headers 2848 * without breaking lines within a character. 2849 * Adapted from a function by paravoid 2850 * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 2851 * @access public 2852 * @param string $str multi-byte text to wrap encode 2853 * @param string $linebreak string to use as linefeed/end-of-line 2854 * @return string 2855 */ 2856 public function base64EncodeWrapMB($str, $linebreak = null) 2857 { 2858 $start = '=?' . $this->CharSet . '?B?'; 2859 $end = '?='; 2860 $encoded = ''; 2861 if ($linebreak === null) { 2862 $linebreak = $this->LE; 2863 } 2864 2865 $mb_length = mb_strlen($str, $this->CharSet); 2866 // Each line must have length <= 75, including $start and $end 2867 $length = 75 - strlen($start) - strlen($end); 2868 // Average multi-byte ratio 2869 $ratio = $mb_length / strlen($str); 2870 // Base64 has a 4:3 ratio 2871 $avgLength = floor($length * $ratio * .75); 2872 2873 for ($i = 0; $i < $mb_length; $i += $offset) { 2874 $lookBack = 0; 2875 do { 2876 $offset = $avgLength - $lookBack; 2877 $chunk = mb_substr($str, $i, $offset, $this->CharSet); 2878 $chunk = base64_encode($chunk); 2879 $lookBack++; 2880 } while (strlen($chunk) > $length); 2881 $encoded .= $chunk . $linebreak; 2882 } 2883 2884 // Chomp the last linefeed 2885 $encoded = substr($encoded, 0, -strlen($linebreak)); 2886 return $encoded; 2887 } 2888 2889 /** 2890 * Encode a string in quoted-printable format. 2891 * According to RFC2045 section 6.7. 2892 * @access public 2893 * @param string $string The text to encode 2894 * @param integer $line_max Number of chars allowed on a line before wrapping 2895 * @return string 2896 * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment 2897 */ 2898 public function encodeQP($string, $line_max = 76) 2899 { 2900 // Use native function if it's available (>= PHP5.3) 2901 if (function_exists('quoted_printable_encode')) { 2902 return quoted_printable_encode($string); 2903 } 2904 // Fall back to a pure PHP implementation 2905 $string = str_replace( 2906 array('%20', '%0D%0A.', '%0D%0A', '%'), 2907 array(' ', "\r\n=2E", "\r\n", '='), 2908 rawurlencode($string) 2909 ); 2910 return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); 2911 } 2912 2913 /** 2914 * Backward compatibility wrapper for an old QP encoding function that was removed. 2915 * @see PHPMailer::encodeQP() 2916 * @access public 2917 * @param string $string 2918 * @param integer $line_max 2919 * @param boolean $space_conv 2920 * @return string 2921 * @deprecated Use encodeQP instead. 2922 */ 2923 public function encodeQPphp( 2924 $string, 2925 $line_max = 76, 2926 /** @noinspection PhpUnusedParameterInspection */ $space_conv = false 2927 ) { 2928 return $this->encodeQP($string, $line_max); 2929 } 2930 2931 /** 2932 * Encode a string using Q encoding. 2933 * @link http://tools.ietf.org/html/rfc2047 2934 * @param string $str the text to encode 2935 * @param string $position Where the text is going to be used, see the RFC for what that means 2936 * @access public 2937 * @return string 2938 */ 2939 public function encodeQ($str, $position = 'text') 2940 { 2941 // There should not be any EOL in the string 2942 $pattern = ''; 2943 $encoded = str_replace(array("\r", "\n"), '', $str); 2944 switch (strtolower($position)) { 2945 case 'phrase': 2946 // RFC 2047 section 5.3 2947 $pattern = '^A-Za-z0-9!*+\/ -'; 2948 break; 2949 /** @noinspection PhpMissingBreakStatementInspection */ 2950 case 'comment': 2951 // RFC 2047 section 5.2 2952 $pattern = '\(\)"'; 2953 // intentional fall-through 2954 // for this reason we build the $pattern without including delimiters and [] 2955 case 'text': 2956 default: 2957 // RFC 2047 section 5.1 2958 // Replace every high ascii, control, =, ? and _ characters 2959 $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; 2960 break; 2961 } 2962 $matches = array(); 2963 if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { 2964 // If the string contains an '=', make sure it's the first thing we replace 2965 // so as to avoid double-encoding 2966 $eqkey = array_search('=', $matches[0]); 2967 if (false !== $eqkey) { 2968 unset($matches[0][$eqkey]); 2969 array_unshift($matches[0], '='); 2970 } 2971 foreach (array_unique($matches[0]) as $char) { 2972 $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); 2973 } 2974 } 2975 // Replace every spaces to _ (more readable than =20) 2976 return str_replace(' ', '_', $encoded); 2977 } 2978 2979 /** 2980 * Add a string or binary attachment (non-filesystem). 2981 * This method can be used to attach ascii or binary data, 2982 * such as a BLOB record from a database. 2983 * @param string $string String attachment data. 2984 * @param string $filename Name of the attachment. 2985 * @param string $encoding File encoding (see $Encoding). 2986 * @param string $type File extension (MIME) type. 2987 * @param string $disposition Disposition to use 2988 * @return void 2989 */ 2990 public function addStringAttachment( 2991 $string, 2992 $filename, 2993 $encoding = 'base64', 2994 $type = '', 2995 $disposition = 'attachment' 2996 ) { 2997 // If a MIME type is not specified, try to work it out from the file name 2998 if ($type == '') { 2999 $type = self::filenameToType($filename); 3000 } 3001 // Append to $attachment array 3002 $this->attachment[] = array( 3003 0 => $string, 3004 1 => $filename, 3005 2 => basename($filename), 3006 3 => $encoding, 3007 4 => $type, 3008 5 => true, // isStringAttachment 3009 6 => $disposition, 3010 7 => 0 3011 ); 3012 } 3013 3014 /** 3015 * Add an embedded (inline) attachment from a file. 3016 * This can include images, sounds, and just about any other document type. 3017 * These differ from 'regular' attachments in that they are intended to be 3018 * displayed inline with the message, not just attached for download. 3019 * This is used in HTML messages that embed the images 3020 * the HTML refers to using the $cid value. 3021 * @param string $path Path to the attachment. 3022 * @param string $cid Content ID of the attachment; Use this to reference 3023 * the content when using an embedded image in HTML. 3024 * @param string $name Overrides the attachment name. 3025 * @param string $encoding File encoding (see $Encoding). 3026 * @param string $type File MIME type. 3027 * @param string $disposition Disposition to use 3028 * @return boolean True on successfully adding an attachment 3029 */ 3030 public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') 3031 { 3032 if (!@is_file($path)) { 3033 $this->setError($this->lang('file_access') . $path); 3034 return false; 3035 } 3036 3037 // If a MIME type is not specified, try to work it out from the file name 3038 if ($type == '') { 3039 $type = self::filenameToType($path); 3040 } 3041 3042 $filename = basename($path); 3043 if ($name == '') { 3044 $name = $filename; 3045 } 3046 3047 // Append to $attachment array 3048 $this->attachment[] = array( 3049 0 => $path, 3050 1 => $filename, 3051 2 => $name, 3052 3 => $encoding, 3053 4 => $type, 3054 5 => false, // isStringAttachment 3055 6 => $disposition, 3056 7 => $cid 3057 ); 3058 return true; 3059 } 3060 3061 /** 3062 * Add an embedded stringified attachment. 3063 * This can include images, sounds, and just about any other document type. 3064 * Be sure to set the $type to an image type for images: 3065 * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. 3066 * @param string $string The attachment binary data. 3067 * @param string $cid Content ID of the attachment; Use this to reference 3068 * the content when using an embedded image in HTML. 3069 * @param string $name 3070 * @param string $encoding File encoding (see $Encoding). 3071 * @param string $type MIME type. 3072 * @param string $disposition Disposition to use 3073 * @return boolean True on successfully adding an attachment 3074 */ 3075 public function addStringEmbeddedImage( 3076 $string, 3077 $cid, 3078 $name = '', 3079 $encoding = 'base64', 3080 $type = '', 3081 $disposition = 'inline' 3082 ) { 3083 // If a MIME type is not specified, try to work it out from the name 3084 if ($type == '' and !empty($name)) { 3085 $type = self::filenameToType($name); 3086 } 3087 3088 // Append to $attachment array 3089 $this->attachment[] = array( 3090 0 => $string, 3091 1 => $name, 3092 2 => $name, 3093 3 => $encoding, 3094 4 => $type, 3095 5 => true, // isStringAttachment 3096 6 => $disposition, 3097 7 => $cid 3098 ); 3099 return true; 3100 } 3101 3102 /** 3103 * Check if an inline attachment is present. 3104 * @access public 3105 * @return boolean 3106 */ 3107 public function inlineImageExists() 3108 { 3109 foreach ($this->attachment as $attachment) { 3110 if ($attachment[6] == 'inline') { 3111 return true; 3112 } 3113 } 3114 return false; 3115 } 3116 3117 /** 3118 * Check if an attachment (non-inline) is present. 3119 * @return boolean 3120 */ 3121 public function attachmentExists() 3122 { 3123 foreach ($this->attachment as $attachment) { 3124 if ($attachment[6] == 'attachment') { 3125 return true; 3126 } 3127 } 3128 return false; 3129 } 3130 3131 /** 3132 * Check if this message has an alternative body set. 3133 * @return boolean 3134 */ 3135 public function alternativeExists() 3136 { 3137 return !empty($this->AltBody); 3138 } 3139 3140 /** 3141 * Clear queued addresses of given kind. 3142 * @access protected 3143 * @param string $kind 'to', 'cc', or 'bcc' 3144 * @return void 3145 */ 3146 public function clearQueuedAddresses($kind) 3147 { 3148 $RecipientsQueue = $this->RecipientsQueue; 3149 foreach ($RecipientsQueue as $address => $params) { 3150 if ($params[0] == $kind) { 3151 unset($this->RecipientsQueue[$address]); 3152 } 3153 } 3154 } 3155 3156 /** 3157 * Clear all To recipients. 3158 * @return void 3159 */ 3160 public function clearAddresses() 3161 { 3162 foreach ($this->to as $to) { 3163 unset($this->all_recipients[strtolower($to[0])]); 3164 } 3165 $this->to = array(); 3166 $this->clearQueuedAddresses('to'); 3167 } 3168 3169 /** 3170 * Clear all CC recipients. 3171 * @return void 3172 */ 3173 public function clearCCs() 3174 { 3175 foreach ($this->cc as $cc) { 3176 unset($this->all_recipients[strtolower($cc[0])]); 3177 } 3178 $this->cc = array(); 3179 $this->clearQueuedAddresses('cc'); 3180 } 3181 3182 /** 3183 * Clear all BCC recipients. 3184 * @return void 3185 */ 3186 public function clearBCCs() 3187 { 3188 foreach ($this->bcc as $bcc) { 3189 unset($this->all_recipients[strtolower($bcc[0])]); 3190 } 3191 $this->bcc = array(); 3192 $this->clearQueuedAddresses('bcc'); 3193 } 3194 3195 /** 3196 * Clear all ReplyTo recipients. 3197 * @return void 3198 */ 3199 public function clearReplyTos() 3200 { 3201 $this->ReplyTo = array(); 3202 $this->ReplyToQueue = array(); 3203 } 3204 3205 /** 3206 * Clear all recipient types. 3207 * @return void 3208 */ 3209 public function clearAllRecipients() 3210 { 3211 $this->to = array(); 3212 $this->cc = array(); 3213 $this->bcc = array(); 3214 $this->all_recipients = array(); 3215 $this->RecipientsQueue = array(); 3216 } 3217 3218 /** 3219 * Clear all filesystem, string, and binary attachments. 3220 * @return void 3221 */ 3222 public function clearAttachments() 3223 { 3224 $this->attachment = array(); 3225 } 3226 3227 /** 3228 * Clear all custom headers. 3229 * @return void 3230 */ 3231 public function clearCustomHeaders() 3232 { 3233 $this->CustomHeader = array(); 3234 } 3235 3236 /** 3237 * Add an error message to the error container. 3238 * @access protected 3239 * @param string $msg 3240 * @return void 3241 */ 3242 protected function setError($msg) 3243 { 3244 $this->error_count++; 3245 if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { 3246 $lasterror = $this->smtp->getError(); 3247 if (!empty($lasterror['error'])) { 3248 $msg .= $this->lang('smtp_error') . $lasterror['error']; 3249 if (!empty($lasterror['detail'])) { 3250 $msg .= ' Detail: '. $lasterror['detail']; 3251 } 3252 if (!empty($lasterror['smtp_code'])) { 3253 $msg .= ' SMTP code: ' . $lasterror['smtp_code']; 3254 } 3255 if (!empty($lasterror['smtp_code_ex'])) { 3256 $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; 3257 } 3258 } 3259 } 3260 $this->ErrorInfo = $msg; 3261 } 3262 3263 /** 3264 * Return an RFC 822 formatted date. 3265 * @access public 3266 * @return string 3267 * @static 3268 */ 3269 public static function rfcDate() 3270 { 3271 // Set the time zone to whatever the default is to avoid 500 errors 3272 // Will default to UTC if it's not set properly in php.ini 3273 date_default_timezone_set(@date_default_timezone_get()); 3274 return date('D, j M Y H:i:s O'); 3275 } 3276 3277 /** 3278 * Get the server hostname. 3279 * Returns 'localhost.localdomain' if unknown. 3280 * @access protected 3281 * @return string 3282 */ 3283 protected function serverHostname() 3284 { 3285 $result = 'localhost.localdomain'; 3286 if (!empty($this->Hostname)) { 3287 $result = $this->Hostname; 3288 } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) { 3289 $result = $_SERVER['SERVER_NAME']; 3290 } elseif (function_exists('gethostname') && gethostname() !== false) { 3291 $result = gethostname(); 3292 } elseif (php_uname('n') !== false) { 3293 $result = php_uname('n'); 3294 } 3295 return $result; 3296 } 3297 3298 /** 3299 * Get an error message in the current language. 3300 * @access protected 3301 * @param string $key 3302 * @return string 3303 */ 3304 protected function lang($key) 3305 { 3306 if (count($this->language) < 1) { 3307 $this->setLanguage('en'); // set the default language 3308 } 3309 3310 if (array_key_exists($key, $this->language)) { 3311 if ($key == 'smtp_connect_failed') { 3312 //Include a link to troubleshooting docs on SMTP connection failure 3313 //this is by far the biggest cause of support questions 3314 //but it's usually not PHPMailer's fault. 3315 return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; 3316 } 3317 return $this->language[$key]; 3318 } else { 3319 //Return the key as a fallback 3320 return $key; 3321 } 3322 } 3323 3324 /** 3325 * Check if an error occurred. 3326 * @access public 3327 * @return boolean True if an error did occur. 3328 */ 3329 public function isError() 3330 { 3331 return ($this->error_count > 0); 3332 } 3333 3334 /** 3335 * Ensure consistent line endings in a string. 3336 * Changes every end of line from CRLF, CR or LF to $this->LE. 3337 * @access public 3338 * @param string $str String to fixEOL 3339 * @return string 3340 */ 3341 public function fixEOL($str) 3342 { 3343 // Normalise to \n 3344 $nstr = str_replace(array("\r\n", "\r"), "\n", $str); 3345 // Now convert LE as needed 3346 if ($this->LE !== "\n") { 3347 $nstr = str_replace("\n", $this->LE, $nstr); 3348 } 3349 return $nstr; 3350 } 3351 3352 /** 3353 * Add a custom header. 3354 * $name value can be overloaded to contain 3355 * both header name and value (name:value) 3356 * @access public 3357 * @param string $name Custom header name 3358 * @param string $value Header value 3359 * @return void 3360 */ 3361 public function addCustomHeader($name, $value = null) 3362 { 3363 if ($value === null) { 3364 // Value passed in as name:value 3365 $this->CustomHeader[] = explode(':', $name, 2); 3366 } else { 3367 $this->CustomHeader[] = array($name, $value); 3368 } 3369 } 3370 3371 /** 3372 * Returns all custom headers. 3373 * @return array 3374 */ 3375 public function getCustomHeaders() 3376 { 3377 return $this->CustomHeader; 3378 } 3379 3380 /** 3381 * Create a message body from an HTML string. 3382 * Automatically inlines images and creates a plain-text version by converting the HTML, 3383 * overwriting any existing values in Body and AltBody. 3384 * $basedir is used when handling relative image paths, e.g. <img src="images/a.png"> 3385 * will look for an image file in $basedir/images/a.png and convert it to inline. 3386 * If you don't want to apply these transformations to your HTML, just set Body and AltBody yourself. 3387 * @access public 3388 * @param string $message HTML message string 3389 * @param string $basedir base directory for relative paths to images 3390 * @param boolean|callable $advanced Whether to use the internal HTML to text converter 3391 * or your own custom converter @see PHPMailer::html2text() 3392 * @return string $message The transformed message Body 3393 */ 3394 public function msgHTML($message, $basedir = '', $advanced = false) 3395 { 3396 preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); 3397 if (array_key_exists(2, $images)) { 3398 foreach ($images[2] as $imgindex => $url) { 3399 // Convert data URIs into embedded images 3400 if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { 3401 $data = substr($url, strpos($url, ',')); 3402 if ($match[2]) { 3403 $data = base64_decode($data); 3404 } else { 3405 $data = rawurldecode($data); 3406 } 3407 $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 3408 if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) { 3409 $message = str_replace( 3410 $images[0][$imgindex], 3411 $images[1][$imgindex] . '="cid:' . $cid . '"', 3412 $message 3413 ); 3414 } 3415 } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) { 3416 // Do not change urls for absolute images (thanks to corvuscorax) 3417 // Do not change urls that are already inline images 3418 $filename = basename($url); 3419 $directory = dirname($url); 3420 if ($directory == '.') { 3421 $directory = ''; 3422 } 3423 $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 3424 if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { 3425 $basedir .= '/'; 3426 } 3427 if (strlen($directory) > 1 && substr($directory, -1) != '/') { 3428 $directory .= '/'; 3429 } 3430 if ($this->addEmbeddedImage( 3431 $basedir . $directory . $filename, 3432 $cid, 3433 $filename, 3434 'base64', 3435 self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION)) 3436 ) 3437 ) { 3438 $message = preg_replace( 3439 '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', 3440 $images[1][$imgindex] . '="cid:' . $cid . '"', 3441 $message 3442 ); 3443 } 3444 } 3445 } 3446 } 3447 $this->isHTML(true); 3448 // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better 3449 $this->Body = $this->normalizeBreaks($message); 3450 $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced)); 3451 if (!$this->alternativeExists()) { 3452 $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . 3453 self::CRLF . self::CRLF; 3454 } 3455 return $this->Body; 3456 } 3457 3458 /** 3459 * Convert an HTML string into plain text. 3460 * This is used by msgHTML(). 3461 * Note - older versions of this function used a bundled advanced converter 3462 * which was been removed for license reasons in #232. 3463 * Example usage: 3464 * <code> 3465 * // Use default conversion 3466 * $plain = $mail->html2text($html); 3467 * // Use your own custom converter 3468 * $plain = $mail->html2text($html, function($html) { 3469 * $converter = new MyHtml2text($html); 3470 * return $converter->get_text(); 3471 * }); 3472 * </code> 3473 * @param string $html The HTML text to convert 3474 * @param boolean|callable $advanced Any boolean value to use the internal converter, 3475 * or provide your own callable for custom conversion. 3476 * @return string 3477 */ 3478 public function html2text($html, $advanced = false) 3479 { 3480 if (is_callable($advanced)) { 3481 return call_user_func($advanced, $html); 3482 } 3483 return html_entity_decode( 3484 trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), 3485 ENT_QUOTES, 3486 $this->CharSet 3487 ); 3488 } 3489 3490 /** 3491 * Get the MIME type for a file extension. 3492 * @param string $ext File extension 3493 * @access public 3494 * @return string MIME type of file. 3495 * @static 3496 */ 3497 public static function _mime_types($ext = '') 3498 { 3499 $mimes = array( 3500 'xl' => 'application/excel', 3501 'js' => 'application/javascript', 3502 'hqx' => 'application/mac-binhex40', 3503 'cpt' => 'application/mac-compactpro', 3504 'bin' => 'application/macbinary', 3505 'doc' => 'application/msword', 3506 'word' => 'application/msword', 3507 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 3508 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 3509 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 3510 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 3511 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 3512 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', 3513 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 3514 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 3515 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', 3516 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 3517 'class' => 'application/octet-stream', 3518 'dll' => 'application/octet-stream', 3519 'dms' => 'application/octet-stream', 3520 'exe' => 'application/octet-stream', 3521 'lha' => 'application/octet-stream', 3522 'lzh' => 'application/octet-stream', 3523 'psd' => 'application/octet-stream', 3524 'sea' => 'application/octet-stream', 3525 'so' => 'application/octet-stream', 3526 'oda' => 'application/oda', 3527 'pdf' => 'application/pdf', 3528 'ai' => 'application/postscript', 3529 'eps' => 'application/postscript', 3530 'ps' => 'application/postscript', 3531 'smi' => 'application/smil', 3532 'smil' => 'application/smil', 3533 'mif' => 'application/vnd.mif', 3534 'xls' => 'application/vnd.ms-excel', 3535 'ppt' => 'application/vnd.ms-powerpoint', 3536 'wbxml' => 'application/vnd.wap.wbxml', 3537 'wmlc' => 'application/vnd.wap.wmlc', 3538 'dcr' => 'application/x-director', 3539 'dir' => 'application/x-director', 3540 'dxr' => 'application/x-director', 3541 'dvi' => 'application/x-dvi', 3542 'gtar' => 'application/x-gtar', 3543 'php3' => 'application/x-httpd-php', 3544 'php4' => 'application/x-httpd-php', 3545 'php' => 'application/x-httpd-php', 3546 'phtml' => 'application/x-httpd-php', 3547 'phps' => 'application/x-httpd-php-source', 3548 'swf' => 'application/x-shockwave-flash', 3549 'sit' => 'application/x-stuffit', 3550 'tar' => 'application/x-tar', 3551 'tgz' => 'application/x-tar', 3552 'xht' => 'application/xhtml+xml', 3553 'xhtml' => 'application/xhtml+xml', 3554 'zip' => 'application/zip', 3555 'mid' => 'audio/midi', 3556 'midi' => 'audio/midi', 3557 'mp2' => 'audio/mpeg', 3558 'mp3' => 'audio/mpeg', 3559 'mpga' => 'audio/mpeg', 3560 'aif' => 'audio/x-aiff', 3561 'aifc' => 'audio/x-aiff', 3562 'aiff' => 'audio/x-aiff', 3563 'ram' => 'audio/x-pn-realaudio', 3564 'rm' => 'audio/x-pn-realaudio', 3565 'rpm' => 'audio/x-pn-realaudio-plugin', 3566 'ra' => 'audio/x-realaudio', 3567 'wav' => 'audio/x-wav', 3568 'bmp' => 'image/bmp', 3569 'gif' => 'image/gif', 3570 'jpeg' => 'image/jpeg', 3571 'jpe' => 'image/jpeg', 3572 'jpg' => 'image/jpeg', 3573 'png' => 'image/png', 3574 'tiff' => 'image/tiff', 3575 'tif' => 'image/tiff', 3576 'eml' => 'message/rfc822', 3577 'css' => 'text/css', 3578 'html' => 'text/html', 3579 'htm' => 'text/html', 3580 'shtml' => 'text/html', 3581 'log' => 'text/plain', 3582 'text' => 'text/plain', 3583 'txt' => 'text/plain', 3584 'rtx' => 'text/richtext', 3585 'rtf' => 'text/rtf', 3586 'vcf' => 'text/vcard', 3587 'vcard' => 'text/vcard', 3588 'xml' => 'text/xml', 3589 'xsl' => 'text/xml', 3590 'mpeg' => 'video/mpeg', 3591 'mpe' => 'video/mpeg', 3592 'mpg' => 'video/mpeg', 3593 'mov' => 'video/quicktime', 3594 'qt' => 'video/quicktime', 3595 'rv' => 'video/vnd.rn-realvideo', 3596 'avi' => 'video/x-msvideo', 3597 'movie' => 'video/x-sgi-movie' 3598 ); 3599 if (array_key_exists(strtolower($ext), $mimes)) { 3600 return $mimes[strtolower($ext)]; 3601 } 3602 return 'application/octet-stream'; 3603 } 3604 3605 /** 3606 * Map a file name to a MIME type. 3607 * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. 3608 * @param string $filename A file name or full path, does not need to exist as a file 3609 * @return string 3610 * @static 3611 */ 3612 public static function filenameToType($filename) 3613 { 3614 // In case the path is a URL, strip any query string before getting extension 3615 $qpos = strpos($filename, '?'); 3616 if (false !== $qpos) { 3617 $filename = substr($filename, 0, $qpos); 3618 } 3619 $pathinfo = self::mb_pathinfo($filename); 3620 return self::_mime_types($pathinfo['extension']); 3621 } 3622 3623 /** 3624 * Multi-byte-safe pathinfo replacement. 3625 * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. 3626 * Works similarly to the one in PHP >= 5.2.0 3627 * @link http://www.php.net/manual/en/function.pathinfo.php#107461 3628 * @param string $path A filename or path, does not need to exist as a file 3629 * @param integer|string $options Either a PATHINFO_* constant, 3630 * or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2 3631 * @return string|array 3632 * @static 3633 */ 3634 public static function mb_pathinfo($path, $options = null) 3635 { 3636 $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); 3637 $pathinfo = array(); 3638 if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) { 3639 if (array_key_exists(1, $pathinfo)) { 3640 $ret['dirname'] = $pathinfo[1]; 3641 } 3642 if (array_key_exists(2, $pathinfo)) { 3643 $ret['basename'] = $pathinfo[2]; 3644 } 3645 if (array_key_exists(5, $pathinfo)) { 3646 $ret['extension'] = $pathinfo[5]; 3647 } 3648 if (array_key_exists(3, $pathinfo)) { 3649 $ret['filename'] = $pathinfo[3]; 3650 } 3651 } 3652 switch ($options) { 3653 case PATHINFO_DIRNAME: 3654 case 'dirname': 3655 return $ret['dirname']; 3656 case PATHINFO_BASENAME: 3657 case 'basename': 3658 return $ret['basename']; 3659 case PATHINFO_EXTENSION: 3660 case 'extension': 3661 return $ret['extension']; 3662 case PATHINFO_FILENAME: 3663 case 'filename': 3664 return $ret['filename']; 3665 default: 3666 return $ret; 3667 } 3668 } 3669 3670 /** 3671 * Set or reset instance properties. 3672 * You should avoid this function - it's more verbose, less efficient, more error-prone and 3673 * harder to debug than setting properties directly. 3674 * Usage Example: 3675 * `$mail->set('SMTPSecure', 'tls');` 3676 * is the same as: 3677 * `$mail->SMTPSecure = 'tls';` 3678 * @access public 3679 * @param string $name The property name to set 3680 * @param mixed $value The value to set the property to 3681 * @return boolean 3682 * @TODO Should this not be using the __set() magic function? 3683 */ 3684 public function set($name, $value = '') 3685 { 3686 if (property_exists($this, $name)) { 3687 $this->$name = $value; 3688 return true; 3689 } else { 3690 $this->setError($this->lang('variable_set') . $name); 3691 return false; 3692 } 3693 } 3694 3695 /** 3696 * Strip newlines to prevent header injection. 3697 * @access public 3698 * @param string $str 3699 * @return string 3700 */ 3701 public function secureHeader($str) 3702 { 3703 return trim(str_replace(array("\r", "\n"), '', $str)); 3704 } 3705 3706 /** 3707 * Normalize line breaks in a string. 3708 * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. 3709 * Defaults to CRLF (for message bodies) and preserves consecutive breaks. 3710 * @param string $text 3711 * @param string $breaktype What kind of line break to use, defaults to CRLF 3712 * @return string 3713 * @access public 3714 * @static 3715 */ 3716 public static function normalizeBreaks($text, $breaktype = "\r\n") 3717 { 3718 return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); 3719 } 3720 3721 /** 3722 * Set the public and private key files and password for S/MIME signing. 3723 * @access public 3724 * @param string $cert_filename 3725 * @param string $key_filename 3726 * @param string $key_pass Password for private key 3727 * @param string $extracerts_filename Optional path to chain certificate 3728 */ 3729 public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '') 3730 { 3731 $this->sign_cert_file = $cert_filename; 3732 $this->sign_key_file = $key_filename; 3733 $this->sign_key_pass = $key_pass; 3734 $this->sign_extracerts_file = $extracerts_filename; 3735 } 3736 3737 /** 3738 * Quoted-Printable-encode a DKIM header. 3739 * @access public 3740 * @param string $txt 3741 * @return string 3742 */ 3743 public function DKIM_QP($txt) 3744 { 3745 $line = ''; 3746 for ($i = 0; $i < strlen($txt); $i++) { 3747 $ord = ord($txt[$i]); 3748 if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) { 3749 $line .= $txt[$i]; 3750 } else { 3751 $line .= '=' . sprintf('%02X', $ord); 3752 } 3753 } 3754 return $line; 3755 } 3756 3757 /** 3758 * Generate a DKIM signature. 3759 * @access public 3760 * @param string $signHeader 3761 * @throws phpmailerException 3762 * @return string The DKIM signature value 3763 */ 3764 public function DKIM_Sign($signHeader) 3765 { 3766 if (!defined('PKCS7_TEXT')) { 3767 if ($this->exceptions) { 3768 throw new phpmailerException($this->lang('extension_missing') . 'openssl'); 3769 } 3770 return ''; 3771 } 3772 $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private); 3773 if ('' != $this->DKIM_passphrase) { 3774 $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); 3775 } else { 3776 $privKey = openssl_pkey_get_private($privKeyStr); 3777 } 3778 //Workaround for missing digest algorithms in old PHP & OpenSSL versions 3779 //@link http://stackoverflow.com/a/11117338/333340 3780 if (version_compare(PHP_VERSION, '5.3.0') >= 0 and 3781 in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) { 3782 if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { 3783 openssl_pkey_free($privKey); 3784 return base64_encode($signature); 3785 } 3786 } else { 3787 $pinfo = openssl_pkey_get_details($privKey); 3788 $hash = hash('sha256', $signHeader); 3789 //'Magic' constant for SHA256 from RFC3447 3790 //@link https://tools.ietf.org/html/rfc3447#page-43 3791 $t = '3031300d060960864801650304020105000420' . $hash; 3792 $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3); 3793 $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t); 3794 3795 if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) { 3796 openssl_pkey_free($privKey); 3797 return base64_encode($signature); 3798 } 3799 } 3800 openssl_pkey_free($privKey); 3801 return ''; 3802 } 3803 3804 /** 3805 * Generate a DKIM canonicalization header. 3806 * @access public 3807 * @param string $signHeader Header 3808 * @return string 3809 */ 3810 public function DKIM_HeaderC($signHeader) 3811 { 3812 $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader); 3813 $lines = explode("\r\n", $signHeader); 3814 foreach ($lines as $key => $line) { 3815 list($heading, $value) = explode(':', $line, 2); 3816 $heading = strtolower($heading); 3817 $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces 3818 $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value 3819 } 3820 $signHeader = implode("\r\n", $lines); 3821 return $signHeader; 3822 } 3823 3824 /** 3825 * Generate a DKIM canonicalization body. 3826 * @access public 3827 * @param string $body Message Body 3828 * @return string 3829 */ 3830 public function DKIM_BodyC($body) 3831 { 3832 if ($body == '') { 3833 return "\r\n"; 3834 } 3835 // stabilize line endings 3836 $body = str_replace("\r\n", "\n", $body); 3837 $body = str_replace("\n", "\r\n", $body); 3838 // END stabilize line endings 3839 while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { 3840 $body = substr($body, 0, strlen($body) - 2); 3841 } 3842 return $body; 3843 } 3844 3845 /** 3846 * Create the DKIM header and body in a new message header. 3847 * @access public 3848 * @param string $headers_line Header lines 3849 * @param string $subject Subject 3850 * @param string $body Body 3851 * @return string 3852 */ 3853 public function DKIM_Add($headers_line, $subject, $body) 3854 { 3855 $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms 3856 $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body 3857 $DKIMquery = 'dns/txt'; // Query method 3858 $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) 3859 $subject_header = "Subject: $subject"; 3860 $headers = explode($this->LE, $headers_line); 3861 $from_header = ''; 3862 $to_header = ''; 3863 $date_header = ''; 3864 $current = ''; 3865 foreach ($headers as $header) { 3866 if (strpos($header, 'From:') === 0) { 3867 $from_header = $header; 3868 $current = 'from_header'; 3869 } elseif (strpos($header, 'To:') === 0) { 3870 $to_header = $header; 3871 $current = 'to_header'; 3872 } elseif (strpos($header, 'Date:') === 0) { 3873 $date_header = $header; 3874 $current = 'date_header'; 3875 } else { 3876 if (!empty($$current) && strpos($header, ' =?') === 0) { 3877 $$current .= $header; 3878 } else { 3879 $current = ''; 3880 } 3881 } 3882 } 3883 $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); 3884 $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); 3885 $date = str_replace('|', '=7C', $this->DKIM_QP($date_header)); 3886 $subject = str_replace( 3887 '|', 3888 '=7C', 3889 $this->DKIM_QP($subject_header) 3890 ); // Copied header fields (dkim-quoted-printable) 3891 $body = $this->DKIM_BodyC($body); 3892 $DKIMlen = strlen($body); // Length of body 3893 $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body 3894 if ('' == $this->DKIM_identity) { 3895 $ident = ''; 3896 } else { 3897 $ident = ' i=' . $this->DKIM_identity . ';'; 3898 } 3899 $dkimhdrs = 'DKIM-Signature: v=1; a=' . 3900 $DKIMsignatureType . '; q=' . 3901 $DKIMquery . '; l=' . 3902 $DKIMlen . '; s=' . 3903 $this->DKIM_selector . 3904 ";\r\n" . 3905 "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" . 3906 "\th=From:To:Date:Subject;\r\n" . 3907 "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" . 3908 "\tz=$from\r\n" . 3909 "\t|$to\r\n" . 3910 "\t|$date\r\n" . 3911 "\t|$subject;\r\n" . 3912 "\tbh=" . $DKIMb64 . ";\r\n" . 3913 "\tb="; 3914 $toSign = $this->DKIM_HeaderC( 3915 $from_header . "\r\n" . 3916 $to_header . "\r\n" . 3917 $date_header . "\r\n" . 3918 $subject_header . "\r\n" . 3919 $dkimhdrs 3920 ); 3921 $signed = $this->DKIM_Sign($toSign); 3922 return $dkimhdrs . $signed . "\r\n"; 3923 } 3924 3925 /** 3926 * Detect if a string contains a line longer than the maximum line length allowed. 3927 * @param string $str 3928 * @return boolean 3929 * @static 3930 */ 3931 public static function hasLineLongerThanMax($str) 3932 { 3933 //+2 to include CRLF line break for a 1000 total 3934 return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str); 3935 } 3936 3937 /** 3938 * Allows for public read access to 'to' property. 3939 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3940 * @access public 3941 * @return array 3942 */ 3943 public function getToAddresses() 3944 { 3945 return $this->to; 3946 } 3947 3948 /** 3949 * Allows for public read access to 'cc' property. 3950 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3951 * @access public 3952 * @return array 3953 */ 3954 public function getCcAddresses() 3955 { 3956 return $this->cc; 3957 } 3958 3959 /** 3960 * Allows for public read access to 'bcc' property. 3961 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3962 * @access public 3963 * @return array 3964 */ 3965 public function getBccAddresses() 3966 { 3967 return $this->bcc; 3968 } 3969 3970 /** 3971 * Allows for public read access to 'ReplyTo' property. 3972 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3973 * @access public 3974 * @return array 3975 */ 3976 public function getReplyToAddresses() 3977 { 3978 return $this->ReplyTo; 3979 } 3980 3981 /** 3982 * Allows for public read access to 'all_recipients' property. 3983 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3984 * @access public 3985 * @return array 3986 */ 3987 public function getAllRecipientAddresses() 3988 { 3989 return $this->all_recipients; 3990 } 3991 3992 /** 3993 * Perform a callback. 3994 * @param boolean $isSent 3995 * @param array $to 3996 * @param array $cc 3997 * @param array $bcc 3998 * @param string $subject 3999 * @param string $body 4000 * @param string $from 4001 */ 4002 protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from) 4003 { 4004 if (!empty($this->action_function) && is_callable($this->action_function)) { 4005 $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); 4006 call_user_func_array($this->action_function, $params); 4007 } 4008 } 4009 } 4010 4011 /** 4012 * PHPMailer exception handler 41 4013 * @package PHPMailer 42 4014 */ 43 class PHPMailer { 44 45 ///////////////////////////////////////////////// 46 // PROPERTIES, PUBLIC 47 ///////////////////////////////////////////////// 48 49 /** 50 * Email priority (1 = High, 3 = Normal, 5 = low). 51 * @var int 52 */ 53 public $Priority = 3; 54 55 /** 56 * Sets the CharSet of the message. 57 * @var string 58 */ 59 public $CharSet = 'iso-8859-1'; 60 61 /** 62 * Sets the Content-type of the message. 63 * @var string 64 */ 65 public $ContentType = 'text/plain'; 66 67 /** 68 * Sets the Encoding of the message. Options for this are 69 * "8bit", "7bit", "binary", "base64", and "quoted-printable". 70 * @var string 71 */ 72 public $Encoding = '8bit'; 73 74 /** 75 * Holds the most recent mailer error message. 76 * @var string 77 */ 78 public $ErrorInfo = ''; 79 80 /** 81 * Sets the From email address for the message. 82 * @var string 83 */ 84 public $From = 'root@localhost'; 85 86 /** 87 * Sets the From name of the message. 88 * @var string 89 */ 90 public $FromName = 'Root User'; 91 92 /** 93 * Sets the Sender email (Return-Path) of the message. If not empty, 94 * will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. 95 * @var string 96 */ 97 public $Sender = ''; 98 99 /** 100 * Sets the Return-Path of the message. If empty, it will 101 * be set to either From or Sender. 102 * @var string 103 */ 104 public $ReturnPath = ''; 105 106 /** 107 * Sets the Subject of the message. 108 * @var string 109 */ 110 public $Subject = ''; 111 112 /** 113 * Sets the Body of the message. This can be either an HTML or text body. 114 * If HTML then run IsHTML(true). 115 * @var string 116 */ 117 public $Body = ''; 118 119 /** 120 * Sets the text-only body of the message. This automatically sets the 121 * email to multipart/alternative. This body can be read by mail 122 * clients that do not have HTML email capability such as mutt. Clients 123 * that can read HTML will view the normal Body. 124 * @var string 125 */ 126 public $AltBody = ''; 127 128 /** 129 * Stores the complete compiled MIME message body. 130 * @var string 131 * @access protected 132 */ 133 protected $MIMEBody = ''; 134 135 /** 136 * Stores the complete compiled MIME message headers. 137 * @var string 138 * @access protected 139 */ 140 protected $MIMEHeader = ''; 141 142 /** 143 * Stores the extra header list which CreateHeader() doesn't fold in 144 * @var string 145 * @access protected 146 */ 147 protected $mailHeader = ''; 148 149 /** 150 * Sets word wrapping on the body of the message to a given number of 151 * characters. 152 * @var int 153 */ 154 public $WordWrap = 0; 155 156 /** 157 * Method to send mail: ("mail", "sendmail", or "smtp"). 158 * @var string 159 */ 160 public $Mailer = 'mail'; 161 162 /** 163 * Sets the path of the sendmail program. 164 * @var string 165 */ 166 public $Sendmail = '/usr/sbin/sendmail'; 167 168 /** 169 * Determine if mail() uses a fully sendmail compatible MTA that 170 * supports sendmail's "-oi -f" options 171 * @var boolean 172 */ 173 public $UseSendmailOptions = true; 174 175 /** 176 * Path to PHPMailer plugins. Useful if the SMTP class 177 * is in a different directory than the PHP include path. 178 * @var string 179 */ 180 public $PluginDir = ''; 181 182 /** 183 * Sets the email address that a reading confirmation will be sent. 184 * @var string 185 */ 186 public $ConfirmReadingTo = ''; 187 188 /** 189 * Sets the hostname to use in Message-Id and Received headers 190 * and as default HELO string. If empty, the value returned 191 * by SERVER_NAME is used or 'localhost.localdomain'. 192 * @var string 193 */ 194 public $Hostname = ''; 195 196 /** 197 * Sets the message ID to be used in the Message-Id header. 198 * If empty, a unique id will be generated. 199 * @var string 200 */ 201 public $MessageID = ''; 202 203 /** 204 * Sets the message Date to be used in the Date header. 205 * If empty, the current date will be added. 206 * @var string 207 */ 208 public $MessageDate = ''; 209 210 ///////////////////////////////////////////////// 211 // PROPERTIES FOR SMTP 212 ///////////////////////////////////////////////// 213 214 /** 215 * Sets the SMTP hosts. 216 * 217 * All hosts must be separated by a 218 * semicolon. You can also specify a different port 219 * for each host by using this format: [hostname:port] 220 * (e.g. "smtp1.example.com:25;smtp2.example.com"). 221 * Hosts will be tried in order. 222 * @var string 223 */ 224 public $Host = 'localhost'; 225 226 /** 227 * Sets the default SMTP server port. 228 * @var int 229 */ 230 public $Port = 25; 231 232 /** 233 * Sets the SMTP HELO of the message (Default is $Hostname). 234 * @var string 235 */ 236 public $Helo = ''; 237 238 /** 239 * Sets connection prefix. Options are "", "ssl" or "tls" 240 * @var string 241 */ 242 public $SMTPSecure = ''; 243 244 /** 245 * Sets SMTP authentication. Utilizes the Username and Password variables. 246 * @var bool 247 */ 248 public $SMTPAuth = false; 249 250 /** 251 * Sets SMTP username. 252 * @var string 253 */ 254 public $Username = ''; 255 256 /** 257 * Sets SMTP password. 258 * @var string 259 */ 260 public $Password = ''; 261 262 /** 263 * Sets SMTP auth type. Options are LOGIN | PLAIN | NTLM (default LOGIN) 264 * @var string 265 */ 266 public $AuthType = ''; 267 268 /** 269 * Sets SMTP realm. 270 * @var string 271 */ 272 public $Realm = ''; 273 274 /** 275 * Sets SMTP workstation. 276 * @var string 277 */ 278 public $Workstation = ''; 279 280 /** 281 * Sets the SMTP server timeout in seconds. 282 * This function will not work with the win32 version. 283 * @var int 284 */ 285 public $Timeout = 10; 286 287 /** 288 * Sets SMTP class debugging on or off. 289 * @var bool 290 */ 291 public $SMTPDebug = false; 292 293 /** 294 * Sets the function/method to use for debugging output. 295 * Right now we only honor "echo" or "error_log" 296 * @var string 297 */ 298 public $Debugoutput = "echo"; 299 300 /** 301 * Prevents the SMTP connection from being closed after each mail 302 * sending. If this is set to true then to close the connection 303 * requires an explicit call to SmtpClose(). 304 * @var bool 305 */ 306 public $SMTPKeepAlive = false; 307 308 /** 309 * Provides the ability to have the TO field process individual 310 * emails, instead of sending to entire TO addresses 311 * @var bool 312 */ 313 public $SingleTo = false; 314 315 /** 316 * If SingleTo is true, this provides the array to hold the email addresses 317 * @var bool 318 */ 319 public $SingleToArray = array(); 320 321 /** 322 * Provides the ability to change the generic line ending 323 * NOTE: The default remains '\n'. We force CRLF where we KNOW 324 * it must be used via self::CRLF 325 * @var string 326 */ 327 public $LE = "\n"; 328 329 /** 330 * Used with DKIM Signing 331 * required parameter if DKIM is enabled 332 * 333 * domain selector example domainkey 334 * @var string 335 */ 336 public $DKIM_selector = ''; 337 338 /** 339 * Used with DKIM Signing 340 * required if DKIM is enabled, in format of email address 'you@yourdomain.com' typically used as the source of the email 341 * @var string 342 */ 343 public $DKIM_identity = ''; 344 345 /** 346 * Used with DKIM Signing 347 * optional parameter if your private key requires a passphras 348 * @var string 349 */ 350 public $DKIM_passphrase = ''; 351 352 /** 353 * Used with DKIM Singing 354 * required if DKIM is enabled, in format of email address 'domain.com' 355 * @var string 356 */ 357 public $DKIM_domain = ''; 358 359 /** 360 * Used with DKIM Signing 361 * required if DKIM is enabled, path to private key file 362 * @var string 363 */ 364 public $DKIM_private = ''; 365 366 /** 367 * Callback Action function name. 368 * The function that handles the result of the send email action. 369 * It is called out by Send() for each email sent. 370 * 371 * Value can be: 372 * - 'function_name' for function names 373 * - 'Class::Method' for static method calls 374 * - array($object, 'Method') for calling methods on $object 375 * See http://php.net/is_callable manual page for more details. 376 * 377 * Parameters: 378 * bool $result result of the send action 379 * string $to email address of the recipient 380 * string $cc cc email addresses 381 * string $bcc bcc email addresses 382 * string $subject the subject 383 * string $body the email body 384 * string $from email address of sender 385 * @var string 386 */ 387 public $action_function = ''; //'callbackAction'; 388 389 /** 390 * Sets the PHPMailer Version number 391 * @var string 392 */ 393 public $Version = '5.2.4'; 394 395 /** 396 * What to use in the X-Mailer header 397 * @var string NULL for default, whitespace for None, or actual string to use 398 */ 399 public $XMailer = ''; 400 401 ///////////////////////////////////////////////// 402 // PROPERTIES, PRIVATE AND PROTECTED 403 ///////////////////////////////////////////////// 404 405 /** 406 * @var SMTP An instance of the SMTP sender class 407 * @access protected 408 */ 409 protected $smtp = null; 410 /** 411 * @var array An array of 'to' addresses 412 * @access protected 413 */ 414 protected $to = array(); 415 /** 416 * @var array An array of 'cc' addresses 417 * @access protected 418 */ 419 protected $cc = array(); 420 /** 421 * @var array An array of 'bcc' addresses 422 * @access protected 423 */ 424 protected $bcc = array(); 425 /** 426 * @var array An array of reply-to name and address 427 * @access protected 428 */ 429 protected $ReplyTo = array(); 430 /** 431 * @var array An array of all kinds of addresses: to, cc, bcc, replyto 432 * @access protected 433 */ 434 protected $all_recipients = array(); 435 /** 436 * @var array An array of attachments 437 * @access protected 438 */ 439 protected $attachment = array(); 440 /** 441 * @var array An array of custom headers 442 * @access protected 443 */ 444 protected $CustomHeader = array(); 445 /** 446 * @var string The message's MIME type 447 * @access protected 448 */ 449 protected $message_type = ''; 450 /** 451 * @var array An array of MIME boundary strings 452 * @access protected 453 */ 454 protected $boundary = array(); 455 /** 456 * @var array An array of available languages 457 * @access protected 458 */ 459 protected $language = array(); 460 /** 461 * @var integer The number of errors encountered 462 * @access protected 463 */ 464 protected $error_count = 0; 465 /** 466 * @var string The filename of a DKIM certificate file 467 * @access protected 468 */ 469 protected $sign_cert_file = ''; 470 /** 471 * @var string The filename of a DKIM key file 472 * @access protected 473 */ 474 protected $sign_key_file = ''; 475 /** 476 * @var string The password of a DKIM key 477 * @access protected 478 */ 479 protected $sign_key_pass = ''; 480 /** 481 * @var boolean Whether to throw exceptions for errors 482 * @access protected 483 */ 484 protected $exceptions = false; 485 486 ///////////////////////////////////////////////// 487 // CONSTANTS 488 ///////////////////////////////////////////////// 489 490 const STOP_MESSAGE = 0; // message only, continue processing 491 const STOP_CONTINUE = 1; // message?, likely ok to continue processing 492 const STOP_CRITICAL = 2; // message, plus full stop, critical error reached 493 const CRLF = "\r\n"; // SMTP RFC specified EOL 494 495 ///////////////////////////////////////////////// 496 // METHODS, VARIABLES 497 ///////////////////////////////////////////////// 498 499 /** 500 * Calls actual mail() function, but in a safe_mode aware fashion 501 * Also, unless sendmail_path points to sendmail (or something that 502 * claims to be sendmail), don't pass params (not a perfect fix, 503 * but it will do) 504 * @param string $to To 505 * @param string $subject Subject 506 * @param string $body Message Body 507 * @param string $header Additional Header(s) 508 * @param string $params Params 509 * @access private 510 * @return bool 511 */ 512 private function mail_passthru($to, $subject, $body, $header, $params) { 513 if ( ini_get('safe_mode') || !($this->UseSendmailOptions) ) { 514 $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($subject)), $body, $header); 515 } else { 516 $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($subject)), $body, $header, $params); 517 } 518 return $rt; 519 } 520 521 /** 522 * Outputs debugging info via user-defined method 523 * @param string $str 524 */ 525 private function edebug($str) { 526 if ($this->Debugoutput == "error_log") { 527 error_log($str); 528 } else { 529 echo $str; 530 } 531 } 532 533 /** 534 * Constructor 535 * @param boolean $exceptions Should we throw external exceptions? 536 */ 537 public function __construct($exceptions = false) { 538 $this->exceptions = ($exceptions == true); 539 } 540 541 /** 542 * Sets message type to HTML. 543 * @param bool $ishtml 544 * @return void 545 */ 546 public function IsHTML($ishtml = true) { 547 if ($ishtml) { 548 $this->ContentType = 'text/html'; 549 } else { 550 $this->ContentType = 'text/plain'; 551 } 552 } 553 554 /** 555 * Sets Mailer to send message using SMTP. 556 * @return void 557 */ 558 public function IsSMTP() { 559 $this->Mailer = 'smtp'; 560 } 561 562 /** 563 * Sets Mailer to send message using PHP mail() function. 564 * @return void 565 */ 566 public function IsMail() { 567 $this->Mailer = 'mail'; 568 } 569 570 /** 571 * Sets Mailer to send message using the $Sendmail program. 572 * @return void 573 */ 574 public function IsSendmail() { 575 if (!stristr(ini_get('sendmail_path'), 'sendmail')) { 576 $this->Sendmail = '/var/qmail/bin/sendmail'; 577 } 578 $this->Mailer = 'sendmail'; 579 } 580 581 /** 582 * Sets Mailer to send message using the qmail MTA. 583 * @return void 584 */ 585 public function IsQmail() { 586 if (stristr(ini_get('sendmail_path'), 'qmail')) { 587 $this->Sendmail = '/var/qmail/bin/sendmail'; 588 } 589 $this->Mailer = 'sendmail'; 590 } 591 592 ///////////////////////////////////////////////// 593 // METHODS, RECIPIENTS 594 ///////////////////////////////////////////////// 595 596 /** 597 * Adds a "To" address. 598 * @param string $address 599 * @param string $name 600 * @return boolean true on success, false if address already used 601 */ 602 public function AddAddress($address, $name = '') { 603 return $this->AddAnAddress('to', $address, $name); 604 } 605 606 /** 607 * Adds a "Cc" address. 608 * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. 609 * @param string $address 610 * @param string $name 611 * @return boolean true on success, false if address already used 612 */ 613 public function AddCC($address, $name = '') { 614 return $this->AddAnAddress('cc', $address, $name); 615 } 616 617 /** 618 * Adds a "Bcc" address. 619 * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. 620 * @param string $address 621 * @param string $name 622 * @return boolean true on success, false if address already used 623 */ 624 public function AddBCC($address, $name = '') { 625 return $this->AddAnAddress('bcc', $address, $name); 626 } 627 628 /** 629 * Adds a "Reply-to" address. 630 * @param string $address 631 * @param string $name 632 * @return boolean 633 */ 634 public function AddReplyTo($address, $name = '') { 635 return $this->AddAnAddress('Reply-To', $address, $name); 636 } 637 638 /** 639 * Adds an address to one of the recipient arrays 640 * Addresses that have been added already return false, but do not throw exceptions 641 * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' 642 * @param string $address The email address to send to 643 * @param string $name 644 * @throws phpmailerException 645 * @return boolean true on success, false if address already used or invalid in some way 646 * @access protected 647 */ 648 protected function AddAnAddress($kind, $address, $name = '') { 649 if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) { 650 $this->SetError($this->Lang('Invalid recipient array').': '.$kind); 651 if ($this->exceptions) { 652 throw new phpmailerException('Invalid recipient array: ' . $kind); 653 } 654 if ($this->SMTPDebug) { 655 $this->edebug($this->Lang('Invalid recipient array').': '.$kind); 656 } 657 return false; 658 } 659 $address = trim($address); 660 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim 661 if (!$this->ValidateAddress($address)) { 662 $this->SetError($this->Lang('invalid_address').': '. $address); 663 if ($this->exceptions) { 664 throw new phpmailerException($this->Lang('invalid_address').': '.$address); 665 } 666 if ($this->SMTPDebug) { 667 $this->edebug($this->Lang('invalid_address').': '.$address); 668 } 669 return false; 670 } 671 if ($kind != 'Reply-To') { 672 if (!isset($this->all_recipients[strtolower($address)])) { 673 array_push($this->$kind, array($address, $name)); 674 $this->all_recipients[strtolower($address)] = true; 675 return true; 676 } 677 } else { 678 if (!array_key_exists(strtolower($address), $this->ReplyTo)) { 679 $this->ReplyTo[strtolower($address)] = array($address, $name); 680 return true; 681 } 682 } 683 return false; 4015 class phpmailerException extends Exception 4016 { 4017 /** 4018 * Prettify error message output 4019 * @return string 4020 */ 4021 public function errorMessage() 4022 { 4023 $errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n"; 4024 return $errorMsg; 4025 } 684 4026 } 685 686 /**687 * Set the From and FromName properties688 * @param string $address689 * @param string $name690 * @param int $auto Also set Reply-To and Sender691 * @throws phpmailerException692 * @return boolean693 */694 public function SetFrom($address, $name = '', $auto = 1) {695 $address = trim($address);696 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim697 if (!$this->ValidateAddress($address)) {698 $this->SetError($this->Lang('invalid_address').': '. $address);699 if ($this->exceptions) {700 throw new phpmailerException($this->Lang('invalid_address').': '.$address);701 }702 if ($this->SMTPDebug) {703 $this->edebug($this->Lang('invalid_address').': '.$address);704 }705 return false;706 }707 $this->From = $address;708 $this->FromName = $name;709 if ($auto) {710 if (empty($this->ReplyTo)) {711 $this->AddAnAddress('Reply-To', $address, $name);712 }713 if (empty($this->Sender)) {714 $this->Sender = $address;715 }716 }717 return true;718 }719 720 /**721 * Check that a string looks roughly like an email address should722 * Static so it can be used without instantiation, public so people can overload723 * Conforms to RFC5322: Uses *correct* regex on which FILTER_VALIDATE_EMAIL is724 * based; So why not use FILTER_VALIDATE_EMAIL? Because it was broken to725 * not allow a@b type valid addresses :(726 * Some Versions of PHP break on the regex though, likely due to PCRE, so use727 * the older validation method for those users. (http://php.net/manual/en/pcre.installation.php)728 * @link http://squiloople.com/2009/12/20/email-address-validation/729 * @copyright regex Copyright Michael Rushton 2009-10 | http://squiloople.com/ | Feel free to use and redistribute this code. But please keep this copyright notice.730 * @param string $address The email address to check731 * @return boolean732 * @static733 * @access public734 */735 public static function ValidateAddress($address) {736 if ((defined('PCRE_VERSION')) && (version_compare(PCRE_VERSION, '8.0') >= 0)) {737 return preg_match('/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)((?>(?>(?>((?>(?>(?>\x0D\x0A)?[ ])+|(?>[ ]*\x0D\x0A)?[ ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f0-9][:\]]){7,})((?6)(?>:(?6)){0,5})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f0-9]:){5,})(?8)?::(?>((?6)(?>:(?6)){0,3}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', $address);738 } elseif (function_exists('filter_var')) { //Introduced in PHP 5.2739 if(filter_var($address, FILTER_VALIDATE_EMAIL) === FALSE) {740 return false;741 } else {742 return true;743 }744 } else {745 return preg_match('/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!\.)){0,61}[a-zA-Z0-9_-]?\.)+[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!$)){0,61}[a-zA-Z0-9_]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/', $address);746 }747 }748 749 /////////////////////////////////////////////////750 // METHODS, MAIL SENDING751 /////////////////////////////////////////////////752 753 /**754 * Creates message and assigns Mailer. If the message is755 * not sent successfully then it returns false. Use the ErrorInfo756 * variable to view description of the error.757 * @throws phpmailerException758 * @return bool759 */760 public function Send() {761 try {762 if(!$this->PreSend()) return false;763 return $this->PostSend();764 } catch (phpmailerException $e) {765 $this->mailHeader = '';766 $this->SetError($e->getMessage());767 if ($this->exceptions) {768 throw $e;769 }770 return false;771 }772 }773 774 /**775 * Prep mail by constructing all message entities776 * @throws phpmailerException777 * @return bool778 */779 public function PreSend() {780 try {781 $this->mailHeader = "";782 if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {783 throw new phpmailerException($this->Lang('provide_address'), self::STOP_CRITICAL);784 }785 786 // Set whether the message is multipart/alternative787 if(!empty($this->AltBody)) {788 $this->ContentType = 'multipart/alternative';789 }790 791 $this->error_count = 0; // reset errors792 $this->SetMessageType();793 //Refuse to send an empty message794 if (empty($this->Body)) {795 throw new phpmailerException($this->Lang('empty_message'), self::STOP_CRITICAL);796 }797 798 $this->MIMEHeader = $this->CreateHeader();799 $this->MIMEBody = $this->CreateBody();800 801 // To capture the complete message when using mail(), create802 // an extra header list which CreateHeader() doesn't fold in803 if ($this->Mailer == 'mail') {804 if (count($this->to) > 0) {805 $this->mailHeader .= $this->AddrAppend("To", $this->to);806 } else {807 $this->mailHeader .= $this->HeaderLine("To", "undisclosed-recipients:;");808 }809 $this->mailHeader .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader(trim($this->Subject))));810 // if(count($this->cc) > 0) {811 // $this->mailHeader .= $this->AddrAppend("Cc", $this->cc);812 // }813 }814 815 // digitally sign with DKIM if enabled816 if (!empty($this->DKIM_domain) && !empty($this->DKIM_private) && !empty($this->DKIM_selector) && !empty($this->DKIM_domain) && file_exists($this->DKIM_private)) {817 $header_dkim = $this->DKIM_Add($this->MIMEHeader, $this->EncodeHeader($this->SecureHeader($this->Subject)), $this->MIMEBody);818 $this->MIMEHeader = str_replace("\r\n", "\n", $header_dkim) . $this->MIMEHeader;819 }820 821 return true;822 823 } catch (phpmailerException $e) {824 $this->SetError($e->getMessage());825 if ($this->exceptions) {826 throw $e;827 }828 return false;829 }830 }831 832 /**833 * Actual Email transport function834 * Send the email via the selected mechanism835 * @throws phpmailerException836 * @return bool837 */838 public function PostSend() {839 try {840 // Choose the mailer and send through it841 switch($this->Mailer) {842 case 'sendmail':843 return $this->SendmailSend($this->MIMEHeader, $this->MIMEBody);844 case 'smtp':845 return $this->SmtpSend($this->MIMEHeader, $this->MIMEBody);846 case 'mail':847 return $this->MailSend($this->MIMEHeader, $this->MIMEBody);848 default:849 return $this->MailSend($this->MIMEHeader, $this->MIMEBody);850 }851 } catch (phpmailerException $e) {852 $this->SetError($e->getMessage());853 if ($this->exceptions) {854 throw $e;855 }856 if ($this->SMTPDebug) {857 $this->edebug($e->getMessage()."\n");858 }859 }860 return false;861 }862 863 /**864 * Sends mail using the $Sendmail program.865 * @param string $header The message headers866 * @param string $body The message body867 * @throws phpmailerException868 * @access protected869 * @return bool870 */871 protected function SendmailSend($header, $body) {872 if ($this->Sender != '') {873 $sendmail = sprintf("%s -oi -f%s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));874 } else {875 $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail));876 }877 if ($this->SingleTo === true) {878 foreach ($this->SingleToArray as $val) {879 if(!@$mail = popen($sendmail, 'w')) {880 throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL);881 }882 fputs($mail, "To: " . $val . "\n");883 fputs($mail, $header);884 fputs($mail, $body);885 $result = pclose($mail);886 // implement call back function if it exists887 $isSent = ($result == 0) ? 1 : 0;888 $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body);889 if($result != 0) {890 throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL);891 }892 }893 } else {894 if(!@$mail = popen($sendmail, 'w')) {895 throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL);896 }897 fputs($mail, $header);898 fputs($mail, $body);899 $result = pclose($mail);900 // implement call back function if it exists901 $isSent = ($result == 0) ? 1 : 0;902 $this->doCallback($isSent, $this->to, $this->cc, $this->bcc, $this->Subject, $body);903 if($result != 0) {904 throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL);905 }906 }907 return true;908 }909 910 /**911 * Sends mail using the PHP mail() function.912 * @param string $header The message headers913 * @param string $body The message body914 * @throws phpmailerException915 * @access protected916 * @return bool917 */918 protected function MailSend($header, $body) {919 $toArr = array();920 foreach($this->to as $t) {921 $toArr[] = $this->AddrFormat($t);922 }923 $to = implode(', ', $toArr);924 925 if (empty($this->Sender)) {926 $params = " ";927 } else {928 $params = sprintf("-f%s", $this->Sender);929 }930 if ($this->Sender != '' and !ini_get('safe_mode')) {931 $old_from = ini_get('sendmail_from');932 ini_set('sendmail_from', $this->Sender);933 }934 $rt = false;935 if ($this->SingleTo === true && count($toArr) > 1) {936 foreach ($toArr as $val) {937 $rt = $this->mail_passthru($val, $this->Subject, $body, $header, $params);938 // implement call back function if it exists939 $isSent = ($rt == 1) ? 1 : 0;940 $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body);941 }942 } else {943 $rt = $this->mail_passthru($to, $this->Subject, $body, $header, $params);944 // implement call back function if it exists945 $isSent = ($rt == 1) ? 1 : 0;946 $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body);947 }948 if (isset($old_from)) {949 ini_set('sendmail_from', $old_from);950 }951 if(!$rt) {952 throw new phpmailerException($this->Lang('instantiate'), self::STOP_CRITICAL);953 }954 return true;955 }956 957 /**958 * Sends mail via SMTP using PhpSMTP959 * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.960 * @param string $header The message headers961 * @param string $body The message body962 * @throws phpmailerException963 * @uses SMTP964 * @access protected965 * @return bool966 */967 protected function SmtpSend($header, $body) {968 require_once $this->PluginDir . 'class-smtp.php';969 $bad_rcpt = array();970 971 if(!$this->SmtpConnect()) {972 throw new phpmailerException($this->Lang('smtp_connect_failed'), self::STOP_CRITICAL);973 }974 $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender;975 if(!$this->smtp->Mail($smtp_from)) {976 $this->SetError($this->Lang('from_failed') . $smtp_from . " : " . implode(",",$this->smtp->getError())) ;977 throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);978 }979 980 // Attempt to send attach all recipients981 foreach($this->to as $to) {982 if (!$this->smtp->Recipient($to[0])) {983 $bad_rcpt[] = $to[0];984 // implement call back function if it exists985 $isSent = 0;986 $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body);987 } else {988 // implement call back function if it exists989 $isSent = 1;990 $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body);991 }992 }993 foreach($this->cc as $cc) {994 if (!$this->smtp->Recipient($cc[0])) {995 $bad_rcpt[] = $cc[0];996 // implement call back function if it exists997 $isSent = 0;998 $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body);999 } else {1000 // implement call back function if it exists1001 $isSent = 1;1002 $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body);1003 }1004 }1005 foreach($this->bcc as $bcc) {1006 if (!$this->smtp->Recipient($bcc[0])) {1007 $bad_rcpt[] = $bcc[0];1008 // implement call back function if it exists1009 $isSent = 0;1010 $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body);1011 } else {1012 // implement call back function if it exists1013 $isSent = 1;1014 $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body);1015 }1016 }1017 1018 1019 if (count($bad_rcpt) > 0 ) { //Create error message for any bad addresses1020 $badaddresses = implode(', ', $bad_rcpt);1021 throw new phpmailerException($this->Lang('recipients_failed') . $badaddresses);1022 }1023 if(!$this->smtp->Data($header . $body)) {1024 throw new phpmailerException($this->Lang('data_not_accepted'), self::STOP_CRITICAL);1025 }1026 if($this->SMTPKeepAlive == true) {1027 $this->smtp->Reset();1028 } else {1029 $this->smtp->Quit();1030 $this->smtp->Close();1031 }1032 return true;1033 }1034 1035 /**1036 * Initiates a connection to an SMTP server.1037 * Returns false if the operation failed.1038 * @uses SMTP1039 * @access public1040 * @throws phpmailerException1041 * @return bool1042 */1043 public function SmtpConnect() {1044 if(is_null($this->smtp)) {1045 $this->smtp = new SMTP;1046 }1047 1048 $this->smtp->Timeout = $this->Timeout;1049 $this->smtp->do_debug = $this->SMTPDebug;1050 $hosts = explode(';', $this->Host);1051 $index = 0;1052 $connection = $this->smtp->Connected();1053 1054 // Retry while there is no connection1055 try {1056 while($index < count($hosts) && !$connection) {1057 $hostinfo = array();1058 if (preg_match('/^(.+):([0-9]+)$/', $hosts[$index], $hostinfo)) {1059 $host = $hostinfo[1];1060 $port = $hostinfo[2];1061 } else {1062 $host = $hosts[$index];1063 $port = $this->Port;1064 }1065 1066 $tls = ($this->SMTPSecure == 'tls');1067 $ssl = ($this->SMTPSecure == 'ssl');1068 1069 if ($this->smtp->Connect(($ssl ? 'ssl://':'').$host, $port, $this->Timeout)) {1070 1071 $hello = ($this->Helo != '' ? $this->Helo : $this->ServerHostname());1072 $this->smtp->Hello($hello);1073 1074 if ($tls) {1075 if (!$this->smtp->StartTLS()) {1076 throw new phpmailerException($this->Lang('connect_host'));1077 }1078 1079 //We must resend HELO after tls negotiation1080 $this->smtp->Hello($hello);1081 }1082 1083 $connection = true;1084 if ($this->SMTPAuth) {1085 if (!$this->smtp->Authenticate($this->Username, $this->Password, $this->AuthType,1086 $this->Realm, $this->Workstation)) {1087 throw new phpmailerException($this->Lang('authenticate'));1088 }1089 }1090 }1091 $index++;1092 if (!$connection) {1093 throw new phpmailerException($this->Lang('connect_host'));1094 }1095 }1096 } catch (phpmailerException $e) {1097 $this->smtp->Reset();1098 if ($this->exceptions) {1099 throw $e;1100 }1101 }1102 return true;1103 }1104 1105 /**1106 * Closes the active SMTP session if one exists.1107 * @return void1108 */1109 public function SmtpClose() {1110 if ($this->smtp !== null) {1111 if($this->smtp->Connected()) {1112 $this->smtp->Quit();1113 $this->smtp->Close();1114 }1115 }1116 }1117 1118 /**1119 * Sets the language for all class error messages.1120 * Returns false if it cannot load the language file. The default language is English.1121 * @param string $langcode ISO 639-1 2-character language code (e.g. Portuguese: "br")1122 * @param string $lang_path Path to the language file directory1123 * @return bool1124 * @access public1125 */1126 function SetLanguage($langcode = 'en', $lang_path = 'language/') {1127 //Define full set of translatable strings1128 $PHPMAILER_LANG = array(1129 'authenticate' => 'SMTP Error: Could not authenticate.',1130 'connect_host' => 'SMTP Error: Could not connect to SMTP host.',1131 'data_not_accepted' => 'SMTP Error: Data not accepted.',1132 'empty_message' => 'Message body empty',1133 'encoding' => 'Unknown encoding: ',1134 'execute' => 'Could not execute: ',1135 'file_access' => 'Could not access file: ',1136 'file_open' => 'File Error: Could not open file: ',1137 'from_failed' => 'The following From address failed: ',1138 'instantiate' => 'Could not instantiate mail function.',1139 'invalid_address' => 'Invalid address',1140 'mailer_not_supported' => ' mailer is not supported.',1141 'provide_address' => 'You must provide at least one recipient email address.',1142 'recipients_failed' => 'SMTP Error: The following recipients failed: ',1143 'signing' => 'Signing Error: ',1144 'smtp_connect_failed' => 'SMTP Connect() failed.',1145 'smtp_error' => 'SMTP server error: ',1146 'variable_set' => 'Cannot set or reset variable: '1147 );1148 //Overwrite language-specific strings. This way we'll never have missing translations - no more "language string failed to load"!1149 $l = true;1150 if ($langcode != 'en') { //There is no English translation file1151 $l = @include $lang_path.'phpmailer.lang-'.$langcode.'.php';1152 }1153 $this->language = $PHPMAILER_LANG;1154 return ($l == true); //Returns false if language not found1155 }1156 1157 /**1158 * Return the current array of language strings1159 * @return array1160 */1161 public function GetTranslations() {1162 return $this->language;1163 }1164 1165 /////////////////////////////////////////////////1166 // METHODS, MESSAGE CREATION1167 /////////////////////////////////////////////////1168 1169 /**1170 * Creates recipient headers.1171 * @access public1172 * @param string $type1173 * @param array $addr1174 * @return string1175 */1176 public function AddrAppend($type, $addr) {1177 $addr_str = $type . ': ';1178 $addresses = array();1179 foreach ($addr as $a) {1180 $addresses[] = $this->AddrFormat($a);1181 }1182 $addr_str .= implode(', ', $addresses);1183 $addr_str .= $this->LE;1184 1185 return $addr_str;1186 }1187 1188 /**1189 * Formats an address correctly.1190 * @access public1191 * @param string $addr1192 * @return string1193 */1194 public function AddrFormat($addr) {1195 if (empty($addr[1])) {1196 return $this->SecureHeader($addr[0]);1197 } else {1198 return $this->EncodeHeader($this->SecureHeader($addr[1]), 'phrase') . " <" . $this->SecureHeader($addr[0]) . ">";1199 }1200 }1201 1202 /**1203 * Wraps message for use with mailers that do not1204 * automatically perform wrapping and for quoted-printable.1205 * Original written by philippe.1206 * @param string $message The message to wrap1207 * @param integer $length The line length to wrap to1208 * @param boolean $qp_mode Whether to run in Quoted-Printable mode1209 * @access public1210 * @return string1211 */1212 public function WrapText($message, $length, $qp_mode = false) {1213 $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE;1214 // If utf-8 encoding is used, we will need to make sure we don't1215 // split multibyte characters when we wrap1216 $is_utf8 = (strtolower($this->CharSet) == "utf-8");1217 $lelen = strlen($this->LE);1218 $crlflen = strlen(self::CRLF);1219 1220 $message = $this->FixEOL($message);1221 if (substr($message, -$lelen) == $this->LE) {1222 $message = substr($message, 0, -$lelen);1223 }1224 1225 $line = explode($this->LE, $message); // Magic. We know FixEOL uses $LE1226 $message = '';1227 for ($i = 0 ;$i < count($line); $i++) {1228 $line_part = explode(' ', $line[$i]);1229 $buf = '';1230 for ($e = 0; $e<count($line_part); $e++) {1231 $word = $line_part[$e];1232 if ($qp_mode and (strlen($word) > $length)) {1233 $space_left = $length - strlen($buf) - $crlflen;1234 if ($e != 0) {1235 if ($space_left > 20) {1236 $len = $space_left;1237 if ($is_utf8) {1238 $len = $this->UTF8CharBoundary($word, $len);1239 } elseif (substr($word, $len - 1, 1) == "=") {1240 $len--;1241 } elseif (substr($word, $len - 2, 1) == "=") {1242 $len -= 2;1243 }1244 $part = substr($word, 0, $len);1245 $word = substr($word, $len);1246 $buf .= ' ' . $part;1247 $message .= $buf . sprintf("=%s", self::CRLF);1248 } else {1249 $message .= $buf . $soft_break;1250 }1251 $buf = '';1252 }1253 while (strlen($word) > 0) {1254 $len = $length;1255 if ($is_utf8) {1256 $len = $this->UTF8CharBoundary($word, $len);1257 } elseif (substr($word, $len - 1, 1) == "=") {1258 $len--;1259 } elseif (substr($word, $len - 2, 1) == "=") {1260 $len -= 2;1261 }1262 $part = substr($word, 0, $len);1263 $word = substr($word, $len);1264 1265 if (strlen($word) > 0) {1266 $message .= $part . sprintf("=%s", self::CRLF);1267 } else {1268 $buf = $part;1269 }1270 }1271 } else {1272 $buf_o = $buf;1273 $buf .= ($e == 0) ? $word : (' ' . $word);1274 1275 if (strlen($buf) > $length and $buf_o != '') {1276 $message .= $buf_o . $soft_break;1277 $buf = $word;1278 }1279 }1280 }1281 $message .= $buf . self::CRLF;1282 }1283 1284 return $message;1285 }1286 1287 /**1288 * Finds last character boundary prior to maxLength in a utf-81289 * quoted (printable) encoded string.1290 * Original written by Colin Brown.1291 * @access public1292 * @param string $encodedText utf-8 QP text1293 * @param int $maxLength find last character boundary prior to this length1294 * @return int1295 */1296 public function UTF8CharBoundary($encodedText, $maxLength) {1297 $foundSplitPos = false;1298 $lookBack = 3;1299 while (!$foundSplitPos) {1300 $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);1301 $encodedCharPos = strpos($lastChunk, "=");1302 if ($encodedCharPos !== false) {1303 // Found start of encoded character byte within $lookBack block.1304 // Check the encoded byte value (the 2 chars after the '=')1305 $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);1306 $dec = hexdec($hex);1307 if ($dec < 128) { // Single byte character.1308 // If the encoded char was found at pos 0, it will fit1309 // otherwise reduce maxLength to start of the encoded char1310 $maxLength = ($encodedCharPos == 0) ? $maxLength :1311 $maxLength - ($lookBack - $encodedCharPos);1312 $foundSplitPos = true;1313 } elseif ($dec >= 192) { // First byte of a multi byte character1314 // Reduce maxLength to split at start of character1315 $maxLength = $maxLength - ($lookBack - $encodedCharPos);1316 $foundSplitPos = true;1317 } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back1318 $lookBack += 3;1319 }1320 } else {1321 // No encoded character found1322 $foundSplitPos = true;1323 }1324 }1325 return $maxLength;1326 }1327 1328 1329 /**1330 * Set the body wrapping.1331 * @access public1332 * @return void1333 */1334 public function SetWordWrap() {1335 if($this->WordWrap < 1) {1336 return;1337 }1338 1339 switch($this->message_type) {1340 case 'alt':1341 case 'alt_inline':1342 case 'alt_attach':1343 case 'alt_inline_attach':1344 $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap);1345 break;1346 default:1347 $this->Body = $this->WrapText($this->Body, $this->WordWrap);1348 break;1349 }1350 }1351 1352 /**1353 * Assembles message header.1354 * @access public1355 * @return string The assembled header1356 */1357 public function CreateHeader() {1358 $result = '';1359 1360 // Set the boundaries1361 $uniq_id = md5(uniqid(time()));1362 $this->boundary[1] = 'b1_' . $uniq_id;1363 $this->boundary[2] = 'b2_' . $uniq_id;1364 $this->boundary[3] = 'b3_' . $uniq_id;1365 1366 if ($this->MessageDate == '') {1367 $result .= $this->HeaderLine('Date', self::RFCDate());1368 } else {1369 $result .= $this->HeaderLine('Date', $this->MessageDate);1370 }1371 1372 if ($this->ReturnPath) {1373 $result .= $this->HeaderLine('Return-Path', trim($this->ReturnPath));1374 } elseif ($this->Sender == '') {1375 $result .= $this->HeaderLine('Return-Path', trim($this->From));1376 } else {1377 $result .= $this->HeaderLine('Return-Path', trim($this->Sender));1378 }1379 1380 // To be created automatically by mail()1381 if($this->Mailer != 'mail') {1382 if ($this->SingleTo === true) {1383 foreach($this->to as $t) {1384 $this->SingleToArray[] = $this->AddrFormat($t);1385 }1386 } else {1387 if(count($this->to) > 0) {1388 $result .= $this->AddrAppend('To', $this->to);1389 } elseif (count($this->cc) == 0) {1390 $result .= $this->HeaderLine('To', 'undisclosed-recipients:;');1391 }1392 }1393 }1394 1395 $from = array();1396 $from[0][0] = trim($this->From);1397 $from[0][1] = $this->FromName;1398 $result .= $this->AddrAppend('From', $from);1399 1400 // sendmail and mail() extract Cc from the header before sending1401 if(count($this->cc) > 0) {1402 $result .= $this->AddrAppend('Cc', $this->cc);1403 }1404 1405 // sendmail and mail() extract Bcc from the header before sending1406 if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) {1407 $result .= $this->AddrAppend('Bcc', $this->bcc);1408 }1409 1410 if(count($this->ReplyTo) > 0) {1411 $result .= $this->AddrAppend('Reply-To', $this->ReplyTo);1412 }1413 1414 // mail() sets the subject itself1415 if($this->Mailer != 'mail') {1416 $result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject)));1417 }1418 1419 if($this->MessageID != '') {1420 $result .= $this->HeaderLine('Message-ID', $this->MessageID);1421 } else {1422 $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE);1423 }1424 $result .= $this->HeaderLine('X-Priority', $this->Priority);1425 if ($this->XMailer == '') {1426 $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (http://code.google.com/a/apache-extras.org/p/phpmailer/)');1427 } else {1428 $myXmailer = trim($this->XMailer);1429 if ($myXmailer) {1430 $result .= $this->HeaderLine('X-Mailer', $myXmailer);1431 }1432 }1433 1434 if($this->ConfirmReadingTo != '') {1435 $result .= $this->HeaderLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>');1436 }1437 1438 // Add custom headers1439 for($index = 0; $index < count($this->CustomHeader); $index++) {1440 $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]), $this->EncodeHeader(trim($this->CustomHeader[$index][1])));1441 }1442 if (!$this->sign_key_file) {1443 $result .= $this->HeaderLine('MIME-Version', '1.0');1444 $result .= $this->GetMailMIME();1445 }1446 1447 return $result;1448 }1449 1450 /**1451 * Returns the message MIME.1452 * @access public1453 * @return string1454 */1455 public function GetMailMIME() {1456 $result = '';1457 switch($this->message_type) {1458 case 'inline':1459 $result .= $this->HeaderLine('Content-Type', 'multipart/related;');1460 $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"');1461 break;1462 case 'attach':1463 case 'inline_attach':1464 case 'alt_attach':1465 case 'alt_inline_attach':1466 $result .= $this->HeaderLine('Content-Type', 'multipart/mixed;');1467 $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"');1468 break;1469 case 'alt':1470 case 'alt_inline':1471 $result .= $this->HeaderLine('Content-Type', 'multipart/alternative;');1472 $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"');1473 break;1474 default:1475 // Catches case 'plain': and case '':1476 $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding);1477 $result .= $this->TextLine('Content-Type: '.$this->ContentType.'; charset='.$this->CharSet);1478 break;1479 }1480 1481 if($this->Mailer != 'mail') {1482 $result .= $this->LE;1483 }1484 1485 return $result;1486 }1487 1488 /**1489 * Returns the MIME message (headers and body). Only really valid post PreSend().1490 * @access public1491 * @return string1492 */1493 public function GetSentMIMEMessage() {1494 return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody;1495 }1496 1497 1498 /**1499 * Assembles the message body. Returns an empty string on failure.1500 * @access public1501 * @throws phpmailerException1502 * @return string The assembled message body1503 */1504 public function CreateBody() {1505 $body = '';1506 1507 if ($this->sign_key_file) {1508 $body .= $this->GetMailMIME().$this->LE;1509 }1510 1511 $this->SetWordWrap();1512 1513 switch($this->message_type) {1514 case 'inline':1515 $body .= $this->GetBoundary($this->boundary[1], '', '', '');1516 $body .= $this->EncodeString($this->Body, $this->Encoding);1517 $body .= $this->LE.$this->LE;1518 $body .= $this->AttachAll("inline", $this->boundary[1]);1519 break;1520 case 'attach':1521 $body .= $this->GetBoundary($this->boundary[1], '', '', '');1522 $body .= $this->EncodeString($this->Body, $this->Encoding);1523 $body .= $this->LE.$this->LE;1524 $body .= $this->AttachAll("attachment", $this->boundary[1]);1525 break;1526 case 'inline_attach':1527 $body .= $this->TextLine("--" . $this->boundary[1]);1528 $body .= $this->HeaderLine('Content-Type', 'multipart/related;');1529 $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"');1530 $body .= $this->LE;1531 $body .= $this->GetBoundary($this->boundary[2], '', '', '');1532 $body .= $this->EncodeString($this->Body, $this->Encoding);1533 $body .= $this->LE.$this->LE;1534 $body .= $this->AttachAll("inline", $this->boundary[2]);1535 $body .= $this->LE;1536 $body .= $this->AttachAll("attachment", $this->boundary[1]);1537 break;1538 case 'alt':1539 $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', '');1540 $body .= $this->EncodeString($this->AltBody, $this->Encoding);1541 $body .= $this->LE.$this->LE;1542 $body .= $this->GetBoundary($this->boundary[1], '', 'text/html', '');1543 $body .= $this->EncodeString($this->Body, $this->Encoding);1544 $body .= $this->LE.$this->LE;1545 $body .= $this->EndBoundary($this->boundary[1]);1546 break;1547 case 'alt_inline':1548 $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', '');1549 $body .= $this->EncodeString($this->AltBody, $this->Encoding);1550 $body .= $this->LE.$this->LE;1551 $body .= $this->TextLine("--" . $this->boundary[1]);1552 $body .= $this->HeaderLine('Content-Type', 'multipart/related;');1553 $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"');1554 $body .= $this->LE;1555 $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', '');1556 $body .= $this->EncodeString($this->Body, $this->Encoding);1557 $body .= $this->LE.$this->LE;1558 $body .= $this->AttachAll("inline", $this->boundary[2]);1559 $body .= $this->LE;1560 $body .= $this->EndBoundary($this->boundary[1]);1561 break;1562 case 'alt_attach':1563 $body .= $this->TextLine("--" . $this->boundary[1]);1564 $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;');1565 $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"');1566 $body .= $this->LE;1567 $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', '');1568 $body .= $this->EncodeString($this->AltBody, $this->Encoding);1569 $body .= $this->LE.$this->LE;1570 $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', '');1571 $body .= $this->EncodeString($this->Body, $this->Encoding);1572 $body .= $this->LE.$this->LE;1573 $body .= $this->EndBoundary($this->boundary[2]);1574 $body .= $this->LE;1575 $body .= $this->AttachAll("attachment", $this->boundary[1]);1576 break;1577 case 'alt_inline_attach':1578 $body .= $this->TextLine("--" . $this->boundary[1]);1579 $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;');1580 $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"');1581 $body .= $this->LE;1582 $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', '');1583 $body .= $this->EncodeString($this->AltBody, $this->Encoding);1584 $body .= $this->LE.$this->LE;1585 $body .= $this->TextLine("--" . $this->boundary[2]);1586 $body .= $this->HeaderLine('Content-Type', 'multipart/related;');1587 $body .= $this->TextLine("\tboundary=\"" . $this->boundary[3] . '"');1588 $body .= $this->LE;1589 $body .= $this->GetBoundary($this->boundary[3], '', 'text/html', '');1590 $body .= $this->EncodeString($this->Body, $this->Encoding);1591 $body .= $this->LE.$this->LE;1592 $body .= $this->AttachAll("inline", $this->boundary[3]);1593 $body .= $this->LE;1594 $body .= $this->EndBoundary($this->boundary[2]);1595 $body .= $this->LE;1596 $body .= $this->AttachAll("attachment", $this->boundary[1]);1597 break;1598 default:1599 // catch case 'plain' and case ''1600 $body .= $this->EncodeString($this->Body, $this->Encoding);1601 break;1602 }1603 1604 if ($this->IsError()) {1605 $body = '';1606 } elseif ($this->sign_key_file) {1607 try {1608 $file = tempnam('', 'mail');1609 file_put_contents($file, $body); //TODO check this worked1610 $signed = tempnam("", "signed");1611 if (@openssl_pkcs7_sign($file, $signed, "file://".$this->sign_cert_file, array("file://".$this->sign_key_file, $this->sign_key_pass), NULL)) {1612 @unlink($file);1613 $body = file_get_contents($signed);1614 @unlink($signed);1615 } else {1616 @unlink($file);1617 @unlink($signed);1618 throw new phpmailerException($this->Lang("signing").openssl_error_string());1619 }1620 } catch (phpmailerException $e) {1621 $body = '';1622 if ($this->exceptions) {1623 throw $e;1624 }1625 }1626 }1627 1628 return $body;1629 }1630 1631 /**1632 * Returns the start of a message boundary.1633 * @access protected1634 * @param string $boundary1635 * @param string $charSet1636 * @param string $contentType1637 * @param string $encoding1638 * @return string1639 */1640 protected function GetBoundary($boundary, $charSet, $contentType, $encoding) {1641 $result = '';1642 if($charSet == '') {1643 $charSet = $this->CharSet;1644 }1645 if($contentType == '') {1646 $contentType = $this->ContentType;1647 }1648 if($encoding == '') {1649 $encoding = $this->Encoding;1650 }1651 $result .= $this->TextLine('--' . $boundary);1652 $result .= sprintf("Content-Type: %s; charset=%s", $contentType, $charSet);1653 $result .= $this->LE;1654 $result .= $this->HeaderLine('Content-Transfer-Encoding', $encoding);1655 $result .= $this->LE;1656 1657 return $result;1658 }1659 1660 /**1661 * Returns the end of a message boundary.1662 * @access protected1663 * @param string $boundary1664 * @return string1665 */1666 protected function EndBoundary($boundary) {1667 return $this->LE . '--' . $boundary . '--' . $this->LE;1668 }1669 1670 /**1671 * Sets the message type.1672 * @access protected1673 * @return void1674 */1675 protected function SetMessageType() {1676 $this->message_type = array();1677 if($this->AlternativeExists()) $this->message_type[] = "alt";1678 if($this->InlineImageExists()) $this->message_type[] = "inline";1679 if($this->AttachmentExists()) $this->message_type[] = "attach";1680 $this->message_type = implode("_", $this->message_type);1681 if($this->message_type == "") $this->message_type = "plain";1682 }1683 1684 /**1685 * Returns a formatted header line.1686 * @access public1687 * @param string $name1688 * @param string $value1689 * @return string1690 */1691 public function HeaderLine($name, $value) {1692 return $name . ': ' . $value . $this->LE;1693 }1694 1695 /**1696 * Returns a formatted mail line.1697 * @access public1698 * @param string $value1699 * @return string1700 */1701 public function TextLine($value) {1702 return $value . $this->LE;1703 }1704 1705 /////////////////////////////////////////////////1706 // CLASS METHODS, ATTACHMENTS1707 /////////////////////////////////////////////////1708 1709 /**1710 * Adds an attachment from a path on the filesystem.1711 * Returns false if the file could not be found1712 * or accessed.1713 * @param string $path Path to the attachment.1714 * @param string $name Overrides the attachment name.1715 * @param string $encoding File encoding (see $Encoding).1716 * @param string $type File extension (MIME) type.1717 * @throws phpmailerException1718 * @return bool1719 */1720 public function AddAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream') {1721 try {1722 if ( !@is_file($path) ) {1723 throw new phpmailerException($this->Lang('file_access') . $path, self::STOP_CONTINUE);1724 }1725 $filename = basename($path);1726 if ( $name == '' ) {1727 $name = $filename;1728 }1729 1730 $this->attachment[] = array(1731 0 => $path,1732 1 => $filename,1733 2 => $name,1734 3 => $encoding,1735 4 => $type,1736 5 => false, // isStringAttachment1737 6 => 'attachment',1738 7 => 01739 );1740 1741 } catch (phpmailerException $e) {1742 $this->SetError($e->getMessage());1743 if ($this->exceptions) {1744 throw $e;1745 }1746 if ($this->SMTPDebug) {1747 $this->edebug($e->getMessage()."\n");1748 }1749 if ( $e->getCode() == self::STOP_CRITICAL ) {1750 return false;1751 }1752 }1753 return true;1754 }1755 1756 /**1757 * Return the current array of attachments1758 * @return array1759 */1760 public function GetAttachments() {1761 return $this->attachment;1762 }1763 1764 /**1765 * Attaches all fs, string, and binary attachments to the message.1766 * Returns an empty string on failure.1767 * @access protected1768 * @param string $disposition_type1769 * @param string $boundary1770 * @return string1771 */1772 protected function AttachAll($disposition_type, $boundary) {1773 // Return text of body1774 $mime = array();1775 $cidUniq = array();1776 $incl = array();1777 1778 // Add all attachments1779 foreach ($this->attachment as $attachment) {1780 // CHECK IF IT IS A VALID DISPOSITION_FILTER1781 if($attachment[6] == $disposition_type) {1782 // Check for string attachment1783 $string = '';1784 $path = '';1785 $bString = $attachment[5];1786 if ($bString) {1787 $string = $attachment[0];1788 } else {1789 $path = $attachment[0];1790 }1791 1792 $inclhash = md5(serialize($attachment));1793 if (in_array($inclhash, $incl)) { continue; }1794 $incl[] = $inclhash;1795 $filename = $attachment[1];1796 $name = $attachment[2];1797 $encoding = $attachment[3];1798 $type = $attachment[4];1799 $disposition = $attachment[6];1800 $cid = $attachment[7];1801 if ( $disposition == 'inline' && isset($cidUniq[$cid]) ) { continue; }1802 $cidUniq[$cid] = true;1803 1804 $mime[] = sprintf("--%s%s", $boundary, $this->LE);1805 $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $this->EncodeHeader($this->SecureHeader($name)), $this->LE);1806 $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE);1807 1808 if($disposition == 'inline') {1809 $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE);1810 }1811 1812 $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE);1813 1814 // Encode as string attachment1815 if($bString) {1816 $mime[] = $this->EncodeString($string, $encoding);1817 if($this->IsError()) {1818 return '';1819 }1820 $mime[] = $this->LE.$this->LE;1821 } else {1822 $mime[] = $this->EncodeFile($path, $encoding);1823 if($this->IsError()) {1824 return '';1825 }1826 $mime[] = $this->LE.$this->LE;1827 }1828 }1829 }1830 1831 $mime[] = sprintf("--%s--%s", $boundary, $this->LE);1832 1833 return implode("", $mime);1834 }1835 1836 /**1837 * Encodes attachment in requested format.1838 * Returns an empty string on failure.1839 * @param string $path The full path to the file1840 * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'1841 * @throws phpmailerException1842 * @see EncodeFile()1843 * @access protected1844 * @return string1845 */1846 protected function EncodeFile($path, $encoding = 'base64') {1847 try {1848 if (!is_readable($path)) {1849 throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE);1850 }1851 // if (!function_exists('get_magic_quotes')) {1852 // function get_magic_quotes() {1853 // return false;1854 // }1855 // }1856 $magic_quotes = get_magic_quotes_runtime();1857 if ($magic_quotes) {1858 if (version_compare(PHP_VERSION, '5.3.0', '<')) {1859 set_magic_quotes_runtime(0);1860 } else {1861 ini_set('magic_quotes_runtime', 0);1862 }1863 }1864 $file_buffer = file_get_contents($path);1865 $file_buffer = $this->EncodeString($file_buffer, $encoding);1866 if ($magic_quotes) {1867 if (version_compare(PHP_VERSION, '5.3.0', '<')) {1868 set_magic_quotes_runtime($magic_quotes);1869 } else {1870 ini_set('magic_quotes_runtime', $magic_quotes);1871 }1872 }1873 return $file_buffer;1874 } catch (Exception $e) {1875 $this->SetError($e->getMessage());1876 return '';1877 }1878 }1879 1880 /**1881 * Encodes string to requested format.1882 * Returns an empty string on failure.1883 * @param string $str The text to encode1884 * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'1885 * @access public1886 * @return string1887 */1888 public function EncodeString($str, $encoding = 'base64') {1889 $encoded = '';1890 switch(strtolower($encoding)) {1891 case 'base64':1892 $encoded = chunk_split(base64_encode($str), 76, $this->LE);1893 break;1894 case '7bit':1895 case '8bit':1896 $encoded = $this->FixEOL($str);1897 //Make sure it ends with a line break1898 if (substr($encoded, -(strlen($this->LE))) != $this->LE)1899 $encoded .= $this->LE;1900 break;1901 case 'binary':1902 $encoded = $str;1903 break;1904 case 'quoted-printable':1905 $encoded = $this->EncodeQP($str);1906 break;1907 default:1908 $this->SetError($this->Lang('encoding') . $encoding);1909 break;1910 }1911 return $encoded;1912 }1913 1914 /**1915 * Encode a header string to best (shortest) of Q, B, quoted or none.1916 * @access public1917 * @param string $str1918 * @param string $position1919 * @return string1920 */1921 public function EncodeHeader($str, $position = 'text') {1922 $x = 0;1923 1924 switch (strtolower($position)) {1925 case 'phrase':1926 if (!preg_match('/[\200-\377]/', $str)) {1927 // Can't use addslashes as we don't know what value has magic_quotes_sybase1928 $encoded = addcslashes($str, "\0..\37\177\\\"");1929 if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {1930 return ($encoded);1931 } else {1932 return ("\"$encoded\"");1933 }1934 }1935 $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);1936 break;1937 case 'comment':1938 $x = preg_match_all('/[()"]/', $str, $matches);1939 // Fall-through1940 case 'text':1941 default:1942 $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);1943 break;1944 }1945 1946 if ($x == 0) {1947 return ($str);1948 }1949 1950 $maxlen = 75 - 7 - strlen($this->CharSet);1951 // Try to select the encoding which should produce the shortest output1952 if (strlen($str)/3 < $x) {1953 $encoding = 'B';1954 if (function_exists('mb_strlen') && $this->HasMultiBytes($str)) {1955 // Use a custom function which correctly encodes and wraps long1956 // multibyte strings without breaking lines within a character1957 $encoded = $this->Base64EncodeWrapMB($str, "\n");1958 } else {1959 $encoded = base64_encode($str);1960 $maxlen -= $maxlen % 4;1961 $encoded = trim(chunk_split($encoded, $maxlen, "\n"));1962 }1963 } else {1964 $encoding = 'Q';1965 $encoded = $this->EncodeQ($str, $position);1966 $encoded = $this->WrapText($encoded, $maxlen, true);1967 $encoded = str_replace('='.self::CRLF, "\n", trim($encoded));1968 }1969 1970 $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded);1971 $encoded = trim(str_replace("\n", $this->LE, $encoded));1972 1973 return $encoded;1974 }1975 1976 /**1977 * Checks if a string contains multibyte characters.1978 * @access public1979 * @param string $str multi-byte text to wrap encode1980 * @return bool1981 */1982 public function HasMultiBytes($str) {1983 if (function_exists('mb_strlen')) {1984 return (strlen($str) > mb_strlen($str, $this->CharSet));1985 } else { // Assume no multibytes (we can't handle without mbstring functions anyway)1986 return false;1987 }1988 }1989 1990 /**1991 * Correctly encodes and wraps long multibyte strings for mail headers1992 * without breaking lines within a character.1993 * Adapted from a function by paravoid at http://uk.php.net/manual/en/function.mb-encode-mimeheader.php1994 * @access public1995 * @param string $str multi-byte text to wrap encode1996 * @param string $lf string to use as linefeed/end-of-line1997 * @return string1998 */1999 public function Base64EncodeWrapMB($str, $lf=null) {2000 $start = "=?".$this->CharSet."?B?";2001 $end = "?=";2002 $encoded = "";2003 if ($lf === null) {2004 $lf = $this->LE;2005 }2006 2007 $mb_length = mb_strlen($str, $this->CharSet);2008 // Each line must have length <= 75, including $start and $end2009 $length = 75 - strlen($start) - strlen($end);2010 // Average multi-byte ratio2011 $ratio = $mb_length / strlen($str);2012 // Base64 has a 4:3 ratio2013 $offset = $avgLength = floor($length * $ratio * .75);2014 2015 for ($i = 0; $i < $mb_length; $i += $offset) {2016 $lookBack = 0;2017 2018 do {2019 $offset = $avgLength - $lookBack;2020 $chunk = mb_substr($str, $i, $offset, $this->CharSet);2021 $chunk = base64_encode($chunk);2022 $lookBack++;2023 }2024 while (strlen($chunk) > $length);2025 2026 $encoded .= $chunk . $lf;2027 }2028 2029 // Chomp the last linefeed2030 $encoded = substr($encoded, 0, -strlen($lf));2031 return $encoded;2032 }2033 2034 /**2035 * Encode string to quoted-printable.2036 * Only uses standard PHP, slow, but will always work2037 * @access public2038 * @param string $input2039 * @param integer $line_max Number of chars allowed on a line before wrapping2040 * @param bool $space_conv2041 * @internal param string $string the text to encode2042 * @return string2043 */2044 public function EncodeQPphp( $input = '', $line_max = 76, $space_conv = false) {2045 $hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');2046 $lines = preg_split('/(?:\r\n|\r|\n)/', $input);2047 $eol = "\r\n";2048 $escape = '=';2049 $output = '';2050 while( list(, $line) = each($lines) ) {2051 $linlen = strlen($line);2052 $newline = '';2053 for($i = 0; $i < $linlen; $i++) {2054 $c = substr( $line, $i, 1 );2055 $dec = ord( $c );2056 if ( ( $i == 0 ) && ( $dec == 46 ) ) { // convert first point in the line into =2E2057 $c = '=2E';2058 }2059 if ( $dec == 32 ) {2060 if ( $i == ( $linlen - 1 ) ) { // convert space at eol only2061 $c = '=20';2062 } else if ( $space_conv ) {2063 $c = '=20';2064 }2065 } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required2066 $h2 = (integer)floor($dec/16);2067 $h1 = (integer)floor($dec%16);2068 $c = $escape.$hex[$h2].$hex[$h1];2069 }2070 if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted2071 $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay2072 $newline = '';2073 // check if newline first character will be point or not2074 if ( $dec == 46 ) {2075 $c = '=2E';2076 }2077 }2078 $newline .= $c;2079 } // end of for2080 $output .= $newline.$eol;2081 } // end of while2082 return $output;2083 }2084 2085 /**2086 * Encode string to RFC2045 (6.7) quoted-printable format2087 * Uses a PHP5 stream filter to do the encoding about 64x faster than the old version2088 * Also results in same content as you started with after decoding2089 * @see EncodeQPphp()2090 * @access public2091 * @param string $string the text to encode2092 * @param integer $line_max Number of chars allowed on a line before wrapping2093 * @param boolean $space_conv Dummy param for compatibility with existing EncodeQP function2094 * @return string2095 * @author Marcus Bointon2096 */2097 public function EncodeQP($string, $line_max = 76, $space_conv = false) {2098 if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3)2099 return quoted_printable_encode($string);2100 }2101 $filters = stream_get_filters();2102 if (!in_array('convert.*', $filters)) { //Got convert stream filter?2103 return $this->EncodeQPphp($string, $line_max, $space_conv); //Fall back to old implementation2104 }2105 $fp = fopen('php://temp/', 'r+');2106 $string = preg_replace('/\r\n?/', $this->LE, $string); //Normalise line breaks2107 $params = array('line-length' => $line_max, 'line-break-chars' => $this->LE);2108 $s = stream_filter_append($fp, 'convert.quoted-printable-encode', STREAM_FILTER_READ, $params);2109 fputs($fp, $string);2110 rewind($fp);2111 $out = stream_get_contents($fp);2112 stream_filter_remove($s);2113 $out = preg_replace('/^\./m', '=2E', $out); //Encode . if it is first char on a line, workaround for bug in Exchange2114 fclose($fp);2115 return $out;2116 }2117 2118 /**2119 * Encode string to q encoding.2120 * @link http://tools.ietf.org/html/rfc20472121 * @param string $str the text to encode2122 * @param string $position Where the text is going to be used, see the RFC for what that means2123 * @access public2124 * @return string2125 */2126 public function EncodeQ($str, $position = 'text') {2127 //There should not be any EOL in the string2128 $pattern="";2129 $encoded = str_replace(array("\r", "\n"), '', $str);2130 switch (strtolower($position)) {2131 case 'phrase':2132 $pattern = '^A-Za-z0-9!*+\/ -';2133 break;2134 2135 case 'comment':2136 $pattern = '\(\)"';2137 //note that we dont break here!2138 //for this reason we build the $pattern withoud including delimiters and []2139 2140 case 'text':2141 default:2142 //Replace every high ascii, control =, ? and _ characters2143 //We put \075 (=) as first value to make sure it's the first one in being converted, preventing double encode2144 $pattern = '\075\000-\011\013\014\016-\037\077\137\177-\377' . $pattern;2145 break;2146 }2147 2148 if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {2149 foreach (array_unique($matches[0]) as $char) {2150 $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);2151 }2152 }2153 2154 //Replace every spaces to _ (more readable than =20)2155 return str_replace(' ', '_', $encoded);2156 }2157 2158 2159 /**2160 * Adds a string or binary attachment (non-filesystem) to the list.2161 * This method can be used to attach ascii or binary data,2162 * such as a BLOB record from a database.2163 * @param string $string String attachment data.2164 * @param string $filename Name of the attachment.2165 * @param string $encoding File encoding (see $Encoding).2166 * @param string $type File extension (MIME) type.2167 * @return void2168 */2169 public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream') {2170 // Append to $attachment array2171 $this->attachment[] = array(2172 0 => $string,2173 1 => $filename,2174 2 => basename($filename),2175 3 => $encoding,2176 4 => $type,2177 5 => true, // isStringAttachment2178 6 => 'attachment',2179 7 => 02180 );2181 }2182 2183 /**2184 * Adds an embedded attachment. This can include images, sounds, and2185 * just about any other document. Make sure to set the $type to an2186 * image type. For JPEG images use "image/jpeg" and for GIF images2187 * use "image/gif".2188 * @param string $path Path to the attachment.2189 * @param string $cid Content ID of the attachment. Use this to identify2190 * the Id for accessing the image in an HTML form.2191 * @param string $name Overrides the attachment name.2192 * @param string $encoding File encoding (see $Encoding).2193 * @param string $type File extension (MIME) type.2194 * @return bool2195 */2196 public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') {2197 2198 if ( !@is_file($path) ) {2199 $this->SetError($this->Lang('file_access') . $path);2200 return false;2201 }2202 2203 $filename = basename($path);2204 if ( $name == '' ) {2205 $name = $filename;2206 }2207 2208 // Append to $attachment array2209 $this->attachment[] = array(2210 0 => $path,2211 1 => $filename,2212 2 => $name,2213 3 => $encoding,2214 4 => $type,2215 5 => false, // isStringAttachment2216 6 => 'inline',2217 7 => $cid2218 );2219 2220 return true;2221 }2222 2223 /**2224 * Adds an embedded stringified attachment. This can include images, sounds, and2225 * just about any other document. Make sure to set the $type to an2226 * image type. For JPEG images use "image/jpeg" and for GIF images2227 * use "image/gif".2228 * @param string $string The attachment.2229 * @param string $cid Content ID of the attachment. Use this to identify2230 * the Id for accessing the image in an HTML form.2231 * @param string $name Overrides the attachment name.2232 * @param string $encoding File encoding (see $Encoding).2233 * @param string $type File extension (MIME) type.2234 * @return bool2235 */2236 public function AddStringEmbeddedImage($string, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') {2237 // Append to $attachment array2238 $this->attachment[] = array(2239 0 => $string,2240 1 => $name,2241 2 => $name,2242 3 => $encoding,2243 4 => $type,2244 5 => true, // isStringAttachment2245 6 => 'inline',2246 7 => $cid2247 );2248 }2249 2250 /**2251 * Returns true if an inline attachment is present.2252 * @access public2253 * @return bool2254 */2255 public function InlineImageExists() {2256 foreach($this->attachment as $attachment) {2257 if ($attachment[6] == 'inline') {2258 return true;2259 }2260 }2261 return false;2262 }2263 2264 /**2265 * Returns true if an attachment (non-inline) is present.2266 * @return bool2267 */2268 public function AttachmentExists() {2269 foreach($this->attachment as $attachment) {2270 if ($attachment[6] == 'attachment') {2271 return true;2272 }2273 }2274 return false;2275 }2276 2277 /**2278 * Does this message have an alternative body set?2279 * @return bool2280 */2281 public function AlternativeExists() {2282 return !empty($this->AltBody);2283 }2284 2285 /////////////////////////////////////////////////2286 // CLASS METHODS, MESSAGE RESET2287 /////////////////////////////////////////////////2288 2289 /**2290 * Clears all recipients assigned in the TO array. Returns void.2291 * @return void2292 */2293 public function ClearAddresses() {2294 foreach($this->to as $to) {2295 unset($this->all_recipients[strtolower($to[0])]);2296 }2297 $this->to = array();2298 }2299 2300 /**2301 * Clears all recipients assigned in the CC array. Returns void.2302 * @return void2303 */2304 public function ClearCCs() {2305 foreach($this->cc as $cc) {2306 unset($this->all_recipients[strtolower($cc[0])]);2307 }2308 $this->cc = array();2309 }2310 2311 /**2312 * Clears all recipients assigned in the BCC array. Returns void.2313 * @return void2314 */2315 public function ClearBCCs() {2316 foreach($this->bcc as $bcc) {2317 unset($this->all_recipients[strtolower($bcc[0])]);2318 }2319 $this->bcc = array();2320 }2321 2322 /**2323 * Clears all recipients assigned in the ReplyTo array. Returns void.2324 * @return void2325 */2326 public function ClearReplyTos() {2327 $this->ReplyTo = array();2328 }2329 2330 /**2331 * Clears all recipients assigned in the TO, CC and BCC2332 * array. Returns void.2333 * @return void2334 */2335 public function ClearAllRecipients() {2336 $this->to = array();2337 $this->cc = array();2338 $this->bcc = array();2339 $this->all_recipients = array();2340 }2341 2342 /**2343 * Clears all previously set filesystem, string, and binary2344 * attachments. Returns void.2345 * @return void2346 */2347 public function ClearAttachments() {2348 $this->attachment = array();2349 }2350 2351 /**2352 * Clears all custom headers. Returns void.2353 * @return void2354 */2355 public function ClearCustomHeaders() {2356 $this->CustomHeader = array();2357 }2358 2359 /////////////////////////////////////////////////2360 // CLASS METHODS, MISCELLANEOUS2361 /////////////////////////////////////////////////2362 2363 /**2364 * Adds the error message to the error container.2365 * @access protected2366 * @param string $msg2367 * @return void2368 */2369 protected function SetError($msg) {2370 $this->error_count++;2371 if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {2372 $lasterror = $this->smtp->getError();2373 if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) {2374 $msg .= '<p>' . $this->Lang('smtp_error') . $lasterror['smtp_msg'] . "</p>\n";2375 }2376 }2377 $this->ErrorInfo = $msg;2378 }2379 2380 /**2381 * Returns the proper RFC 822 formatted date.2382 * @access public2383 * @return string2384 * @static2385 */2386 public static function RFCDate() {2387 $tz = date('Z');2388 $tzs = ($tz < 0) ? '-' : '+';2389 $tz = abs($tz);2390 $tz = (int)($tz/3600)*100 + ($tz%3600)/60;2391 $result = sprintf("%s %s%04d", date('D, j M Y H:i:s'), $tzs, $tz);2392 2393 return $result;2394 }2395 2396 /**2397 * Returns the server hostname or 'localhost.localdomain' if unknown.2398 * @access protected2399 * @return string2400 */2401 protected function ServerHostname() {2402 if (!empty($this->Hostname)) {2403 $result = $this->Hostname;2404 } elseif (isset($_SERVER['SERVER_NAME'])) {2405 $result = $_SERVER['SERVER_NAME'];2406 } else {2407 $result = 'localhost.localdomain';2408 }2409 2410 return $result;2411 }2412 2413 /**2414 * Returns a message in the appropriate language.2415 * @access protected2416 * @param string $key2417 * @return string2418 */2419 protected function Lang($key) {2420 if(count($this->language) < 1) {2421 $this->SetLanguage('en'); // set the default language2422 }2423 2424 if(isset($this->language[$key])) {2425 return $this->language[$key];2426 } else {2427 return 'Language string failed to load: ' . $key;2428 }2429 }2430 2431 /**2432 * Returns true if an error occurred.2433 * @access public2434 * @return bool2435 */2436 public function IsError() {2437 return ($this->error_count > 0);2438 }2439 2440 /**2441 * Changes every end of line from CRLF, CR or LF to $this->LE.2442 * @access public2443 * @param string $str String to FixEOL2444 * @return string2445 */2446 public function FixEOL($str) {2447 // condense down to \n2448 $nstr = str_replace(array("\r\n", "\r"), "\n", $str);2449 // Now convert LE as needed2450 if ($this->LE !== "\n") {2451 $nstr = str_replace("\n", $this->LE, $nstr);2452 }2453 return $nstr;2454 }2455 2456 /**2457 * Adds a custom header. $name value can be overloaded to contain2458 * both header name and value (name:value)2459 * @access public2460 * @param string $name custom header name2461 * @param string $value header value2462 * @return void2463 */2464 public function AddCustomHeader($name, $value=null) {2465 if ($value === null) {2466 // Value passed in as name:value2467 $this->CustomHeader[] = explode(':', $name, 2);2468 } else {2469 $this->CustomHeader[] = array($name, $value);2470 }2471 }2472 2473 /**2474 * Evaluates the message and returns modifications for inline images and backgrounds2475 * @access public2476 * @param string $message Text to be HTML modified2477 * @param string $basedir baseline directory for path2478 * @return string $message2479 */2480 public function MsgHTML($message, $basedir = '') {2481 preg_match_all("/(src|background)=[\"'](.*)[\"']/Ui", $message, $images);2482 if(isset($images[2])) {2483 foreach($images[2] as $i => $url) {2484 // do not change urls for absolute images (thanks to corvuscorax)2485 if (!preg_match('#^[A-z]+://#', $url)) {2486 $filename = basename($url);2487 $directory = dirname($url);2488 if ($directory == '.') {2489 $directory = '';2490 }2491 $cid = 'cid:' . md5($url);2492 $ext = pathinfo($filename, PATHINFO_EXTENSION);2493 $mimeType = self::_mime_types($ext);2494 if ( strlen($basedir) > 1 && substr($basedir, -1) != '/') { $basedir .= '/'; }2495 if ( strlen($directory) > 1 && substr($directory, -1) != '/') { $directory .= '/'; }2496 if ( $this->AddEmbeddedImage($basedir.$directory.$filename, md5($url), $filename, 'base64', $mimeType) ) {2497 $message = preg_replace("/".$images[1][$i]."=[\"']".preg_quote($url, '/')."[\"']/Ui", $images[1][$i]."=\"".$cid."\"", $message);2498 }2499 }2500 }2501 }2502 $this->IsHTML(true);2503 $this->Body = $message;2504 if (empty($this->AltBody)) {2505 $textMsg = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s', '', $message)));2506 if (!empty($textMsg)) {2507 $this->AltBody = html_entity_decode($textMsg, ENT_QUOTES, $this->CharSet);2508 }2509 }2510 if (empty($this->AltBody)) {2511 $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n";2512 }2513 return $message;2514 }2515 2516 /**2517 * Gets the MIME type of the embedded or inline image2518 * @param string $ext File extension2519 * @access public2520 * @return string MIME type of ext2521 * @static2522 */2523 public static function _mime_types($ext = '') {2524 $mimes = array(2525 'xl' => 'application/excel',2526 'hqx' => 'application/mac-binhex40',2527 'cpt' => 'application/mac-compactpro',2528 'bin' => 'application/macbinary',2529 'doc' => 'application/msword',2530 'word' => 'application/msword',2531 'class' => 'application/octet-stream',2532 'dll' => 'application/octet-stream',2533 'dms' => 'application/octet-stream',2534 'exe' => 'application/octet-stream',2535 'lha' => 'application/octet-stream',2536 'lzh' => 'application/octet-stream',2537 'psd' => 'application/octet-stream',2538 'sea' => 'application/octet-stream',2539 'so' => 'application/octet-stream',2540 'oda' => 'application/oda',2541 'pdf' => 'application/pdf',2542 'ai' => 'application/postscript',2543 'eps' => 'application/postscript',2544 'ps' => 'application/postscript',2545 'smi' => 'application/smil',2546 'smil' => 'application/smil',2547 'mif' => 'application/vnd.mif',2548 'xls' => 'application/vnd.ms-excel',2549 'ppt' => 'application/vnd.ms-powerpoint',2550 'wbxml' => 'application/vnd.wap.wbxml',2551 'wmlc' => 'application/vnd.wap.wmlc',2552 'dcr' => 'application/x-director',2553 'dir' => 'application/x-director',2554 'dxr' => 'application/x-director',2555 'dvi' => 'application/x-dvi',2556 'gtar' => 'application/x-gtar',2557 'php3' => 'application/x-httpd-php',2558 'php4' => 'application/x-httpd-php',2559 'php' => 'application/x-httpd-php',2560 'phtml' => 'application/x-httpd-php',2561 'phps' => 'application/x-httpd-php-source',2562 'js' => 'application/x-javascript',2563 'swf' => 'application/x-shockwave-flash',2564 'sit' => 'application/x-stuffit',2565 'tar' => 'application/x-tar',2566 'tgz' => 'application/x-tar',2567 'xht' => 'application/xhtml+xml',2568 'xhtml' => 'application/xhtml+xml',2569 'zip' => 'application/zip',2570 'mid' => 'audio/midi',2571 'midi' => 'audio/midi',2572 'mp2' => 'audio/mpeg',2573 'mp3' => 'audio/mpeg',2574 'mpga' => 'audio/mpeg',2575 'aif' => 'audio/x-aiff',2576 'aifc' => 'audio/x-aiff',2577 'aiff' => 'audio/x-aiff',2578 'ram' => 'audio/x-pn-realaudio',2579 'rm' => 'audio/x-pn-realaudio',2580 'rpm' => 'audio/x-pn-realaudio-plugin',2581 'ra' => 'audio/x-realaudio',2582 'wav' => 'audio/x-wav',2583 'bmp' => 'image/bmp',2584 'gif' => 'image/gif',2585 'jpeg' => 'image/jpeg',2586 'jpe' => 'image/jpeg',2587 'jpg' => 'image/jpeg',2588 'png' => 'image/png',2589 'tiff' => 'image/tiff',2590 'tif' => 'image/tiff',2591 'eml' => 'message/rfc822',2592 'css' => 'text/css',2593 'html' => 'text/html',2594 'htm' => 'text/html',2595 'shtml' => 'text/html',2596 'log' => 'text/plain',2597 'text' => 'text/plain',2598 'txt' => 'text/plain',2599 'rtx' => 'text/richtext',2600 'rtf' => 'text/rtf',2601 'xml' => 'text/xml',2602 'xsl' => 'text/xml',2603 'mpeg' => 'video/mpeg',2604 'mpe' => 'video/mpeg',2605 'mpg' => 'video/mpeg',2606 'mov' => 'video/quicktime',2607 'qt' => 'video/quicktime',2608 'rv' => 'video/vnd.rn-realvideo',2609 'avi' => 'video/x-msvideo',2610 'movie' => 'video/x-sgi-movie'2611 );2612 return (!isset($mimes[strtolower($ext)])) ? 'application/octet-stream' : $mimes[strtolower($ext)];2613 }2614 2615 /**2616 * Set (or reset) Class Objects (variables)2617 *2618 * Usage Example:2619 * $page->set('X-Priority', '3');2620 *2621 * @access public2622 * @param string $name Parameter Name2623 * @param mixed $value Parameter Value2624 * NOTE: will not work with arrays, there are no arrays to set/reset2625 * @throws phpmailerException2626 * @return bool2627 * @todo Should this not be using __set() magic function?2628 */2629 public function set($name, $value = '') {2630 try {2631 if (isset($this->$name) ) {2632 $this->$name = $value;2633 } else {2634 throw new phpmailerException($this->Lang('variable_set') . $name, self::STOP_CRITICAL);2635 }2636 } catch (Exception $e) {2637 $this->SetError($e->getMessage());2638 if ($e->getCode() == self::STOP_CRITICAL) {2639 return false;2640 }2641 }2642 return true;2643 }2644 2645 /**2646 * Strips newlines to prevent header injection.2647 * @access public2648 * @param string $str String2649 * @return string2650 */2651 public function SecureHeader($str) {2652 return trim(str_replace(array("\r", "\n"), '', $str));2653 }2654 2655 /**2656 * Set the private key file and password to sign the message.2657 *2658 * @access public2659 * @param $cert_filename2660 * @param string $key_filename Parameter File Name2661 * @param string $key_pass Password for private key2662 */2663 public function Sign($cert_filename, $key_filename, $key_pass) {2664 $this->sign_cert_file = $cert_filename;2665 $this->sign_key_file = $key_filename;2666 $this->sign_key_pass = $key_pass;2667 }2668 2669 /**2670 * Set the private key file and password to sign the message.2671 *2672 * @access public2673 * @param string $txt2674 * @return string2675 */2676 public function DKIM_QP($txt) {2677 $line = '';2678 for ($i = 0; $i < strlen($txt); $i++) {2679 $ord = ord($txt[$i]);2680 if ( ((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E)) ) {2681 $line .= $txt[$i];2682 } else {2683 $line .= "=".sprintf("%02X", $ord);2684 }2685 }2686 return $line;2687 }2688 2689 /**2690 * Generate DKIM signature2691 *2692 * @access public2693 * @param string $s Header2694 * @return string2695 */2696 public function DKIM_Sign($s) {2697 $privKeyStr = file_get_contents($this->DKIM_private);2698 if ($this->DKIM_passphrase != '') {2699 $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);2700 } else {2701 $privKey = $privKeyStr;2702 }2703 if (openssl_sign($s, $signature, $privKey)) {2704 return base64_encode($signature);2705 }2706 return '';2707 }2708 2709 /**2710 * Generate DKIM Canonicalization Header2711 *2712 * @access public2713 * @param string $s Header2714 * @return string2715 */2716 public function DKIM_HeaderC($s) {2717 $s = preg_replace("/\r\n\s+/", " ", $s);2718 $lines = explode("\r\n", $s);2719 foreach ($lines as $key => $line) {2720 list($heading, $value) = explode(":", $line, 2);2721 $heading = strtolower($heading);2722 $value = preg_replace("/\s+/", " ", $value) ; // Compress useless spaces2723 $lines[$key] = $heading.":".trim($value) ; // Don't forget to remove WSP around the value2724 }2725 $s = implode("\r\n", $lines);2726 return $s;2727 }2728 2729 /**2730 * Generate DKIM Canonicalization Body2731 *2732 * @access public2733 * @param string $body Message Body2734 * @return string2735 */2736 public function DKIM_BodyC($body) {2737 if ($body == '') return "\r\n";2738 // stabilize line endings2739 $body = str_replace("\r\n", "\n", $body);2740 $body = str_replace("\n", "\r\n", $body);2741 // END stabilize line endings2742 while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {2743 $body = substr($body, 0, strlen($body) - 2);2744 }2745 return $body;2746 }2747 2748 /**2749 * Create the DKIM header, body, as new header2750 *2751 * @access public2752 * @param string $headers_line Header lines2753 * @param string $subject Subject2754 * @param string $body Body2755 * @return string2756 */2757 public function DKIM_Add($headers_line, $subject, $body) {2758 $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms2759 $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body2760 $DKIMquery = 'dns/txt'; // Query method2761 $DKIMtime = time() ; // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)2762 $subject_header = "Subject: $subject";2763 $headers = explode($this->LE, $headers_line);2764 $from_header = "";2765 $to_header = "";2766 foreach($headers as $header) {2767 if (strpos($header, 'From:') === 0) {2768 $from_header = $header;2769 } elseif (strpos($header, 'To:') === 0) {2770 $to_header = $header;2771 }2772 }2773 $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));2774 $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));2775 $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header)) ; // Copied header fields (dkim-quoted-printable2776 $body = $this->DKIM_BodyC($body);2777 $DKIMlen = strlen($body) ; // Length of body2778 $DKIMb64 = base64_encode(pack("H*", sha1($body))) ; // Base64 of packed binary SHA-1 hash of body2779 $ident = ($this->DKIM_identity == '')? '' : " i=" . $this->DKIM_identity . ";";2780 $dkimhdrs = "DKIM-Signature: v=1; a=" . $DKIMsignatureType . "; q=" . $DKIMquery . "; l=" . $DKIMlen . "; s=" . $this->DKIM_selector . ";\r\n".2781 "\tt=" . $DKIMtime . "; c=" . $DKIMcanonicalization . ";\r\n".2782 "\th=From:To:Subject;\r\n".2783 "\td=" . $this->DKIM_domain . ";" . $ident . "\r\n".2784 "\tz=$from\r\n".2785 "\t|$to\r\n".2786 "\t|$subject;\r\n".2787 "\tbh=" . $DKIMb64 . ";\r\n".2788 "\tb=";2789 $toSign = $this->DKIM_HeaderC($from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs);2790 $signed = $this->DKIM_Sign($toSign);2791 return "X-PHPMAILER-DKIM: code.google.com/a/apache-extras.org/p/phpmailer/\r\n".$dkimhdrs.$signed."\r\n";2792 }2793 2794 /**2795 * Perform callback2796 * @param boolean $isSent2797 * @param string $to2798 * @param string $cc2799 * @param string $bcc2800 * @param string $subject2801 * @param string $body2802 * @param string $from2803 */2804 protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from=null) {2805 if (!empty($this->action_function) && is_callable($this->action_function)) {2806 $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);2807 call_user_func_array($this->action_function, $params);2808 }2809 }2810 }2811 2812 /**2813 * Exception handler for PHPMailer2814 * @package PHPMailer2815 */2816 class phpmailerException extends Exception {2817 /**2818 * Prettify error message output2819 * @return string2820 */2821 public function errorMessage() {2822 $errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n";2823 return $errorMsg;2824 }2825 }2826 ?> -
branches/3.8/src/wp-includes/class-smtp.php
r23522 r39730 1 1 <?php 2 /*~ class.smtp.php3 .---------------------------------------------------------------------------.4 | Software: PHPMailer - PHP email class |5 | Version: 5.2.4 |6 | Site: https://code.google.com/a/apache-extras.org/p/phpmailer/ |7 | ------------------------------------------------------------------------- |8 | Admin: Jim Jagielski (project admininistrator) |9 | Authors: Andy Prevost (codeworxtech) codeworxtech@users.sourceforge.net |10 | : Marcus Bointon (coolbru) coolbru@users.sourceforge.net |11 | : Jim Jagielski (jimjag) jimjag@gmail.com |12 | Founder: Brent R. Matzelle (original founder) |13 | Copyright (c) 2010-2012, Jim Jagielski. All Rights Reserved. |14 | Copyright (c) 2004-2009, Andy Prevost. All Rights Reserved. |15 | Copyright (c) 2001-2003, Brent R. Matzelle |16 | ------------------------------------------------------------------------- |17 | License: Distributed under the Lesser General Public License (LGPL) |18 | http://www.gnu.org/copyleft/lesser.html |19 | This program is distributed in the hope that it will be useful - WITHOUT |20 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |21 | FITNESS FOR A PARTICULAR PURPOSE. |22 '---------------------------------------------------------------------------'23 */24 25 2 /** 26 * PHPMailer - PHP SMTP email transport class27 * NOTE: Designed for use with PHP version 5 and up3 * PHPMailer RFC821 SMTP email transport class. 4 * PHP Version 5 28 5 * @package PHPMailer 29 * @author Andy Prevost 30 * @author Marcus Bointon 31 * @copyright 2004 - 2008 Andy Prevost 32 * @author Jim Jagielski 6 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project 7 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk> 8 * @author Jim Jagielski (jimjag) <jimjag@gmail.com> 9 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net> 10 * @author Brent R. Matzelle (original founder) 11 * @copyright 2014 Marcus Bointon 33 12 * @copyright 2010 - 2012 Jim Jagielski 34 * @license http://www.gnu.org/copyleft/lesser.html Distributed under the Lesser General Public License (LGPL) 13 * @copyright 2004 - 2009 Andy Prevost 14 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 * @note This program is distributed in the hope that it will be useful - WITHOUT 16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 * FITNESS FOR A PARTICULAR PURPOSE. 35 18 */ 36 19 37 20 /** 38 * PHP RFC821 SMTP client 39 * 40 * Implements all the RFC 821 SMTP commands except TURN which will always return a not implemented error. 41 * SMTP also provides some utility methods for sending mail to an SMTP server. 21 * PHPMailer RFC821 SMTP email transport class. 22 * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. 23 * @package PHPMailer 42 24 * @author Chris Ryan 43 * @ package PHPMailer25 * @author Marcus Bointon <phpmailer@synchromedia.co.uk> 44 26 */ 45 46 class SMTP { 47 /** 48 * SMTP server port 49 * @var int 50 */ 51 public $SMTP_PORT = 25; 52 53 /** 54 * SMTP reply line ending (don't change) 55 * @var string 56 */ 57 public $CRLF = "\r\n"; 58 59 /** 60 * Sets whether debugging is turned on 61 * @var bool 62 */ 63 public $do_debug; // the level of debug to perform 64 65 /** 66 * Sets the function/method to use for debugging output. 67 * Right now we only honor "echo" or "error_log" 68 * @var string 69 */ 70 public $Debugoutput = "echo"; 71 72 /** 73 * Sets VERP use on/off (default is off) 74 * @var bool 75 */ 76 public $do_verp = false; 77 78 /** 79 * Sets the SMTP timeout value for reads, in seconds 80 * @var int 81 */ 82 public $Timeout = 15; 83 84 /** 85 * Sets the SMTP timelimit value for reads, in seconds 86 * @var int 87 */ 88 public $Timelimit = 30; 89 90 /** 91 * Sets the SMTP PHPMailer Version number 92 * @var string 93 */ 94 public $Version = '5.2.4'; 95 96 ///////////////////////////////////////////////// 97 // PROPERTIES, PRIVATE AND PROTECTED 98 ///////////////////////////////////////////////// 99 100 /** 101 * @var resource The socket to the server 102 */ 103 private $smtp_conn; 104 /** 105 * @var string Error message, if any, for the last call 106 */ 107 private $error; 108 /** 109 * @var string The reply the server sent to us for HELO 110 */ 111 private $helo_rply; 112 113 /** 114 * Outputs debugging info via user-defined method 115 * @param string $str 116 */ 117 private function edebug($str) { 118 if ($this->Debugoutput == "error_log") { 119 error_log($str); 120 } else { 121 echo $str; 122 } 123 } 124 125 /** 126 * Initialize the class so that the data is in a known state. 127 * @access public 128 * @return SMTP 129 */ 130 public function __construct() { 131 $this->smtp_conn = 0; 132 $this->error = null; 133 $this->helo_rply = null; 134 135 $this->do_debug = 0; 136 } 137 138 ///////////////////////////////////////////////// 139 // CONNECTION FUNCTIONS 140 ///////////////////////////////////////////////// 141 142 /** 143 * Connect to the server specified on the port specified. 144 * If the port is not specified use the default SMTP_PORT. 145 * If tval is specified then a connection will try and be 146 * established with the server for that number of seconds. 147 * If tval is not specified the default is 30 seconds to 148 * try on the connection. 149 * 150 * SMTP CODE SUCCESS: 220 151 * SMTP CODE FAILURE: 421 152 * @access public 153 * @param string $host 154 * @param int $port 155 * @param int $tval 156 * @return bool 157 */ 158 public function Connect($host, $port = 0, $tval = 30) { 159 // set the error val to null so there is no confusion 160 $this->error = null; 161 162 // make sure we are __not__ connected 163 if($this->connected()) { 164 // already connected, generate error 165 $this->error = array("error" => "Already connected to a server"); 166 return false; 167 } 168 169 if(empty($port)) { 170 $port = $this->SMTP_PORT; 171 } 172 173 // connect to the smtp server 174 $this->smtp_conn = @fsockopen($host, // the host of the server 175 $port, // the port to use 176 $errno, // error number if any 177 $errstr, // error message if any 178 $tval); // give up after ? secs 179 // verify we connected properly 180 if(empty($this->smtp_conn)) { 181 $this->error = array("error" => "Failed to connect to server", 182 "errno" => $errno, 183 "errstr" => $errstr); 184 if($this->do_debug >= 1) { 185 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": $errstr ($errno)" . $this->CRLF . '<br />'); 186 } 187 return false; 188 } 189 190 // SMTP server can take longer to respond, give longer timeout for first read 191 // Windows does not have support for this timeout function 192 if(substr(PHP_OS, 0, 3) != "WIN") { 193 $max = ini_get('max_execution_time'); 194 if ($max != 0 && $tval > $max) { // don't bother if unlimited 195 @set_time_limit($tval); 196 } 197 stream_set_timeout($this->smtp_conn, $tval, 0); 198 } 199 200 // get any announcement 201 $announce = $this->get_lines(); 202 203 if($this->do_debug >= 2) { 204 $this->edebug("SMTP -> FROM SERVER:" . $announce . $this->CRLF . '<br />'); 205 } 206 207 return true; 208 } 209 210 /** 211 * Initiate a TLS communication with the server. 212 * 213 * SMTP CODE 220 Ready to start TLS 214 * SMTP CODE 501 Syntax error (no parameters allowed) 215 * SMTP CODE 454 TLS not available due to temporary reason 216 * @access public 217 * @return bool success 218 */ 219 public function StartTLS() { 220 $this->error = null; # to avoid confusion 221 222 if(!$this->connected()) { 223 $this->error = array("error" => "Called StartTLS() without being connected"); 224 return false; 225 } 226 227 fputs($this->smtp_conn,"STARTTLS" . $this->CRLF); 228 229 $rply = $this->get_lines(); 230 $code = substr($rply,0,3); 231 232 if($this->do_debug >= 2) { 233 $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '<br />'); 234 } 235 236 if($code != 220) { 237 $this->error = 238 array("error" => "STARTTLS not accepted from server", 239 "smtp_code" => $code, 240 "smtp_msg" => substr($rply,4)); 241 if($this->do_debug >= 1) { 242 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 243 } 244 return false; 245 } 246 247 // Begin encrypted connection 248 if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { 249 return false; 250 } 251 252 return true; 253 } 254 255 /** 256 * Performs SMTP authentication. Must be run after running the 257 * Hello() method. Returns true if successfully authenticated. 258 * @access public 259 * @param string $username 260 * @param string $password 261 * @param string $authtype 262 * @param string $realm 263 * @param string $workstation 264 * @return bool 265 */ 266 public function Authenticate($username, $password, $authtype='LOGIN', $realm='', $workstation='') { 267 if (empty($authtype)) { 268 $authtype = 'LOGIN'; 269 } 270 271 switch ($authtype) { 272 case 'PLAIN': 273 // Start authentication 274 fputs($this->smtp_conn,"AUTH PLAIN" . $this->CRLF); 275 276 $rply = $this->get_lines(); 277 $code = substr($rply,0,3); 278 279 if($code != 334) { 280 $this->error = 281 array("error" => "AUTH not accepted from server", 282 "smtp_code" => $code, 283 "smtp_msg" => substr($rply,4)); 284 if($this->do_debug >= 1) { 285 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 286 } 287 return false; 288 } 289 // Send encoded username and password 290 fputs($this->smtp_conn, base64_encode("\0".$username."\0".$password) . $this->CRLF); 291 292 $rply = $this->get_lines(); 293 $code = substr($rply,0,3); 294 295 if($code != 235) { 296 $this->error = 297 array("error" => "Authentication not accepted from server", 298 "smtp_code" => $code, 299 "smtp_msg" => substr($rply,4)); 300 if($this->do_debug >= 1) { 301 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 302 } 303 return false; 304 } 305 break; 306 case 'LOGIN': 307 // Start authentication 308 fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF); 309 310 $rply = $this->get_lines(); 311 $code = substr($rply,0,3); 312 313 if($code != 334) { 314 $this->error = 315 array("error" => "AUTH not accepted from server", 316 "smtp_code" => $code, 317 "smtp_msg" => substr($rply,4)); 318 if($this->do_debug >= 1) { 319 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 320 } 321 return false; 322 } 323 324 // Send encoded username 325 fputs($this->smtp_conn, base64_encode($username) . $this->CRLF); 326 327 $rply = $this->get_lines(); 328 $code = substr($rply,0,3); 329 330 if($code != 334) { 331 $this->error = 332 array("error" => "Username not accepted from server", 333 "smtp_code" => $code, 334 "smtp_msg" => substr($rply,4)); 335 if($this->do_debug >= 1) { 336 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 337 } 338 return false; 339 } 340 341 // Send encoded password 342 fputs($this->smtp_conn, base64_encode($password) . $this->CRLF); 343 344 $rply = $this->get_lines(); 345 $code = substr($rply,0,3); 346 347 if($code != 235) { 348 $this->error = 349 array("error" => "Password not accepted from server", 350 "smtp_code" => $code, 351 "smtp_msg" => substr($rply,4)); 352 if($this->do_debug >= 1) { 353 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 354 } 355 return false; 356 } 357 break; 358 case 'NTLM': 359 /* 360 * ntlm_sasl_client.php 361 ** Bundled with Permission 362 ** 363 ** How to telnet in windows: http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx 364 ** PROTOCOL Documentation http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication 27 class SMTP 28 { 29 /** 30 * The PHPMailer SMTP version number. 31 * @var string 32 */ 33 const VERSION = '5.2.21'; 34 35 /** 36 * SMTP line break constant. 37 * @var string 38 */ 39 const CRLF = "\r\n"; 40 41 /** 42 * The SMTP port to use if one is not specified. 43 * @var integer 44 */ 45 const DEFAULT_SMTP_PORT = 25; 46 47 /** 48 * The maximum line length allowed by RFC 2822 section 2.1.1 49 * @var integer 50 */ 51 const MAX_LINE_LENGTH = 998; 52 53 /** 54 * Debug level for no output 55 */ 56 const DEBUG_OFF = 0; 57 58 /** 59 * Debug level to show client -> server messages 60 */ 61 const DEBUG_CLIENT = 1; 62 63 /** 64 * Debug level to show client -> server and server -> client messages 65 */ 66 const DEBUG_SERVER = 2; 67 68 /** 69 * Debug level to show connection status, client -> server and server -> client messages 70 */ 71 const DEBUG_CONNECTION = 3; 72 73 /** 74 * Debug level to show all messages 75 */ 76 const DEBUG_LOWLEVEL = 4; 77 78 /** 79 * The PHPMailer SMTP Version number. 80 * @var string 81 * @deprecated Use the `VERSION` constant instead 82 * @see SMTP::VERSION 83 */ 84 public $Version = '5.2.21'; 85 86 /** 87 * SMTP server port number. 88 * @var integer 89 * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead 90 * @see SMTP::DEFAULT_SMTP_PORT 91 */ 92 public $SMTP_PORT = 25; 93 94 /** 95 * SMTP reply line ending. 96 * @var string 97 * @deprecated Use the `CRLF` constant instead 98 * @see SMTP::CRLF 99 */ 100 public $CRLF = "\r\n"; 101 102 /** 103 * Debug output level. 104 * Options: 105 * * self::DEBUG_OFF (`0`) No debug output, default 106 * * self::DEBUG_CLIENT (`1`) Client commands 107 * * self::DEBUG_SERVER (`2`) Client commands and server responses 108 * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status 109 * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages 110 * @var integer 111 */ 112 public $do_debug = self::DEBUG_OFF; 113 114 /** 115 * How to handle debug output. 116 * Options: 117 * * `echo` Output plain-text as-is, appropriate for CLI 118 * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output 119 * * `error_log` Output to error log as configured in php.ini 120 * 121 * Alternatively, you can provide a callable expecting two params: a message string and the debug level: 122 * <code> 123 * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; 124 * </code> 125 * @var string|callable 126 */ 127 public $Debugoutput = 'echo'; 128 129 /** 130 * Whether to use VERP. 131 * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path 132 * @link http://www.postfix.org/VERP_README.html Info on VERP 133 * @var boolean 134 */ 135 public $do_verp = false; 136 137 /** 138 * The timeout value for connection, in seconds. 139 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 140 * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. 141 * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 142 * @var integer 143 */ 144 public $Timeout = 300; 145 146 /** 147 * How long to wait for commands to complete, in seconds. 148 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 149 * @var integer 150 */ 151 public $Timelimit = 300; 152 153 /** 154 * @var array patterns to extract smtp transaction id from smtp reply 155 * Only first capture group will be use, use non-capturing group to deal with it 156 * Extend this class to override this property to fulfil your needs. 157 */ 158 protected $smtp_transaction_id_patterns = array( 159 'exim' => '/[0-9]{3} OK id=(.*)/', 160 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/', 161 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/' 162 ); 163 164 /** 165 * The socket for the server connection. 166 * @var resource 167 */ 168 protected $smtp_conn; 169 170 /** 171 * Error information, if any, for the last SMTP command. 172 * @var array 173 */ 174 protected $error = array( 175 'error' => '', 176 'detail' => '', 177 'smtp_code' => '', 178 'smtp_code_ex' => '' 179 ); 180 181 /** 182 * The reply the server sent to us for HELO. 183 * If null, no HELO string has yet been received. 184 * @var string|null 185 */ 186 protected $helo_rply = null; 187 188 /** 189 * The set of SMTP extensions sent in reply to EHLO command. 190 * Indexes of the array are extension names. 191 * Value at index 'HELO' or 'EHLO' (according to command that was sent) 192 * represents the server name. In case of HELO it is the only element of the array. 193 * Other values can be boolean TRUE or an array containing extension options. 194 * If null, no HELO/EHLO string has yet been received. 195 * @var array|null 196 */ 197 protected $server_caps = null; 198 199 /** 200 * The most recent reply received from the server. 201 * @var string 202 */ 203 protected $last_reply = ''; 204 205 /** 206 * Output debugging info via a user-selected method. 207 * @see SMTP::$Debugoutput 208 * @see SMTP::$do_debug 209 * @param string $str Debug string to output 210 * @param integer $level The debug level of this message; see DEBUG_* constants 211 * @return void 212 */ 213 protected function edebug($str, $level = 0) 214 { 215 if ($level > $this->do_debug) { 216 return; 217 } 218 //Avoid clash with built-in function names 219 if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { 220 call_user_func($this->Debugoutput, $str, $level); 221 return; 222 } 223 switch ($this->Debugoutput) { 224 case 'error_log': 225 //Don't output, just log 226 error_log($str); 227 break; 228 case 'html': 229 //Cleans up output a bit for a better looking, HTML-safe output 230 echo htmlentities( 231 preg_replace('/[\r\n]+/', '', $str), 232 ENT_QUOTES, 233 'UTF-8' 234 ) 235 . "<br>\n"; 236 break; 237 case 'echo': 238 default: 239 //Normalize line breaks 240 $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); 241 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 242 "\n", 243 "\n \t ", 244 trim($str) 245 )."\n"; 246 } 247 } 248 249 /** 250 * Connect to an SMTP server. 251 * @param string $host SMTP server IP or host name 252 * @param integer $port The port number to connect to 253 * @param integer $timeout How long to wait for the connection to open 254 * @param array $options An array of options for stream_context_create() 255 * @access public 256 * @return boolean 257 */ 258 public function connect($host, $port = null, $timeout = 30, $options = array()) 259 { 260 static $streamok; 261 //This is enabled by default since 5.0.0 but some providers disable it 262 //Check this once and cache the result 263 if (is_null($streamok)) { 264 $streamok = function_exists('stream_socket_client'); 265 } 266 // Clear errors to avoid confusion 267 $this->setError(''); 268 // Make sure we are __not__ connected 269 if ($this->connected()) { 270 // Already connected, generate error 271 $this->setError('Already connected to a server'); 272 return false; 273 } 274 if (empty($port)) { 275 $port = self::DEFAULT_SMTP_PORT; 276 } 277 // Connect to the SMTP server 278 $this->edebug( 279 "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true), 280 self::DEBUG_CONNECTION 281 ); 282 $errno = 0; 283 $errstr = ''; 284 if ($streamok) { 285 $socket_context = stream_context_create($options); 286 set_error_handler(array($this, 'errorHandler')); 287 $this->smtp_conn = stream_socket_client( 288 $host . ":" . $port, 289 $errno, 290 $errstr, 291 $timeout, 292 STREAM_CLIENT_CONNECT, 293 $socket_context 294 ); 295 restore_error_handler(); 296 } else { 297 //Fall back to fsockopen which should work in more places, but is missing some features 298 $this->edebug( 299 "Connection: stream_socket_client not available, falling back to fsockopen", 300 self::DEBUG_CONNECTION 301 ); 302 set_error_handler(array($this, 'errorHandler')); 303 $this->smtp_conn = fsockopen( 304 $host, 305 $port, 306 $errno, 307 $errstr, 308 $timeout 309 ); 310 restore_error_handler(); 311 } 312 // Verify we connected properly 313 if (!is_resource($this->smtp_conn)) { 314 $this->setError( 315 'Failed to connect to server', 316 $errno, 317 $errstr 318 ); 319 $this->edebug( 320 'SMTP ERROR: ' . $this->error['error'] 321 . ": $errstr ($errno)", 322 self::DEBUG_CLIENT 323 ); 324 return false; 325 } 326 $this->edebug('Connection: opened', self::DEBUG_CONNECTION); 327 // SMTP server can take longer to respond, give longer timeout for first read 328 // Windows does not have support for this timeout function 329 if (substr(PHP_OS, 0, 3) != 'WIN') { 330 $max = ini_get('max_execution_time'); 331 // Don't bother if unlimited 332 if ($max != 0 && $timeout > $max) { 333 @set_time_limit($timeout); 334 } 335 stream_set_timeout($this->smtp_conn, $timeout, 0); 336 } 337 // Get any announcement 338 $announce = $this->get_lines(); 339 $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); 340 return true; 341 } 342 343 /** 344 * Initiate a TLS (encrypted) session. 345 * @access public 346 * @return boolean 347 */ 348 public function startTLS() 349 { 350 if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { 351 return false; 352 } 353 354 //Allow the best TLS version(s) we can 355 $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; 356 357 //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT 358 //so add them back in manually if we can 359 if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { 360 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; 361 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; 362 } 363 364 // Begin encrypted connection 365 if (!stream_socket_enable_crypto( 366 $this->smtp_conn, 367 true, 368 $crypto_method 369 )) { 370 return false; 371 } 372 return true; 373 } 374 375 /** 376 * Perform SMTP authentication. 377 * Must be run after hello(). 378 * @see hello() 379 * @param string $username The user name 380 * @param string $password The password 381 * @param string $authtype The auth type (PLAIN, LOGIN, CRAM-MD5) 382 * @param string $realm The auth realm for NTLM 383 * @param string $workstation The auth workstation for NTLM 384 * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) 385 * @return bool True if successfully authenticated.* @access public 386 */ 387 public function authenticate( 388 $username, 389 $password, 390 $authtype = null, 391 $realm = '', 392 $workstation = '', 393 $OAuth = null 394 ) { 395 if (!$this->server_caps) { 396 $this->setError('Authentication is not allowed before HELO/EHLO'); 397 return false; 398 } 399 400 if (array_key_exists('EHLO', $this->server_caps)) { 401 // SMTP extensions are available. Let's try to find a proper authentication method 402 403 if (!array_key_exists('AUTH', $this->server_caps)) { 404 $this->setError('Authentication is not allowed at this stage'); 405 // 'at this stage' means that auth may be allowed after the stage changes 406 // e.g. after STARTTLS 407 return false; 408 } 409 410 self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); 411 self::edebug( 412 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), 413 self::DEBUG_LOWLEVEL 414 ); 415 416 if (empty($authtype)) { 417 foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN') as $method) { 418 if (in_array($method, $this->server_caps['AUTH'])) { 419 $authtype = $method; 420 break; 421 } 422 } 423 if (empty($authtype)) { 424 $this->setError('No supported authentication methods found'); 425 return false; 426 } 427 self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL); 428 } 429 430 if (!in_array($authtype, $this->server_caps['AUTH'])) { 431 $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); 432 return false; 433 } 434 } elseif (empty($authtype)) { 435 $authtype = 'LOGIN'; 436 } 437 switch ($authtype) { 438 case 'PLAIN': 439 // Start authentication 440 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { 441 return false; 442 } 443 // Send encoded username and password 444 if (!$this->sendCommand( 445 'User & Password', 446 base64_encode("\0" . $username . "\0" . $password), 447 235 448 ) 449 ) { 450 return false; 451 } 452 break; 453 case 'LOGIN': 454 // Start authentication 455 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { 456 return false; 457 } 458 if (!$this->sendCommand("Username", base64_encode($username), 334)) { 459 return false; 460 } 461 if (!$this->sendCommand("Password", base64_encode($password), 235)) { 462 return false; 463 } 464 break; 465 case 'CRAM-MD5': 466 // Start authentication 467 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { 468 return false; 469 } 470 // Get the challenge 471 $challenge = base64_decode(substr($this->last_reply, 4)); 472 473 // Build the response 474 $response = $username . ' ' . $this->hmac($challenge, $password); 475 476 // send encoded credentials 477 return $this->sendCommand('Username', base64_encode($response), 235); 478 default: 479 $this->setError("Authentication method \"$authtype\" is not supported"); 480 return false; 481 } 482 return true; 483 } 484 485 /** 486 * Calculate an MD5 HMAC hash. 487 * Works like hash_hmac('md5', $data, $key) 488 * in case that function is not available 489 * @param string $data The data to hash 490 * @param string $key The key to hash with 491 * @access protected 492 * @return string 493 */ 494 protected function hmac($data, $key) 495 { 496 if (function_exists('hash_hmac')) { 497 return hash_hmac('md5', $data, $key); 498 } 499 500 // The following borrowed from 501 // http://php.net/manual/en/function.mhash.php#27225 502 503 // RFC 2104 HMAC implementation for php. 504 // Creates an md5 HMAC. 505 // Eliminates the need to install mhash to compute a HMAC 506 // by Lance Rushing 507 508 $bytelen = 64; // byte length for md5 509 if (strlen($key) > $bytelen) { 510 $key = pack('H*', md5($key)); 511 } 512 $key = str_pad($key, $bytelen, chr(0x00)); 513 $ipad = str_pad('', $bytelen, chr(0x36)); 514 $opad = str_pad('', $bytelen, chr(0x5c)); 515 $k_ipad = $key ^ $ipad; 516 $k_opad = $key ^ $opad; 517 518 return md5($k_opad . pack('H*', md5($k_ipad . $data))); 519 } 520 521 /** 522 * Check connection state. 523 * @access public 524 * @return boolean True if connected. 525 */ 526 public function connected() 527 { 528 if (is_resource($this->smtp_conn)) { 529 $sock_status = stream_get_meta_data($this->smtp_conn); 530 if ($sock_status['eof']) { 531 // The socket is valid but we are not connected 532 $this->edebug( 533 'SMTP NOTICE: EOF caught while checking if connected', 534 self::DEBUG_CLIENT 535 ); 536 $this->close(); 537 return false; 538 } 539 return true; // everything looks good 540 } 541 return false; 542 } 543 544 /** 545 * Close the socket and clean up the state of the class. 546 * Don't use this function without first trying to use QUIT. 547 * @see quit() 548 * @access public 549 * @return void 550 */ 551 public function close() 552 { 553 $this->setError(''); 554 $this->server_caps = null; 555 $this->helo_rply = null; 556 if (is_resource($this->smtp_conn)) { 557 // close the connection and cleanup 558 fclose($this->smtp_conn); 559 $this->smtp_conn = null; //Makes for cleaner serialization 560 $this->edebug('Connection: closed', self::DEBUG_CONNECTION); 561 } 562 } 563 564 /** 565 * Send an SMTP DATA command. 566 * Issues a data command and sends the msg_data to the server, 567 * finializing the mail transaction. $msg_data is the message 568 * that is to be send with the headers. Each header needs to be 569 * on a single line followed by a <CRLF> with the message headers 570 * and the message body being separated by and additional <CRLF>. 571 * Implements rfc 821: DATA <CRLF> 572 * @param string $msg_data Message data to send 573 * @access public 574 * @return boolean 575 */ 576 public function data($msg_data) 577 { 578 //This will use the standard timelimit 579 if (!$this->sendCommand('DATA', 'DATA', 354)) { 580 return false; 581 } 582 583 /* The server is ready to accept data! 584 * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) 585 * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into 586 * smaller lines to fit within the limit. 587 * We will also look for lines that start with a '.' and prepend an additional '.'. 588 * NOTE: this does not count towards line-length limit. 365 589 */ 366 require_once('ntlm_sasl_client.php'); 367 $temp = new stdClass(); 368 $ntlm_client = new ntlm_sasl_client_class; 369 if(! $ntlm_client->Initialize($temp)){//let's test if every function its available 370 $this->error = array("error" => $temp->error); 371 if($this->do_debug >= 1) { 372 $this->edebug("You need to enable some modules in your php.ini file: " . $this->error["error"] . $this->CRLF); 373 } 590 591 // Normalize line breaks before exploding 592 $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); 593 594 /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field 595 * of the first line (':' separated) does not contain a space then it _should_ be a header and we will 596 * process all lines before a blank line as headers. 597 */ 598 599 $field = substr($lines[0], 0, strpos($lines[0], ':')); 600 $in_headers = false; 601 if (!empty($field) && strpos($field, ' ') === false) { 602 $in_headers = true; 603 } 604 605 foreach ($lines as $line) { 606 $lines_out = array(); 607 if ($in_headers and $line == '') { 608 $in_headers = false; 609 } 610 //Break this line up into several smaller lines if it's too long 611 //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), 612 while (isset($line[self::MAX_LINE_LENGTH])) { 613 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on 614 //so as to avoid breaking in the middle of a word 615 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); 616 //Deliberately matches both false and 0 617 if (!$pos) { 618 //No nice break found, add a hard break 619 $pos = self::MAX_LINE_LENGTH - 1; 620 $lines_out[] = substr($line, 0, $pos); 621 $line = substr($line, $pos); 622 } else { 623 //Break at the found point 624 $lines_out[] = substr($line, 0, $pos); 625 //Move along by the amount we dealt with 626 $line = substr($line, $pos + 1); 627 } 628 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 629 if ($in_headers) { 630 $line = "\t" . $line; 631 } 632 } 633 $lines_out[] = $line; 634 635 //Send the lines to the server 636 foreach ($lines_out as $line_out) { 637 //RFC2821 section 4.5.2 638 if (!empty($line_out) and $line_out[0] == '.') { 639 $line_out = '.' . $line_out; 640 } 641 $this->client_send($line_out . self::CRLF); 642 } 643 } 644 645 //Message data has been sent, complete the command 646 //Increase timelimit for end of DATA command 647 $savetimelimit = $this->Timelimit; 648 $this->Timelimit = $this->Timelimit * 2; 649 $result = $this->sendCommand('DATA END', '.', 250); 650 //Restore timelimit 651 $this->Timelimit = $savetimelimit; 652 return $result; 653 } 654 655 /** 656 * Send an SMTP HELO or EHLO command. 657 * Used to identify the sending server to the receiving server. 658 * This makes sure that client and server are in a known state. 659 * Implements RFC 821: HELO <SP> <domain> <CRLF> 660 * and RFC 2821 EHLO. 661 * @param string $host The host name or IP to connect to 662 * @access public 663 * @return boolean 664 */ 665 public function hello($host = '') 666 { 667 //Try extended hello first (RFC 2821) 668 return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); 669 } 670 671 /** 672 * Send an SMTP HELO or EHLO command. 673 * Low-level implementation used by hello() 674 * @see hello() 675 * @param string $hello The HELO string 676 * @param string $host The hostname to say we are 677 * @access protected 678 * @return boolean 679 */ 680 protected function sendHello($hello, $host) 681 { 682 $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); 683 $this->helo_rply = $this->last_reply; 684 if ($noerror) { 685 $this->parseHelloFields($hello); 686 } else { 687 $this->server_caps = null; 688 } 689 return $noerror; 690 } 691 692 /** 693 * Parse a reply to HELO/EHLO command to discover server extensions. 694 * In case of HELO, the only parameter that can be discovered is a server name. 695 * @access protected 696 * @param string $type - 'HELO' or 'EHLO' 697 */ 698 protected function parseHelloFields($type) 699 { 700 $this->server_caps = array(); 701 $lines = explode("\n", $this->helo_rply); 702 703 foreach ($lines as $n => $s) { 704 //First 4 chars contain response code followed by - or space 705 $s = trim(substr($s, 4)); 706 if (empty($s)) { 707 continue; 708 } 709 $fields = explode(' ', $s); 710 if (!empty($fields)) { 711 if (!$n) { 712 $name = $type; 713 $fields = $fields[0]; 714 } else { 715 $name = array_shift($fields); 716 switch ($name) { 717 case 'SIZE': 718 $fields = ($fields ? $fields[0] : 0); 719 break; 720 case 'AUTH': 721 if (!is_array($fields)) { 722 $fields = array(); 723 } 724 break; 725 default: 726 $fields = true; 727 } 728 } 729 $this->server_caps[$name] = $fields; 730 } 731 } 732 } 733 734 /** 735 * Send an SMTP MAIL command. 736 * Starts a mail transaction from the email address specified in 737 * $from. Returns true if successful or false otherwise. If True 738 * the mail transaction is started and then one or more recipient 739 * commands may be called followed by a data command. 740 * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF> 741 * @param string $from Source address of this message 742 * @access public 743 * @return boolean 744 */ 745 public function mail($from) 746 { 747 $useVerp = ($this->do_verp ? ' XVERP' : ''); 748 return $this->sendCommand( 749 'MAIL FROM', 750 'MAIL FROM:<' . $from . '>' . $useVerp, 751 250 752 ); 753 } 754 755 /** 756 * Send an SMTP QUIT command. 757 * Closes the socket if there is no error or the $close_on_error argument is true. 758 * Implements from rfc 821: QUIT <CRLF> 759 * @param boolean $close_on_error Should the connection close if an error occurs? 760 * @access public 761 * @return boolean 762 */ 763 public function quit($close_on_error = true) 764 { 765 $noerror = $this->sendCommand('QUIT', 'QUIT', 221); 766 $err = $this->error; //Save any error 767 if ($noerror or $close_on_error) { 768 $this->close(); 769 $this->error = $err; //Restore any error from the quit command 770 } 771 return $noerror; 772 } 773 774 /** 775 * Send an SMTP RCPT command. 776 * Sets the TO argument to $toaddr. 777 * Returns true if the recipient was accepted false if it was rejected. 778 * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF> 779 * @param string $address The address the message is being sent to 780 * @access public 781 * @return boolean 782 */ 783 public function recipient($address) 784 { 785 return $this->sendCommand( 786 'RCPT TO', 787 'RCPT TO:<' . $address . '>', 788 array(250, 251) 789 ); 790 } 791 792 /** 793 * Send an SMTP RSET command. 794 * Abort any transaction that is currently in progress. 795 * Implements rfc 821: RSET <CRLF> 796 * @access public 797 * @return boolean True on success. 798 */ 799 public function reset() 800 { 801 return $this->sendCommand('RSET', 'RSET', 250); 802 } 803 804 /** 805 * Send a command to an SMTP server and check its return code. 806 * @param string $command The command name - not sent to the server 807 * @param string $commandstring The actual command to send 808 * @param integer|array $expect One or more expected integer success codes 809 * @access protected 810 * @return boolean True on success. 811 */ 812 protected function sendCommand($command, $commandstring, $expect) 813 { 814 if (!$this->connected()) { 815 $this->setError("Called $command without being connected"); 374 816 return false; 375 817 } 376 $msg1 = $ntlm_client->TypeMsg1($realm, $workstation);//msg1 377 378 fputs($this->smtp_conn,"AUTH NTLM " . base64_encode($msg1) . $this->CRLF); 379 380 $rply = $this->get_lines(); 381 $code = substr($rply,0,3); 382 383 384 if($code != 334) { 385 $this->error = 386 array("error" => "AUTH not accepted from server", 387 "smtp_code" => $code, 388 "smtp_msg" => substr($rply,4)); 389 if($this->do_debug >= 1) { 390 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF); 391 } 818 //Reject line breaks in all commands 819 if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { 820 $this->setError("Command '$command' contained line breaks"); 392 821 return false; 393 822 } 394 395 $challange = substr($rply,3);//though 0 based, there is a white space after the 3 digit number....//msg2 396 $challange = base64_decode($challange); 397 $ntlm_res = $ntlm_client->NTLMResponse(substr($challange,24,8),$password); 398 $msg3 = $ntlm_client->TypeMsg3($ntlm_res,$username,$realm,$workstation);//msg3 399 // Send encoded username 400 fputs($this->smtp_conn, base64_encode($msg3) . $this->CRLF); 401 402 $rply = $this->get_lines(); 403 $code = substr($rply,0,3); 404 405 if($code != 235) { 406 $this->error = 407 array("error" => "Could not authenticate", 408 "smtp_code" => $code, 409 "smtp_msg" => substr($rply,4)); 410 if($this->do_debug >= 1) { 411 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF); 412 } 823 $this->client_send($commandstring . self::CRLF); 824 825 $this->last_reply = $this->get_lines(); 826 // Fetch SMTP code and possible error code explanation 827 $matches = array(); 828 if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { 829 $code = $matches[1]; 830 $code_ex = (count($matches) > 2 ? $matches[2] : null); 831 // Cut off error code from each response line 832 $detail = preg_replace( 833 "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m", 834 '', 835 $this->last_reply 836 ); 837 } else { 838 // Fall back to simple parsing if regex fails 839 $code = substr($this->last_reply, 0, 3); 840 $code_ex = null; 841 $detail = substr($this->last_reply, 4); 842 } 843 844 $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); 845 846 if (!in_array($code, (array)$expect)) { 847 $this->setError( 848 "$command command failed", 849 $detail, 850 $code, 851 $code_ex 852 ); 853 $this->edebug( 854 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, 855 self::DEBUG_CLIENT 856 ); 413 857 return false; 414 858 } 415 break; 416 } 417 return true; 418 } 419 420 /** 421 * Returns true if connected to a server otherwise false 422 * @access public 423 * @return bool 424 */ 425 public function Connected() { 426 if(!empty($this->smtp_conn)) { 427 $sock_status = socket_get_status($this->smtp_conn); 428 if($sock_status["eof"]) { 429 // the socket is valid but we are not connected 430 if($this->do_debug >= 1) { 431 $this->edebug("SMTP -> NOTICE:" . $this->CRLF . "EOF caught while checking if connected"); 432 } 433 $this->Close(); 859 860 $this->setError(''); 861 return true; 862 } 863 864 /** 865 * Send an SMTP SAML command. 866 * Starts a mail transaction from the email address specified in $from. 867 * Returns true if successful or false otherwise. If True 868 * the mail transaction is started and then one or more recipient 869 * commands may be called followed by a data command. This command 870 * will send the message to the users terminal if they are logged 871 * in and send them an email. 872 * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF> 873 * @param string $from The address the message is from 874 * @access public 875 * @return boolean 876 */ 877 public function sendAndMail($from) 878 { 879 return $this->sendCommand('SAML', "SAML FROM:$from", 250); 880 } 881 882 /** 883 * Send an SMTP VRFY command. 884 * @param string $name The name to verify 885 * @access public 886 * @return boolean 887 */ 888 public function verify($name) 889 { 890 return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); 891 } 892 893 /** 894 * Send an SMTP NOOP command. 895 * Used to keep keep-alives alive, doesn't actually do anything 896 * @access public 897 * @return boolean 898 */ 899 public function noop() 900 { 901 return $this->sendCommand('NOOP', 'NOOP', 250); 902 } 903 904 /** 905 * Send an SMTP TURN command. 906 * This is an optional command for SMTP that this class does not support. 907 * This method is here to make the RFC821 Definition complete for this class 908 * and _may_ be implemented in future 909 * Implements from rfc 821: TURN <CRLF> 910 * @access public 911 * @return boolean 912 */ 913 public function turn() 914 { 915 $this->setError('The SMTP TURN command is not implemented'); 916 $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); 434 917 return false; 435 } 436 return true; // everything looks good 437 } 438 return false; 439 } 440 441 /** 442 * Closes the socket and cleans up the state of the class. 443 * It is not considered good to use this function without 444 * first trying to use QUIT. 445 * @access public 446 * @return void 447 */ 448 public function Close() { 449 $this->error = null; // so there is no confusion 450 $this->helo_rply = null; 451 if(!empty($this->smtp_conn)) { 452 // close the connection and cleanup 453 fclose($this->smtp_conn); 454 $this->smtp_conn = 0; 455 } 456 } 457 458 ///////////////////////////////////////////////// 459 // SMTP COMMANDS 460 ///////////////////////////////////////////////// 461 462 /** 463 * Issues a data command and sends the msg_data to the server 464 * finializing the mail transaction. $msg_data is the message 465 * that is to be send with the headers. Each header needs to be 466 * on a single line followed by a <CRLF> with the message headers 467 * and the message body being seperated by and additional <CRLF>. 468 * 469 * Implements rfc 821: DATA <CRLF> 470 * 471 * SMTP CODE INTERMEDIATE: 354 472 * [data] 473 * <CRLF>.<CRLF> 474 * SMTP CODE SUCCESS: 250 475 * SMTP CODE FAILURE: 552,554,451,452 476 * SMTP CODE FAILURE: 451,554 477 * SMTP CODE ERROR : 500,501,503,421 478 * @access public 479 * @param string $msg_data 480 * @return bool 481 */ 482 public function Data($msg_data) { 483 $this->error = null; // so no confusion is caused 484 485 if(!$this->connected()) { 486 $this->error = array( 487 "error" => "Called Data() without being connected"); 488 return false; 489 } 490 491 fputs($this->smtp_conn,"DATA" . $this->CRLF); 492 493 $rply = $this->get_lines(); 494 $code = substr($rply,0,3); 495 496 if($this->do_debug >= 2) { 497 $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '<br />'); 498 } 499 500 if($code != 354) { 501 $this->error = 502 array("error" => "DATA command not accepted from server", 503 "smtp_code" => $code, 504 "smtp_msg" => substr($rply,4)); 505 if($this->do_debug >= 1) { 506 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 507 } 508 return false; 509 } 510 511 /* the server is ready to accept data! 512 * according to rfc 821 we should not send more than 1000 513 * including the CRLF 514 * characters on a single line so we will break the data up 515 * into lines by \r and/or \n then if needed we will break 516 * each of those into smaller lines to fit within the limit. 517 * in addition we will be looking for lines that start with 518 * a period '.' and append and additional period '.' to that 519 * line. NOTE: this does not count towards limit. 520 */ 521 522 // normalize the line breaks so we know the explode works 523 $msg_data = str_replace("\r\n","\n",$msg_data); 524 $msg_data = str_replace("\r","\n",$msg_data); 525 $lines = explode("\n",$msg_data); 526 527 /* we need to find a good way to determine is headers are 528 * in the msg_data or if it is a straight msg body 529 * currently I am assuming rfc 822 definitions of msg headers 530 * and if the first field of the first line (':' sperated) 531 * does not contain a space then it _should_ be a header 532 * and we can process all lines before a blank "" line as 533 * headers. 534 */ 535 536 $field = substr($lines[0],0,strpos($lines[0],":")); 537 $in_headers = false; 538 if(!empty($field) && !strstr($field," ")) { 539 $in_headers = true; 540 } 541 542 $max_line_length = 998; // used below; set here for ease in change 543 544 while(list(,$line) = @each($lines)) { 545 $lines_out = null; 546 if($line == "" && $in_headers) { 547 $in_headers = false; 548 } 549 // ok we need to break this line up into several smaller lines 550 while(strlen($line) > $max_line_length) { 551 $pos = strrpos(substr($line,0,$max_line_length)," "); 552 553 // Patch to fix DOS attack 554 if(!$pos) { 555 $pos = $max_line_length - 1; 556 $lines_out[] = substr($line,0,$pos); 557 $line = substr($line,$pos); 558 } else { 559 $lines_out[] = substr($line,0,$pos); 560 $line = substr($line,$pos + 1); 561 } 562 563 /* if processing headers add a LWSP-char to the front of new line 564 * rfc 822 on long msg headers 565 */ 566 if($in_headers) { 567 $line = "\t" . $line; 568 } 569 } 570 $lines_out[] = $line; 571 572 // send the lines to the server 573 while(list(,$line_out) = @each($lines_out)) { 574 if(strlen($line_out) > 0) 575 { 576 if(substr($line_out, 0, 1) == ".") { 577 $line_out = "." . $line_out; 578 } 579 } 580 fputs($this->smtp_conn,$line_out . $this->CRLF); 581 } 582 } 583 584 // message data has been sent 585 fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF); 586 587 $rply = $this->get_lines(); 588 $code = substr($rply,0,3); 589 590 if($this->do_debug >= 2) { 591 $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '<br />'); 592 } 593 594 if($code != 250) { 595 $this->error = 596 array("error" => "DATA not accepted from server", 597 "smtp_code" => $code, 598 "smtp_msg" => substr($rply,4)); 599 if($this->do_debug >= 1) { 600 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 601 } 602 return false; 603 } 604 return true; 605 } 606 607 /** 608 * Sends the HELO command to the smtp server. 609 * This makes sure that we and the server are in 610 * the same known state. 611 * 612 * Implements from rfc 821: HELO <SP> <domain> <CRLF> 613 * 614 * SMTP CODE SUCCESS: 250 615 * SMTP CODE ERROR : 500, 501, 504, 421 616 * @access public 617 * @param string $host 618 * @return bool 619 */ 620 public function Hello($host = '') { 621 $this->error = null; // so no confusion is caused 622 623 if(!$this->connected()) { 624 $this->error = array( 625 "error" => "Called Hello() without being connected"); 626 return false; 627 } 628 629 // if hostname for HELO was not specified send default 630 if(empty($host)) { 631 // determine appropriate default to send to server 632 $host = "localhost"; 633 } 634 635 // Send extended hello first (RFC 2821) 636 if(!$this->SendHello("EHLO", $host)) { 637 if(!$this->SendHello("HELO", $host)) { 638 return false; 639 } 640 } 641 642 return true; 643 } 644 645 /** 646 * Sends a HELO/EHLO command. 647 * @access private 648 * @param string $hello 649 * @param string $host 650 * @return bool 651 */ 652 private function SendHello($hello, $host) { 653 fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF); 654 655 $rply = $this->get_lines(); 656 $code = substr($rply,0,3); 657 658 if($this->do_debug >= 2) { 659 $this->edebug("SMTP -> FROM SERVER: " . $rply . $this->CRLF . '<br />'); 660 } 661 662 if($code != 250) { 663 $this->error = 664 array("error" => $hello . " not accepted from server", 665 "smtp_code" => $code, 666 "smtp_msg" => substr($rply,4)); 667 if($this->do_debug >= 1) { 668 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 669 } 670 return false; 671 } 672 673 $this->helo_rply = $rply; 674 675 return true; 676 } 677 678 /** 679 * Starts a mail transaction from the email address specified in 680 * $from. Returns true if successful or false otherwise. If True 681 * the mail transaction is started and then one or more Recipient 682 * commands may be called followed by a Data command. 683 * 684 * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF> 685 * 686 * SMTP CODE SUCCESS: 250 687 * SMTP CODE SUCCESS: 552,451,452 688 * SMTP CODE SUCCESS: 500,501,421 689 * @access public 690 * @param string $from 691 * @return bool 692 */ 693 public function Mail($from) { 694 $this->error = null; // so no confusion is caused 695 696 if(!$this->connected()) { 697 $this->error = array( 698 "error" => "Called Mail() without being connected"); 699 return false; 700 } 701 702 $useVerp = ($this->do_verp ? " XVERP" : ""); 703 fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF); 704 705 $rply = $this->get_lines(); 706 $code = substr($rply,0,3); 707 708 if($this->do_debug >= 2) { 709 $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '<br />'); 710 } 711 712 if($code != 250) { 713 $this->error = 714 array("error" => "MAIL not accepted from server", 715 "smtp_code" => $code, 716 "smtp_msg" => substr($rply,4)); 717 if($this->do_debug >= 1) { 718 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 719 } 720 return false; 721 } 722 return true; 723 } 724 725 /** 726 * Sends the quit command to the server and then closes the socket 727 * if there is no error or the $close_on_error argument is true. 728 * 729 * Implements from rfc 821: QUIT <CRLF> 730 * 731 * SMTP CODE SUCCESS: 221 732 * SMTP CODE ERROR : 500 733 * @access public 734 * @param bool $close_on_error 735 * @return bool 736 */ 737 public function Quit($close_on_error = true) { 738 $this->error = null; // so there is no confusion 739 740 if(!$this->connected()) { 741 $this->error = array( 742 "error" => "Called Quit() without being connected"); 743 return false; 744 } 745 746 // send the quit command to the server 747 fputs($this->smtp_conn,"quit" . $this->CRLF); 748 749 // get any good-bye messages 750 $byemsg = $this->get_lines(); 751 752 if($this->do_debug >= 2) { 753 $this->edebug("SMTP -> FROM SERVER:" . $byemsg . $this->CRLF . '<br />'); 754 } 755 756 $rval = true; 757 $e = null; 758 759 $code = substr($byemsg,0,3); 760 if($code != 221) { 761 // use e as a tmp var cause Close will overwrite $this->error 762 $e = array("error" => "SMTP server rejected quit command", 763 "smtp_code" => $code, 764 "smtp_rply" => substr($byemsg,4)); 765 $rval = false; 766 if($this->do_debug >= 1) { 767 $this->edebug("SMTP -> ERROR: " . $e["error"] . ": " . $byemsg . $this->CRLF . '<br />'); 768 } 769 } 770 771 if(empty($e) || $close_on_error) { 772 $this->Close(); 773 } 774 775 return $rval; 776 } 777 778 /** 779 * Sends the command RCPT to the SMTP server with the TO: argument of $to. 780 * Returns true if the recipient was accepted false if it was rejected. 781 * 782 * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF> 783 * 784 * SMTP CODE SUCCESS: 250,251 785 * SMTP CODE FAILURE: 550,551,552,553,450,451,452 786 * SMTP CODE ERROR : 500,501,503,421 787 * @access public 788 * @param string $to 789 * @return bool 790 */ 791 public function Recipient($to) { 792 $this->error = null; // so no confusion is caused 793 794 if(!$this->connected()) { 795 $this->error = array( 796 "error" => "Called Recipient() without being connected"); 797 return false; 798 } 799 800 fputs($this->smtp_conn,"RCPT TO:<" . $to . ">" . $this->CRLF); 801 802 $rply = $this->get_lines(); 803 $code = substr($rply,0,3); 804 805 if($this->do_debug >= 2) { 806 $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '<br />'); 807 } 808 809 if($code != 250 && $code != 251) { 810 $this->error = 811 array("error" => "RCPT not accepted from server", 812 "smtp_code" => $code, 813 "smtp_msg" => substr($rply,4)); 814 if($this->do_debug >= 1) { 815 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 816 } 817 return false; 818 } 819 return true; 820 } 821 822 /** 823 * Sends the RSET command to abort and transaction that is 824 * currently in progress. Returns true if successful false 825 * otherwise. 826 * 827 * Implements rfc 821: RSET <CRLF> 828 * 829 * SMTP CODE SUCCESS: 250 830 * SMTP CODE ERROR : 500,501,504,421 831 * @access public 832 * @return bool 833 */ 834 public function Reset() { 835 $this->error = null; // so no confusion is caused 836 837 if(!$this->connected()) { 838 $this->error = array( 839 "error" => "Called Reset() without being connected"); 840 return false; 841 } 842 843 fputs($this->smtp_conn,"RSET" . $this->CRLF); 844 845 $rply = $this->get_lines(); 846 $code = substr($rply,0,3); 847 848 if($this->do_debug >= 2) { 849 $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '<br />'); 850 } 851 852 if($code != 250) { 853 $this->error = 854 array("error" => "RSET failed", 855 "smtp_code" => $code, 856 "smtp_msg" => substr($rply,4)); 857 if($this->do_debug >= 1) { 858 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 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 and send them an email. 873 * 874 * Implements rfc 821: SAML <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 * @param string $from 881 * @return bool 882 */ 883 public function SendAndMail($from) { 884 $this->error = null; // so no confusion is caused 885 886 if(!$this->connected()) { 887 $this->error = array( 888 "error" => "Called SendAndMail() without being connected"); 889 return false; 890 } 891 892 fputs($this->smtp_conn,"SAML FROM:" . $from . $this->CRLF); 893 894 $rply = $this->get_lines(); 895 $code = substr($rply,0,3); 896 897 if($this->do_debug >= 2) { 898 $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '<br />'); 899 } 900 901 if($code != 250) { 902 $this->error = 903 array("error" => "SAML not accepted from server", 904 "smtp_code" => $code, 905 "smtp_msg" => substr($rply,4)); 906 if($this->do_debug >= 1) { 907 $this->edebug("SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '<br />'); 908 } 909 return false; 910 } 911 return true; 912 } 913 914 /** 915 * This is an optional command for SMTP that this class does not 916 * support. This method is here to make the RFC821 Definition 917 * complete for this class and __may__ be implimented in the future 918 * 919 * Implements from rfc 821: TURN <CRLF> 920 * 921 * SMTP CODE SUCCESS: 250 922 * SMTP CODE FAILURE: 502 923 * SMTP CODE ERROR : 500, 503 924 * @access public 925 * @return bool 926 */ 927 public function Turn() { 928 $this->error = array("error" => "This method, TURN, of the SMTP ". 929 "is not implemented"); 930 if($this->do_debug >= 1) { 931 $this->edebug("SMTP -> NOTICE: " . $this->error["error"] . $this->CRLF . '<br />'); 932 } 933 return false; 934 } 935 936 /** 937 * Get the current error 938 * @access public 939 * @return array 940 */ 941 public function getError() { 942 return $this->error; 943 } 944 945 ///////////////////////////////////////////////// 946 // INTERNAL FUNCTIONS 947 ///////////////////////////////////////////////// 948 949 /** 950 * Read in as many lines as possible 951 * either before eof or socket timeout occurs on the operation. 952 * With SMTP we can tell if we have more lines to read if the 953 * 4th character is '-' symbol. If it is a space then we don't 954 * need to read anything else. 955 * @access private 956 * @return string 957 */ 958 private function get_lines() { 959 $data = ""; 960 $endtime = 0; 961 /* If for some reason the fp is bad, don't inf loop */ 962 if (!is_resource($this->smtp_conn)) { 963 return $data; 964 } 965 stream_set_timeout($this->smtp_conn, $this->Timeout); 966 if ($this->Timelimit > 0) { 967 $endtime = time() + $this->Timelimit; 968 } 969 while(is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { 970 $str = @fgets($this->smtp_conn,515); 971 if($this->do_debug >= 4) { 972 $this->edebug("SMTP -> get_lines(): \$data was \"$data\"" . $this->CRLF . '<br />'); 973 $this->edebug("SMTP -> get_lines(): \$str is \"$str\"" . $this->CRLF . '<br />'); 974 } 975 $data .= $str; 976 if($this->do_debug >= 4) { 977 $this->edebug("SMTP -> get_lines(): \$data is \"$data\"" . $this->CRLF . '<br />'); 978 } 979 // if 4th character is a space, we are done reading, break the loop 980 if(substr($str,3,1) == " ") { break; } 981 // Timed-out? Log and break 982 $info = stream_get_meta_data($this->smtp_conn); 983 if ($info['timed_out']) { 984 if($this->do_debug >= 4) { 985 $this->edebug("SMTP -> get_lines(): timed-out (" . $this->Timeout . " seconds) <br />"); 986 } 987 break; 988 } 989 // Now check if reads took too long 990 if ($endtime) { 991 if (time() > $endtime) { 992 if($this->do_debug >= 4) { 993 $this->edebug("SMTP -> get_lines(): timelimit reached (" . $this->Timelimit . " seconds) <br />"); 994 } 995 break; 996 } 997 } 998 } 999 return $data; 1000 } 1001 918 } 919 920 /** 921 * Send raw data to the server. 922 * @param string $data The data to send 923 * @access public 924 * @return integer|boolean The number of bytes sent to the server or false on error 925 */ 926 public function client_send($data) 927 { 928 $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); 929 return fwrite($this->smtp_conn, $data); 930 } 931 932 /** 933 * Get the latest error. 934 * @access public 935 * @return array 936 */ 937 public function getError() 938 { 939 return $this->error; 940 } 941 942 /** 943 * Get SMTP extensions available on the server 944 * @access public 945 * @return array|null 946 */ 947 public function getServerExtList() 948 { 949 return $this->server_caps; 950 } 951 952 /** 953 * A multipurpose method 954 * The method works in three ways, dependent on argument value and current state 955 * 1. HELO/EHLO was not sent - returns null and set up $this->error 956 * 2. HELO was sent 957 * $name = 'HELO': returns server name 958 * $name = 'EHLO': returns boolean false 959 * $name = any string: returns null and set up $this->error 960 * 3. EHLO was sent 961 * $name = 'HELO'|'EHLO': returns server name 962 * $name = any string: if extension $name exists, returns boolean True 963 * or its options. Otherwise returns boolean False 964 * In other words, one can use this method to detect 3 conditions: 965 * - null returned: handshake was not or we don't know about ext (refer to $this->error) 966 * - false returned: the requested feature exactly not exists 967 * - positive value returned: the requested feature exists 968 * @param string $name Name of SMTP extension or 'HELO'|'EHLO' 969 * @return mixed 970 */ 971 public function getServerExt($name) 972 { 973 if (!$this->server_caps) { 974 $this->setError('No HELO/EHLO was sent'); 975 return null; 976 } 977 978 // the tight logic knot ;) 979 if (!array_key_exists($name, $this->server_caps)) { 980 if ($name == 'HELO') { 981 return $this->server_caps['EHLO']; 982 } 983 if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { 984 return false; 985 } 986 $this->setError('HELO handshake was used. Client knows nothing about server extensions'); 987 return null; 988 } 989 990 return $this->server_caps[$name]; 991 } 992 993 /** 994 * Get the last reply from the server. 995 * @access public 996 * @return string 997 */ 998 public function getLastReply() 999 { 1000 return $this->last_reply; 1001 } 1002 1003 /** 1004 * Read the SMTP server's response. 1005 * Either before eof or socket timeout occurs on the operation. 1006 * With SMTP we can tell if we have more lines to read if the 1007 * 4th character is '-' symbol. If it is a space then we don't 1008 * need to read anything else. 1009 * @access protected 1010 * @return string 1011 */ 1012 protected function get_lines() 1013 { 1014 // If the connection is bad, give up straight away 1015 if (!is_resource($this->smtp_conn)) { 1016 return ''; 1017 } 1018 $data = ''; 1019 $endtime = 0; 1020 stream_set_timeout($this->smtp_conn, $this->Timeout); 1021 if ($this->Timelimit > 0) { 1022 $endtime = time() + $this->Timelimit; 1023 } 1024 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { 1025 $str = @fgets($this->smtp_conn, 515); 1026 $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); 1027 $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); 1028 $data .= $str; 1029 // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen 1030 if ((isset($str[3]) and $str[3] == ' ')) { 1031 break; 1032 } 1033 // Timed-out? Log and break 1034 $info = stream_get_meta_data($this->smtp_conn); 1035 if ($info['timed_out']) { 1036 $this->edebug( 1037 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', 1038 self::DEBUG_LOWLEVEL 1039 ); 1040 break; 1041 } 1042 // Now check if reads took too long 1043 if ($endtime and time() > $endtime) { 1044 $this->edebug( 1045 'SMTP -> get_lines(): timelimit reached ('. 1046 $this->Timelimit . ' sec)', 1047 self::DEBUG_LOWLEVEL 1048 ); 1049 break; 1050 } 1051 } 1052 return $data; 1053 } 1054 1055 /** 1056 * Enable or disable VERP address generation. 1057 * @param boolean $enabled 1058 */ 1059 public function setVerp($enabled = false) 1060 { 1061 $this->do_verp = $enabled; 1062 } 1063 1064 /** 1065 * Get VERP address generation mode. 1066 * @return boolean 1067 */ 1068 public function getVerp() 1069 { 1070 return $this->do_verp; 1071 } 1072 1073 /** 1074 * Set error messages and codes. 1075 * @param string $message The error message 1076 * @param string $detail Further detail on the error 1077 * @param string $smtp_code An associated SMTP error code 1078 * @param string $smtp_code_ex Extended SMTP code 1079 */ 1080 protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') 1081 { 1082 $this->error = array( 1083 'error' => $message, 1084 'detail' => $detail, 1085 'smtp_code' => $smtp_code, 1086 'smtp_code_ex' => $smtp_code_ex 1087 ); 1088 } 1089 1090 /** 1091 * Set debug output method. 1092 * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. 1093 */ 1094 public function setDebugOutput($method = 'echo') 1095 { 1096 $this->Debugoutput = $method; 1097 } 1098 1099 /** 1100 * Get debug output method. 1101 * @return string 1102 */ 1103 public function getDebugOutput() 1104 { 1105 return $this->Debugoutput; 1106 } 1107 1108 /** 1109 * Set debug output level. 1110 * @param integer $level 1111 */ 1112 public function setDebugLevel($level = 0) 1113 { 1114 $this->do_debug = $level; 1115 } 1116 1117 /** 1118 * Get debug output level. 1119 * @return integer 1120 */ 1121 public function getDebugLevel() 1122 { 1123 return $this->do_debug; 1124 } 1125 1126 /** 1127 * Set SMTP timeout. 1128 * @param integer $timeout 1129 */ 1130 public function setTimeout($timeout = 0) 1131 { 1132 $this->Timeout = $timeout; 1133 } 1134 1135 /** 1136 * Get SMTP timeout. 1137 * @return integer 1138 */ 1139 public function getTimeout() 1140 { 1141 return $this->Timeout; 1142 } 1143 1144 /** 1145 * Reports an error number and string. 1146 * @param integer $errno The error number returned by PHP. 1147 * @param string $errmsg The error message returned by PHP. 1148 */ 1149 protected function errorHandler($errno, $errmsg) 1150 { 1151 $notice = 'Connection: Failed to connect to server.'; 1152 $this->setError( 1153 $notice, 1154 $errno, 1155 $errmsg 1156 ); 1157 $this->edebug( 1158 $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg, 1159 self::DEBUG_CONNECTION 1160 ); 1161 } 1162 1163 /** 1164 * Will return the ID of the last smtp transaction based on a list of patterns provided 1165 * in SMTP::$smtp_transaction_id_patterns. 1166 * If no reply has been received yet, it will return null. 1167 * If no pattern has been matched, it will return false. 1168 * @return bool|null|string 1169 */ 1170 public function getLastTransactionID() 1171 { 1172 $reply = $this->getLastReply(); 1173 1174 if (empty($reply)) { 1175 return null; 1176 } 1177 1178 foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { 1179 if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) { 1180 return $matches[1]; 1181 } 1182 } 1183 1184 return false; 1185 } 1002 1186 } 1003 ?>
Note: See TracChangeset
for help on using the changeset viewer.