[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/PHPMailer/ -> PHPMailer.php (source)

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