Make WordPress Core

Ticket #41750: 41750.6.diff

File 41750.6.diff, 410.7 KB (added by desrosj, 5 years ago)
  • phpcs.xml.dist

    diff --git a/phpcs.xml.dist b/phpcs.xml.dist
    index 3718c5aab1..746bb09319 100644
    a b  
    112112        <exclude-pattern>/src/wp-includes/class-IXR\.php</exclude-pattern>
    113113        <exclude-pattern>/src/wp-includes/class-json\.php</exclude-pattern>
    114114        <exclude-pattern>/src/wp-includes/class-phpass\.php</exclude-pattern>
    115         <exclude-pattern>/src/wp-includes/class-phpmailer\.php</exclude-pattern>
    116115        <exclude-pattern>/src/wp-includes/class-pop3\.php</exclude-pattern>
    117116        <exclude-pattern>/src/wp-includes/class-requests\.php</exclude-pattern>
    118117        <exclude-pattern>/src/wp-includes/class-simplepie\.php</exclude-pattern>
    119         <exclude-pattern>/src/wp-includes/class-smtp\.php</exclude-pattern>
     118        <exclude-pattern>/src/wp-includes/PHPMailer/Exception\.php</exclude-pattern>
     119        <exclude-pattern>/src/wp-includes/PHPMailer/PHPMailer\.php</exclude-pattern>
     120        <exclude-pattern>/src/wp-includes/PHPMailer/SMTP\.php</exclude-pattern>
    120121        <exclude-pattern>/src/wp-includes/class-snoopy\.php</exclude-pattern>
    121122        <exclude-pattern>/src/wp-includes/class-wp-block-parser\.php</exclude-pattern>
    122123        <exclude-pattern>/src/wp-includes/deprecated\.php</exclude-pattern>
  • new file src/wp-includes/PHPMailer/Exception.php

    diff --git a/src/wp-includes/PHPMailer/Exception.php b/src/wp-includes/PHPMailer/Exception.php
    new file mode 100644
    index 0000000000..b1e552f50b
    - +  
     1<?php
     2/**
     3 * PHPMailer Exception 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 - 2017 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
     21namespace PHPMailer\PHPMailer;
     22
     23/**
     24 * PHPMailer exception handler.
     25 *
     26 * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
     27 */
     28class Exception extends \Exception
     29{
     30    /**
     31     * Prettify error message output.
     32     *
     33     * @return string
     34     */
     35    public function errorMessage()
     36    {
     37        return '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
     38    }
     39}
  • new file src/wp-includes/PHPMailer/PHPMailer.php

    diff --git a/src/wp-includes/PHPMailer/PHPMailer.php b/src/wp-includes/PHPMailer/PHPMailer.php
    new file mode 100644
    index 0000000000..ed14d7c7a2
    - +  
     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
     21namespace 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 */
     31class 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) {
     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                    $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46);
     1405                } elseif (defined('INTL_IDNA_VARIANT_2003')) {
     1406                    $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003);
     1407                } else {
     1408                    $punycode = idn_to_ascii($domain, $errorcode);
     1409                }
     1410                if (false !== $punycode) {
     1411                    return substr($address, 0, $pos) . $punycode;
     1412                }
     1413            }
     1414        }
     1415
     1416        return $address;
     1417    }
     1418
     1419    /**
     1420     * Create a message and send it.
     1421     * Uses the sending method specified by $Mailer.
     1422     *
     1423     * @throws Exception
     1424     *
     1425     * @return bool false on error - See the ErrorInfo property for details of the error
     1426     */
     1427    public function send()
     1428    {
     1429        try {
     1430            if (!$this->preSend()) {
     1431                return false;
     1432            }
     1433
     1434            return $this->postSend();
     1435        } catch (Exception $exc) {
     1436            $this->mailHeader = '';
     1437            $this->setError($exc->getMessage());
     1438            if ($this->exceptions) {
     1439                throw $exc;
     1440            }
     1441
     1442            return false;
     1443        }
     1444    }
     1445
     1446    /**
     1447     * Prepare a message for sending.
     1448     *
     1449     * @throws Exception
     1450     *
     1451     * @return bool
     1452     */
     1453    public function preSend()
     1454    {
     1455        if ('smtp' === $this->Mailer
     1456            || ('mail' === $this->Mailer && stripos(PHP_OS, 'WIN') === 0)
     1457        ) {
     1458            //SMTP mandates RFC-compliant line endings
     1459            //and it's also used with mail() on Windows
     1460            static::setLE(self::CRLF);
     1461        } else {
     1462            //Maintain backward compatibility with legacy Linux command line mailers
     1463            static::setLE(PHP_EOL);
     1464        }
     1465        //Check for buggy PHP versions that add a header with an incorrect line break
     1466        if ('mail' === $this->Mailer
     1467            && ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017)
     1468                || (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103))
     1469            && ini_get('mail.add_x_header') === '1'
     1470            && stripos(PHP_OS, 'WIN') === 0
     1471        ) {
     1472            trigger_error(
     1473                'Your version of PHP is affected by a bug that may result in corrupted messages.' .
     1474                ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' .
     1475                ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
     1476                E_USER_WARNING
     1477            );
     1478        }
     1479
     1480        try {
     1481            $this->error_count = 0; // Reset errors
     1482            $this->mailHeader = '';
     1483
     1484            // Dequeue recipient and Reply-To addresses with IDN
     1485            foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
     1486                $params[1] = $this->punyencodeAddress($params[1]);
     1487                call_user_func_array([$this, 'addAnAddress'], $params);
     1488            }
     1489            if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
     1490                throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
     1491            }
     1492
     1493            // Validate From, Sender, and ConfirmReadingTo addresses
     1494            foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
     1495                $this->$address_kind = trim($this->$address_kind);
     1496                if (empty($this->$address_kind)) {
     1497                    continue;
     1498                }
     1499                $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
     1500                if (!static::validateAddress($this->$address_kind)) {
     1501                    $error_message = sprintf(
     1502                        '%s (%s): %s',
     1503                        $this->lang('invalid_address'),
     1504                        $address_kind,
     1505                        $this->$address_kind
     1506                    );
     1507                    $this->setError($error_message);
     1508                    $this->edebug($error_message);
     1509                    if ($this->exceptions) {
     1510                        throw new Exception($error_message);
     1511                    }
     1512
     1513                    return false;
     1514                }
     1515            }
     1516
     1517            // Set whether the message is multipart/alternative
     1518            if ($this->alternativeExists()) {
     1519                $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;
     1520            }
     1521
     1522            $this->setMessageType();
     1523            // Refuse to send an empty message unless we are specifically allowing it
     1524            if (!$this->AllowEmpty && empty($this->Body)) {
     1525                throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
     1526            }
     1527
     1528            //Trim subject consistently
     1529            $this->Subject = trim($this->Subject);
     1530            // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
     1531            $this->MIMEHeader = '';
     1532            $this->MIMEBody = $this->createBody();
     1533            // createBody may have added some headers, so retain them
     1534            $tempheaders = $this->MIMEHeader;
     1535            $this->MIMEHeader = $this->createHeader();
     1536            $this->MIMEHeader .= $tempheaders;
     1537
     1538            // To capture the complete message when using mail(), create
     1539            // an extra header list which createHeader() doesn't fold in
     1540            if ('mail' === $this->Mailer) {
     1541                if (count($this->to) > 0) {
     1542                    $this->mailHeader .= $this->addrAppend('To', $this->to);
     1543                } else {
     1544                    $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
     1545                }
     1546                $this->mailHeader .= $this->headerLine(
     1547                    'Subject',
     1548                    $this->encodeHeader($this->secureHeader($this->Subject))
     1549                );
     1550            }
     1551
     1552            // Sign with DKIM if enabled
     1553            if (!empty($this->DKIM_domain)
     1554                && !empty($this->DKIM_selector)
     1555                && (!empty($this->DKIM_private_string)
     1556                    || (!empty($this->DKIM_private)
     1557                        && static::isPermittedPath($this->DKIM_private)
     1558                        && file_exists($this->DKIM_private)
     1559                    )
     1560                )
     1561            ) {
     1562                $header_dkim = $this->DKIM_Add(
     1563                    $this->MIMEHeader . $this->mailHeader,
     1564                    $this->encodeHeader($this->secureHeader($this->Subject)),
     1565                    $this->MIMEBody
     1566                );
     1567                $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE .
     1568                    static::normalizeBreaks($header_dkim) . static::$LE;
     1569            }
     1570
     1571            return true;
     1572        } catch (Exception $exc) {
     1573            $this->setError($exc->getMessage());
     1574            if ($this->exceptions) {
     1575                throw $exc;
     1576            }
     1577
     1578            return false;
     1579        }
     1580    }
     1581
     1582    /**
     1583     * Actually send a message via the selected mechanism.
     1584     *
     1585     * @throws Exception
     1586     *
     1587     * @return bool
     1588     */
     1589    public function postSend()
     1590    {
     1591        try {
     1592            // Choose the mailer and send through it
     1593            switch ($this->Mailer) {
     1594                case 'sendmail':
     1595                case 'qmail':
     1596                    return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
     1597                case 'smtp':
     1598                    return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
     1599                case 'mail':
     1600                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
     1601                default:
     1602                    $sendMethod = $this->Mailer . 'Send';
     1603                    if (method_exists($this, $sendMethod)) {
     1604                        return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
     1605                    }
     1606
     1607                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
     1608            }
     1609        } catch (Exception $exc) {
     1610            $this->setError($exc->getMessage());
     1611            $this->edebug($exc->getMessage());
     1612            if ($this->exceptions) {
     1613                throw $exc;
     1614            }
     1615        }
     1616
     1617        return false;
     1618    }
     1619
     1620    /**
     1621     * Send mail using the $Sendmail program.
     1622     *
     1623     * @see PHPMailer::$Sendmail
     1624     *
     1625     * @param string $header The message headers
     1626     * @param string $body   The message body
     1627     *
     1628     * @throws Exception
     1629     *
     1630     * @return bool
     1631     */
     1632    protected function sendmailSend($header, $body)
     1633    {
     1634        $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
     1635
     1636        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
     1637        if (!empty($this->Sender) && self::isShellSafe($this->Sender)) {
     1638            if ('qmail' === $this->Mailer) {
     1639                $sendmailFmt = '%s -f%s';
     1640            } else {
     1641                $sendmailFmt = '%s -oi -f%s -t';
     1642            }
     1643        } elseif ('qmail' === $this->Mailer) {
     1644            $sendmailFmt = '%s';
     1645        } else {
     1646            $sendmailFmt = '%s -oi -t';
     1647        }
     1648
     1649        $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
     1650
     1651        if ($this->SingleTo) {
     1652            foreach ($this->SingleToArray as $toAddr) {
     1653                $mail = @popen($sendmail, 'w');
     1654                if (!$mail) {
     1655                    throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
     1656                }
     1657                fwrite($mail, 'To: ' . $toAddr . "\n");
     1658                fwrite($mail, $header);
     1659                fwrite($mail, $body);
     1660                $result = pclose($mail);
     1661                $this->doCallback(
     1662                    ($result === 0),
     1663                    [$toAddr],
     1664                    $this->cc,
     1665                    $this->bcc,
     1666                    $this->Subject,
     1667                    $body,
     1668                    $this->From,
     1669                    []
     1670                );
     1671                if (0 !== $result) {
     1672                    throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
     1673                }
     1674            }
     1675        } else {
     1676            $mail = @popen($sendmail, 'w');
     1677            if (!$mail) {
     1678                throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
     1679            }
     1680            fwrite($mail, $header);
     1681            fwrite($mail, $body);
     1682            $result = pclose($mail);
     1683            $this->doCallback(
     1684                ($result === 0),
     1685                $this->to,
     1686                $this->cc,
     1687                $this->bcc,
     1688                $this->Subject,
     1689                $body,
     1690                $this->From,
     1691                []
     1692            );
     1693            if (0 !== $result) {
     1694                throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
     1695            }
     1696        }
     1697
     1698        return true;
     1699    }
     1700
     1701    /**
     1702     * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
     1703     * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
     1704     *
     1705     * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
     1706     *
     1707     * @param string $string The string to be validated
     1708     *
     1709     * @return bool
     1710     */
     1711    protected static function isShellSafe($string)
     1712    {
     1713        // Future-proof
     1714        if (escapeshellcmd($string) !== $string
     1715            || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
     1716        ) {
     1717            return false;
     1718        }
     1719
     1720        $length = strlen($string);
     1721
     1722        for ($i = 0; $i < $length; ++$i) {
     1723            $c = $string[$i];
     1724
     1725            // All other characters have a special meaning in at least one common shell, including = and +.
     1726            // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
     1727            // Note that this does permit non-Latin alphanumeric characters based on the current locale.
     1728            if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
     1729                return false;
     1730            }
     1731        }
     1732
     1733        return true;
     1734    }
     1735
     1736    /**
     1737     * Check whether a file path is of a permitted type.
     1738     * Used to reject URLs and phar files from functions that access local file paths,
     1739     * such as addAttachment.
     1740     *
     1741     * @param string $path A relative or absolute path to a file
     1742     *
     1743     * @return bool
     1744     */
     1745    protected static function isPermittedPath($path)
     1746    {
     1747        return !preg_match('#^[a-z]+://#i', $path);
     1748    }
     1749
     1750    /**
     1751     * Send mail using the PHP mail() function.
     1752     *
     1753     * @see http://www.php.net/manual/en/book.mail.php
     1754     *
     1755     * @param string $header The message headers
     1756     * @param string $body   The message body
     1757     *
     1758     * @throws Exception
     1759     *
     1760     * @return bool
     1761     */
     1762    protected function mailSend($header, $body)
     1763    {
     1764        $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
     1765
     1766        $toArr = [];
     1767        foreach ($this->to as $toaddr) {
     1768            $toArr[] = $this->addrFormat($toaddr);
     1769        }
     1770        $to = implode(', ', $toArr);
     1771
     1772        $params = null;
     1773        //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
     1774        //A space after `-f` is optional, but there is a long history of its presence
     1775        //causing problems, so we don't use one
     1776        //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
     1777        //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
     1778        //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
     1779        //Example problem: https://www.drupal.org/node/1057954
     1780        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
     1781        if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {
     1782            $params = sprintf('-f%s', $this->Sender);
     1783        }
     1784        if (!empty($this->Sender) && static::validateAddress($this->Sender)) {
     1785            $old_from = ini_get('sendmail_from');
     1786            ini_set('sendmail_from', $this->Sender);
     1787        }
     1788        $result = false;
     1789        if ($this->SingleTo && count($toArr) > 1) {
     1790            foreach ($toArr as $toAddr) {
     1791                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
     1792                $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
     1793            }
     1794        } else {
     1795            $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
     1796            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
     1797        }
     1798        if (isset($old_from)) {
     1799            ini_set('sendmail_from', $old_from);
     1800        }
     1801        if (!$result) {
     1802            throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
     1803        }
     1804
     1805        return true;
     1806    }
     1807
     1808    /**
     1809     * Get an instance to use for SMTP operations.
     1810     * Override this function to load your own SMTP implementation,
     1811     * or set one with setSMTPInstance.
     1812     *
     1813     * @return SMTP
     1814     */
     1815    public function getSMTPInstance()
     1816    {
     1817        if (!is_object($this->smtp)) {
     1818            $this->smtp = new SMTP();
     1819        }
     1820
     1821        return $this->smtp;
     1822    }
     1823
     1824    /**
     1825     * Provide an instance to use for SMTP operations.
     1826     *
     1827     * @return SMTP
     1828     */
     1829    public function setSMTPInstance(SMTP $smtp)
     1830    {
     1831        $this->smtp = $smtp;
     1832
     1833        return $this->smtp;
     1834    }
     1835
     1836    /**
     1837     * Send mail via SMTP.
     1838     * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
     1839     *
     1840     * @see PHPMailer::setSMTPInstance() to use a different class.
     1841     *
     1842     * @uses \PHPMailer\PHPMailer\SMTP
     1843     *
     1844     * @param string $header The message headers
     1845     * @param string $body   The message body
     1846     *
     1847     * @throws Exception
     1848     *
     1849     * @return bool
     1850     */
     1851    protected function smtpSend($header, $body)
     1852    {
     1853        $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
     1854        $bad_rcpt = [];
     1855        if (!$this->smtpConnect($this->SMTPOptions)) {
     1856            throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
     1857        }
     1858        //Sender already validated in preSend()
     1859        if ('' === $this->Sender) {
     1860            $smtp_from = $this->From;
     1861        } else {
     1862            $smtp_from = $this->Sender;
     1863        }
     1864        if (!$this->smtp->mail($smtp_from)) {
     1865            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
     1866            throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
     1867        }
     1868
     1869        $callbacks = [];
     1870        // Attempt to send to all recipients
     1871        foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {
     1872            foreach ($togroup as $to) {
     1873                if (!$this->smtp->recipient($to[0], $this->dsn)) {
     1874                    $error = $this->smtp->getError();
     1875                    $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
     1876                    $isSent = false;
     1877                } else {
     1878                    $isSent = true;
     1879                }
     1880
     1881                $callbacks[] = ['issent'=>$isSent, 'to'=>$to[0]];
     1882            }
     1883        }
     1884
     1885        // Only send the DATA command if we have viable recipients
     1886        if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
     1887            throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
     1888        }
     1889
     1890        $smtp_transaction_id = $this->smtp->getLastTransactionID();
     1891
     1892        if ($this->SMTPKeepAlive) {
     1893            $this->smtp->reset();
     1894        } else {
     1895            $this->smtp->quit();
     1896            $this->smtp->close();
     1897        }
     1898
     1899        foreach ($callbacks as $cb) {
     1900            $this->doCallback(
     1901                $cb['issent'],
     1902                [$cb['to']],
     1903                [],
     1904                [],
     1905                $this->Subject,
     1906                $body,
     1907                $this->From,
     1908                ['smtp_transaction_id' => $smtp_transaction_id]
     1909            );
     1910        }
     1911
     1912        //Create error message for any bad addresses
     1913        if (count($bad_rcpt) > 0) {
     1914            $errstr = '';
     1915            foreach ($bad_rcpt as $bad) {
     1916                $errstr .= $bad['to'] . ': ' . $bad['error'];
     1917            }
     1918            throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
     1919        }
     1920
     1921        return true;
     1922    }
     1923
     1924    /**
     1925     * Initiate a connection to an SMTP server.
     1926     * Returns false if the operation failed.
     1927     *
     1928     * @param array $options An array of options compatible with stream_context_create()
     1929     *
     1930     * @throws Exception
     1931     *
     1932     * @uses \PHPMailer\PHPMailer\SMTP
     1933     *
     1934     * @return bool
     1935     */
     1936    public function smtpConnect($options = null)
     1937    {
     1938        if (null === $this->smtp) {
     1939            $this->smtp = $this->getSMTPInstance();
     1940        }
     1941
     1942        //If no options are provided, use whatever is set in the instance
     1943        if (null === $options) {
     1944            $options = $this->SMTPOptions;
     1945        }
     1946
     1947        // Already connected?
     1948        if ($this->smtp->connected()) {
     1949            return true;
     1950        }
     1951
     1952        $this->smtp->setTimeout($this->Timeout);
     1953        $this->smtp->setDebugLevel($this->SMTPDebug);
     1954        $this->smtp->setDebugOutput($this->Debugoutput);
     1955        $this->smtp->setVerp($this->do_verp);
     1956        $hosts = explode(';', $this->Host);
     1957        $lastexception = null;
     1958
     1959        foreach ($hosts as $hostentry) {
     1960            $hostinfo = [];
     1961            if (!preg_match(
     1962                '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/',
     1963                trim($hostentry),
     1964                $hostinfo
     1965            )) {
     1966                $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
     1967                // Not a valid host entry
     1968                continue;
     1969            }
     1970            // $hostinfo[1]: optional ssl or tls prefix
     1971            // $hostinfo[2]: the hostname
     1972            // $hostinfo[3]: optional port number
     1973            // The host string prefix can temporarily override the current setting for SMTPSecure
     1974            // If it's not specified, the default value is used
     1975
     1976            //Check the host name is a valid name or IP address before trying to use it
     1977            if (!static::isValidHost($hostinfo[2])) {
     1978                $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
     1979                continue;
     1980            }
     1981            $prefix = '';
     1982            $secure = $this->SMTPSecure;
     1983            $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure);
     1984            if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) {
     1985                $prefix = 'ssl://';
     1986                $tls = false; // Can't have SSL and TLS at the same time
     1987                $secure = static::ENCRYPTION_SMTPS;
     1988            } elseif ('tls' === $hostinfo[1]) {
     1989                $tls = true;
     1990                // tls doesn't use a prefix
     1991                $secure = static::ENCRYPTION_STARTTLS;
     1992            }
     1993            //Do we need the OpenSSL extension?
     1994            $sslext = defined('OPENSSL_ALGO_SHA256');
     1995            if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
     1996                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
     1997                if (!$sslext) {
     1998                    throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
     1999                }
     2000            }
     2001            $host = $hostinfo[2];
     2002            $port = $this->Port;
     2003            if (array_key_exists(3, $hostinfo) && is_numeric($hostinfo[3]) && $hostinfo[3] > 0 && $hostinfo[3] < 65536) {
     2004                $port = (int) $hostinfo[3];
     2005            }
     2006            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
     2007                try {
     2008                    if ($this->Helo) {
     2009                        $hello = $this->Helo;
     2010                    } else {
     2011                        $hello = $this->serverHostname();
     2012                    }
     2013                    $this->smtp->hello($hello);
     2014                    //Automatically enable TLS encryption if:
     2015                    // * it's not disabled
     2016                    // * we have openssl extension
     2017                    // * we are not already using SSL
     2018                    // * the server offers STARTTLS
     2019                    if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) {
     2020                        $tls = true;
     2021                    }
     2022                    if ($tls) {
     2023                        if (!$this->smtp->startTLS()) {
     2024                            throw new Exception($this->lang('connect_host'));
     2025                        }
     2026                        // We must resend EHLO after TLS negotiation
     2027                        $this->smtp->hello($hello);
     2028                    }
     2029                    if ($this->SMTPAuth && !$this->smtp->authenticate(
     2030                        $this->Username,
     2031                        $this->Password,
     2032                        $this->AuthType,
     2033                        $this->oauth
     2034                    )) {
     2035                        throw new Exception($this->lang('authenticate'));
     2036                    }
     2037
     2038                    return true;
     2039                } catch (Exception $exc) {
     2040                    $lastexception = $exc;
     2041                    $this->edebug($exc->getMessage());
     2042                    // We must have connected, but then failed TLS or Auth, so close connection nicely
     2043                    $this->smtp->quit();
     2044                }
     2045            }
     2046        }
     2047        // If we get here, all connection attempts have failed, so close connection hard
     2048        $this->smtp->close();
     2049        // As we've caught all exceptions, just report whatever the last one was
     2050        if ($this->exceptions && null !== $lastexception) {
     2051            throw $lastexception;
     2052        }
     2053
     2054        return false;
     2055    }
     2056
     2057    /**
     2058     * Close the active SMTP session if one exists.
     2059     */
     2060    public function smtpClose()
     2061    {
     2062        if ((null !== $this->smtp) && $this->smtp->connected()) {
     2063            $this->smtp->quit();
     2064            $this->smtp->close();
     2065        }
     2066    }
     2067
     2068    /**
     2069     * Set the language for error messages.
     2070     * Returns false if it cannot load the language file.
     2071     * The default language is English.
     2072     *
     2073     * @param string $langcode  ISO 639-1 2-character language code (e.g. French is "fr")
     2074     * @param string $lang_path Path to the language file directory, with trailing separator (slash)
     2075     *
     2076     * @return bool
     2077     */
     2078    public function setLanguage($langcode = 'en', $lang_path = '')
     2079    {
     2080        // Backwards compatibility for renamed language codes
     2081        $renamed_langcodes = [
     2082            'br' => 'pt_br',
     2083            'cz' => 'cs',
     2084            'dk' => 'da',
     2085            'no' => 'nb',
     2086            'se' => 'sv',
     2087            'rs' => 'sr',
     2088            'tg' => 'tl',
     2089            'am' => 'hy',
     2090        ];
     2091
     2092        if (isset($renamed_langcodes[$langcode])) {
     2093            $langcode = $renamed_langcodes[$langcode];
     2094        }
     2095
     2096        // Define full set of translatable strings in English
     2097        $PHPMAILER_LANG = [
     2098            'authenticate' => 'SMTP Error: Could not authenticate.',
     2099            'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
     2100            'data_not_accepted' => 'SMTP Error: data not accepted.',
     2101            'empty_message' => 'Message body empty',
     2102            'encoding' => 'Unknown encoding: ',
     2103            'execute' => 'Could not execute: ',
     2104            'file_access' => 'Could not access file: ',
     2105            'file_open' => 'File Error: Could not open file: ',
     2106            'from_failed' => 'The following From address failed: ',
     2107            'instantiate' => 'Could not instantiate mail function.',
     2108            'invalid_address' => 'Invalid address: ',
     2109            'invalid_hostentry' => 'Invalid hostentry: ',
     2110            'invalid_host' => 'Invalid host: ',
     2111            'mailer_not_supported' => ' mailer is not supported.',
     2112            'provide_address' => 'You must provide at least one recipient email address.',
     2113            'recipients_failed' => 'SMTP Error: The following recipients failed: ',
     2114            'signing' => 'Signing Error: ',
     2115            'smtp_connect_failed' => 'SMTP connect() failed.',
     2116            'smtp_error' => 'SMTP server error: ',
     2117            'variable_set' => 'Cannot set or reset variable: ',
     2118            'extension_missing' => 'Extension missing: ',
     2119        ];
     2120        if (empty($lang_path)) {
     2121            // Calculate an absolute path so it can work if CWD is not here
     2122            $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR;
     2123        }
     2124        //Validate $langcode
     2125        if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
     2126            $langcode = 'en';
     2127        }
     2128        $foundlang = true;
     2129        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
     2130        // There is no English translation file
     2131        if ('en' !== $langcode) {
     2132            // Make sure language file path is readable
     2133            if (!static::isPermittedPath($lang_file) || !file_exists($lang_file)) {
     2134                $foundlang = false;
     2135            } else {
     2136                // Overwrite language-specific strings.
     2137                // This way we'll never have missing translation keys.
     2138                $foundlang = include $lang_file;
     2139            }
     2140        }
     2141        $this->language = $PHPMAILER_LANG;
     2142
     2143        return (bool) $foundlang; // Returns false if language not found
     2144    }
     2145
     2146    /**
     2147     * Get the array of strings for the current language.
     2148     *
     2149     * @return array
     2150     */
     2151    public function getTranslations()
     2152    {
     2153        return $this->language;
     2154    }
     2155
     2156    /**
     2157     * Create recipient headers.
     2158     *
     2159     * @param string $type
     2160     * @param array  $addr An array of recipients,
     2161     *                     where each recipient is a 2-element indexed array with element 0 containing an address
     2162     *                     and element 1 containing a name, like:
     2163     *                     [['joe@example.com', 'Joe User'], ['zoe@example.com', 'Zoe User']]
     2164     *
     2165     * @return string
     2166     */
     2167    public function addrAppend($type, $addr)
     2168    {
     2169        $addresses = [];
     2170        foreach ($addr as $address) {
     2171            $addresses[] = $this->addrFormat($address);
     2172        }
     2173
     2174        return $type . ': ' . implode(', ', $addresses) . static::$LE;
     2175    }
     2176
     2177    /**
     2178     * Format an address for use in a message header.
     2179     *
     2180     * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like
     2181     *                    ['joe@example.com', 'Joe User']
     2182     *
     2183     * @return string
     2184     */
     2185    public function addrFormat($addr)
     2186    {
     2187        if (empty($addr[1])) { // No name provided
     2188            return $this->secureHeader($addr[0]);
     2189        }
     2190
     2191        return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') .
     2192            ' <' . $this->secureHeader($addr[0]) . '>';
     2193    }
     2194
     2195    /**
     2196     * Word-wrap message.
     2197     * For use with mailers that do not automatically perform wrapping
     2198     * and for quoted-printable encoded messages.
     2199     * Original written by philippe.
     2200     *
     2201     * @param string $message The message to wrap
     2202     * @param int    $length  The line length to wrap to
     2203     * @param bool   $qp_mode Whether to run in Quoted-Printable mode
     2204     *
     2205     * @return string
     2206     */
     2207    public function wrapText($message, $length, $qp_mode = false)
     2208    {
     2209        if ($qp_mode) {
     2210            $soft_break = sprintf(' =%s', static::$LE);
     2211        } else {
     2212            $soft_break = static::$LE;
     2213        }
     2214        // If utf-8 encoding is used, we will need to make sure we don't
     2215        // split multibyte characters when we wrap
     2216        $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);
     2217        $lelen = strlen(static::$LE);
     2218        $crlflen = strlen(static::$LE);
     2219
     2220        $message = static::normalizeBreaks($message);
     2221        //Remove a trailing line break
     2222        if (substr($message, -$lelen) === static::$LE) {
     2223            $message = substr($message, 0, -$lelen);
     2224        }
     2225
     2226        //Split message into lines
     2227        $lines = explode(static::$LE, $message);
     2228        //Message will be rebuilt in here
     2229        $message = '';
     2230        foreach ($lines as $line) {
     2231            $words = explode(' ', $line);
     2232            $buf = '';
     2233            $firstword = true;
     2234            foreach ($words as $word) {
     2235                if ($qp_mode && (strlen($word) > $length)) {
     2236                    $space_left = $length - strlen($buf) - $crlflen;
     2237                    if (!$firstword) {
     2238                        if ($space_left > 20) {
     2239                            $len = $space_left;
     2240                            if ($is_utf8) {
     2241                                $len = $this->utf8CharBoundary($word, $len);
     2242                            } elseif ('=' === substr($word, $len - 1, 1)) {
     2243                                --$len;
     2244                            } elseif ('=' === substr($word, $len - 2, 1)) {
     2245                                $len -= 2;
     2246                            }
     2247                            $part = substr($word, 0, $len);
     2248                            $word = substr($word, $len);
     2249                            $buf .= ' ' . $part;
     2250                            $message .= $buf . sprintf('=%s', static::$LE);
     2251                        } else {
     2252                            $message .= $buf . $soft_break;
     2253                        }
     2254                        $buf = '';
     2255                    }
     2256                    while ($word !== '') {
     2257                        if ($length <= 0) {
     2258                            break;
     2259                        }
     2260                        $len = $length;
     2261                        if ($is_utf8) {
     2262                            $len = $this->utf8CharBoundary($word, $len);
     2263                        } elseif ('=' === substr($word, $len - 1, 1)) {
     2264                            --$len;
     2265                        } elseif ('=' === substr($word, $len - 2, 1)) {
     2266                            $len -= 2;
     2267                        }
     2268                        $part = substr($word, 0, $len);
     2269                        $word = (string) substr($word, $len);
     2270
     2271                        if ($word !== '') {
     2272                            $message .= $part . sprintf('=%s', static::$LE);
     2273                        } else {
     2274                            $buf = $part;
     2275                        }
     2276                    }
     2277                } else {
     2278                    $buf_o = $buf;
     2279                    if (!$firstword) {
     2280                        $buf .= ' ';
     2281                    }
     2282                    $buf .= $word;
     2283
     2284                    if ('' !== $buf_o && strlen($buf) > $length) {
     2285                        $message .= $buf_o . $soft_break;
     2286                        $buf = $word;
     2287                    }
     2288                }
     2289                $firstword = false;
     2290            }
     2291            $message .= $buf . static::$LE;
     2292        }
     2293
     2294        return $message;
     2295    }
     2296
     2297    /**
     2298     * Find the last character boundary prior to $maxLength in a utf-8
     2299     * quoted-printable encoded string.
     2300     * Original written by Colin Brown.
     2301     *
     2302     * @param string $encodedText utf-8 QP text
     2303     * @param int    $maxLength   Find the last character boundary prior to this length
     2304     *
     2305     * @return int
     2306     */
     2307    public function utf8CharBoundary($encodedText, $maxLength)
     2308    {
     2309        $foundSplitPos = false;
     2310        $lookBack = 3;
     2311        while (!$foundSplitPos) {
     2312            $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
     2313            $encodedCharPos = strpos($lastChunk, '=');
     2314            if (false !== $encodedCharPos) {
     2315                // Found start of encoded character byte within $lookBack block.
     2316                // Check the encoded byte value (the 2 chars after the '=')
     2317                $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
     2318                $dec = hexdec($hex);
     2319                if ($dec < 128) {
     2320                    // Single byte character.
     2321                    // If the encoded char was found at pos 0, it will fit
     2322                    // otherwise reduce maxLength to start of the encoded char
     2323                    if ($encodedCharPos > 0) {
     2324                        $maxLength -= $lookBack - $encodedCharPos;
     2325                    }
     2326                    $foundSplitPos = true;
     2327                } elseif ($dec >= 192) {
     2328                    // First byte of a multi byte character
     2329                    // Reduce maxLength to split at start of character
     2330                    $maxLength -= $lookBack - $encodedCharPos;
     2331                    $foundSplitPos = true;
     2332                } elseif ($dec < 192) {
     2333                    // Middle byte of a multi byte character, look further back
     2334                    $lookBack += 3;
     2335                }
     2336            } else {
     2337                // No encoded character found
     2338                $foundSplitPos = true;
     2339            }
     2340        }
     2341
     2342        return $maxLength;
     2343    }
     2344
     2345    /**
     2346     * Apply word wrapping to the message body.
     2347     * Wraps the message body to the number of chars set in the WordWrap property.
     2348     * You should only do this to plain-text bodies as wrapping HTML tags may break them.
     2349     * This is called automatically by createBody(), so you don't need to call it yourself.
     2350     */
     2351    public function setWordWrap()
     2352    {
     2353        if ($this->WordWrap < 1) {
     2354            return;
     2355        }
     2356
     2357        switch ($this->message_type) {
     2358            case 'alt':
     2359            case 'alt_inline':
     2360            case 'alt_attach':
     2361            case 'alt_inline_attach':
     2362                $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
     2363                break;
     2364            default:
     2365                $this->Body = $this->wrapText($this->Body, $this->WordWrap);
     2366                break;
     2367        }
     2368    }
     2369
     2370    /**
     2371     * Assemble message headers.
     2372     *
     2373     * @return string The assembled headers
     2374     */
     2375    public function createHeader()
     2376    {
     2377        $result = '';
     2378
     2379        $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);
     2380
     2381        // To be created automatically by mail()
     2382        if ($this->SingleTo) {
     2383            if ('mail' !== $this->Mailer) {
     2384                foreach ($this->to as $toaddr) {
     2385                    $this->SingleToArray[] = $this->addrFormat($toaddr);
     2386                }
     2387            }
     2388        } elseif (count($this->to) > 0) {
     2389            if ('mail' !== $this->Mailer) {
     2390                $result .= $this->addrAppend('To', $this->to);
     2391            }
     2392        } elseif (count($this->cc) === 0) {
     2393            $result .= $this->headerLine('To', 'undisclosed-recipients:;');
     2394        }
     2395
     2396        $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
     2397
     2398        // sendmail and mail() extract Cc from the header before sending
     2399        if (count($this->cc) > 0) {
     2400            $result .= $this->addrAppend('Cc', $this->cc);
     2401        }
     2402
     2403        // sendmail and mail() extract Bcc from the header before sending
     2404        if ((
     2405                'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer
     2406            )
     2407            && count($this->bcc) > 0
     2408        ) {
     2409            $result .= $this->addrAppend('Bcc', $this->bcc);
     2410        }
     2411
     2412        if (count($this->ReplyTo) > 0) {
     2413            $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
     2414        }
     2415
     2416        // mail() sets the subject itself
     2417        if ('mail' !== $this->Mailer) {
     2418            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
     2419        }
     2420
     2421        // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
     2422        // https://tools.ietf.org/html/rfc5322#section-3.6.4
     2423        if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) {
     2424            $this->lastMessageID = $this->MessageID;
     2425        } else {
     2426            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
     2427        }
     2428        $result .= $this->headerLine('Message-ID', $this->lastMessageID);
     2429        if (null !== $this->Priority) {
     2430            $result .= $this->headerLine('X-Priority', $this->Priority);
     2431        }
     2432        if ('' === $this->XMailer) {
     2433            $result .= $this->headerLine(
     2434                'X-Mailer',
     2435                'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)'
     2436            );
     2437        } else {
     2438            $myXmailer = trim($this->XMailer);
     2439            if ($myXmailer) {
     2440                $result .= $this->headerLine('X-Mailer', $myXmailer);
     2441            }
     2442        }
     2443
     2444        if ('' !== $this->ConfirmReadingTo) {
     2445            $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
     2446        }
     2447
     2448        // Add custom headers
     2449        foreach ($this->CustomHeader as $header) {
     2450            $result .= $this->headerLine(
     2451                trim($header[0]),
     2452                $this->encodeHeader(trim($header[1]))
     2453            );
     2454        }
     2455        if (!$this->sign_key_file) {
     2456            $result .= $this->headerLine('MIME-Version', '1.0');
     2457            $result .= $this->getMailMIME();
     2458        }
     2459
     2460        return $result;
     2461    }
     2462
     2463    /**
     2464     * Get the message MIME type headers.
     2465     *
     2466     * @return string
     2467     */
     2468    public function getMailMIME()
     2469    {
     2470        $result = '';
     2471        $ismultipart = true;
     2472        switch ($this->message_type) {
     2473            case 'inline':
     2474                $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
     2475                $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
     2476                break;
     2477            case 'attach':
     2478            case 'inline_attach':
     2479            case 'alt_attach':
     2480            case 'alt_inline_attach':
     2481                $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';');
     2482                $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
     2483                break;
     2484            case 'alt':
     2485            case 'alt_inline':
     2486                $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
     2487                $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
     2488                break;
     2489            default:
     2490                // Catches case 'plain': and case '':
     2491                $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
     2492                $ismultipart = false;
     2493                break;
     2494        }
     2495        // RFC1341 part 5 says 7bit is assumed if not specified
     2496        if (static::ENCODING_7BIT !== $this->Encoding) {
     2497            // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
     2498            if ($ismultipart) {
     2499                if (static::ENCODING_8BIT === $this->Encoding) {
     2500                    $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
     2501                }
     2502                // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
     2503            } else {
     2504                $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
     2505            }
     2506        }
     2507
     2508        if ('mail' !== $this->Mailer) {
     2509//            $result .= static::$LE;
     2510        }
     2511
     2512        return $result;
     2513    }
     2514
     2515    /**
     2516     * Returns the whole MIME message.
     2517     * Includes complete headers and body.
     2518     * Only valid post preSend().
     2519     *
     2520     * @see PHPMailer::preSend()
     2521     *
     2522     * @return string
     2523     */
     2524    public function getSentMIMEMessage()
     2525    {
     2526        return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) .
     2527            static::$LE . static::$LE . $this->MIMEBody;
     2528    }
     2529
     2530    /**
     2531     * Create a unique ID to use for boundaries.
     2532     *
     2533     * @return string
     2534     */
     2535    protected function generateId()
     2536    {
     2537        $len = 32; //32 bytes = 256 bits
     2538        $bytes = '';
     2539        if (function_exists('random_bytes')) {
     2540            try {
     2541                $bytes = random_bytes($len);
     2542            } catch (\Exception $e) {
     2543                //Do nothing
     2544            }
     2545        } elseif (function_exists('openssl_random_pseudo_bytes')) {
     2546            /** @noinspection CryptographicallySecureRandomnessInspection */
     2547            $bytes = openssl_random_pseudo_bytes($len);
     2548        }
     2549        if ($bytes === '') {
     2550            //We failed to produce a proper random string, so make do.
     2551            //Use a hash to force the length to the same as the other methods
     2552            $bytes = hash('sha256', uniqid((string) mt_rand(), true), true);
     2553        }
     2554
     2555        //We don't care about messing up base64 format here, just want a random string
     2556        return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));
     2557    }
     2558
     2559    /**
     2560     * Assemble the message body.
     2561     * Returns an empty string on failure.
     2562     *
     2563     * @throws Exception
     2564     *
     2565     * @return string The assembled message body
     2566     */
     2567    public function createBody()
     2568    {
     2569        $body = '';
     2570        //Create unique IDs and preset boundaries
     2571        $this->uniqueid = $this->generateId();
     2572        $this->boundary[1] = 'b1_' . $this->uniqueid;
     2573        $this->boundary[2] = 'b2_' . $this->uniqueid;
     2574        $this->boundary[3] = 'b3_' . $this->uniqueid;
     2575
     2576        if ($this->sign_key_file) {
     2577            $body .= $this->getMailMIME() . static::$LE;
     2578        }
     2579
     2580        $this->setWordWrap();
     2581
     2582        $bodyEncoding = $this->Encoding;
     2583        $bodyCharSet = $this->CharSet;
     2584        //Can we do a 7-bit downgrade?
     2585        if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
     2586            $bodyEncoding = static::ENCODING_7BIT;
     2587            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
     2588            $bodyCharSet = static::CHARSET_ASCII;
     2589        }
     2590        //If lines are too long, and we're not already using an encoding that will shorten them,
     2591        //change to quoted-printable transfer encoding for the body part only
     2592        if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) {
     2593            $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
     2594        }
     2595
     2596        $altBodyEncoding = $this->Encoding;
     2597        $altBodyCharSet = $this->CharSet;
     2598        //Can we do a 7-bit downgrade?
     2599        if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) {
     2600            $altBodyEncoding = static::ENCODING_7BIT;
     2601            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
     2602            $altBodyCharSet = static::CHARSET_ASCII;
     2603        }
     2604        //If lines are too long, and we're not already using an encoding that will shorten them,
     2605        //change to quoted-printable transfer encoding for the alt body part only
     2606        if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
     2607            $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
     2608        }
     2609        //Use this as a preamble in all multipart message types
     2610        $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE;
     2611        switch ($this->message_type) {
     2612            case 'inline':
     2613                $body .= $mimepre;
     2614                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
     2615                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2616                $body .= static::$LE;
     2617                $body .= $this->attachAll('inline', $this->boundary[1]);
     2618                break;
     2619            case 'attach':
     2620                $body .= $mimepre;
     2621                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
     2622                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2623                $body .= static::$LE;
     2624                $body .= $this->attachAll('attachment', $this->boundary[1]);
     2625                break;
     2626            case 'inline_attach':
     2627                $body .= $mimepre;
     2628                $body .= $this->textLine('--' . $this->boundary[1]);
     2629                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
     2630                $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
     2631                $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
     2632                $body .= static::$LE;
     2633                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
     2634                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2635                $body .= static::$LE;
     2636                $body .= $this->attachAll('inline', $this->boundary[2]);
     2637                $body .= static::$LE;
     2638                $body .= $this->attachAll('attachment', $this->boundary[1]);
     2639                break;
     2640            case 'alt':
     2641                $body .= $mimepre;
     2642                $body .= $this->getBoundary(
     2643                    $this->boundary[1],
     2644                    $altBodyCharSet,
     2645                    static::CONTENT_TYPE_PLAINTEXT,
     2646                    $altBodyEncoding
     2647                );
     2648                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
     2649                $body .= static::$LE;
     2650                $body .= $this->getBoundary(
     2651                    $this->boundary[1],
     2652                    $bodyCharSet,
     2653                    static::CONTENT_TYPE_TEXT_HTML,
     2654                    $bodyEncoding
     2655                );
     2656                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2657                $body .= static::$LE;
     2658                if (!empty($this->Ical)) {
     2659                    $method = static::ICAL_METHOD_REQUEST;
     2660                    foreach (static::$IcalMethods as $imethod) {
     2661                        if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
     2662                            $method = $imethod;
     2663                            break;
     2664                        }
     2665                    }
     2666                    $body .= $this->getBoundary(
     2667                        $this->boundary[1],
     2668                        '',
     2669                        static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
     2670                        ''
     2671                    );
     2672                    $body .= $this->encodeString($this->Ical, $this->Encoding);
     2673                    $body .= static::$LE;
     2674                }
     2675                $body .= $this->endBoundary($this->boundary[1]);
     2676                break;
     2677            case 'alt_inline':
     2678                $body .= $mimepre;
     2679                $body .= $this->getBoundary(
     2680                    $this->boundary[1],
     2681                    $altBodyCharSet,
     2682                    static::CONTENT_TYPE_PLAINTEXT,
     2683                    $altBodyEncoding
     2684                );
     2685                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
     2686                $body .= static::$LE;
     2687                $body .= $this->textLine('--' . $this->boundary[1]);
     2688                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
     2689                $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
     2690                $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
     2691                $body .= static::$LE;
     2692                $body .= $this->getBoundary(
     2693                    $this->boundary[2],
     2694                    $bodyCharSet,
     2695                    static::CONTENT_TYPE_TEXT_HTML,
     2696                    $bodyEncoding
     2697                );
     2698                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2699                $body .= static::$LE;
     2700                $body .= $this->attachAll('inline', $this->boundary[2]);
     2701                $body .= static::$LE;
     2702                $body .= $this->endBoundary($this->boundary[1]);
     2703                break;
     2704            case 'alt_attach':
     2705                $body .= $mimepre;
     2706                $body .= $this->textLine('--' . $this->boundary[1]);
     2707                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
     2708                $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
     2709                $body .= static::$LE;
     2710                $body .= $this->getBoundary(
     2711                    $this->boundary[2],
     2712                    $altBodyCharSet,
     2713                    static::CONTENT_TYPE_PLAINTEXT,
     2714                    $altBodyEncoding
     2715                );
     2716                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
     2717                $body .= static::$LE;
     2718                $body .= $this->getBoundary(
     2719                    $this->boundary[2],
     2720                    $bodyCharSet,
     2721                    static::CONTENT_TYPE_TEXT_HTML,
     2722                    $bodyEncoding
     2723                );
     2724                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2725                $body .= static::$LE;
     2726                if (!empty($this->Ical)) {
     2727                    $method = static::ICAL_METHOD_REQUEST;
     2728                    foreach (static::$IcalMethods as $imethod) {
     2729                        if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
     2730                            $method = $imethod;
     2731                            break;
     2732                        }
     2733                    }
     2734                    $body .= $this->getBoundary(
     2735                        $this->boundary[2],
     2736                        '',
     2737                        static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
     2738                        ''
     2739                    );
     2740                    $body .= $this->encodeString($this->Ical, $this->Encoding);
     2741                }
     2742                $body .= $this->endBoundary($this->boundary[2]);
     2743                $body .= static::$LE;
     2744                $body .= $this->attachAll('attachment', $this->boundary[1]);
     2745                break;
     2746            case 'alt_inline_attach':
     2747                $body .= $mimepre;
     2748                $body .= $this->textLine('--' . $this->boundary[1]);
     2749                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
     2750                $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
     2751                $body .= static::$LE;
     2752                $body .= $this->getBoundary(
     2753                    $this->boundary[2],
     2754                    $altBodyCharSet,
     2755                    static::CONTENT_TYPE_PLAINTEXT,
     2756                    $altBodyEncoding
     2757                );
     2758                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
     2759                $body .= static::$LE;
     2760                $body .= $this->textLine('--' . $this->boundary[2]);
     2761                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
     2762                $body .= $this->textLine(' boundary="' . $this->boundary[3] . '";');
     2763                $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
     2764                $body .= static::$LE;
     2765                $body .= $this->getBoundary(
     2766                    $this->boundary[3],
     2767                    $bodyCharSet,
     2768                    static::CONTENT_TYPE_TEXT_HTML,
     2769                    $bodyEncoding
     2770                );
     2771                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2772                $body .= static::$LE;
     2773                $body .= $this->attachAll('inline', $this->boundary[3]);
     2774                $body .= static::$LE;
     2775                $body .= $this->endBoundary($this->boundary[2]);
     2776                $body .= static::$LE;
     2777                $body .= $this->attachAll('attachment', $this->boundary[1]);
     2778                break;
     2779            default:
     2780                // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
     2781                //Reset the `Encoding` property in case we changed it for line length reasons
     2782                $this->Encoding = $bodyEncoding;
     2783                $body .= $this->encodeString($this->Body, $this->Encoding);
     2784                break;
     2785        }
     2786
     2787        if ($this->isError()) {
     2788            $body = '';
     2789            if ($this->exceptions) {
     2790                throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
     2791            }
     2792        } elseif ($this->sign_key_file) {
     2793            try {
     2794                if (!defined('PKCS7_TEXT')) {
     2795                    throw new Exception($this->lang('extension_missing') . 'openssl');
     2796                }
     2797
     2798                $file = tempnam(sys_get_temp_dir(), 'srcsign');
     2799                $signed = tempnam(sys_get_temp_dir(), 'mailsign');
     2800                file_put_contents($file, $body);
     2801
     2802                //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
     2803                if (empty($this->sign_extracerts_file)) {
     2804                    $sign = @openssl_pkcs7_sign(
     2805                        $file,
     2806                        $signed,
     2807                        'file://' . realpath($this->sign_cert_file),
     2808                        ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
     2809                        []
     2810                    );
     2811                } else {
     2812                    $sign = @openssl_pkcs7_sign(
     2813                        $file,
     2814                        $signed,
     2815                        'file://' . realpath($this->sign_cert_file),
     2816                        ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
     2817                        [],
     2818                        PKCS7_DETACHED,
     2819                        $this->sign_extracerts_file
     2820                    );
     2821                }
     2822
     2823                @unlink($file);
     2824                if ($sign) {
     2825                    $body = file_get_contents($signed);
     2826                    @unlink($signed);
     2827                    //The message returned by openssl contains both headers and body, so need to split them up
     2828                    $parts = explode("\n\n", $body, 2);
     2829                    $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE;
     2830                    $body = $parts[1];
     2831                } else {
     2832                    @unlink($signed);
     2833                    throw new Exception($this->lang('signing') . openssl_error_string());
     2834                }
     2835            } catch (Exception $exc) {
     2836                $body = '';
     2837                if ($this->exceptions) {
     2838                    throw $exc;
     2839                }
     2840            }
     2841        }
     2842
     2843        return $body;
     2844    }
     2845
     2846    /**
     2847     * Return the start of a message boundary.
     2848     *
     2849     * @param string $boundary
     2850     * @param string $charSet
     2851     * @param string $contentType
     2852     * @param string $encoding
     2853     *
     2854     * @return string
     2855     */
     2856    protected function getBoundary($boundary, $charSet, $contentType, $encoding)
     2857    {
     2858        $result = '';
     2859        if ('' === $charSet) {
     2860            $charSet = $this->CharSet;
     2861        }
     2862        if ('' === $contentType) {
     2863            $contentType = $this->ContentType;
     2864        }
     2865        if ('' === $encoding) {
     2866            $encoding = $this->Encoding;
     2867        }
     2868        $result .= $this->textLine('--' . $boundary);
     2869        $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
     2870        $result .= static::$LE;
     2871        // RFC1341 part 5 says 7bit is assumed if not specified
     2872        if (static::ENCODING_7BIT !== $encoding) {
     2873            $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
     2874        }
     2875        $result .= static::$LE;
     2876
     2877        return $result;
     2878    }
     2879
     2880    /**
     2881     * Return the end of a message boundary.
     2882     *
     2883     * @param string $boundary
     2884     *
     2885     * @return string
     2886     */
     2887    protected function endBoundary($boundary)
     2888    {
     2889        return static::$LE . '--' . $boundary . '--' . static::$LE;
     2890    }
     2891
     2892    /**
     2893     * Set the message type.
     2894     * PHPMailer only supports some preset message types, not arbitrary MIME structures.
     2895     */
     2896    protected function setMessageType()
     2897    {
     2898        $type = [];
     2899        if ($this->alternativeExists()) {
     2900            $type[] = 'alt';
     2901        }
     2902        if ($this->inlineImageExists()) {
     2903            $type[] = 'inline';
     2904        }
     2905        if ($this->attachmentExists()) {
     2906            $type[] = 'attach';
     2907        }
     2908        $this->message_type = implode('_', $type);
     2909        if ('' === $this->message_type) {
     2910            //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
     2911            $this->message_type = 'plain';
     2912        }
     2913    }
     2914
     2915    /**
     2916     * Format a header line.
     2917     *
     2918     * @param string     $name
     2919     * @param string|int $value
     2920     *
     2921     * @return string
     2922     */
     2923    public function headerLine($name, $value)
     2924    {
     2925        return $name . ': ' . $value . static::$LE;
     2926    }
     2927
     2928    /**
     2929     * Return a formatted mail line.
     2930     *
     2931     * @param string $value
     2932     *
     2933     * @return string
     2934     */
     2935    public function textLine($value)
     2936    {
     2937        return $value . static::$LE;
     2938    }
     2939
     2940    /**
     2941     * Add an attachment from a path on the filesystem.
     2942     * Never use a user-supplied path to a file!
     2943     * Returns false if the file could not be found or read.
     2944     * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
     2945     * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
     2946     *
     2947     * @param string $path        Path to the attachment
     2948     * @param string $name        Overrides the attachment name
     2949     * @param string $encoding    File encoding (see $Encoding)
     2950     * @param string $type        File extension (MIME) type
     2951     * @param string $disposition Disposition to use
     2952     *
     2953     * @throws Exception
     2954     *
     2955     * @return bool
     2956     */
     2957    public function addAttachment(
     2958        $path,
     2959        $name = '',
     2960        $encoding = self::ENCODING_BASE64,
     2961        $type = '',
     2962        $disposition = 'attachment'
     2963    ) {
     2964        try {
     2965            if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) {
     2966                throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
     2967            }
     2968
     2969            // If a MIME type is not specified, try to work it out from the file name
     2970            if ('' === $type) {
     2971                $type = static::filenameToType($path);
     2972            }
     2973
     2974            $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
     2975            if ('' === $name) {
     2976                $name = $filename;
     2977            }
     2978
     2979            if (!$this->validateEncoding($encoding)) {
     2980                throw new Exception($this->lang('encoding') . $encoding);
     2981            }
     2982
     2983            $this->attachment[] = [
     2984                0 => $path,
     2985                1 => $filename,
     2986                2 => $name,
     2987                3 => $encoding,
     2988                4 => $type,
     2989                5 => false, // isStringAttachment
     2990                6 => $disposition,
     2991                7 => $name,
     2992            ];
     2993        } catch (Exception $exc) {
     2994            $this->setError($exc->getMessage());
     2995            $this->edebug($exc->getMessage());
     2996            if ($this->exceptions) {
     2997                throw $exc;
     2998            }
     2999
     3000            return false;
     3001        }
     3002
     3003        return true;
     3004    }
     3005
     3006    /**
     3007     * Return the array of attachments.
     3008     *
     3009     * @return array
     3010     */
     3011    public function getAttachments()
     3012    {
     3013        return $this->attachment;
     3014    }
     3015
     3016    /**
     3017     * Attach all file, string, and binary attachments to the message.
     3018     * Returns an empty string on failure.
     3019     *
     3020     * @param string $disposition_type
     3021     * @param string $boundary
     3022     *
     3023     * @throws Exception
     3024     *
     3025     * @return string
     3026     */
     3027    protected function attachAll($disposition_type, $boundary)
     3028    {
     3029        // Return text of body
     3030        $mime = [];
     3031        $cidUniq = [];
     3032        $incl = [];
     3033
     3034        // Add all attachments
     3035        foreach ($this->attachment as $attachment) {
     3036            // Check if it is a valid disposition_filter
     3037            if ($attachment[6] === $disposition_type) {
     3038                // Check for string attachment
     3039                $string = '';
     3040                $path = '';
     3041                $bString = $attachment[5];
     3042                if ($bString) {
     3043                    $string = $attachment[0];
     3044                } else {
     3045                    $path = $attachment[0];
     3046                }
     3047
     3048                $inclhash = hash('sha256', serialize($attachment));
     3049                if (in_array($inclhash, $incl, true)) {
     3050                    continue;
     3051                }
     3052                $incl[] = $inclhash;
     3053                $name = $attachment[2];
     3054                $encoding = $attachment[3];
     3055                $type = $attachment[4];
     3056                $disposition = $attachment[6];
     3057                $cid = $attachment[7];
     3058                if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) {
     3059                    continue;
     3060                }
     3061                $cidUniq[$cid] = true;
     3062
     3063                $mime[] = sprintf('--%s%s', $boundary, static::$LE);
     3064                //Only include a filename property if we have one
     3065                if (!empty($name)) {
     3066                    $mime[] = sprintf(
     3067                        'Content-Type: %s; name=%s%s',
     3068                        $type,
     3069                        static::quotedString($this->encodeHeader($this->secureHeader($name))),
     3070                        static::$LE
     3071                    );
     3072                } else {
     3073                    $mime[] = sprintf(
     3074                        'Content-Type: %s%s',
     3075                        $type,
     3076                        static::$LE
     3077                    );
     3078                }
     3079                // RFC1341 part 5 says 7bit is assumed if not specified
     3080                if (static::ENCODING_7BIT !== $encoding) {
     3081                    $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE);
     3082                }
     3083
     3084                //Only set Content-IDs on inline attachments
     3085                if ((string) $cid !== '' && $disposition === 'inline') {
     3086                    $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE;
     3087                }
     3088
     3089                // Allow for bypassing the Content-Disposition header
     3090                if (!empty($disposition)) {
     3091                    $encoded_name = $this->encodeHeader($this->secureHeader($name));
     3092                    if (!empty($encoded_name)) {
     3093                        $mime[] = sprintf(
     3094                            'Content-Disposition: %s; filename=%s%s',
     3095                            $disposition,
     3096                            static::quotedString($encoded_name),
     3097                            static::$LE . static::$LE
     3098                        );
     3099                    } else {
     3100                        $mime[] = sprintf(
     3101                            'Content-Disposition: %s%s',
     3102                            $disposition,
     3103                            static::$LE . static::$LE
     3104                        );
     3105                    }
     3106                } else {
     3107                    $mime[] = static::$LE;
     3108                }
     3109
     3110                // Encode as string attachment
     3111                if ($bString) {
     3112                    $mime[] = $this->encodeString($string, $encoding);
     3113                } else {
     3114                    $mime[] = $this->encodeFile($path, $encoding);
     3115                }
     3116                if ($this->isError()) {
     3117                    return '';
     3118                }
     3119                $mime[] = static::$LE;
     3120            }
     3121        }
     3122
     3123        $mime[] = sprintf('--%s--%s', $boundary, static::$LE);
     3124
     3125        return implode('', $mime);
     3126    }
     3127
     3128    /**
     3129     * Encode a file attachment in requested format.
     3130     * Returns an empty string on failure.
     3131     *
     3132     * @param string $path     The full path to the file
     3133     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
     3134     *
     3135     * @return string
     3136     */
     3137    protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
     3138    {
     3139        try {
     3140            if (!static::isPermittedPath($path) || !file_exists($path) || !is_readable($path)) {
     3141                throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
     3142            }
     3143            $file_buffer = file_get_contents($path);
     3144            if (false === $file_buffer) {
     3145                throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
     3146            }
     3147            $file_buffer = $this->encodeString($file_buffer, $encoding);
     3148
     3149            return $file_buffer;
     3150        } catch (Exception $exc) {
     3151            $this->setError($exc->getMessage());
     3152            $this->edebug($exc->getMessage());
     3153            if ($this->exceptions) {
     3154                throw $exc;
     3155            }
     3156
     3157            return '';
     3158        }
     3159    }
     3160
     3161    /**
     3162     * Encode a string in requested format.
     3163     * Returns an empty string on failure.
     3164     *
     3165     * @param string $str      The text to encode
     3166     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
     3167     *
     3168     * @throws Exception
     3169     *
     3170     * @return string
     3171     */
     3172    public function encodeString($str, $encoding = self::ENCODING_BASE64)
     3173    {
     3174        $encoded = '';
     3175        switch (strtolower($encoding)) {
     3176            case static::ENCODING_BASE64:
     3177                $encoded = chunk_split(
     3178                    base64_encode($str),
     3179                    static::STD_LINE_LENGTH,
     3180                    static::$LE
     3181                );
     3182                break;
     3183            case static::ENCODING_7BIT:
     3184            case static::ENCODING_8BIT:
     3185                $encoded = static::normalizeBreaks($str);
     3186                // Make sure it ends with a line break
     3187                if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) {
     3188                    $encoded .= static::$LE;
     3189                }
     3190                break;
     3191            case static::ENCODING_BINARY:
     3192                $encoded = $str;
     3193                break;
     3194            case static::ENCODING_QUOTED_PRINTABLE:
     3195                $encoded = $this->encodeQP($str);
     3196                break;
     3197            default:
     3198                $this->setError($this->lang('encoding') . $encoding);
     3199                if ($this->exceptions) {
     3200                    throw new Exception($this->lang('encoding') . $encoding);
     3201                }
     3202                break;
     3203        }
     3204
     3205        return $encoded;
     3206    }
     3207
     3208    /**
     3209     * Encode a header value (not including its label) optimally.
     3210     * Picks shortest of Q, B, or none. Result includes folding if needed.
     3211     * See RFC822 definitions for phrase, comment and text positions.
     3212     *
     3213     * @param string $str      The header value to encode
     3214     * @param string $position What context the string will be used in
     3215     *
     3216     * @return string
     3217     */
     3218    public function encodeHeader($str, $position = 'text')
     3219    {
     3220        $matchcount = 0;
     3221        switch (strtolower($position)) {
     3222            case 'phrase':
     3223                if (!preg_match('/[\200-\377]/', $str)) {
     3224                    // Can't use addslashes as we don't know the value of magic_quotes_sybase
     3225                    $encoded = addcslashes($str, "\0..\37\177\\\"");
     3226                    if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
     3227                        return $encoded;
     3228                    }
     3229
     3230                    return "\"$encoded\"";
     3231                }
     3232                $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
     3233                break;
     3234            /* @noinspection PhpMissingBreakStatementInspection */
     3235            case 'comment':
     3236                $matchcount = preg_match_all('/[()"]/', $str, $matches);
     3237            //fallthrough
     3238            case 'text':
     3239            default:
     3240                $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
     3241                break;
     3242        }
     3243
     3244        if ($this->has8bitChars($str)) {
     3245            $charset = $this->CharSet;
     3246        } else {
     3247            $charset = static::CHARSET_ASCII;
     3248        }
     3249
     3250        // Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<content>?=`").
     3251        $overhead = 8 + strlen($charset);
     3252
     3253        if ('mail' === $this->Mailer) {
     3254            $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead;
     3255        } else {
     3256            $maxlen = static::MAX_LINE_LENGTH - $overhead;
     3257        }
     3258
     3259        // Select the encoding that produces the shortest output and/or prevents corruption.
     3260        if ($matchcount > strlen($str) / 3) {
     3261            // More than 1/3 of the content needs encoding, use B-encode.
     3262            $encoding = 'B';
     3263        } elseif ($matchcount > 0) {
     3264            // Less than 1/3 of the content needs encoding, use Q-encode.
     3265            $encoding = 'Q';
     3266        } elseif (strlen($str) > $maxlen) {
     3267            // No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption.
     3268            $encoding = 'Q';
     3269        } else {
     3270            // No reformatting needed
     3271            $encoding = false;
     3272        }
     3273
     3274        switch ($encoding) {
     3275            case 'B':
     3276                if ($this->hasMultiBytes($str)) {
     3277                    // Use a custom function which correctly encodes and wraps long
     3278                    // multibyte strings without breaking lines within a character
     3279                    $encoded = $this->base64EncodeWrapMB($str, "\n");
     3280                } else {
     3281                    $encoded = base64_encode($str);
     3282                    $maxlen -= $maxlen % 4;
     3283                    $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
     3284                }
     3285                $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
     3286                break;
     3287            case 'Q':
     3288                $encoded = $this->encodeQ($str, $position);
     3289                $encoded = $this->wrapText($encoded, $maxlen, true);
     3290                $encoded = str_replace('=' . static::$LE, "\n", trim($encoded));
     3291                $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
     3292                break;
     3293            default:
     3294                return $str;
     3295        }
     3296
     3297        return trim(static::normalizeBreaks($encoded));
     3298    }
     3299
     3300    /**
     3301     * Check if a string contains multi-byte characters.
     3302     *
     3303     * @param string $str multi-byte text to wrap encode
     3304     *
     3305     * @return bool
     3306     */
     3307    public function hasMultiBytes($str)
     3308    {
     3309        if (function_exists('mb_strlen')) {
     3310            return strlen($str) > mb_strlen($str, $this->CharSet);
     3311        }
     3312
     3313        // Assume no multibytes (we can't handle without mbstring functions anyway)
     3314        return false;
     3315    }
     3316
     3317    /**
     3318     * Does a string contain any 8-bit chars (in any charset)?
     3319     *
     3320     * @param string $text
     3321     *
     3322     * @return bool
     3323     */
     3324    public function has8bitChars($text)
     3325    {
     3326        return (bool) preg_match('/[\x80-\xFF]/', $text);
     3327    }
     3328
     3329    /**
     3330     * Encode and wrap long multibyte strings for mail headers
     3331     * without breaking lines within a character.
     3332     * Adapted from a function by paravoid.
     3333     *
     3334     * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
     3335     *
     3336     * @param string $str       multi-byte text to wrap encode
     3337     * @param string $linebreak string to use as linefeed/end-of-line
     3338     *
     3339     * @return string
     3340     */
     3341    public function base64EncodeWrapMB($str, $linebreak = null)
     3342    {
     3343        $start = '=?' . $this->CharSet . '?B?';
     3344        $end = '?=';
     3345        $encoded = '';
     3346        if (null === $linebreak) {
     3347            $linebreak = static::$LE;
     3348        }
     3349
     3350        $mb_length = mb_strlen($str, $this->CharSet);
     3351        // Each line must have length <= 75, including $start and $end
     3352        $length = 75 - strlen($start) - strlen($end);
     3353        // Average multi-byte ratio
     3354        $ratio = $mb_length / strlen($str);
     3355        // Base64 has a 4:3 ratio
     3356        $avgLength = floor($length * $ratio * .75);
     3357
     3358        $offset = 0;
     3359        for ($i = 0; $i < $mb_length; $i += $offset) {
     3360            $lookBack = 0;
     3361            do {
     3362                $offset = $avgLength - $lookBack;
     3363                $chunk = mb_substr($str, $i, $offset, $this->CharSet);
     3364                $chunk = base64_encode($chunk);
     3365                ++$lookBack;
     3366            } while (strlen($chunk) > $length);
     3367            $encoded .= $chunk . $linebreak;
     3368        }
     3369
     3370        // Chomp the last linefeed
     3371        return substr($encoded, 0, -strlen($linebreak));
     3372    }
     3373
     3374    /**
     3375     * Encode a string in quoted-printable format.
     3376     * According to RFC2045 section 6.7.
     3377     *
     3378     * @param string $string The text to encode
     3379     *
     3380     * @return string
     3381     */
     3382    public function encodeQP($string)
     3383    {
     3384        return static::normalizeBreaks(quoted_printable_encode($string));
     3385    }
     3386
     3387    /**
     3388     * Encode a string using Q encoding.
     3389     *
     3390     * @see http://tools.ietf.org/html/rfc2047#section-4.2
     3391     *
     3392     * @param string $str      the text to encode
     3393     * @param string $position Where the text is going to be used, see the RFC for what that means
     3394     *
     3395     * @return string
     3396     */
     3397    public function encodeQ($str, $position = 'text')
     3398    {
     3399        // There should not be any EOL in the string
     3400        $pattern = '';
     3401        $encoded = str_replace(["\r", "\n"], '', $str);
     3402        switch (strtolower($position)) {
     3403            case 'phrase':
     3404                // RFC 2047 section 5.3
     3405                $pattern = '^A-Za-z0-9!*+\/ -';
     3406                break;
     3407            /*
     3408             * RFC 2047 section 5.2.
     3409             * Build $pattern without including delimiters and []
     3410             */
     3411            /* @noinspection PhpMissingBreakStatementInspection */
     3412            case 'comment':
     3413                $pattern = '\(\)"';
     3414            /* Intentional fall through */
     3415            case 'text':
     3416            default:
     3417                // RFC 2047 section 5.1
     3418                // Replace every high ascii, control, =, ? and _ characters
     3419                $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
     3420                break;
     3421        }
     3422        $matches = [];
     3423        if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
     3424            // If the string contains an '=', make sure it's the first thing we replace
     3425            // so as to avoid double-encoding
     3426            $eqkey = array_search('=', $matches[0], true);
     3427            if (false !== $eqkey) {
     3428                unset($matches[0][$eqkey]);
     3429                array_unshift($matches[0], '=');
     3430            }
     3431            foreach (array_unique($matches[0]) as $char) {
     3432                $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
     3433            }
     3434        }
     3435        // Replace spaces with _ (more readable than =20)
     3436        // RFC 2047 section 4.2(2)
     3437        return str_replace(' ', '_', $encoded);
     3438    }
     3439
     3440    /**
     3441     * Add a string or binary attachment (non-filesystem).
     3442     * This method can be used to attach ascii or binary data,
     3443     * such as a BLOB record from a database.
     3444     *
     3445     * @param string $string      String attachment data
     3446     * @param string $filename    Name of the attachment
     3447     * @param string $encoding    File encoding (see $Encoding)
     3448     * @param string $type        File extension (MIME) type
     3449     * @param string $disposition Disposition to use
     3450     *
     3451     * @throws Exception
     3452     *
     3453     * @return bool True on successfully adding an attachment
     3454     */
     3455    public function addStringAttachment(
     3456        $string,
     3457        $filename,
     3458        $encoding = self::ENCODING_BASE64,
     3459        $type = '',
     3460        $disposition = 'attachment'
     3461    ) {
     3462        try {
     3463            // If a MIME type is not specified, try to work it out from the file name
     3464            if ('' === $type) {
     3465                $type = static::filenameToType($filename);
     3466            }
     3467
     3468            if (!$this->validateEncoding($encoding)) {
     3469                throw new Exception($this->lang('encoding') . $encoding);
     3470            }
     3471
     3472            // Append to $attachment array
     3473            $this->attachment[] = [
     3474                0 => $string,
     3475                1 => $filename,
     3476                2 => static::mb_pathinfo($filename, PATHINFO_BASENAME),
     3477                3 => $encoding,
     3478                4 => $type,
     3479                5 => true, // isStringAttachment
     3480                6 => $disposition,
     3481                7 => 0,
     3482            ];
     3483        } catch (Exception $exc) {
     3484            $this->setError($exc->getMessage());
     3485            $this->edebug($exc->getMessage());
     3486            if ($this->exceptions) {
     3487                throw $exc;
     3488            }
     3489
     3490            return false;
     3491        }
     3492
     3493        return true;
     3494    }
     3495
     3496    /**
     3497     * Add an embedded (inline) attachment from a file.
     3498     * This can include images, sounds, and just about any other document type.
     3499     * These differ from 'regular' attachments in that they are intended to be
     3500     * displayed inline with the message, not just attached for download.
     3501     * This is used in HTML messages that embed the images
     3502     * the HTML refers to using the $cid value.
     3503     * Never use a user-supplied path to a file!
     3504     *
     3505     * @param string $path        Path to the attachment
     3506     * @param string $cid         Content ID of the attachment; Use this to reference
     3507     *                            the content when using an embedded image in HTML
     3508     * @param string $name        Overrides the attachment name
     3509     * @param string $encoding    File encoding (see $Encoding)
     3510     * @param string $type        File MIME type
     3511     * @param string $disposition Disposition to use
     3512     *
     3513     * @throws Exception
     3514     *
     3515     * @return bool True on successfully adding an attachment
     3516     */
     3517    public function addEmbeddedImage(
     3518        $path,
     3519        $cid,
     3520        $name = '',
     3521        $encoding = self::ENCODING_BASE64,
     3522        $type = '',
     3523        $disposition = 'inline'
     3524    ) {
     3525        try {
     3526            if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) {
     3527                throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
     3528            }
     3529
     3530            // If a MIME type is not specified, try to work it out from the file name
     3531            if ('' === $type) {
     3532                $type = static::filenameToType($path);
     3533            }
     3534
     3535            if (!$this->validateEncoding($encoding)) {
     3536                throw new Exception($this->lang('encoding') . $encoding);
     3537            }
     3538
     3539            $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
     3540            if ('' === $name) {
     3541                $name = $filename;
     3542            }
     3543
     3544            // Append to $attachment array
     3545            $this->attachment[] = [
     3546                0 => $path,
     3547                1 => $filename,
     3548                2 => $name,
     3549                3 => $encoding,
     3550                4 => $type,
     3551                5 => false, // isStringAttachment
     3552                6 => $disposition,
     3553                7 => $cid,
     3554            ];
     3555        } catch (Exception $exc) {
     3556            $this->setError($exc->getMessage());
     3557            $this->edebug($exc->getMessage());
     3558            if ($this->exceptions) {
     3559                throw $exc;
     3560            }
     3561
     3562            return false;
     3563        }
     3564
     3565        return true;
     3566    }
     3567
     3568    /**
     3569     * Add an embedded stringified attachment.
     3570     * This can include images, sounds, and just about any other document type.
     3571     * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type.
     3572     *
     3573     * @param string $string      The attachment binary data
     3574     * @param string $cid         Content ID of the attachment; Use this to reference
     3575     *                            the content when using an embedded image in HTML
     3576     * @param string $name        A filename for the attachment. If this contains an extension,
     3577     *                            PHPMailer will attempt to set a MIME type for the attachment.
     3578     *                            For example 'file.jpg' would get an 'image/jpeg' MIME type.
     3579     * @param string $encoding    File encoding (see $Encoding), defaults to 'base64'
     3580     * @param string $type        MIME type - will be used in preference to any automatically derived type
     3581     * @param string $disposition Disposition to use
     3582     *
     3583     * @throws Exception
     3584     *
     3585     * @return bool True on successfully adding an attachment
     3586     */
     3587    public function addStringEmbeddedImage(
     3588        $string,
     3589        $cid,
     3590        $name = '',
     3591        $encoding = self::ENCODING_BASE64,
     3592        $type = '',
     3593        $disposition = 'inline'
     3594    ) {
     3595        try {
     3596            // If a MIME type is not specified, try to work it out from the name
     3597            if ('' === $type && !empty($name)) {
     3598                $type = static::filenameToType($name);
     3599            }
     3600
     3601            if (!$this->validateEncoding($encoding)) {
     3602                throw new Exception($this->lang('encoding') . $encoding);
     3603            }
     3604
     3605            // Append to $attachment array
     3606            $this->attachment[] = [
     3607                0 => $string,
     3608                1 => $name,
     3609                2 => $name,
     3610                3 => $encoding,
     3611                4 => $type,
     3612                5 => true, // isStringAttachment
     3613                6 => $disposition,
     3614                7 => $cid,
     3615            ];
     3616        } catch (Exception $exc) {
     3617            $this->setError($exc->getMessage());
     3618            $this->edebug($exc->getMessage());
     3619            if ($this->exceptions) {
     3620                throw $exc;
     3621            }
     3622
     3623            return false;
     3624        }
     3625
     3626        return true;
     3627    }
     3628
     3629    /**
     3630     * Validate encodings.
     3631     *
     3632     * @param string $encoding
     3633     *
     3634     * @return bool
     3635     */
     3636    protected function validateEncoding($encoding)
     3637    {
     3638        return in_array(
     3639            $encoding,
     3640            [
     3641                self::ENCODING_7BIT,
     3642                self::ENCODING_QUOTED_PRINTABLE,
     3643                self::ENCODING_BASE64,
     3644                self::ENCODING_8BIT,
     3645                self::ENCODING_BINARY,
     3646            ],
     3647            true
     3648        );
     3649    }
     3650
     3651    /**
     3652     * Check if an embedded attachment is present with this cid.
     3653     *
     3654     * @param string $cid
     3655     *
     3656     * @return bool
     3657     */
     3658    protected function cidExists($cid)
     3659    {
     3660        foreach ($this->attachment as $attachment) {
     3661            if ('inline' === $attachment[6] && $cid === $attachment[7]) {
     3662                return true;
     3663            }
     3664        }
     3665
     3666        return false;
     3667    }
     3668
     3669    /**
     3670     * Check if an inline attachment is present.
     3671     *
     3672     * @return bool
     3673     */
     3674    public function inlineImageExists()
     3675    {
     3676        foreach ($this->attachment as $attachment) {
     3677            if ('inline' === $attachment[6]) {
     3678                return true;
     3679            }
     3680        }
     3681
     3682        return false;
     3683    }
     3684
     3685    /**
     3686     * Check if an attachment (non-inline) is present.
     3687     *
     3688     * @return bool
     3689     */
     3690    public function attachmentExists()
     3691    {
     3692        foreach ($this->attachment as $attachment) {
     3693            if ('attachment' === $attachment[6]) {
     3694                return true;
     3695            }
     3696        }
     3697
     3698        return false;
     3699    }
     3700
     3701    /**
     3702     * Check if this message has an alternative body set.
     3703     *
     3704     * @return bool
     3705     */
     3706    public function alternativeExists()
     3707    {
     3708        return !empty($this->AltBody);
     3709    }
     3710
     3711    /**
     3712     * Clear queued addresses of given kind.
     3713     *
     3714     * @param string $kind 'to', 'cc', or 'bcc'
     3715     */
     3716    public function clearQueuedAddresses($kind)
     3717    {
     3718        $this->RecipientsQueue = array_filter(
     3719            $this->RecipientsQueue,
     3720            static function ($params) use ($kind) {
     3721                return $params[0] !== $kind;
     3722            }
     3723        );
     3724    }
     3725
     3726    /**
     3727     * Clear all To recipients.
     3728     */
     3729    public function clearAddresses()
     3730    {
     3731        foreach ($this->to as $to) {
     3732            unset($this->all_recipients[strtolower($to[0])]);
     3733        }
     3734        $this->to = [];
     3735        $this->clearQueuedAddresses('to');
     3736    }
     3737
     3738    /**
     3739     * Clear all CC recipients.
     3740     */
     3741    public function clearCCs()
     3742    {
     3743        foreach ($this->cc as $cc) {
     3744            unset($this->all_recipients[strtolower($cc[0])]);
     3745        }
     3746        $this->cc = [];
     3747        $this->clearQueuedAddresses('cc');
     3748    }
     3749
     3750    /**
     3751     * Clear all BCC recipients.
     3752     */
     3753    public function clearBCCs()
     3754    {
     3755        foreach ($this->bcc as $bcc) {
     3756            unset($this->all_recipients[strtolower($bcc[0])]);
     3757        }
     3758        $this->bcc = [];
     3759        $this->clearQueuedAddresses('bcc');
     3760    }
     3761
     3762    /**
     3763     * Clear all ReplyTo recipients.
     3764     */
     3765    public function clearReplyTos()
     3766    {
     3767        $this->ReplyTo = [];
     3768        $this->ReplyToQueue = [];
     3769    }
     3770
     3771    /**
     3772     * Clear all recipient types.
     3773     */
     3774    public function clearAllRecipients()
     3775    {
     3776        $this->to = [];
     3777        $this->cc = [];
     3778        $this->bcc = [];
     3779        $this->all_recipients = [];
     3780        $this->RecipientsQueue = [];
     3781    }
     3782
     3783    /**
     3784     * Clear all filesystem, string, and binary attachments.
     3785     */
     3786    public function clearAttachments()
     3787    {
     3788        $this->attachment = [];
     3789    }
     3790
     3791    /**
     3792     * Clear all custom headers.
     3793     */
     3794    public function clearCustomHeaders()
     3795    {
     3796        $this->CustomHeader = [];
     3797    }
     3798
     3799    /**
     3800     * Add an error message to the error container.
     3801     *
     3802     * @param string $msg
     3803     */
     3804    protected function setError($msg)
     3805    {
     3806        ++$this->error_count;
     3807        if ('smtp' === $this->Mailer && null !== $this->smtp) {
     3808            $lasterror = $this->smtp->getError();
     3809            if (!empty($lasterror['error'])) {
     3810                $msg .= $this->lang('smtp_error') . $lasterror['error'];
     3811                if (!empty($lasterror['detail'])) {
     3812                    $msg .= ' Detail: ' . $lasterror['detail'];
     3813                }
     3814                if (!empty($lasterror['smtp_code'])) {
     3815                    $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
     3816                }
     3817                if (!empty($lasterror['smtp_code_ex'])) {
     3818                    $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
     3819                }
     3820            }
     3821        }
     3822        $this->ErrorInfo = $msg;
     3823    }
     3824
     3825    /**
     3826     * Return an RFC 822 formatted date.
     3827     *
     3828     * @return string
     3829     */
     3830    public static function rfcDate()
     3831    {
     3832        // Set the time zone to whatever the default is to avoid 500 errors
     3833        // Will default to UTC if it's not set properly in php.ini
     3834        date_default_timezone_set(@date_default_timezone_get());
     3835
     3836        return date('D, j M Y H:i:s O');
     3837    }
     3838
     3839    /**
     3840     * Get the server hostname.
     3841     * Returns 'localhost.localdomain' if unknown.
     3842     *
     3843     * @return string
     3844     */
     3845    protected function serverHostname()
     3846    {
     3847        $result = '';
     3848        if (!empty($this->Hostname)) {
     3849            $result = $this->Hostname;
     3850        } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) {
     3851            $result = $_SERVER['SERVER_NAME'];
     3852        } elseif (function_exists('gethostname') && gethostname() !== false) {
     3853            $result = gethostname();
     3854        } elseif (php_uname('n') !== false) {
     3855            $result = php_uname('n');
     3856        }
     3857        if (!static::isValidHost($result)) {
     3858            return 'localhost.localdomain';
     3859        }
     3860
     3861        return $result;
     3862    }
     3863
     3864    /**
     3865     * Validate whether a string contains a valid value to use as a hostname or IP address.
     3866     * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`.
     3867     *
     3868     * @param string $host The host name or IP address to check
     3869     *
     3870     * @return bool
     3871     */
     3872    public static function isValidHost($host)
     3873    {
     3874        //Simple syntax limits
     3875        if (empty($host)
     3876            || !is_string($host)
     3877            || strlen($host) > 256
     3878            || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+])$/', $host)
     3879        ) {
     3880            return false;
     3881        }
     3882        //Looks like a bracketed IPv6 address
     3883        if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') {
     3884            return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
     3885        }
     3886        //If removing all the dots results in a numeric string, it must be an IPv4 address.
     3887        //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names
     3888        if (is_numeric(str_replace('.', '', $host))) {
     3889            //Is it a valid IPv4 address?
     3890            return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
     3891        }
     3892        if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) {
     3893            //Is it a syntactically valid hostname?
     3894            return true;
     3895        }
     3896
     3897        return false;
     3898    }
     3899
     3900    /**
     3901     * Get an error message in the current language.
     3902     *
     3903     * @param string $key
     3904     *
     3905     * @return string
     3906     */
     3907    protected function lang($key)
     3908    {
     3909        if (count($this->language) < 1) {
     3910            $this->setLanguage(); // set the default language
     3911        }
     3912
     3913        if (array_key_exists($key, $this->language)) {
     3914            if ('smtp_connect_failed' === $key) {
     3915                //Include a link to troubleshooting docs on SMTP connection failure
     3916                //this is by far the biggest cause of support questions
     3917                //but it's usually not PHPMailer's fault.
     3918                return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
     3919            }
     3920
     3921            return $this->language[$key];
     3922        }
     3923
     3924        //Return the key as a fallback
     3925        return $key;
     3926    }
     3927
     3928    /**
     3929     * Check if an error occurred.
     3930     *
     3931     * @return bool True if an error did occur
     3932     */
     3933    public function isError()
     3934    {
     3935        return $this->error_count > 0;
     3936    }
     3937
     3938    /**
     3939     * Add a custom header.
     3940     * $name value can be overloaded to contain
     3941     * both header name and value (name:value).
     3942     *
     3943     * @param string      $name  Custom header name
     3944     * @param string|null $value Header value
     3945     *
     3946     * @throws Exception
     3947     */
     3948    public function addCustomHeader($name, $value = null)
     3949    {
     3950        if (null === $value && strpos($name, ':') !== false) {
     3951            // Value passed in as name:value
     3952            list($name, $value) = explode(':', $name, 2);
     3953        }
     3954        $name = trim($name);
     3955        $value = trim($value);
     3956        //Ensure name is not empty, and that neither name nor value contain line breaks
     3957        if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
     3958            if ($this->exceptions) {
     3959                throw new Exception('Invalid header name or value');
     3960            }
     3961
     3962            return false;
     3963        }
     3964        $this->CustomHeader[] = [$name, $value];
     3965
     3966        return true;
     3967    }
     3968
     3969    /**
     3970     * Returns all custom headers.
     3971     *
     3972     * @return array
     3973     */
     3974    public function getCustomHeaders()
     3975    {
     3976        return $this->CustomHeader;
     3977    }
     3978
     3979    /**
     3980     * Create a message body from an HTML string.
     3981     * Automatically inlines images and creates a plain-text version by converting the HTML,
     3982     * overwriting any existing values in Body and AltBody.
     3983     * Do not source $message content from user input!
     3984     * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
     3985     * will look for an image file in $basedir/images/a.png and convert it to inline.
     3986     * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
     3987     * Converts data-uri images into embedded attachments.
     3988     * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
     3989     *
     3990     * @param string        $message  HTML message string
     3991     * @param string        $basedir  Absolute path to a base directory to prepend to relative paths to images
     3992     * @param bool|callable $advanced Whether to use the internal HTML to text converter
     3993     *                                or your own custom converter @return string $message The transformed message Body
     3994     *
     3995     * @throws Exception
     3996     *
     3997     * @see PHPMailer::html2text()
     3998     */
     3999    public function msgHTML($message, $basedir = '', $advanced = false)
     4000    {
     4001        preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $images);
     4002        if (array_key_exists(2, $images)) {
     4003            if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
     4004                // Ensure $basedir has a trailing /
     4005                $basedir .= '/';
     4006            }
     4007            foreach ($images[2] as $imgindex => $url) {
     4008                // Convert data URIs into embedded images
     4009                //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
     4010                $match = [];
     4011                if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
     4012                    if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) {
     4013                        $data = base64_decode($match[3]);
     4014                    } elseif ('' === $match[2]) {
     4015                        $data = rawurldecode($match[3]);
     4016                    } else {
     4017                        //Not recognised so leave it alone
     4018                        continue;
     4019                    }
     4020                    //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places
     4021                    //will only be embedded once, even if it used a different encoding
     4022                    $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // RFC2392 S 2
     4023
     4024                    if (!$this->cidExists($cid)) {
     4025                        $this->addStringEmbeddedImage(
     4026                            $data,
     4027                            $cid,
     4028                            'embed' . $imgindex,
     4029                            static::ENCODING_BASE64,
     4030                            $match[1]
     4031                        );
     4032                    }
     4033                    $message = str_replace(
     4034                        $images[0][$imgindex],
     4035                        $images[1][$imgindex] . '="cid:' . $cid . '"',
     4036                        $message
     4037                    );
     4038                    continue;
     4039                }
     4040                if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
     4041                    !empty($basedir)
     4042                    // Ignore URLs containing parent dir traversal (..)
     4043                    && (strpos($url, '..') === false)
     4044                    // Do not change urls that are already inline images
     4045                    && 0 !== strpos($url, 'cid:')
     4046                    // Do not change absolute URLs, including anonymous protocol
     4047                    && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
     4048                ) {
     4049                    $filename = static::mb_pathinfo($url, PATHINFO_BASENAME);
     4050                    $directory = dirname($url);
     4051                    if ('.' === $directory) {
     4052                        $directory = '';
     4053                    }
     4054                    // RFC2392 S 2
     4055                    $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0';
     4056                    if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
     4057                        $basedir .= '/';
     4058                    }
     4059                    if (strlen($directory) > 1 && '/' !== substr($directory, -1)) {
     4060                        $directory .= '/';
     4061                    }
     4062                    if ($this->addEmbeddedImage(
     4063                        $basedir . $directory . $filename,
     4064                        $cid,
     4065                        $filename,
     4066                        static::ENCODING_BASE64,
     4067                        static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION))
     4068                    )
     4069                    ) {
     4070                        $message = preg_replace(
     4071                            '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
     4072                            $images[1][$imgindex] . '="cid:' . $cid . '"',
     4073                            $message
     4074                        );
     4075                    }
     4076                }
     4077            }
     4078        }
     4079        $this->isHTML();
     4080        // Convert all message body line breaks to LE, makes quoted-printable encoding work much better
     4081        $this->Body = static::normalizeBreaks($message);
     4082        $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));
     4083        if (!$this->alternativeExists()) {
     4084            $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'
     4085                . static::$LE;
     4086        }
     4087
     4088        return $this->Body;
     4089    }
     4090
     4091    /**
     4092     * Convert an HTML string into plain text.
     4093     * This is used by msgHTML().
     4094     * Note - older versions of this function used a bundled advanced converter
     4095     * which was removed for license reasons in #232.
     4096     * Example usage:
     4097     *
     4098     * ```php
     4099     * // Use default conversion
     4100     * $plain = $mail->html2text($html);
     4101     * // Use your own custom converter
     4102     * $plain = $mail->html2text($html, function($html) {
     4103     *     $converter = new MyHtml2text($html);
     4104     *     return $converter->get_text();
     4105     * });
     4106     * ```
     4107     *
     4108     * @param string        $html     The HTML text to convert
     4109     * @param bool|callable $advanced Any boolean value to use the internal converter,
     4110     *                                or provide your own callable for custom conversion
     4111     *
     4112     * @return string
     4113     */
     4114    public function html2text($html, $advanced = false)
     4115    {
     4116        if (is_callable($advanced)) {
     4117            return $advanced($html);
     4118        }
     4119
     4120        return html_entity_decode(
     4121            trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
     4122            ENT_QUOTES,
     4123            $this->CharSet
     4124        );
     4125    }
     4126
     4127    /**
     4128     * Get the MIME type for a file extension.
     4129     *
     4130     * @param string $ext File extension
     4131     *
     4132     * @return string MIME type of file
     4133     */
     4134    public static function _mime_types($ext = '')
     4135    {
     4136        $mimes = [
     4137            'xl' => 'application/excel',
     4138            'js' => 'application/javascript',
     4139            'hqx' => 'application/mac-binhex40',
     4140            'cpt' => 'application/mac-compactpro',
     4141            'bin' => 'application/macbinary',
     4142            'doc' => 'application/msword',
     4143            'word' => 'application/msword',
     4144            'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
     4145            'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
     4146            'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
     4147            'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
     4148            'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
     4149            'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
     4150            'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
     4151            'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
     4152            'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
     4153            'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
     4154            'class' => 'application/octet-stream',
     4155            'dll' => 'application/octet-stream',
     4156            'dms' => 'application/octet-stream',
     4157            'exe' => 'application/octet-stream',
     4158            'lha' => 'application/octet-stream',
     4159            'lzh' => 'application/octet-stream',
     4160            'psd' => 'application/octet-stream',
     4161            'sea' => 'application/octet-stream',
     4162            'so' => 'application/octet-stream',
     4163            'oda' => 'application/oda',
     4164            'pdf' => 'application/pdf',
     4165            'ai' => 'application/postscript',
     4166            'eps' => 'application/postscript',
     4167            'ps' => 'application/postscript',
     4168            'smi' => 'application/smil',
     4169            'smil' => 'application/smil',
     4170            'mif' => 'application/vnd.mif',
     4171            'xls' => 'application/vnd.ms-excel',
     4172            'ppt' => 'application/vnd.ms-powerpoint',
     4173            'wbxml' => 'application/vnd.wap.wbxml',
     4174            'wmlc' => 'application/vnd.wap.wmlc',
     4175            'dcr' => 'application/x-director',
     4176            'dir' => 'application/x-director',
     4177            'dxr' => 'application/x-director',
     4178            'dvi' => 'application/x-dvi',
     4179            'gtar' => 'application/x-gtar',
     4180            'php3' => 'application/x-httpd-php',
     4181            'php4' => 'application/x-httpd-php',
     4182            'php' => 'application/x-httpd-php',
     4183            'phtml' => 'application/x-httpd-php',
     4184            'phps' => 'application/x-httpd-php-source',
     4185            'swf' => 'application/x-shockwave-flash',
     4186            'sit' => 'application/x-stuffit',
     4187            'tar' => 'application/x-tar',
     4188            'tgz' => 'application/x-tar',
     4189            'xht' => 'application/xhtml+xml',
     4190            'xhtml' => 'application/xhtml+xml',
     4191            'zip' => 'application/zip',
     4192            'mid' => 'audio/midi',
     4193            'midi' => 'audio/midi',
     4194            'mp2' => 'audio/mpeg',
     4195            'mp3' => 'audio/mpeg',
     4196            'm4a' => 'audio/mp4',
     4197            'mpga' => 'audio/mpeg',
     4198            'aif' => 'audio/x-aiff',
     4199            'aifc' => 'audio/x-aiff',
     4200            'aiff' => 'audio/x-aiff',
     4201            'ram' => 'audio/x-pn-realaudio',
     4202            'rm' => 'audio/x-pn-realaudio',
     4203            'rpm' => 'audio/x-pn-realaudio-plugin',
     4204            'ra' => 'audio/x-realaudio',
     4205            'wav' => 'audio/x-wav',
     4206            'mka' => 'audio/x-matroska',
     4207            'bmp' => 'image/bmp',
     4208            'gif' => 'image/gif',
     4209            'jpeg' => 'image/jpeg',
     4210            'jpe' => 'image/jpeg',
     4211            'jpg' => 'image/jpeg',
     4212            'png' => 'image/png',
     4213            'tiff' => 'image/tiff',
     4214            'tif' => 'image/tiff',
     4215            'webp' => 'image/webp',
     4216            'heif' => 'image/heif',
     4217            'heifs' => 'image/heif-sequence',
     4218            'heic' => 'image/heic',
     4219            'heics' => 'image/heic-sequence',
     4220            'eml' => 'message/rfc822',
     4221            'css' => 'text/css',
     4222            'html' => 'text/html',
     4223            'htm' => 'text/html',
     4224            'shtml' => 'text/html',
     4225            'log' => 'text/plain',
     4226            'text' => 'text/plain',
     4227            'txt' => 'text/plain',
     4228            'rtx' => 'text/richtext',
     4229            'rtf' => 'text/rtf',
     4230            'vcf' => 'text/vcard',
     4231            'vcard' => 'text/vcard',
     4232            'ics' => 'text/calendar',
     4233            'xml' => 'text/xml',
     4234            'xsl' => 'text/xml',
     4235            'wmv' => 'video/x-ms-wmv',
     4236            'mpeg' => 'video/mpeg',
     4237            'mpe' => 'video/mpeg',
     4238            'mpg' => 'video/mpeg',
     4239            'mp4' => 'video/mp4',
     4240            'm4v' => 'video/mp4',
     4241            'mov' => 'video/quicktime',
     4242            'qt' => 'video/quicktime',
     4243            'rv' => 'video/vnd.rn-realvideo',
     4244            'avi' => 'video/x-msvideo',
     4245            'movie' => 'video/x-sgi-movie',
     4246            'webm' => 'video/webm',
     4247            'mkv' => 'video/x-matroska',
     4248        ];
     4249        $ext = strtolower($ext);
     4250        if (array_key_exists($ext, $mimes)) {
     4251            return $mimes[$ext];
     4252        }
     4253
     4254        return 'application/octet-stream';
     4255    }
     4256
     4257    /**
     4258     * Map a file name to a MIME type.
     4259     * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
     4260     *
     4261     * @param string $filename A file name or full path, does not need to exist as a file
     4262     *
     4263     * @return string
     4264     */
     4265    public static function filenameToType($filename)
     4266    {
     4267        // In case the path is a URL, strip any query string before getting extension
     4268        $qpos = strpos($filename, '?');
     4269        if (false !== $qpos) {
     4270            $filename = substr($filename, 0, $qpos);
     4271        }
     4272        $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);
     4273
     4274        return static::_mime_types($ext);
     4275    }
     4276
     4277    /**
     4278     * Multi-byte-safe pathinfo replacement.
     4279     * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
     4280     *
     4281     * @see http://www.php.net/manual/en/function.pathinfo.php#107461
     4282     *
     4283     * @param string     $path    A filename or path, does not need to exist as a file
     4284     * @param int|string $options Either a PATHINFO_* constant,
     4285     *                            or a string name to return only the specified piece
     4286     *
     4287     * @return string|array
     4288     */
     4289    public static function mb_pathinfo($path, $options = null)
     4290    {
     4291        $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
     4292        $pathinfo = [];
     4293        if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {
     4294            if (array_key_exists(1, $pathinfo)) {
     4295                $ret['dirname'] = $pathinfo[1];
     4296            }
     4297            if (array_key_exists(2, $pathinfo)) {
     4298                $ret['basename'] = $pathinfo[2];
     4299            }
     4300            if (array_key_exists(5, $pathinfo)) {
     4301                $ret['extension'] = $pathinfo[5];
     4302            }
     4303            if (array_key_exists(3, $pathinfo)) {
     4304                $ret['filename'] = $pathinfo[3];
     4305            }
     4306        }
     4307        switch ($options) {
     4308            case PATHINFO_DIRNAME:
     4309            case 'dirname':
     4310                return $ret['dirname'];
     4311            case PATHINFO_BASENAME:
     4312            case 'basename':
     4313                return $ret['basename'];
     4314            case PATHINFO_EXTENSION:
     4315            case 'extension':
     4316                return $ret['extension'];
     4317            case PATHINFO_FILENAME:
     4318            case 'filename':
     4319                return $ret['filename'];
     4320            default:
     4321                return $ret;
     4322        }
     4323    }
     4324
     4325    /**
     4326     * Set or reset instance properties.
     4327     * You should avoid this function - it's more verbose, less efficient, more error-prone and
     4328     * harder to debug than setting properties directly.
     4329     * Usage Example:
     4330     * `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);`
     4331     *   is the same as:
     4332     * `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`.
     4333     *
     4334     * @param string $name  The property name to set
     4335     * @param mixed  $value The value to set the property to
     4336     *
     4337     * @return bool
     4338     */
     4339    public function set($name, $value = '')
     4340    {
     4341        if (property_exists($this, $name)) {
     4342            $this->$name = $value;
     4343
     4344            return true;
     4345        }
     4346        $this->setError($this->lang('variable_set') . $name);
     4347
     4348        return false;
     4349    }
     4350
     4351    /**
     4352     * Strip newlines to prevent header injection.
     4353     *
     4354     * @param string $str
     4355     *
     4356     * @return string
     4357     */
     4358    public function secureHeader($str)
     4359    {
     4360        return trim(str_replace(["\r", "\n"], '', $str));
     4361    }
     4362
     4363    /**
     4364     * Normalize line breaks in a string.
     4365     * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
     4366     * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
     4367     *
     4368     * @param string $text
     4369     * @param string $breaktype What kind of line break to use; defaults to static::$LE
     4370     *
     4371     * @return string
     4372     */
     4373    public static function normalizeBreaks($text, $breaktype = null)
     4374    {
     4375        if (null === $breaktype) {
     4376            $breaktype = static::$LE;
     4377        }
     4378        // Normalise to \n
     4379        $text = str_replace([self::CRLF, "\r"], "\n", $text);
     4380        // Now convert LE as needed
     4381        if ("\n" !== $breaktype) {
     4382            $text = str_replace("\n", $breaktype, $text);
     4383        }
     4384
     4385        return $text;
     4386    }
     4387
     4388    /**
     4389     * Remove trailing breaks from a string.
     4390     *
     4391     * @param string $text
     4392     *
     4393     * @return string The text to remove breaks from
     4394     */
     4395    public static function stripTrailingWSP($text)
     4396    {
     4397        return rtrim($text, " \r\n\t");
     4398    }
     4399
     4400    /**
     4401     * Return the current line break format string.
     4402     *
     4403     * @return string
     4404     */
     4405    public static function getLE()
     4406    {
     4407        return static::$LE;
     4408    }
     4409
     4410    /**
     4411     * Set the line break format string, e.g. "\r\n".
     4412     *
     4413     * @param string $le
     4414     */
     4415    protected static function setLE($le)
     4416    {
     4417        static::$LE = $le;
     4418    }
     4419
     4420    /**
     4421     * Set the public and private key files and password for S/MIME signing.
     4422     *
     4423     * @param string $cert_filename
     4424     * @param string $key_filename
     4425     * @param string $key_pass            Password for private key
     4426     * @param string $extracerts_filename Optional path to chain certificate
     4427     */
     4428    public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
     4429    {
     4430        $this->sign_cert_file = $cert_filename;
     4431        $this->sign_key_file = $key_filename;
     4432        $this->sign_key_pass = $key_pass;
     4433        $this->sign_extracerts_file = $extracerts_filename;
     4434    }
     4435
     4436    /**
     4437     * Quoted-Printable-encode a DKIM header.
     4438     *
     4439     * @param string $txt
     4440     *
     4441     * @return string
     4442     */
     4443    public function DKIM_QP($txt)
     4444    {
     4445        $line = '';
     4446        $len = strlen($txt);
     4447        for ($i = 0; $i < $len; ++$i) {
     4448            $ord = ord($txt[$i]);
     4449            if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
     4450                $line .= $txt[$i];
     4451            } else {
     4452                $line .= '=' . sprintf('%02X', $ord);
     4453            }
     4454        }
     4455
     4456        return $line;
     4457    }
     4458
     4459    /**
     4460     * Generate a DKIM signature.
     4461     *
     4462     * @param string $signHeader
     4463     *
     4464     * @throws Exception
     4465     *
     4466     * @return string The DKIM signature value
     4467     */
     4468    public function DKIM_Sign($signHeader)
     4469    {
     4470        if (!defined('PKCS7_TEXT')) {
     4471            if ($this->exceptions) {
     4472                throw new Exception($this->lang('extension_missing') . 'openssl');
     4473            }
     4474
     4475            return '';
     4476        }
     4477        $privKeyStr = !empty($this->DKIM_private_string) ?
     4478            $this->DKIM_private_string :
     4479            file_get_contents($this->DKIM_private);
     4480        if ('' !== $this->DKIM_passphrase) {
     4481            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
     4482        } else {
     4483            $privKey = openssl_pkey_get_private($privKeyStr);
     4484        }
     4485        if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
     4486            openssl_pkey_free($privKey);
     4487
     4488            return base64_encode($signature);
     4489        }
     4490        openssl_pkey_free($privKey);
     4491
     4492        return '';
     4493    }
     4494
     4495    /**
     4496     * Generate a DKIM canonicalization header.
     4497     * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
     4498     * Canonicalized headers should *always* use CRLF, regardless of mailer setting.
     4499     *
     4500     * @see https://tools.ietf.org/html/rfc6376#section-3.4.2
     4501     *
     4502     * @param string $signHeader Header
     4503     *
     4504     * @return string
     4505     */
     4506    public function DKIM_HeaderC($signHeader)
     4507    {
     4508        //Normalize breaks to CRLF (regardless of the mailer)
     4509        $signHeader = static::normalizeBreaks($signHeader, self::CRLF);
     4510        //Unfold header lines
     4511        //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
     4512        //@see https://tools.ietf.org/html/rfc5322#section-2.2
     4513        //That means this may break if you do something daft like put vertical tabs in your headers.
     4514        $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
     4515        //Break headers out into an array
     4516        $lines = explode(self::CRLF, $signHeader);
     4517        foreach ($lines as $key => $line) {
     4518            //If the header is missing a :, skip it as it's invalid
     4519            //This is likely to happen because the explode() above will also split
     4520            //on the trailing LE, leaving an empty line
     4521            if (strpos($line, ':') === false) {
     4522                continue;
     4523            }
     4524            list($heading, $value) = explode(':', $line, 2);
     4525            //Lower-case header name
     4526            $heading = strtolower($heading);
     4527            //Collapse white space within the value, also convert WSP to space
     4528            $value = preg_replace('/[ \t]+/', ' ', $value);
     4529            //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value
     4530            //But then says to delete space before and after the colon.
     4531            //Net result is the same as trimming both ends of the value.
     4532            //By elimination, the same applies to the field name
     4533            $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");
     4534        }
     4535
     4536        return implode(self::CRLF, $lines);
     4537    }
     4538
     4539    /**
     4540     * Generate a DKIM canonicalization body.
     4541     * Uses the 'simple' algorithm from RFC6376 section 3.4.3.
     4542     * Canonicalized bodies should *always* use CRLF, regardless of mailer setting.
     4543     *
     4544     * @see https://tools.ietf.org/html/rfc6376#section-3.4.3
     4545     *
     4546     * @param string $body Message Body
     4547     *
     4548     * @return string
     4549     */
     4550    public function DKIM_BodyC($body)
     4551    {
     4552        if (empty($body)) {
     4553            return self::CRLF;
     4554        }
     4555        // Normalize line endings to CRLF
     4556        $body = static::normalizeBreaks($body, self::CRLF);
     4557
     4558        //Reduce multiple trailing line breaks to a single one
     4559        return static::stripTrailingWSP($body) . self::CRLF;
     4560    }
     4561
     4562    /**
     4563     * Create the DKIM header and body in a new message header.
     4564     *
     4565     * @param string $headers_line Header lines
     4566     * @param string $subject      Subject
     4567     * @param string $body         Body
     4568     *
     4569     * @throws Exception
     4570     *
     4571     * @return string
     4572     */
     4573    public function DKIM_Add($headers_line, $subject, $body)
     4574    {
     4575        $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
     4576        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of header & body
     4577        $DKIMquery = 'dns/txt'; // Query method
     4578        $DKIMtime = time();
     4579        //Always sign these headers without being asked
     4580        //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1
     4581        $autoSignHeaders = [
     4582            'from',
     4583            'to',
     4584            'cc',
     4585            'date',
     4586            'subject',
     4587            'reply-to',
     4588            'message-id',
     4589            'content-type',
     4590            'mime-version',
     4591            'x-mailer',
     4592        ];
     4593        if (stripos($headers_line, 'Subject') === false) {
     4594            $headers_line .= 'Subject: ' . $subject . static::$LE;
     4595        }
     4596        $headerLines = explode(static::$LE, $headers_line);
     4597        $currentHeaderLabel = '';
     4598        $currentHeaderValue = '';
     4599        $parsedHeaders = [];
     4600        $headerLineIndex = 0;
     4601        $headerLineCount = count($headerLines);
     4602        foreach ($headerLines as $headerLine) {
     4603            $matches = [];
     4604            if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) {
     4605                if ($currentHeaderLabel !== '') {
     4606                    //We were previously in another header; This is the start of a new header, so save the previous one
     4607                    $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
     4608                }
     4609                $currentHeaderLabel = $matches[1];
     4610                $currentHeaderValue = $matches[2];
     4611            } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) {
     4612                //This is a folded continuation of the current header, so unfold it
     4613                $currentHeaderValue .= ' ' . $matches[1];
     4614            }
     4615            ++$headerLineIndex;
     4616            if ($headerLineIndex >= $headerLineCount) {
     4617                //This was the last line, so finish off this header
     4618                $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
     4619            }
     4620        }
     4621        $copiedHeaders = [];
     4622        $headersToSignKeys = [];
     4623        $headersToSign = [];
     4624        foreach ($parsedHeaders as $header) {
     4625            //Is this header one that must be included in the DKIM signature?
     4626            if (in_array(strtolower($header['label']), $autoSignHeaders, true)) {
     4627                $headersToSignKeys[] = $header['label'];
     4628                $headersToSign[] = $header['label'] . ': ' . $header['value'];
     4629                if ($this->DKIM_copyHeaderFields) {
     4630                    $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
     4631                        str_replace('|', '=7C', $this->DKIM_QP($header['value']));
     4632                }
     4633                continue;
     4634            }
     4635            //Is this an extra custom header we've been asked to sign?
     4636            if (in_array($header['label'], $this->DKIM_extraHeaders, true)) {
     4637                //Find its value in custom headers
     4638                foreach ($this->CustomHeader as $customHeader) {
     4639                    if ($customHeader[0] === $header['label']) {
     4640                        $headersToSignKeys[] = $header['label'];
     4641                        $headersToSign[] = $header['label'] . ': ' . $header['value'];
     4642                        if ($this->DKIM_copyHeaderFields) {
     4643                            $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
     4644                                str_replace('|', '=7C', $this->DKIM_QP($header['value']));
     4645                        }
     4646                        //Skip straight to the next header
     4647                        continue 2;
     4648                    }
     4649                }
     4650            }
     4651        }
     4652        $copiedHeaderFields = '';
     4653        if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) {
     4654            //Assemble a DKIM 'z' tag
     4655            $copiedHeaderFields = ' z=';
     4656            $first = true;
     4657            foreach ($copiedHeaders as $copiedHeader) {
     4658                if (!$first) {
     4659                    $copiedHeaderFields .= static::$LE . ' |';
     4660                }
     4661                //Fold long values
     4662                if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {
     4663                    $copiedHeaderFields .= substr(
     4664                        chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS),
     4665                        0,
     4666                        -strlen(static::$LE . self::FWS)
     4667                    );
     4668                } else {
     4669                    $copiedHeaderFields .= $copiedHeader;
     4670                }
     4671                $first = false;
     4672            }
     4673            $copiedHeaderFields .= ';' . static::$LE;
     4674        }
     4675        $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE;
     4676        $headerValues = implode(static::$LE, $headersToSign);
     4677        $body = $this->DKIM_BodyC($body);
     4678        $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
     4679        $ident = '';
     4680        if ('' !== $this->DKIM_identity) {
     4681            $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE;
     4682        }
     4683        //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag
     4684        //which is appended after calculating the signature
     4685        //https://tools.ietf.org/html/rfc6376#section-3.5
     4686        $dkimSignatureHeader = 'DKIM-Signature: v=1;' .
     4687            ' d=' . $this->DKIM_domain . ';' .
     4688            ' s=' . $this->DKIM_selector . ';' . static::$LE .
     4689            ' a=' . $DKIMsignatureType . ';' .
     4690            ' q=' . $DKIMquery . ';' .
     4691            ' t=' . $DKIMtime . ';' .
     4692            ' c=' . $DKIMcanonicalization . ';' . static::$LE .
     4693            $headerKeys .
     4694            $ident .
     4695            $copiedHeaderFields .
     4696            ' bh=' . $DKIMb64 . ';' . static::$LE .
     4697            ' b=';
     4698        //Canonicalize the set of headers
     4699        $canonicalizedHeaders = $this->DKIM_HeaderC(
     4700            $headerValues . static::$LE . $dkimSignatureHeader
     4701        );
     4702        $signature = $this->DKIM_Sign($canonicalizedHeaders);
     4703        $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS));
     4704
     4705        return static::normalizeBreaks($dkimSignatureHeader . $signature);
     4706    }
     4707
     4708    /**
     4709     * Detect if a string contains a line longer than the maximum line length
     4710     * allowed by RFC 2822 section 2.1.1.
     4711     *
     4712     * @param string $str
     4713     *
     4714     * @return bool
     4715     */
     4716    public static function hasLineLongerThanMax($str)
     4717    {
     4718        return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
     4719    }
     4720
     4721    /**
     4722     * If a string contains any "special" characters, double-quote the name,
     4723     * and escape any double quotes with a backslash.
     4724     *
     4725     * @param string $str
     4726     *
     4727     * @return string
     4728     *
     4729     * @see RFC822 3.4.1
     4730     */
     4731    public static function quotedString($str)
     4732    {
     4733        if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) {
     4734            //If the string contains any of these chars, it must be double-quoted
     4735            //and any double quotes must be escaped with a backslash
     4736            return '"' . str_replace('"', '\\"', $str) . '"';
     4737        }
     4738
     4739        //Return the string untouched, it doesn't need quoting
     4740        return $str;
     4741    }
     4742
     4743    /**
     4744     * Allows for public read access to 'to' property.
     4745     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4746     *
     4747     * @return array
     4748     */
     4749    public function getToAddresses()
     4750    {
     4751        return $this->to;
     4752    }
     4753
     4754    /**
     4755     * Allows for public read access to 'cc' property.
     4756     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4757     *
     4758     * @return array
     4759     */
     4760    public function getCcAddresses()
     4761    {
     4762        return $this->cc;
     4763    }
     4764
     4765    /**
     4766     * Allows for public read access to 'bcc' property.
     4767     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4768     *
     4769     * @return array
     4770     */
     4771    public function getBccAddresses()
     4772    {
     4773        return $this->bcc;
     4774    }
     4775
     4776    /**
     4777     * Allows for public read access to 'ReplyTo' property.
     4778     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4779     *
     4780     * @return array
     4781     */
     4782    public function getReplyToAddresses()
     4783    {
     4784        return $this->ReplyTo;
     4785    }
     4786
     4787    /**
     4788     * Allows for public read access to 'all_recipients' property.
     4789     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4790     *
     4791     * @return array
     4792     */
     4793    public function getAllRecipientAddresses()
     4794    {
     4795        return $this->all_recipients;
     4796    }
     4797
     4798    /**
     4799     * Perform a callback.
     4800     *
     4801     * @param bool   $isSent
     4802     * @param array  $to
     4803     * @param array  $cc
     4804     * @param array  $bcc
     4805     * @param string $subject
     4806     * @param string $body
     4807     * @param string $from
     4808     * @param array  $extra
     4809     */
     4810    protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
     4811    {
     4812        if (!empty($this->action_function) && is_callable($this->action_function)) {
     4813            call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);
     4814        }
     4815    }
     4816
     4817    /**
     4818     * Get the OAuth instance.
     4819     *
     4820     * @return OAuth
     4821     */
     4822    public function getOAuth()
     4823    {
     4824        return $this->oauth;
     4825    }
     4826
     4827    /**
     4828     * Set an OAuth instance.
     4829     */
     4830    public function setOAuth(OAuth $oauth)
     4831    {
     4832        $this->oauth = $oauth;
     4833    }
     4834}
  • new file src/wp-includes/PHPMailer/SMTP.php

    diff --git a/src/wp-includes/PHPMailer/SMTP.php b/src/wp-includes/PHPMailer/SMTP.php
    new file mode 100644
    index 0000000000..aa5555149a
    - +  
     1<?php
     2/**
     3 * PHPMailer RFC821 SMTP email 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
     21namespace PHPMailer\PHPMailer;
     22
     23/**
     24 * PHPMailer RFC821 SMTP email transport class.
     25 * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
     26 *
     27 * @author Chris Ryan
     28 * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
     29 */
     30class SMTP
     31{
     32    /**
     33     * The PHPMailer SMTP version number.
     34     *
     35     * @var string
     36     */
     37    const VERSION = '6.1.6';
     38
     39    /**
     40     * SMTP line break constant.
     41     *
     42     * @var string
     43     */
     44    const LE = "\r\n";
     45
     46    /**
     47     * The SMTP port to use if one is not specified.
     48     *
     49     * @var int
     50     */
     51    const DEFAULT_PORT = 25;
     52
     53    /**
     54     * The maximum line length allowed by RFC 5321 section 4.5.3.1.6,
     55     * *excluding* a trailing CRLF break.
     56     *
     57     * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6
     58     *
     59     * @var int
     60     */
     61    const MAX_LINE_LENGTH = 998;
     62
     63    /**
     64     * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5,
     65     * *including* a trailing CRLF line break.
     66     *
     67     * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5
     68     *
     69     * @var int
     70     */
     71    const MAX_REPLY_LENGTH = 512;
     72
     73    /**
     74     * Debug level for no output.
     75     *
     76     * @var int
     77     */
     78    const DEBUG_OFF = 0;
     79
     80    /**
     81     * Debug level to show client -> server messages.
     82     *
     83     * @var int
     84     */
     85    const DEBUG_CLIENT = 1;
     86
     87    /**
     88     * Debug level to show client -> server and server -> client messages.
     89     *
     90     * @var int
     91     */
     92    const DEBUG_SERVER = 2;
     93
     94    /**
     95     * Debug level to show connection status, client -> server and server -> client messages.
     96     *
     97     * @var int
     98     */
     99    const DEBUG_CONNECTION = 3;
     100
     101    /**
     102     * Debug level to show all messages.
     103     *
     104     * @var int
     105     */
     106    const DEBUG_LOWLEVEL = 4;
     107
     108    /**
     109     * Debug output level.
     110     * Options:
     111     * * self::DEBUG_OFF (`0`) No debug output, default
     112     * * self::DEBUG_CLIENT (`1`) Client commands
     113     * * self::DEBUG_SERVER (`2`) Client commands and server responses
     114     * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
     115     * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages.
     116     *
     117     * @var int
     118     */
     119    public $do_debug = self::DEBUG_OFF;
     120
     121    /**
     122     * How to handle debug output.
     123     * Options:
     124     * * `echo` Output plain-text as-is, appropriate for CLI
     125     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
     126     * * `error_log` Output to error log as configured in php.ini
     127     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
     128     *
     129     * ```php
     130     * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
     131     * ```
     132     *
     133     * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
     134     * level output is used:
     135     *
     136     * ```php
     137     * $mail->Debugoutput = new myPsr3Logger;
     138     * ```
     139     *
     140     * @var string|callable|\Psr\Log\LoggerInterface
     141     */
     142    public $Debugoutput = 'echo';
     143
     144    /**
     145     * Whether to use VERP.
     146     *
     147     * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path
     148     * @see http://www.postfix.org/VERP_README.html Info on VERP
     149     *
     150     * @var bool
     151     */
     152    public $do_verp = false;
     153
     154    /**
     155     * The timeout value for connection, in seconds.
     156     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
     157     * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
     158     *
     159     * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2
     160     *
     161     * @var int
     162     */
     163    public $Timeout = 300;
     164
     165    /**
     166     * How long to wait for commands to complete, in seconds.
     167     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
     168     *
     169     * @var int
     170     */
     171    public $Timelimit = 300;
     172
     173    /**
     174     * Patterns to extract an SMTP transaction id from reply to a DATA command.
     175     * The first capture group in each regex will be used as the ID.
     176     * MS ESMTP returns the message ID, which may not be correct for internal tracking.
     177     *
     178     * @var string[]
     179     */
     180    protected $smtp_transaction_id_patterns = [
     181        'exim' => '/[\d]{3} OK id=(.*)/',
     182        'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/',
     183        'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/',
     184        'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/',
     185        'Amazon_SES' => '/[\d]{3} Ok (.*)/',
     186        'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
     187        'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
     188    ];
     189
     190    /**
     191     * The last transaction ID issued in response to a DATA command,
     192     * if one was detected.
     193     *
     194     * @var string|bool|null
     195     */
     196    protected $last_smtp_transaction_id;
     197
     198    /**
     199     * The socket for the server connection.
     200     *
     201     * @var ?resource
     202     */
     203    protected $smtp_conn;
     204
     205    /**
     206     * Error information, if any, for the last SMTP command.
     207     *
     208     * @var array
     209     */
     210    protected $error = [
     211        'error' => '',
     212        'detail' => '',
     213        'smtp_code' => '',
     214        'smtp_code_ex' => '',
     215    ];
     216
     217    /**
     218     * The reply the server sent to us for HELO.
     219     * If null, no HELO string has yet been received.
     220     *
     221     * @var string|null
     222     */
     223    protected $helo_rply;
     224
     225    /**
     226     * The set of SMTP extensions sent in reply to EHLO command.
     227     * Indexes of the array are extension names.
     228     * Value at index 'HELO' or 'EHLO' (according to command that was sent)
     229     * represents the server name. In case of HELO it is the only element of the array.
     230     * Other values can be boolean TRUE or an array containing extension options.
     231     * If null, no HELO/EHLO string has yet been received.
     232     *
     233     * @var array|null
     234     */
     235    protected $server_caps;
     236
     237    /**
     238     * The most recent reply received from the server.
     239     *
     240     * @var string
     241     */
     242    protected $last_reply = '';
     243
     244    /**
     245     * Output debugging info via a user-selected method.
     246     *
     247     * @param string $str   Debug string to output
     248     * @param int    $level The debug level of this message; see DEBUG_* constants
     249     *
     250     * @see SMTP::$Debugoutput
     251     * @see SMTP::$do_debug
     252     */
     253    protected function edebug($str, $level = 0)
     254    {
     255        if ($level > $this->do_debug) {
     256            return;
     257        }
     258        //Is this a PSR-3 logger?
     259        if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
     260            $this->Debugoutput->debug($str);
     261
     262            return;
     263        }
     264        //Avoid clash with built-in function names
     265        if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
     266            call_user_func($this->Debugoutput, $str, $level);
     267
     268            return;
     269        }
     270        switch ($this->Debugoutput) {
     271            case 'error_log':
     272                //Don't output, just log
     273                error_log($str);
     274                break;
     275            case 'html':
     276                //Cleans up output a bit for a better looking, HTML-safe output
     277                echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
     278                    preg_replace('/[\r\n]+/', '', $str),
     279                    ENT_QUOTES,
     280                    'UTF-8'
     281                ), "<br>\n";
     282                break;
     283            case 'echo':
     284            default:
     285                //Normalize line breaks
     286                $str = preg_replace('/\r\n|\r/m', "\n", $str);
     287                echo gmdate('Y-m-d H:i:s'),
     288                "\t",
     289                    //Trim trailing space
     290                trim(
     291                    //Indent for readability, except for trailing break
     292                    str_replace(
     293                        "\n",
     294                        "\n                   \t                  ",
     295                        trim($str)
     296                    )
     297                ),
     298                "\n";
     299        }
     300    }
     301
     302    /**
     303     * Connect to an SMTP server.
     304     *
     305     * @param string $host    SMTP server IP or host name
     306     * @param int    $port    The port number to connect to
     307     * @param int    $timeout How long to wait for the connection to open
     308     * @param array  $options An array of options for stream_context_create()
     309     *
     310     * @return bool
     311     */
     312    public function connect($host, $port = null, $timeout = 30, $options = [])
     313    {
     314        static $streamok;
     315        //This is enabled by default since 5.0.0 but some providers disable it
     316        //Check this once and cache the result
     317        if (null === $streamok) {
     318            $streamok = function_exists('stream_socket_client');
     319        }
     320        // Clear errors to avoid confusion
     321        $this->setError('');
     322        // Make sure we are __not__ connected
     323        if ($this->connected()) {
     324            // Already connected, generate error
     325            $this->setError('Already connected to a server');
     326
     327            return false;
     328        }
     329        if (empty($port)) {
     330            $port = self::DEFAULT_PORT;
     331        }
     332        // Connect to the SMTP server
     333        $this->edebug(
     334            "Connection: opening to $host:$port, timeout=$timeout, options=" .
     335            (count($options) > 0 ? var_export($options, true) : 'array()'),
     336            self::DEBUG_CONNECTION
     337        );
     338        $errno = 0;
     339        $errstr = '';
     340        if ($streamok) {
     341            $socket_context = stream_context_create($options);
     342            set_error_handler([$this, 'errorHandler']);
     343            $this->smtp_conn = stream_socket_client(
     344                $host . ':' . $port,
     345                $errno,
     346                $errstr,
     347                $timeout,
     348                STREAM_CLIENT_CONNECT,
     349                $socket_context
     350            );
     351            restore_error_handler();
     352        } else {
     353            //Fall back to fsockopen which should work in more places, but is missing some features
     354            $this->edebug(
     355                'Connection: stream_socket_client not available, falling back to fsockopen',
     356                self::DEBUG_CONNECTION
     357            );
     358            set_error_handler([$this, 'errorHandler']);
     359            $this->smtp_conn = fsockopen(
     360                $host,
     361                $port,
     362                $errno,
     363                $errstr,
     364                $timeout
     365            );
     366            restore_error_handler();
     367        }
     368        // Verify we connected properly
     369        if (!is_resource($this->smtp_conn)) {
     370            $this->setError(
     371                'Failed to connect to server',
     372                '',
     373                (string) $errno,
     374                $errstr
     375            );
     376            $this->edebug(
     377                'SMTP ERROR: ' . $this->error['error']
     378                . ": $errstr ($errno)",
     379                self::DEBUG_CLIENT
     380            );
     381
     382            return false;
     383        }
     384        $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
     385        // SMTP server can take longer to respond, give longer timeout for first read
     386        // Windows does not have support for this timeout function
     387        if (strpos(PHP_OS, 'WIN') !== 0) {
     388            $max = (int) ini_get('max_execution_time');
     389            // Don't bother if unlimited
     390            if (0 !== $max && $timeout > $max) {
     391                @set_time_limit($timeout);
     392            }
     393            stream_set_timeout($this->smtp_conn, $timeout, 0);
     394        }
     395        // Get any announcement
     396        $announce = $this->get_lines();
     397        $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
     398
     399        return true;
     400    }
     401
     402    /**
     403     * Initiate a TLS (encrypted) session.
     404     *
     405     * @return bool
     406     */
     407    public function startTLS()
     408    {
     409        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
     410            return false;
     411        }
     412
     413        //Allow the best TLS version(s) we can
     414        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
     415
     416        //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
     417        //so add them back in manually if we can
     418        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
     419            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
     420            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
     421        }
     422
     423        // Begin encrypted connection
     424        set_error_handler([$this, 'errorHandler']);
     425        $crypto_ok = stream_socket_enable_crypto(
     426            $this->smtp_conn,
     427            true,
     428            $crypto_method
     429        );
     430        restore_error_handler();
     431
     432        return (bool) $crypto_ok;
     433    }
     434
     435    /**
     436     * Perform SMTP authentication.
     437     * Must be run after hello().
     438     *
     439     * @see    hello()
     440     *
     441     * @param string $username The user name
     442     * @param string $password The password
     443     * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
     444     * @param OAuth  $OAuth    An optional OAuth instance for XOAUTH2 authentication
     445     *
     446     * @return bool True if successfully authenticated
     447     */
     448    public function authenticate(
     449        $username,
     450        $password,
     451        $authtype = null,
     452        $OAuth = null
     453    ) {
     454        if (!$this->server_caps) {
     455            $this->setError('Authentication is not allowed before HELO/EHLO');
     456
     457            return false;
     458        }
     459
     460        if (array_key_exists('EHLO', $this->server_caps)) {
     461            // SMTP extensions are available; try to find a proper authentication method
     462            if (!array_key_exists('AUTH', $this->server_caps)) {
     463                $this->setError('Authentication is not allowed at this stage');
     464                // 'at this stage' means that auth may be allowed after the stage changes
     465                // e.g. after STARTTLS
     466
     467                return false;
     468            }
     469
     470            $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
     471            $this->edebug(
     472                'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
     473                self::DEBUG_LOWLEVEL
     474            );
     475
     476            //If we have requested a specific auth type, check the server supports it before trying others
     477            if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) {
     478                $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
     479                $authtype = null;
     480            }
     481
     482            if (empty($authtype)) {
     483                //If no auth mechanism is specified, attempt to use these, in this order
     484                //Try CRAM-MD5 first as it's more secure than the others
     485                foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
     486                    if (in_array($method, $this->server_caps['AUTH'], true)) {
     487                        $authtype = $method;
     488                        break;
     489                    }
     490                }
     491                if (empty($authtype)) {
     492                    $this->setError('No supported authentication methods found');
     493
     494                    return false;
     495                }
     496                $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
     497            }
     498
     499            if (!in_array($authtype, $this->server_caps['AUTH'], true)) {
     500                $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
     501
     502                return false;
     503            }
     504        } elseif (empty($authtype)) {
     505            $authtype = 'LOGIN';
     506        }
     507        switch ($authtype) {
     508            case 'PLAIN':
     509                // Start authentication
     510                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
     511                    return false;
     512                }
     513                // Send encoded username and password
     514                if (!$this->sendCommand(
     515                    'User & Password',
     516                    base64_encode("\0" . $username . "\0" . $password),
     517                    235
     518                )
     519                ) {
     520                    return false;
     521                }
     522                break;
     523            case 'LOGIN':
     524                // Start authentication
     525                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
     526                    return false;
     527                }
     528                if (!$this->sendCommand('Username', base64_encode($username), 334)) {
     529                    return false;
     530                }
     531                if (!$this->sendCommand('Password', base64_encode($password), 235)) {
     532                    return false;
     533                }
     534                break;
     535            case 'CRAM-MD5':
     536                // Start authentication
     537                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
     538                    return false;
     539                }
     540                // Get the challenge
     541                $challenge = base64_decode(substr($this->last_reply, 4));
     542
     543                // Build the response
     544                $response = $username . ' ' . $this->hmac($challenge, $password);
     545
     546                // send encoded credentials
     547                return $this->sendCommand('Username', base64_encode($response), 235);
     548            case 'XOAUTH2':
     549                //The OAuth instance must be set up prior to requesting auth.
     550                if (null === $OAuth) {
     551                    return false;
     552                }
     553                $oauth = $OAuth->getOauth64();
     554
     555                // Start authentication
     556                if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
     557                    return false;
     558                }
     559                break;
     560            default:
     561                $this->setError("Authentication method \"$authtype\" is not supported");
     562
     563                return false;
     564        }
     565
     566        return true;
     567    }
     568
     569    /**
     570     * Calculate an MD5 HMAC hash.
     571     * Works like hash_hmac('md5', $data, $key)
     572     * in case that function is not available.
     573     *
     574     * @param string $data The data to hash
     575     * @param string $key  The key to hash with
     576     *
     577     * @return string
     578     */
     579    protected function hmac($data, $key)
     580    {
     581        if (function_exists('hash_hmac')) {
     582            return hash_hmac('md5', $data, $key);
     583        }
     584
     585        // The following borrowed from
     586        // http://php.net/manual/en/function.mhash.php#27225
     587
     588        // RFC 2104 HMAC implementation for php.
     589        // Creates an md5 HMAC.
     590        // Eliminates the need to install mhash to compute a HMAC
     591        // by Lance Rushing
     592
     593        $bytelen = 64; // byte length for md5
     594        if (strlen($key) > $bytelen) {
     595            $key = pack('H*', md5($key));
     596        }
     597        $key = str_pad($key, $bytelen, chr(0x00));
     598        $ipad = str_pad('', $bytelen, chr(0x36));
     599        $opad = str_pad('', $bytelen, chr(0x5c));
     600        $k_ipad = $key ^ $ipad;
     601        $k_opad = $key ^ $opad;
     602
     603        return md5($k_opad . pack('H*', md5($k_ipad . $data)));
     604    }
     605
     606    /**
     607     * Check connection state.
     608     *
     609     * @return bool True if connected
     610     */
     611    public function connected()
     612    {
     613        if (is_resource($this->smtp_conn)) {
     614            $sock_status = stream_get_meta_data($this->smtp_conn);
     615            if ($sock_status['eof']) {
     616                // The socket is valid but we are not connected
     617                $this->edebug(
     618                    'SMTP NOTICE: EOF caught while checking if connected',
     619                    self::DEBUG_CLIENT
     620                );
     621                $this->close();
     622
     623                return false;
     624            }
     625
     626            return true; // everything looks good
     627        }
     628
     629        return false;
     630    }
     631
     632    /**
     633     * Close the socket and clean up the state of the class.
     634     * Don't use this function without first trying to use QUIT.
     635     *
     636     * @see quit()
     637     */
     638    public function close()
     639    {
     640        $this->setError('');
     641        $this->server_caps = null;
     642        $this->helo_rply = null;
     643        if (is_resource($this->smtp_conn)) {
     644            // close the connection and cleanup
     645            fclose($this->smtp_conn);
     646            $this->smtp_conn = null; //Makes for cleaner serialization
     647            $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
     648        }
     649    }
     650
     651    /**
     652     * Send an SMTP DATA command.
     653     * Issues a data command and sends the msg_data to the server,
     654     * finializing the mail transaction. $msg_data is the message
     655     * that is to be send with the headers. Each header needs to be
     656     * on a single line followed by a <CRLF> with the message headers
     657     * and the message body being separated by an additional <CRLF>.
     658     * Implements RFC 821: DATA <CRLF>.
     659     *
     660     * @param string $msg_data Message data to send
     661     *
     662     * @return bool
     663     */
     664    public function data($msg_data)
     665    {
     666        //This will use the standard timelimit
     667        if (!$this->sendCommand('DATA', 'DATA', 354)) {
     668            return false;
     669        }
     670
     671        /* The server is ready to accept data!
     672         * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
     673         * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
     674         * smaller lines to fit within the limit.
     675         * We will also look for lines that start with a '.' and prepend an additional '.'.
     676         * NOTE: this does not count towards line-length limit.
     677         */
     678
     679        // Normalize line breaks before exploding
     680        $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
     681
     682        /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
     683         * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
     684         * process all lines before a blank line as headers.
     685         */
     686
     687        $field = substr($lines[0], 0, strpos($lines[0], ':'));
     688        $in_headers = false;
     689        if (!empty($field) && strpos($field, ' ') === false) {
     690            $in_headers = true;
     691        }
     692
     693        foreach ($lines as $line) {
     694            $lines_out = [];
     695            if ($in_headers && $line === '') {
     696                $in_headers = false;
     697            }
     698            //Break this line up into several smaller lines if it's too long
     699            //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
     700            while (isset($line[self::MAX_LINE_LENGTH])) {
     701                //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
     702                //so as to avoid breaking in the middle of a word
     703                $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
     704                //Deliberately matches both false and 0
     705                if (!$pos) {
     706                    //No nice break found, add a hard break
     707                    $pos = self::MAX_LINE_LENGTH - 1;
     708                    $lines_out[] = substr($line, 0, $pos);
     709                    $line = substr($line, $pos);
     710                } else {
     711                    //Break at the found point
     712                    $lines_out[] = substr($line, 0, $pos);
     713                    //Move along by the amount we dealt with
     714                    $line = substr($line, $pos + 1);
     715                }
     716                //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
     717                if ($in_headers) {
     718                    $line = "\t" . $line;
     719                }
     720            }
     721            $lines_out[] = $line;
     722
     723            //Send the lines to the server
     724            foreach ($lines_out as $line_out) {
     725                //RFC2821 section 4.5.2
     726                if (!empty($line_out) && $line_out[0] === '.') {
     727                    $line_out = '.' . $line_out;
     728                }
     729                $this->client_send($line_out . static::LE, 'DATA');
     730            }
     731        }
     732
     733        //Message data has been sent, complete the command
     734        //Increase timelimit for end of DATA command
     735        $savetimelimit = $this->Timelimit;
     736        $this->Timelimit *= 2;
     737        $result = $this->sendCommand('DATA END', '.', 250);
     738        $this->recordLastTransactionID();
     739        //Restore timelimit
     740        $this->Timelimit = $savetimelimit;
     741
     742        return $result;
     743    }
     744
     745    /**
     746     * Send an SMTP HELO or EHLO command.
     747     * Used to identify the sending server to the receiving server.
     748     * This makes sure that client and server are in a known state.
     749     * Implements RFC 821: HELO <SP> <domain> <CRLF>
     750     * and RFC 2821 EHLO.
     751     *
     752     * @param string $host The host name or IP to connect to
     753     *
     754     * @return bool
     755     */
     756    public function hello($host = '')
     757    {
     758        //Try extended hello first (RFC 2821)
     759        return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host);
     760    }
     761
     762    /**
     763     * Send an SMTP HELO or EHLO command.
     764     * Low-level implementation used by hello().
     765     *
     766     * @param string $hello The HELO string
     767     * @param string $host  The hostname to say we are
     768     *
     769     * @return bool
     770     *
     771     * @see hello()
     772     */
     773    protected function sendHello($hello, $host)
     774    {
     775        $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
     776        $this->helo_rply = $this->last_reply;
     777        if ($noerror) {
     778            $this->parseHelloFields($hello);
     779        } else {
     780            $this->server_caps = null;
     781        }
     782
     783        return $noerror;
     784    }
     785
     786    /**
     787     * Parse a reply to HELO/EHLO command to discover server extensions.
     788     * In case of HELO, the only parameter that can be discovered is a server name.
     789     *
     790     * @param string $type `HELO` or `EHLO`
     791     */
     792    protected function parseHelloFields($type)
     793    {
     794        $this->server_caps = [];
     795        $lines = explode("\n", $this->helo_rply);
     796
     797        foreach ($lines as $n => $s) {
     798            //First 4 chars contain response code followed by - or space
     799            $s = trim(substr($s, 4));
     800            if (empty($s)) {
     801                continue;
     802            }
     803            $fields = explode(' ', $s);
     804            if (!empty($fields)) {
     805                if (!$n) {
     806                    $name = $type;
     807                    $fields = $fields[0];
     808                } else {
     809                    $name = array_shift($fields);
     810                    switch ($name) {
     811                        case 'SIZE':
     812                            $fields = ($fields ? $fields[0] : 0);
     813                            break;
     814                        case 'AUTH':
     815                            if (!is_array($fields)) {
     816                                $fields = [];
     817                            }
     818                            break;
     819                        default:
     820                            $fields = true;
     821                    }
     822                }
     823                $this->server_caps[$name] = $fields;
     824            }
     825        }
     826    }
     827
     828    /**
     829     * Send an SMTP MAIL command.
     830     * Starts a mail transaction from the email address specified in
     831     * $from. Returns true if successful or false otherwise. If True
     832     * the mail transaction is started and then one or more recipient
     833     * commands may be called followed by a data command.
     834     * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
     835     *
     836     * @param string $from Source address of this message
     837     *
     838     * @return bool
     839     */
     840    public function mail($from)
     841    {
     842        $useVerp = ($this->do_verp ? ' XVERP' : '');
     843
     844        return $this->sendCommand(
     845            'MAIL FROM',
     846            'MAIL FROM:<' . $from . '>' . $useVerp,
     847            250
     848        );
     849    }
     850
     851    /**
     852     * Send an SMTP QUIT command.
     853     * Closes the socket if there is no error or the $close_on_error argument is true.
     854     * Implements from RFC 821: QUIT <CRLF>.
     855     *
     856     * @param bool $close_on_error Should the connection close if an error occurs?
     857     *
     858     * @return bool
     859     */
     860    public function quit($close_on_error = true)
     861    {
     862        $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
     863        $err = $this->error; //Save any error
     864        if ($noerror || $close_on_error) {
     865            $this->close();
     866            $this->error = $err; //Restore any error from the quit command
     867        }
     868
     869        return $noerror;
     870    }
     871
     872    /**
     873     * Send an SMTP RCPT command.
     874     * Sets the TO argument to $toaddr.
     875     * Returns true if the recipient was accepted false if it was rejected.
     876     * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
     877     *
     878     * @param string $address The address the message is being sent to
     879     * @param string $dsn     Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE
     880     *                        or DELAY. If you specify NEVER all other notifications are ignored.
     881     *
     882     * @return bool
     883     */
     884    public function recipient($address, $dsn = '')
     885    {
     886        if (empty($dsn)) {
     887            $rcpt = 'RCPT TO:<' . $address . '>';
     888        } else {
     889            $dsn = strtoupper($dsn);
     890            $notify = [];
     891
     892            if (strpos($dsn, 'NEVER') !== false) {
     893                $notify[] = 'NEVER';
     894            } else {
     895                foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) {
     896                    if (strpos($dsn, $value) !== false) {
     897                        $notify[] = $value;
     898                    }
     899                }
     900            }
     901
     902            $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify);
     903        }
     904
     905        return $this->sendCommand(
     906            'RCPT TO',
     907            $rcpt,
     908            [250, 251]
     909        );
     910    }
     911
     912    /**
     913     * Send an SMTP RSET command.
     914     * Abort any transaction that is currently in progress.
     915     * Implements RFC 821: RSET <CRLF>.
     916     *
     917     * @return bool True on success
     918     */
     919    public function reset()
     920    {
     921        return $this->sendCommand('RSET', 'RSET', 250);
     922    }
     923
     924    /**
     925     * Send a command to an SMTP server and check its return code.
     926     *
     927     * @param string    $command       The command name - not sent to the server
     928     * @param string    $commandstring The actual command to send
     929     * @param int|array $expect        One or more expected integer success codes
     930     *
     931     * @return bool True on success
     932     */
     933    protected function sendCommand($command, $commandstring, $expect)
     934    {
     935        if (!$this->connected()) {
     936            $this->setError("Called $command without being connected");
     937
     938            return false;
     939        }
     940        //Reject line breaks in all commands
     941        if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) {
     942            $this->setError("Command '$command' contained line breaks");
     943
     944            return false;
     945        }
     946        $this->client_send($commandstring . static::LE, $command);
     947
     948        $this->last_reply = $this->get_lines();
     949        // Fetch SMTP code and possible error code explanation
     950        $matches = [];
     951        if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
     952            $code = (int) $matches[1];
     953            $code_ex = (count($matches) > 2 ? $matches[2] : null);
     954            // Cut off error code from each response line
     955            $detail = preg_replace(
     956                "/{$code}[ -]" .
     957                ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
     958                '',
     959                $this->last_reply
     960            );
     961        } else {
     962            // Fall back to simple parsing if regex fails
     963            $code = (int) substr($this->last_reply, 0, 3);
     964            $code_ex = null;
     965            $detail = substr($this->last_reply, 4);
     966        }
     967
     968        $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
     969
     970        if (!in_array($code, (array) $expect, true)) {
     971            $this->setError(
     972                "$command command failed",
     973                $detail,
     974                $code,
     975                $code_ex
     976            );
     977            $this->edebug(
     978                'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
     979                self::DEBUG_CLIENT
     980            );
     981
     982            return false;
     983        }
     984
     985        $this->setError('');
     986
     987        return true;
     988    }
     989
     990    /**
     991     * Send an SMTP SAML command.
     992     * Starts a mail transaction from the email address specified in $from.
     993     * Returns true if successful or false otherwise. If True
     994     * the mail transaction is started and then one or more recipient
     995     * commands may be called followed by a data command. This command
     996     * will send the message to the users terminal if they are logged
     997     * in and send them an email.
     998     * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
     999     *
     1000     * @param string $from The address the message is from
     1001     *
     1002     * @return bool
     1003     */
     1004    public function sendAndMail($from)
     1005    {
     1006        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
     1007    }
     1008
     1009    /**
     1010     * Send an SMTP VRFY command.
     1011     *
     1012     * @param string $name The name to verify
     1013     *
     1014     * @return bool
     1015     */
     1016    public function verify($name)
     1017    {
     1018        return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
     1019    }
     1020
     1021    /**
     1022     * Send an SMTP NOOP command.
     1023     * Used to keep keep-alives alive, doesn't actually do anything.
     1024     *
     1025     * @return bool
     1026     */
     1027    public function noop()
     1028    {
     1029        return $this->sendCommand('NOOP', 'NOOP', 250);
     1030    }
     1031
     1032    /**
     1033     * Send an SMTP TURN command.
     1034     * This is an optional command for SMTP that this class does not support.
     1035     * This method is here to make the RFC821 Definition complete for this class
     1036     * and _may_ be implemented in future.
     1037     * Implements from RFC 821: TURN <CRLF>.
     1038     *
     1039     * @return bool
     1040     */
     1041    public function turn()
     1042    {
     1043        $this->setError('The SMTP TURN command is not implemented');
     1044        $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
     1045
     1046        return false;
     1047    }
     1048
     1049    /**
     1050     * Send raw data to the server.
     1051     *
     1052     * @param string $data    The data to send
     1053     * @param string $command Optionally, the command this is part of, used only for controlling debug output
     1054     *
     1055     * @return int|bool The number of bytes sent to the server or false on error
     1056     */
     1057    public function client_send($data, $command = '')
     1058    {
     1059        //If SMTP transcripts are left enabled, or debug output is posted online
     1060        //it can leak credentials, so hide credentials in all but lowest level
     1061        if (self::DEBUG_LOWLEVEL > $this->do_debug &&
     1062            in_array($command, ['User & Password', 'Username', 'Password'], true)) {
     1063            $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
     1064        } else {
     1065            $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
     1066        }
     1067        set_error_handler([$this, 'errorHandler']);
     1068        $result = fwrite($this->smtp_conn, $data);
     1069        restore_error_handler();
     1070
     1071        return $result;
     1072    }
     1073
     1074    /**
     1075     * Get the latest error.
     1076     *
     1077     * @return array
     1078     */
     1079    public function getError()
     1080    {
     1081        return $this->error;
     1082    }
     1083
     1084    /**
     1085     * Get SMTP extensions available on the server.
     1086     *
     1087     * @return array|null
     1088     */
     1089    public function getServerExtList()
     1090    {
     1091        return $this->server_caps;
     1092    }
     1093
     1094    /**
     1095     * Get metadata about the SMTP server from its HELO/EHLO response.
     1096     * The method works in three ways, dependent on argument value and current state:
     1097     *   1. HELO/EHLO has not been sent - returns null and populates $this->error.
     1098     *   2. HELO has been sent -
     1099     *     $name == 'HELO': returns server name
     1100     *     $name == 'EHLO': returns boolean false
     1101     *     $name == any other string: returns null and populates $this->error
     1102     *   3. EHLO has been sent -
     1103     *     $name == 'HELO'|'EHLO': returns the server name
     1104     *     $name == any other string: if extension $name exists, returns True
     1105     *       or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
     1106     *
     1107     * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
     1108     *
     1109     * @return string|bool|null
     1110     */
     1111    public function getServerExt($name)
     1112    {
     1113        if (!$this->server_caps) {
     1114            $this->setError('No HELO/EHLO was sent');
     1115
     1116            return;
     1117        }
     1118
     1119        if (!array_key_exists($name, $this->server_caps)) {
     1120            if ('HELO' === $name) {
     1121                return $this->server_caps['EHLO'];
     1122            }
     1123            if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) {
     1124                return false;
     1125            }
     1126            $this->setError('HELO handshake was used; No information about server extensions available');
     1127
     1128            return;
     1129        }
     1130
     1131        return $this->server_caps[$name];
     1132    }
     1133
     1134    /**
     1135     * Get the last reply from the server.
     1136     *
     1137     * @return string
     1138     */
     1139    public function getLastReply()
     1140    {
     1141        return $this->last_reply;
     1142    }
     1143
     1144    /**
     1145     * Read the SMTP server's response.
     1146     * Either before eof or socket timeout occurs on the operation.
     1147     * With SMTP we can tell if we have more lines to read if the
     1148     * 4th character is '-' symbol. If it is a space then we don't
     1149     * need to read anything else.
     1150     *
     1151     * @return string
     1152     */
     1153    protected function get_lines()
     1154    {
     1155        // If the connection is bad, give up straight away
     1156        if (!is_resource($this->smtp_conn)) {
     1157            return '';
     1158        }
     1159        $data = '';
     1160        $endtime = 0;
     1161        stream_set_timeout($this->smtp_conn, $this->Timeout);
     1162        if ($this->Timelimit > 0) {
     1163            $endtime = time() + $this->Timelimit;
     1164        }
     1165        $selR = [$this->smtp_conn];
     1166        $selW = null;
     1167        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
     1168            //Must pass vars in here as params are by reference
     1169            if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
     1170                $this->edebug(
     1171                    'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
     1172                    self::DEBUG_LOWLEVEL
     1173                );
     1174                break;
     1175            }
     1176            //Deliberate noise suppression - errors are handled afterwards
     1177            $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
     1178            $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
     1179            $data .= $str;
     1180            // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
     1181            // or 4th character is a space or a line break char, we are done reading, break the loop.
     1182            // String array access is a significant micro-optimisation over strlen
     1183            if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
     1184                break;
     1185            }
     1186            // Timed-out? Log and break
     1187            $info = stream_get_meta_data($this->smtp_conn);
     1188            if ($info['timed_out']) {
     1189                $this->edebug(
     1190                    'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
     1191                    self::DEBUG_LOWLEVEL
     1192                );
     1193                break;
     1194            }
     1195            // Now check if reads took too long
     1196            if ($endtime && time() > $endtime) {
     1197                $this->edebug(
     1198                    'SMTP -> get_lines(): timelimit reached (' .
     1199                    $this->Timelimit . ' sec)',
     1200                    self::DEBUG_LOWLEVEL
     1201                );
     1202                break;
     1203            }
     1204        }
     1205
     1206        return $data;
     1207    }
     1208
     1209    /**
     1210     * Enable or disable VERP address generation.
     1211     *
     1212     * @param bool $enabled
     1213     */
     1214    public function setVerp($enabled = false)
     1215    {
     1216        $this->do_verp = $enabled;
     1217    }
     1218
     1219    /**
     1220     * Get VERP address generation mode.
     1221     *
     1222     * @return bool
     1223     */
     1224    public function getVerp()
     1225    {
     1226        return $this->do_verp;
     1227    }
     1228
     1229    /**
     1230     * Set error messages and codes.
     1231     *
     1232     * @param string $message      The error message
     1233     * @param string $detail       Further detail on the error
     1234     * @param string $smtp_code    An associated SMTP error code
     1235     * @param string $smtp_code_ex Extended SMTP code
     1236     */
     1237    protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
     1238    {
     1239        $this->error = [
     1240            'error' => $message,
     1241            'detail' => $detail,
     1242            'smtp_code' => $smtp_code,
     1243            'smtp_code_ex' => $smtp_code_ex,
     1244        ];
     1245    }
     1246
     1247    /**
     1248     * Set debug output method.
     1249     *
     1250     * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
     1251     */
     1252    public function setDebugOutput($method = 'echo')
     1253    {
     1254        $this->Debugoutput = $method;
     1255    }
     1256
     1257    /**
     1258     * Get debug output method.
     1259     *
     1260     * @return string
     1261     */
     1262    public function getDebugOutput()
     1263    {
     1264        return $this->Debugoutput;
     1265    }
     1266
     1267    /**
     1268     * Set debug output level.
     1269     *
     1270     * @param int $level
     1271     */
     1272    public function setDebugLevel($level = 0)
     1273    {
     1274        $this->do_debug = $level;
     1275    }
     1276
     1277    /**
     1278     * Get debug output level.
     1279     *
     1280     * @return int
     1281     */
     1282    public function getDebugLevel()
     1283    {
     1284        return $this->do_debug;
     1285    }
     1286
     1287    /**
     1288     * Set SMTP timeout.
     1289     *
     1290     * @param int $timeout The timeout duration in seconds
     1291     */
     1292    public function setTimeout($timeout = 0)
     1293    {
     1294        $this->Timeout = $timeout;
     1295    }
     1296
     1297    /**
     1298     * Get SMTP timeout.
     1299     *
     1300     * @return int
     1301     */
     1302    public function getTimeout()
     1303    {
     1304        return $this->Timeout;
     1305    }
     1306
     1307    /**
     1308     * Reports an error number and string.
     1309     *
     1310     * @param int    $errno   The error number returned by PHP
     1311     * @param string $errmsg  The error message returned by PHP
     1312     * @param string $errfile The file the error occurred in
     1313     * @param int    $errline The line number the error occurred on
     1314     */
     1315    protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
     1316    {
     1317        $notice = 'Connection failed.';
     1318        $this->setError(
     1319            $notice,
     1320            $errmsg,
     1321            (string) $errno
     1322        );
     1323        $this->edebug(
     1324            "$notice Error #$errno: $errmsg [$errfile line $errline]",
     1325            self::DEBUG_CONNECTION
     1326        );
     1327    }
     1328
     1329    /**
     1330     * Extract and return the ID of the last SMTP transaction based on
     1331     * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
     1332     * Relies on the host providing the ID in response to a DATA command.
     1333     * If no reply has been received yet, it will return null.
     1334     * If no pattern was matched, it will return false.
     1335     *
     1336     * @return bool|string|null
     1337     */
     1338    protected function recordLastTransactionID()
     1339    {
     1340        $reply = $this->getLastReply();
     1341
     1342        if (empty($reply)) {
     1343            $this->last_smtp_transaction_id = null;
     1344        } else {
     1345            $this->last_smtp_transaction_id = false;
     1346            foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
     1347                $matches = [];
     1348                if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
     1349                    $this->last_smtp_transaction_id = trim($matches[1]);
     1350                    break;
     1351                }
     1352            }
     1353        }
     1354
     1355        return $this->last_smtp_transaction_id;
     1356    }
     1357
     1358    /**
     1359     * Get the queue/transaction ID of the last SMTP transaction
     1360     * If no reply has been received yet, it will return null.
     1361     * If no pattern was matched, it will return false.
     1362     *
     1363     * @return bool|string|null
     1364     *
     1365     * @see recordLastTransactionID()
     1366     */
     1367    public function getLastTransactionID()
     1368    {
     1369        return $this->last_smtp_transaction_id;
     1370    }
     1371}
  • src/wp-includes/class-phpmailer.php

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

    diff --git a/src/wp-includes/class-smtp.php b/src/wp-includes/class-smtp.php
    index 8b11c73f22..28144c7125 100644
    a b  
    11<?php
    2 /**
    3  * PHPMailer RFC821 SMTP email transport class.
    4  * PHP Version 5
    5  * @package PHPMailer
    6  * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
    7  * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
    8  * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
    9  * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
    10  * @author Brent R. Matzelle (original founder)
    11  * @copyright 2014 Marcus Bointon
    12  * @copyright 2010 - 2012 Jim Jagielski
    13  * @copyright 2004 - 2009 Andy Prevost
    14  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
    15  * @note This program is distributed in the hope that it will be useful - WITHOUT
    16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    17  * FITNESS FOR A PARTICULAR PURPOSE.
    18  */
    192
    203/**
    21  * PHPMailer RFC821 SMTP email transport class.
    22  * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
    23  * @package PHPMailer
    24  * @author Chris Ryan
    25  * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
     4 * The SMTP class has been moved to the wp-includes/PHPMailer subdirectory and now uses the PHPMailer\PHPMailer namespace.
    265 */
    27 class SMTP
    28 {
    29     /**
    30      * The PHPMailer SMTP version number.
    31      * @var string
    32      */
    33     const VERSION = '5.2.27';
    34 
    35     /**
    36      * SMTP line break constant.
    37      * @var string
    38      */
    39     const CRLF = "\r\n";
    40 
    41     /**
    42      * The SMTP port to use if one is not specified.
    43      * @var integer
    44      */
    45     const DEFAULT_SMTP_PORT = 25;
    46 
    47     /**
    48      * The maximum line length allowed by RFC 2822 section 2.1.1
    49      * @var integer
    50      */
    51     const MAX_LINE_LENGTH = 998;
    52 
    53     /**
    54      * Debug level for no output
    55      */
    56     const DEBUG_OFF = 0;
    57 
    58     /**
    59      * Debug level to show client -> server messages
    60      */
    61     const DEBUG_CLIENT = 1;
    62 
    63     /**
    64      * Debug level to show client -> server and server -> client messages
    65      */
    66     const DEBUG_SERVER = 2;
    67 
    68     /**
    69      * Debug level to show connection status, client -> server and server -> client messages
    70      */
    71     const DEBUG_CONNECTION = 3;
    72 
    73     /**
    74      * Debug level to show all messages
    75      */
    76     const DEBUG_LOWLEVEL = 4;
    77 
    78     /**
    79      * The PHPMailer SMTP Version number.
    80      * @var string
    81      * @deprecated Use the `VERSION` constant instead
    82      * @see SMTP::VERSION
    83      */
    84     public $Version = '5.2.27';
    85 
    86     /**
    87      * SMTP server port number.
    88      * @var integer
    89      * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead
    90      * @see SMTP::DEFAULT_SMTP_PORT
    91      */
    92     public $SMTP_PORT = 25;
    93 
    94     /**
    95      * SMTP reply line ending.
    96      * @var string
    97      * @deprecated Use the `CRLF` constant instead
    98      * @see SMTP::CRLF
    99      */
    100     public $CRLF = "\r\n";
    101 
    102     /**
    103      * Debug output level.
    104      * Options:
    105      * * self::DEBUG_OFF (`0`) No debug output, default
    106      * * self::DEBUG_CLIENT (`1`) Client commands
    107      * * self::DEBUG_SERVER (`2`) Client commands and server responses
    108      * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
    109      * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages
    110      * @var integer
    111      */
    112     public $do_debug = self::DEBUG_OFF;
    113 
    114     /**
    115      * How to handle debug output.
    116      * Options:
    117      * * `echo` Output plain-text as-is, appropriate for CLI
    118      * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
    119      * * `error_log` Output to error log as configured in php.ini
    120      *
    121      * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
    122      * <code>
    123      * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
    124      * </code>
    125      * @var string|callable
    126      */
    127     public $Debugoutput = 'echo';
    128 
    129     /**
    130      * Whether to use VERP.
    131      * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
    132      * @link http://www.postfix.org/VERP_README.html Info on VERP
    133      * @var boolean
    134      */
    135     public $do_verp = false;
    136 
    137     /**
    138      * The timeout value for connection, in seconds.
    139      * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
    140      * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
    141      * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2
    142      * @var integer
    143      */
    144     public $Timeout = 300;
    145 
    146     /**
    147      * How long to wait for commands to complete, in seconds.
    148      * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
    149      * @var integer
    150      */
    151     public $Timelimit = 300;
    152 
    153     /**
    154      * @var array Patterns to extract an SMTP transaction id from reply to a DATA command.
    155      * The first capture group in each regex will be used as the ID.
    156      */
    157     protected $smtp_transaction_id_patterns = array(
    158         'exim' => '/[0-9]{3} OK id=(.*)/',
    159         'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
    160         'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
    161     );
    162 
    163     /**
    164      * @var string The last transaction ID issued in response to a DATA command,
    165      * if one was detected
    166      */
    167     protected $last_smtp_transaction_id;
    168 
    169     /**
    170      * The socket for the server connection.
    171      * @var resource
    172      */
    173     protected $smtp_conn;
    174 
    175     /**
    176      * Error information, if any, for the last SMTP command.
    177      * @var array
    178      */
    179     protected $error = array(
    180         'error' => '',
    181         'detail' => '',
    182         'smtp_code' => '',
    183         'smtp_code_ex' => ''
    184     );
    185 
    186     /**
    187      * The reply the server sent to us for HELO.
    188      * If null, no HELO string has yet been received.
    189      * @var string|null
    190      */
    191     protected $helo_rply = null;
    192 
    193     /**
    194      * The set of SMTP extensions sent in reply to EHLO command.
    195      * Indexes of the array are extension names.
    196      * Value at index 'HELO' or 'EHLO' (according to command that was sent)
    197      * represents the server name. In case of HELO it is the only element of the array.
    198      * Other values can be boolean TRUE or an array containing extension options.
    199      * If null, no HELO/EHLO string has yet been received.
    200      * @var array|null
    201      */
    202     protected $server_caps = null;
    203 
    204     /**
    205      * The most recent reply received from the server.
    206      * @var string
    207      */
    208     protected $last_reply = '';
    209 
    210     /**
    211      * Output debugging info via a user-selected method.
    212      * @see SMTP::$Debugoutput
    213      * @see SMTP::$do_debug
    214      * @param string $str Debug string to output
    215      * @param integer $level The debug level of this message; see DEBUG_* constants
    216      * @return void
    217      */
    218     protected function edebug($str, $level = 0)
    219     {
    220         if ($level > $this->do_debug) {
    221             return;
    222         }
    223         //Avoid clash with built-in function names
    224         if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
    225             call_user_func($this->Debugoutput, $str, $level);
    226             return;
    227         }
    228         switch ($this->Debugoutput) {
    229             case 'error_log':
    230                 //Don't output, just log
    231                 error_log($str);
    232                 break;
    233             case 'html':
    234                 //Cleans up output a bit for a better looking, HTML-safe output
    235                 echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities(
    236                     preg_replace('/[\r\n]+/', '', $str),
    237                     ENT_QUOTES,
    238                     'UTF-8'
    239                 ) . "<br>\n";
    240                 break;
    241             case 'echo':
    242             default:
    243                 //Normalize line breaks
    244                 $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
    245                 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
    246                     "\n",
    247                     "\n                   \t                  ",
    248                     trim($str)
    249                 ) . "\n";
    250         }
    251     }
    252 
    253     /**
    254      * Connect to an SMTP server.
    255      * @param string $host SMTP server IP or host name
    256      * @param integer $port The port number to connect to
    257      * @param integer $timeout How long to wait for the connection to open
    258      * @param array $options An array of options for stream_context_create()
    259      * @access public
    260      * @return boolean
    261      */
    262     public function connect($host, $port = null, $timeout = 30, $options = array())
    263     {
    264         static $streamok;
    265         //This is enabled by default since 5.0.0 but some providers disable it
    266         //Check this once and cache the result
    267         if (is_null($streamok)) {
    268             $streamok = function_exists('stream_socket_client');
    269         }
    270         // Clear errors to avoid confusion
    271         $this->setError('');
    272         // Make sure we are __not__ connected
    273         if ($this->connected()) {
    274             // Already connected, generate error
    275             $this->setError('Already connected to a server');
    276             return false;
    277         }
    278         if (empty($port)) {
    279             $port = self::DEFAULT_SMTP_PORT;
    280         }
    281         // Connect to the SMTP server
    282         $this->edebug(
    283             "Connection: opening to $host:$port, timeout=$timeout, options=" .
    284             var_export($options, true),
    285             self::DEBUG_CONNECTION
    286         );
    287         $errno = 0;
    288         $errstr = '';
    289         if ($streamok) {
    290             $socket_context = stream_context_create($options);
    291             set_error_handler(array($this, 'errorHandler'));
    292             $this->smtp_conn = stream_socket_client(
    293                 $host . ":" . $port,
    294                 $errno,
    295                 $errstr,
    296                 $timeout,
    297                 STREAM_CLIENT_CONNECT,
    298                 $socket_context
    299             );
    300             restore_error_handler();
    301         } else {
    302             //Fall back to fsockopen which should work in more places, but is missing some features
    303             $this->edebug(
    304                 "Connection: stream_socket_client not available, falling back to fsockopen",
    305                 self::DEBUG_CONNECTION
    306             );
    307             set_error_handler(array($this, 'errorHandler'));
    308             $this->smtp_conn = fsockopen(
    309                 $host,
    310                 $port,
    311                 $errno,
    312                 $errstr,
    313                 $timeout
    314             );
    315             restore_error_handler();
    316         }
    317         // Verify we connected properly
    318         if (!is_resource($this->smtp_conn)) {
    319             $this->setError(
    320                 'Failed to connect to server',
    321                 $errno,
    322                 $errstr
    323             );
    324             $this->edebug(
    325                 'SMTP ERROR: ' . $this->error['error']
    326                 . ": $errstr ($errno)",
    327                 self::DEBUG_CLIENT
    328             );
    329             return false;
    330         }
    331         $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
    332         // SMTP server can take longer to respond, give longer timeout for first read
    333         // Windows does not have support for this timeout function
    334         if (substr(PHP_OS, 0, 3) != 'WIN') {
    335             $max = ini_get('max_execution_time');
    336             // Don't bother if unlimited
    337             if ($max != 0 && $timeout > $max) {
    338                 @set_time_limit($timeout);
    339             }
    340             stream_set_timeout($this->smtp_conn, $timeout, 0);
    341         }
    342         // Get any announcement
    343         $announce = $this->get_lines();
    344         $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
    345         return true;
    346     }
    347 
    348     /**
    349      * Initiate a TLS (encrypted) session.
    350      * @access public
    351      * @return boolean
    352      */
    353     public function startTLS()
    354     {
    355         if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
    356             return false;
    357         }
    358 
    359         //Allow the best TLS version(s) we can
    360         $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
    361 
    362         //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
    363         //so add them back in manually if we can
    364         if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
    365             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
    366             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
    367         }
    368 
    369         // Begin encrypted connection
    370         set_error_handler(array($this, 'errorHandler'));
    371         $crypto_ok = stream_socket_enable_crypto(
    372             $this->smtp_conn,
    373             true,
    374             $crypto_method
    375         );
    376         restore_error_handler();
    377         return $crypto_ok;
    378     }
    379 
    380     /**
    381      * Perform SMTP authentication.
    382      * Must be run after hello().
    383      * @see hello()
    384      * @param string $username The user name
    385      * @param string $password The password
    386      * @param string $authtype The auth type (PLAIN, LOGIN, CRAM-MD5)
    387      * @param string $realm The auth realm for NTLM
    388      * @param string $workstation The auth workstation for NTLM
    389      * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth)
    390      * @return bool True if successfully authenticated.* @access public
    391      */
    392     public function authenticate(
    393         $username,
    394         $password,
    395         $authtype = null,
    396         $realm = '',
    397         $workstation = '',
    398         $OAuth = null
    399     ) {
    400         if (!$this->server_caps) {
    401             $this->setError('Authentication is not allowed before HELO/EHLO');
    402             return false;
    403         }
    404 
    405         if (array_key_exists('EHLO', $this->server_caps)) {
    406             // SMTP extensions are available; try to find a proper authentication method
    407             if (!array_key_exists('AUTH', $this->server_caps)) {
    408                 $this->setError('Authentication is not allowed at this stage');
    409                 // 'at this stage' means that auth may be allowed after the stage changes
    410                 // e.g. after STARTTLS
    411                 return false;
    412             }
    413 
    414             self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
    415             self::edebug(
    416                 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
    417                 self::DEBUG_LOWLEVEL
    418             );
    419 
    420             if (empty($authtype)) {
    421                 foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN') as $method) {
    422                     if (in_array($method, $this->server_caps['AUTH'])) {
    423                         $authtype = $method;
    424                         break;
    425                     }
    426                 }
    427                 if (empty($authtype)) {
    428                     $this->setError('No supported authentication methods found');
    429                     return false;
    430                 }
    431                 self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
    432             }
    433 
    434             if (!in_array($authtype, $this->server_caps['AUTH'])) {
    435                 $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
    436                 return false;
    437             }
    438         } elseif (empty($authtype)) {
    439             $authtype = 'LOGIN';
    440         }
    441         switch ($authtype) {
    442             case 'PLAIN':
    443                 // Start authentication
    444                 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
    445                     return false;
    446                 }
    447                 // Send encoded username and password
    448                 if (!$this->sendCommand(
    449                     'User & Password',
    450                     base64_encode("\0" . $username . "\0" . $password),
    451                     235
    452                 )
    453                 ) {
    454                     return false;
    455                 }
    456                 break;
    457             case 'LOGIN':
    458                 // Start authentication
    459                 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
    460                     return false;
    461                 }
    462                 if (!$this->sendCommand("Username", base64_encode($username), 334)) {
    463                     return false;
    464                 }
    465                 if (!$this->sendCommand("Password", base64_encode($password), 235)) {
    466                     return false;
    467                 }
    468                 break;
    469             case 'CRAM-MD5':
    470                 // Start authentication
    471                 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
    472                     return false;
    473                 }
    474                 // Get the challenge
    475                 $challenge = base64_decode(substr($this->last_reply, 4));
    476 
    477                 // Build the response
    478                 $response = $username . ' ' . $this->hmac($challenge, $password);
    479 
    480                 // send encoded credentials
    481                 return $this->sendCommand('Username', base64_encode($response), 235);
    482             default:
    483                 $this->setError("Authentication method \"$authtype\" is not supported");
    484                 return false;
    485         }
    486         return true;
    487     }
    488 
    489     /**
    490      * Calculate an MD5 HMAC hash.
    491      * Works like hash_hmac('md5', $data, $key)
    492      * in case that function is not available
    493      * @param string $data The data to hash
    494      * @param string $key The key to hash with
    495      * @access protected
    496      * @return string
    497      */
    498     protected function hmac($data, $key)
    499     {
    500         if (function_exists('hash_hmac')) {
    501             return hash_hmac('md5', $data, $key);
    502         }
    503 
    504         // The following borrowed from
    505         // http://php.net/manual/en/function.mhash.php#27225
    506 
    507         // RFC 2104 HMAC implementation for php.
    508         // Creates an md5 HMAC.
    509         // Eliminates the need to install mhash to compute a HMAC
    510         // by Lance Rushing
    511 
    512         $bytelen = 64; // byte length for md5
    513         if (strlen($key) > $bytelen) {
    514             $key = pack('H*', md5($key));
    515         }
    516         $key = str_pad($key, $bytelen, chr(0x00));
    517         $ipad = str_pad('', $bytelen, chr(0x36));
    518         $opad = str_pad('', $bytelen, chr(0x5c));
    519         $k_ipad = $key ^ $ipad;
    520         $k_opad = $key ^ $opad;
    521 
    522         return md5($k_opad . pack('H*', md5($k_ipad . $data)));
    523     }
    524 
    525     /**
    526      * Check connection state.
    527      * @access public
    528      * @return boolean True if connected.
    529      */
    530     public function connected()
    531     {
    532         if (is_resource($this->smtp_conn)) {
    533             $sock_status = stream_get_meta_data($this->smtp_conn);
    534             if ($sock_status['eof']) {
    535                 // The socket is valid but we are not connected
    536                 $this->edebug(
    537                     'SMTP NOTICE: EOF caught while checking if connected',
    538                     self::DEBUG_CLIENT
    539                 );
    540                 $this->close();
    541                 return false;
    542             }
    543             return true; // everything looks good
    544         }
    545         return false;
    546     }
    547 
    548     /**
    549      * Close the socket and clean up the state of the class.
    550      * Don't use this function without first trying to use QUIT.
    551      * @see quit()
    552      * @access public
    553      * @return void
    554      */
    555     public function close()
    556     {
    557         $this->setError('');
    558         $this->server_caps = null;
    559         $this->helo_rply = null;
    560         if (is_resource($this->smtp_conn)) {
    561             // close the connection and cleanup
    562             fclose($this->smtp_conn);
    563             $this->smtp_conn = null; //Makes for cleaner serialization
    564             $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
    565         }
    566     }
    567 
    568     /**
    569      * Send an SMTP DATA command.
    570      * Issues a data command and sends the msg_data to the server,
    571      * finializing the mail transaction. $msg_data is the message
    572      * that is to be send with the headers. Each header needs to be
    573      * on a single line followed by a <CRLF> with the message headers
    574      * and the message body being separated by and additional <CRLF>.
    575      * Implements rfc 821: DATA <CRLF>
    576      * @param string $msg_data Message data to send
    577      * @access public
    578      * @return boolean
    579      */
    580     public function data($msg_data)
    581     {
    582         //This will use the standard timelimit
    583         if (!$this->sendCommand('DATA', 'DATA', 354)) {
    584             return false;
    585         }
    586 
    587         /* The server is ready to accept data!
    588          * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
    589          * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
    590          * smaller lines to fit within the limit.
    591          * We will also look for lines that start with a '.' and prepend an additional '.'.
    592          * NOTE: this does not count towards line-length limit.
    593          */
    594 
    595         // Normalize line breaks before exploding
    596         $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
    597 
    598         /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
    599          * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
    600          * process all lines before a blank line as headers.
    601          */
    602 
    603         $field = substr($lines[0], 0, strpos($lines[0], ':'));
    604         $in_headers = false;
    605         if (!empty($field) && strpos($field, ' ') === false) {
    606             $in_headers = true;
    607         }
    608 
    609         foreach ($lines as $line) {
    610             $lines_out = array();
    611             if ($in_headers and $line == '') {
    612                 $in_headers = false;
    613             }
    614             //Break this line up into several smaller lines if it's too long
    615             //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
    616             while (isset($line[self::MAX_LINE_LENGTH])) {
    617                 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
    618                 //so as to avoid breaking in the middle of a word
    619                 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
    620                 //Deliberately matches both false and 0
    621                 if (!$pos) {
    622                     //No nice break found, add a hard break
    623                     $pos = self::MAX_LINE_LENGTH - 1;
    624                     $lines_out[] = substr($line, 0, $pos);
    625                     $line = substr($line, $pos);
    626                 } else {
    627                     //Break at the found point
    628                     $lines_out[] = substr($line, 0, $pos);
    629                     //Move along by the amount we dealt with
    630                     $line = substr($line, $pos + 1);
    631                 }
    632                 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
    633                 if ($in_headers) {
    634                     $line = "\t" . $line;
    635                 }
    636             }
    637             $lines_out[] = $line;
    638 
    639             //Send the lines to the server
    640             foreach ($lines_out as $line_out) {
    641                 //RFC2821 section 4.5.2
    642                 if (!empty($line_out) and $line_out[0] == '.') {
    643                     $line_out = '.' . $line_out;
    644                 }
    645                 $this->client_send($line_out . self::CRLF);
    646             }
    647         }
    648 
    649         //Message data has been sent, complete the command
    650         //Increase timelimit for end of DATA command
    651         $savetimelimit = $this->Timelimit;
    652         $this->Timelimit = $this->Timelimit * 2;
    653         $result = $this->sendCommand('DATA END', '.', 250);
    654         $this->recordLastTransactionID();
    655         //Restore timelimit
    656         $this->Timelimit = $savetimelimit;
    657         return $result;
    658     }
    659 
    660     /**
    661      * Send an SMTP HELO or EHLO command.
    662      * Used to identify the sending server to the receiving server.
    663      * This makes sure that client and server are in a known state.
    664      * Implements RFC 821: HELO <SP> <domain> <CRLF>
    665      * and RFC 2821 EHLO.
    666      * @param string $host The host name or IP to connect to
    667      * @access public
    668      * @return boolean
    669      */
    670     public function hello($host = '')
    671     {
    672         //Try extended hello first (RFC 2821)
    673         return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
    674     }
    675 
    676     /**
    677      * Send an SMTP HELO or EHLO command.
    678      * Low-level implementation used by hello()
    679      * @see hello()
    680      * @param string $hello The HELO string
    681      * @param string $host The hostname to say we are
    682      * @access protected
    683      * @return boolean
    684      */
    685     protected function sendHello($hello, $host)
    686     {
    687         $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
    688         $this->helo_rply = $this->last_reply;
    689         if ($noerror) {
    690             $this->parseHelloFields($hello);
    691         } else {
    692             $this->server_caps = null;
    693         }
    694         return $noerror;
    695     }
    696 
    697     /**
    698      * Parse a reply to HELO/EHLO command to discover server extensions.
    699      * In case of HELO, the only parameter that can be discovered is a server name.
    700      * @access protected
    701      * @param string $type - 'HELO' or 'EHLO'
    702      */
    703     protected function parseHelloFields($type)
    704     {
    705         $this->server_caps = array();
    706         $lines = explode("\n", $this->helo_rply);
    707 
    708         foreach ($lines as $n => $s) {
    709             //First 4 chars contain response code followed by - or space
    710             $s = trim(substr($s, 4));
    711             if (empty($s)) {
    712                 continue;
    713             }
    714             $fields = explode(' ', $s);
    715             if (!empty($fields)) {
    716                 if (!$n) {
    717                     $name = $type;
    718                     $fields = $fields[0];
    719                 } else {
    720                     $name = array_shift($fields);
    721                     switch ($name) {
    722                         case 'SIZE':
    723                             $fields = ($fields ? $fields[0] : 0);
    724                             break;
    725                         case 'AUTH':
    726                             if (!is_array($fields)) {
    727                                 $fields = array();
    728                             }
    729                             break;
    730                         default:
    731                             $fields = true;
    732                     }
    733                 }
    734                 $this->server_caps[$name] = $fields;
    735             }
    736         }
    737     }
    738 
    739     /**
    740      * Send an SMTP MAIL command.
    741      * Starts a mail transaction from the email address specified in
    742      * $from. Returns true if successful or false otherwise. If True
    743      * the mail transaction is started and then one or more recipient
    744      * commands may be called followed by a data command.
    745      * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
    746      * @param string $from Source address of this message
    747      * @access public
    748      * @return boolean
    749      */
    750     public function mail($from)
    751     {
    752         $useVerp = ($this->do_verp ? ' XVERP' : '');
    753         return $this->sendCommand(
    754             'MAIL FROM',
    755             'MAIL FROM:<' . $from . '>' . $useVerp,
    756             250
    757         );
    758     }
    759 
    760     /**
    761      * Send an SMTP QUIT command.
    762      * Closes the socket if there is no error or the $close_on_error argument is true.
    763      * Implements from rfc 821: QUIT <CRLF>
    764      * @param boolean $close_on_error Should the connection close if an error occurs?
    765      * @access public
    766      * @return boolean
    767      */
    768     public function quit($close_on_error = true)
    769     {
    770         $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
    771         $err = $this->error; //Save any error
    772         if ($noerror or $close_on_error) {
    773             $this->close();
    774             $this->error = $err; //Restore any error from the quit command
    775         }
    776         return $noerror;
    777     }
    778 
    779     /**
    780      * Send an SMTP RCPT command.
    781      * Sets the TO argument to $toaddr.
    782      * Returns true if the recipient was accepted false if it was rejected.
    783      * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
    784      * @param string $address The address the message is being sent to
    785      * @access public
    786      * @return boolean
    787      */
    788     public function recipient($address)
    789     {
    790         return $this->sendCommand(
    791             'RCPT TO',
    792             'RCPT TO:<' . $address . '>',
    793             array(250, 251)
    794         );
    795     }
    796 
    797     /**
    798      * Send an SMTP RSET command.
    799      * Abort any transaction that is currently in progress.
    800      * Implements rfc 821: RSET <CRLF>
    801      * @access public
    802      * @return boolean True on success.
    803      */
    804     public function reset()
    805     {
    806         return $this->sendCommand('RSET', 'RSET', 250);
    807     }
    808 
    809     /**
    810      * Send a command to an SMTP server and check its return code.
    811      * @param string $command The command name - not sent to the server
    812      * @param string $commandstring The actual command to send
    813      * @param integer|array $expect One or more expected integer success codes
    814      * @access protected
    815      * @return boolean True on success.
    816      */
    817     protected function sendCommand($command, $commandstring, $expect)
    818     {
    819         if (!$this->connected()) {
    820             $this->setError("Called $command without being connected");
    821             return false;
    822         }
    823         //Reject line breaks in all commands
    824         if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
    825             $this->setError("Command '$command' contained line breaks");
    826             return false;
    827         }
    828         $this->client_send($commandstring . self::CRLF);
    829 
    830         $this->last_reply = $this->get_lines();
    831         // Fetch SMTP code and possible error code explanation
    832         $matches = array();
    833         if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
    834             $code = $matches[1];
    835             $code_ex = (count($matches) > 2 ? $matches[2] : null);
    836             // Cut off error code from each response line
    837             $detail = preg_replace(
    838                 "/{$code}[ -]" .
    839                 ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m",
    840                 '',
    841                 $this->last_reply
    842             );
    843         } else {
    844             // Fall back to simple parsing if regex fails
    845             $code = substr($this->last_reply, 0, 3);
    846             $code_ex = null;
    847             $detail = substr($this->last_reply, 4);
    848         }
    849 
    850         $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
    851 
    852         if (!in_array($code, (array)$expect)) {
    853             $this->setError(
    854                 "$command command failed",
    855                 $detail,
    856                 $code,
    857                 $code_ex
    858             );
    859             $this->edebug(
    860                 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
    861                 self::DEBUG_CLIENT
    862             );
    863             return false;
    864         }
    865 
    866         $this->setError('');
    867         return true;
    868     }
    869 
    870     /**
    871      * Send an SMTP SAML command.
    872      * Starts a mail transaction from the email address specified in $from.
    873      * Returns true if successful or false otherwise. If True
    874      * the mail transaction is started and then one or more recipient
    875      * commands may be called followed by a data command. This command
    876      * will send the message to the users terminal if they are logged
    877      * in and send them an email.
    878      * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
    879      * @param string $from The address the message is from
    880      * @access public
    881      * @return boolean
    882      */
    883     public function sendAndMail($from)
    884     {
    885         return $this->sendCommand('SAML', "SAML FROM:$from", 250);
    886     }
    887 
    888     /**
    889      * Send an SMTP VRFY command.
    890      * @param string $name The name to verify
    891      * @access public
    892      * @return boolean
    893      */
    894     public function verify($name)
    895     {
    896         return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
    897     }
    898 
    899     /**
    900      * Send an SMTP NOOP command.
    901      * Used to keep keep-alives alive, doesn't actually do anything
    902      * @access public
    903      * @return boolean
    904      */
    905     public function noop()
    906     {
    907         return $this->sendCommand('NOOP', 'NOOP', 250);
    908     }
    909 
    910     /**
    911      * Send an SMTP TURN command.
    912      * This is an optional command for SMTP that this class does not support.
    913      * This method is here to make the RFC821 Definition complete for this class
    914      * and _may_ be implemented in future
    915      * Implements from rfc 821: TURN <CRLF>
    916      * @access public
    917      * @return boolean
    918      */
    919     public function turn()
    920     {
    921         $this->setError('The SMTP TURN command is not implemented');
    922         $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
    923         return false;
    924     }
    925 
    926     /**
    927      * Send raw data to the server.
    928      * @param string $data The data to send
    929      * @access public
    930      * @return integer|boolean The number of bytes sent to the server or false on error
    931      */
    932     public function client_send($data)
    933     {
    934         $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
    935         set_error_handler(array($this, 'errorHandler'));
    936         $result = fwrite($this->smtp_conn, $data);
    937         restore_error_handler();
    938         return $result;
    939     }
    940 
    941     /**
    942      * Get the latest error.
    943      * @access public
    944      * @return array
    945      */
    946     public function getError()
    947     {
    948         return $this->error;
    949     }
    950 
    951     /**
    952      * Get SMTP extensions available on the server
    953      * @access public
    954      * @return array|null
    955      */
    956     public function getServerExtList()
    957     {
    958         return $this->server_caps;
    959     }
    960 
    961     /**
    962      * A multipurpose method
    963      * The method works in three ways, dependent on argument value and current state
    964      *   1. HELO/EHLO was not sent - returns null and set up $this->error
    965      *   2. HELO was sent
    966      *     $name = 'HELO': returns server name
    967      *     $name = 'EHLO': returns boolean false
    968      *     $name = any string: returns null and set up $this->error
    969      *   3. EHLO was sent
    970      *     $name = 'HELO'|'EHLO': returns server name
    971      *     $name = any string: if extension $name exists, returns boolean True
    972      *       or its options. Otherwise returns boolean False
    973      * In other words, one can use this method to detect 3 conditions:
    974      *  - null returned: handshake was not or we don't know about ext (refer to $this->error)
    975      *  - false returned: the requested feature exactly not exists
    976      *  - positive value returned: the requested feature exists
    977      * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
    978      * @return mixed
    979      */
    980     public function getServerExt($name)
    981     {
    982         if (!$this->server_caps) {
    983             $this->setError('No HELO/EHLO was sent');
    984             return null;
    985         }
    986 
    987         // the tight logic knot ;)
    988         if (!array_key_exists($name, $this->server_caps)) {
    989             if ($name == 'HELO') {
    990                 return $this->server_caps['EHLO'];
    991             }
    992             if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
    993                 return false;
    994             }
    995             $this->setError('HELO handshake was used. Client knows nothing about server extensions');
    996             return null;
    997         }
    998 
    999         return $this->server_caps[$name];
    1000     }
    1001 
    1002     /**
    1003      * Get the last reply from the server.
    1004      * @access public
    1005      * @return string
    1006      */
    1007     public function getLastReply()
    1008     {
    1009         return $this->last_reply;
    1010     }
    1011 
    1012     /**
    1013      * Read the SMTP server's response.
    1014      * Either before eof or socket timeout occurs on the operation.
    1015      * With SMTP we can tell if we have more lines to read if the
    1016      * 4th character is '-' symbol. If it is a space then we don't
    1017      * need to read anything else.
    1018      * @access protected
    1019      * @return string
    1020      */
    1021     protected function get_lines()
    1022     {
    1023         // If the connection is bad, give up straight away
    1024         if (!is_resource($this->smtp_conn)) {
    1025             return '';
    1026         }
    1027         $data = '';
    1028         $endtime = 0;
    1029         stream_set_timeout($this->smtp_conn, $this->Timeout);
    1030         if ($this->Timelimit > 0) {
    1031             $endtime = time() + $this->Timelimit;
    1032         }
    1033         while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
    1034             $str = @fgets($this->smtp_conn, 515);
    1035             $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
    1036             $this->edebug("SMTP -> get_lines(): \$str is  \"$str\"", self::DEBUG_LOWLEVEL);
    1037             $data .= $str;
    1038             // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
    1039             // or 4th character is a space, we are done reading, break the loop,
    1040             // string array access is a micro-optimisation over strlen
    1041             if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
    1042                 break;
    1043             }
    1044             // Timed-out? Log and break
    1045             $info = stream_get_meta_data($this->smtp_conn);
    1046             if ($info['timed_out']) {
    1047                 $this->edebug(
    1048                     'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
    1049                     self::DEBUG_LOWLEVEL
    1050                 );
    1051                 break;
    1052             }
    1053             // Now check if reads took too long
    1054             if ($endtime and time() > $endtime) {
    1055                 $this->edebug(
    1056                     'SMTP -> get_lines(): timelimit reached (' .
    1057                     $this->Timelimit . ' sec)',
    1058                     self::DEBUG_LOWLEVEL
    1059                 );
    1060                 break;
    1061             }
    1062         }
    1063         return $data;
    1064     }
    1065 
    1066     /**
    1067      * Enable or disable VERP address generation.
    1068      * @param boolean $enabled
    1069      */
    1070     public function setVerp($enabled = false)
    1071     {
    1072         $this->do_verp = $enabled;
    1073     }
    1074 
    1075     /**
    1076      * Get VERP address generation mode.
    1077      * @return boolean
    1078      */
    1079     public function getVerp()
    1080     {
    1081         return $this->do_verp;
    1082     }
    1083 
    1084     /**
    1085      * Set error messages and codes.
    1086      * @param string $message The error message
    1087      * @param string $detail Further detail on the error
    1088      * @param string $smtp_code An associated SMTP error code
    1089      * @param string $smtp_code_ex Extended SMTP code
    1090      */
    1091     protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
    1092     {
    1093         $this->error = array(
    1094             'error' => $message,
    1095             'detail' => $detail,
    1096             'smtp_code' => $smtp_code,
    1097             'smtp_code_ex' => $smtp_code_ex
    1098         );
    1099     }
    1100 
    1101     /**
    1102      * Set debug output method.
    1103      * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
    1104      */
    1105     public function setDebugOutput($method = 'echo')
    1106     {
    1107         $this->Debugoutput = $method;
    1108     }
    1109 
    1110     /**
    1111      * Get debug output method.
    1112      * @return string
    1113      */
    1114     public function getDebugOutput()
    1115     {
    1116         return $this->Debugoutput;
    1117     }
    1118 
    1119     /**
    1120      * Set debug output level.
    1121      * @param integer $level
    1122      */
    1123     public function setDebugLevel($level = 0)
    1124     {
    1125         $this->do_debug = $level;
    1126     }
    1127 
    1128     /**
    1129      * Get debug output level.
    1130      * @return integer
    1131      */
    1132     public function getDebugLevel()
    1133     {
    1134         return $this->do_debug;
    1135     }
    1136 
    1137     /**
    1138      * Set SMTP timeout.
    1139      * @param integer $timeout
    1140      */
    1141     public function setTimeout($timeout = 0)
    1142     {
    1143         $this->Timeout = $timeout;
    1144     }
    1145 
    1146     /**
    1147      * Get SMTP timeout.
    1148      * @return integer
    1149      */
    1150     public function getTimeout()
    1151     {
    1152         return $this->Timeout;
    1153     }
    1154 
    1155     /**
    1156      * Reports an error number and string.
    1157      * @param integer $errno The error number returned by PHP.
    1158      * @param string $errmsg The error message returned by PHP.
    1159      * @param string $errfile The file the error occurred in
    1160      * @param integer $errline The line number the error occurred on
    1161      */
    1162     protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
    1163     {
    1164         $notice = 'Connection failed.';
    1165         $this->setError(
    1166             $notice,
    1167             $errno,
    1168             $errmsg
    1169         );
    1170         $this->edebug(
    1171             $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]",
    1172             self::DEBUG_CONNECTION
    1173         );
    1174     }
    1175 
    1176     /**
    1177      * Extract and return the ID of the last SMTP transaction based on
    1178      * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
    1179      * Relies on the host providing the ID in response to a DATA command.
    1180      * If no reply has been received yet, it will return null.
    1181      * If no pattern was matched, it will return false.
    1182      * @return bool|null|string
    1183      */
    1184     protected function recordLastTransactionID()
    1185     {
    1186         $reply = $this->getLastReply();
    1187 
    1188         if (empty($reply)) {
    1189             $this->last_smtp_transaction_id = null;
    1190         } else {
    1191             $this->last_smtp_transaction_id = false;
    1192             foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
    1193                 if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
    1194                     $this->last_smtp_transaction_id = $matches[1];
    1195                 }
    1196             }
    1197         }
    1198 
    1199         return $this->last_smtp_transaction_id;
    1200     }
    1201 
    1202     /**
    1203      * Get the queue/transaction ID of the last SMTP transaction
    1204      * If no reply has been received yet, it will return null.
    1205      * If no pattern was matched, it will return false.
    1206      * @return bool|null|string
    1207      * @see recordLastTransactionID()
    1208      */
    1209     public function getLastTransactionID()
    1210     {
    1211         return $this->last_smtp_transaction_id;
    1212     }
    1213 }
     6_deprecated_file( basename( __FILE__ ), '5.5.0', WPINC . '/PHPMailer/SMTP.php', __( 'The SMTP class has been moved to the wp-includes/PHPMailer subdirectory and now uses the PHPMailer\PHPMailer namespace.' ) );
     7require __DIR__ . '/PHPMailer/SMTP.php';
  • src/wp-includes/pluggable.php

    diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php
    index 39180c6513..e48d0dca97 100644
    a b function cache_users( $user_ids ) { 
    158158         *
    159159         * @since 1.2.1
    160160         *
    161          * @global PHPMailer $phpmailer
     161         * @global PHPMailer\PHPMailer\PHPMailer $phpmailer
    162162         *
    163163         * @param string|array $to          Array or comma-separated list of email addresses to send message.
    164164         * @param string       $subject     Email subject
    function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() 
    210210                global $phpmailer;
    211211
    212212                // (Re)create it, if it's gone missing.
    213                 if ( ! ( $phpmailer instanceof PHPMailer ) ) {
    214                         require_once ABSPATH . WPINC . '/class-phpmailer.php';
    215                         require_once ABSPATH . WPINC . '/class-smtp.php';
    216                         $phpmailer = new PHPMailer( true );
     213                if ( ! ( $phpmailer instanceof PHPMailer\PHPMailer\PHPMailer ) ) {
     214                        require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
     215                        require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
     216                        require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
     217                        $phpmailer = new PHPMailer\PHPMailer\PHPMailer( true );
    217218                }
    218219
    219220                // Headers.
    function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() 
    356357
    357358                try {
    358359                        $phpmailer->setFrom( $from_email, $from_name, false );
    359                 } catch ( phpmailerException $e ) {
     360                } catch ( PHPMailer\PHPMailer\Exception $e ) {
    360361                        $mail_error_data                             = compact( 'to', 'subject', 'message', 'headers', 'attachments' );
    361362                        $mail_error_data['phpmailer_exception_code'] = $e->getCode();
    362363
    function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() 
    404405                                                        $phpmailer->addReplyTo( $address, $recipient_name );
    405406                                                        break;
    406407                                        }
    407                                 } catch ( phpmailerException $e ) {
     408                                } catch ( PHPMailer\PHPMailer\Exception $e ) {
    408409                                        continue;
    409410                                }
    410411                        }
    function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() 
    455456                        foreach ( (array) $headers as $name => $content ) {
    456457                                // Only add custom headers not added automatically by PHPMailer.
    457458                                if ( ! in_array( $name, array( 'MIME-Version', 'X-Mailer' ), true ) ) {
    458                                         $phpmailer->addCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
     459                                        try {
     460                                                $phpmailer->addCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
     461                                        } catch ( PHPMailer\PHPMailer\Exception $e ) {
     462                                                continue;
     463                                        }
    459464                                }
    460465                        }
    461466
    462467                        if ( false !== stripos( $content_type, 'multipart' ) && ! empty( $boundary ) ) {
    463                                 $phpmailer->addCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) );
     468                                $phpmailer->addCustomHeader( sprintf( 'Content-Type: %s; boundary="%s"', $content_type, $boundary ) );
    464469                        }
    465470                }
    466471
    function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() 
    468473                        foreach ( $attachments as $attachment ) {
    469474                                try {
    470475                                        $phpmailer->addAttachment( $attachment );
    471                                 } catch ( phpmailerException $e ) {
     476                                } catch ( PHPMailer\PHPMailer\Exception $e ) {
    472477                                        continue;
    473478                                }
    474479                        }
    function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() 
    486491                // Send!
    487492                try {
    488493                        return $phpmailer->send();
    489                 } catch ( phpmailerException $e ) {
     494                } catch ( PHPMailer\PHPMailer\Exception $e ) {
    490495
    491496                        $mail_error_data                             = compact( 'to', 'subject', 'message', 'headers', 'attachments' );
    492497                        $mail_error_data['phpmailer_exception_code'] = $e->getCode();
    493498
    494499                        /**
    495                          * Fires after a phpmailerException is caught.
     500                         * Fires after a PHPMailer\PHPMailer\Exception is caught.
    496501                         *
    497502                         * @since 4.4.0
    498503                         *
    499                          * @param WP_Error $error A WP_Error object with the phpmailerException message, and an array
     504                         * @param WP_Error $error A WP_Error object with the PHPMailer\PHPMailer\Exception message, and an array
    500505                         *                        containing the mail recipient, subject, message, headers, and attachments.
    501506                         */
    502507                        do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_failed', $e->getMessage(), $mail_error_data ) );
  • tests/phpunit/includes/mock-mailer.php

    diff --git a/tests/phpunit/includes/mock-mailer.php b/tests/phpunit/includes/mock-mailer.php
    index ef497ceacd..cae2cbc655 100644
    a b  
    11<?php
    2 require_once ABSPATH . '/wp-includes/class-phpmailer.php';
     2require_once ABSPATH . '/wp-includes/PHPMailer/PHPMailer.php';
     3require_once ABSPATH . '/wp-includes/PHPMailer/Exception.php';
    34
    4 class MockPHPMailer extends PHPMailer {
     5class MockPHPMailer extends PHPMailer\PHPMailer\PHPMailer {
    56        var $mock_sent = array();
    67
    78        function preSend() {
  • tests/phpunit/tests/mail.php

    diff --git a/tests/phpunit/tests/mail.php b/tests/phpunit/tests/mail.php
    index e2db915265..c5897a8bad 100644
    a b function test_wp_mail_custom_boundaries() { 
    8181
    8282                // We need some better assertions here but these catch the failure for now.
    8383                $this->assertEquals( $body, $mailer->get_sent()->body );
    84                 $this->assertTrue( strpos( $mailer->get_sent()->header, 'boundary="----=_Part_4892_25692638.1192452070893"' ) > 0 );
     84                $this->assertTrue( strpos( iconv_mime_decode_headers( ( $mailer->get_sent()->header ) )['Content-Type'][0], 'boundary="----=_Part_4892_25692638.1192452070893"' ) > 0 );
    8585                $this->assertTrue( strpos( $mailer->get_sent()->header, 'charset=' ) > 0 );
    8686        }
    8787