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