Guest User

Untitled

a guest
Dec 19th, 2020
194
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 225.78 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4.  * PHPMailer - PHP email creation and transport class.
  5.  * PHP Version 5.5.
  6.  *
  7.  * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
  8.  *
  9.  * @author    Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
  10.  * @author    Jim Jagielski (jimjag) <jimjag@gmail.com>
  11.  * @author    Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
  12.  * @author    Brent R. Matzelle (original founder)
  13.  * @copyright 2012 - 2020 Marcus Bointon
  14.  * @copyright 2010 - 2012 Jim Jagielski
  15.  * @copyright 2004 - 2009 Andy Prevost
  16.  * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  17.  * @note      This program is distributed in the hope that it will be useful - WITHOUT
  18.  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  19.  * FITNESS FOR A PARTICULAR PURPOSE.
  20.  */
  21.  
  22. /**
  23.  * PHPMailer - PHP email creation and transport class.
  24.  *
  25.  * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
  26.  * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
  27.  * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
  28.  * @author Brent R. Matzelle (original founder)
  29.  */
  30. class PHPMailer
  31. {
  32.     const CHARSET_ASCII = 'us-ascii';
  33.     const CHARSET_ISO88591 = 'iso-8859-1';
  34.     const CHARSET_UTF8 = 'utf-8';
  35.  
  36.     const CONTENT_TYPE_PLAINTEXT = 'text/plain';
  37.     const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar';
  38.     const CONTENT_TYPE_TEXT_HTML = 'text/html';
  39.     const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative';
  40.     const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed';
  41.     const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related';
  42.  
  43.     const ENCODING_7BIT = '7bit';
  44.     const ENCODING_8BIT = '8bit';
  45.     const ENCODING_BASE64 = 'base64';
  46.     const ENCODING_BINARY = 'binary';
  47.     const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
  48.  
  49.     const ENCRYPTION_STARTTLS = 'tls';
  50.     const ENCRYPTION_SMTPS = 'ssl';
  51.  
  52.     const ICAL_METHOD_REQUEST = 'REQUEST';
  53.     const ICAL_METHOD_PUBLISH = 'PUBLISH';
  54.     const ICAL_METHOD_REPLY = 'REPLY';
  55.     const ICAL_METHOD_ADD = 'ADD';
  56.     const ICAL_METHOD_CANCEL = 'CANCEL';
  57.     const ICAL_METHOD_REFRESH = 'REFRESH';
  58.     const ICAL_METHOD_COUNTER = 'COUNTER';
  59.     const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER';
  60.  
  61.     /**
  62.      * Email priority.
  63.      * Options: null (default), 1 = High, 3 = Normal, 5 = low.
  64.      * When null, the header is not set at all.
  65.      *
  66.      * @var int|null
  67.      */
  68.     public $Priority;
  69.  
  70.     /**
  71.      * The character set of the message.
  72.      *
  73.      * @var string
  74.      */
  75.     public $CharSet = self::CHARSET_ISO88591;
  76.  
  77.     /**
  78.      * The MIME Content-type of the message.
  79.      *
  80.      * @var string
  81.      */
  82.     public $ContentType = self::CONTENT_TYPE_PLAINTEXT;
  83.  
  84.     /**
  85.      * The message encoding.
  86.      * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
  87.      *
  88.      * @var string
  89.      */
  90.     public $Encoding = self::ENCODING_8BIT;
  91.  
  92.     /**
  93.      * Holds the most recent mailer error message.
  94.      *
  95.      * @var string
  96.      */
  97.     public $ErrorInfo = '';
  98.  
  99.     /**
  100.      * The From email address for the message.
  101.      *
  102.      * @var string
  103.      */
  104.     public $From = 'root@localhost';
  105.  
  106.     /**
  107.      * The From name of the message.
  108.      *
  109.      * @var string
  110.      */
  111.     public $FromName = 'Root User';
  112.  
  113.     /**
  114.      * The envelope sender of the message.
  115.      * This will usually be turned into a Return-Path header by the receiver,
  116.      * and is the address that bounces will be sent to.
  117.      * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP.
  118.      *
  119.      * @var string
  120.      */
  121.     public $Sender = '';
  122.  
  123.     /**
  124.      * The Subject of the message.
  125.      *
  126.      * @var string
  127.      */
  128.     public $Subject = '';
  129.  
  130.     /**
  131.      * An HTML or plain text message body.
  132.      * If HTML then call isHTML(true).
  133.      *
  134.      * @var string
  135.      */
  136.     public $Body = '';
  137.  
  138.     /**
  139.      * The plain-text message body.
  140.      * This body can be read by mail clients that do not have HTML email
  141.      * capability such as mutt & Eudora.
  142.      * Clients that can read HTML will view the normal Body.
  143.      *
  144.      * @var string
  145.      */
  146.     public $AltBody = '';
  147.  
  148.     /**
  149.      * An iCal message part body.
  150.      * Only supported in simple alt or alt_inline message types
  151.      * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator.
  152.      *
  153.      * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
  154.      * @see http://kigkonsult.se/iCalcreator/
  155.      *
  156.      * @var string
  157.      */
  158.     public $Ical = '';
  159.  
  160.     /**
  161.      * Value-array of "method" in Contenttype header "text/calendar"
  162.      *
  163.      * @var string[]
  164.      */
  165.     protected static $IcalMethods = [
  166.         self::ICAL_METHOD_REQUEST,
  167.         self::ICAL_METHOD_PUBLISH,
  168.         self::ICAL_METHOD_REPLY,
  169.         self::ICAL_METHOD_ADD,
  170.         self::ICAL_METHOD_CANCEL,
  171.         self::ICAL_METHOD_REFRESH,
  172.         self::ICAL_METHOD_COUNTER,
  173.         self::ICAL_METHOD_DECLINECOUNTER,
  174.     ];
  175.  
  176.     /**
  177.      * The complete compiled MIME message body.
  178.      *
  179.      * @var string
  180.      */
  181.     protected $MIMEBody = '';
  182.  
  183.     /**
  184.      * The complete compiled MIME message headers.
  185.      *
  186.      * @var string
  187.      */
  188.     protected $MIMEHeader = '';
  189.  
  190.     /**
  191.      * Extra headers that createHeader() doesn't fold in.
  192.      *
  193.      * @var string
  194.      */
  195.     protected $mailHeader = '';
  196.  
  197.     /**
  198.      * Word-wrap the message body to this number of chars.
  199.      * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
  200.      *
  201.      * @see static::STD_LINE_LENGTH
  202.      *
  203.      * @var int
  204.      */
  205.     public $WordWrap = 0;
  206.  
  207.     /**
  208.      * Which method to use to send mail.
  209.      * Options: "mail", "sendmail", or "smtp".
  210.      *
  211.      * @var string
  212.      */
  213.     public $Mailer = 'mail';
  214.  
  215.     /**
  216.      * The path to the sendmail program.
  217.      *
  218.      * @var string
  219.      */
  220.     public $Sendmail = '/usr/sbin/sendmail';
  221.  
  222.     /**
  223.      * Whether mail() uses a fully sendmail-compatible MTA.
  224.      * One which supports sendmail's "-oi -f" options.
  225.      *
  226.      * @var bool
  227.      */
  228.     public $UseSendmailOptions = true;
  229.  
  230.     /**
  231.      * The email address that a reading confirmation should be sent to, also known as read receipt.
  232.      *
  233.      * @var string
  234.      */
  235.     public $ConfirmReadingTo = '';
  236.  
  237.     /**
  238.      * The hostname to use in the Message-ID header and as default HELO string.
  239.      * If empty, PHPMailer attempts to find one with, in order,
  240.      * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
  241.      * 'localhost.localdomain'.
  242.      *
  243.      * @see PHPMailer::$Helo
  244.      *
  245.      * @var string
  246.      */
  247.     public $Hostname = '';
  248.  
  249.     /**
  250.      * An ID to be used in the Message-ID header.
  251.      * If empty, a unique id will be generated.
  252.      * You can set your own, but it must be in the format "<id@domain>",
  253.      * as defined in RFC5322 section 3.6.4 or it will be ignored.
  254.      *
  255.      * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
  256.      *
  257.      * @var string
  258.      */
  259.     public $MessageID = '';
  260.  
  261.     /**
  262.      * The message Date to be used in the Date header.
  263.      * If empty, the current date will be added.
  264.      *
  265.      * @var string
  266.      */
  267.     public $MessageDate = '';
  268.  
  269.     /**
  270.      * SMTP hosts.
  271.      * Either a single hostname or multiple semicolon-delimited hostnames.
  272.      * You can also specify a different port
  273.      * for each host by using this format: [hostname:port]
  274.      * (e.g. "smtp1.example.com:25;smtp2.example.com").
  275.      * You can also specify encryption type, for example:
  276.      * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
  277.      * Hosts will be tried in order.
  278.      *
  279.      * @var string
  280.      */
  281.     public $Host = 'localhost';
  282.  
  283.     /**
  284.      * The default SMTP server port.
  285.      *
  286.      * @var int
  287.      */
  288.     public $Port = 25;
  289.  
  290.     /**
  291.      * The SMTP HELO/EHLO name used for the SMTP connection.
  292.      * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
  293.      * one with the same method described above for $Hostname.
  294.      *
  295.      * @see PHPMailer::$Hostname
  296.      *
  297.      * @var string
  298.      */
  299.     public $Helo = '';
  300.  
  301.     /**
  302.      * What kind of encryption to use on the SMTP connection.
  303.      * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS.
  304.      *
  305.      * @var string
  306.      */
  307.     public $SMTPSecure = '';
  308.  
  309.     /**
  310.      * Whether to enable TLS encryption automatically if a server supports it,
  311.      * even if `SMTPSecure` is not set to 'tls'.
  312.      * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
  313.      *
  314.      * @var bool
  315.      */
  316.     public $SMTPAutoTLS = true;
  317.  
  318.     /**
  319.      * Whether to use SMTP authentication.
  320.      * Uses the Username and Password properties.
  321.      *
  322.      * @see PHPMailer::$Username
  323.      * @see PHPMailer::$Password
  324.      *
  325.      * @var bool
  326.      */
  327.     public $SMTPAuth = false;
  328.  
  329.     /**
  330.      * Options array passed to stream_context_create when connecting via SMTP.
  331.      *
  332.      * @var array
  333.      */
  334.     public $SMTPOptions = [];
  335.  
  336.     /**
  337.      * SMTP username.
  338.      *
  339.      * @var string
  340.      */
  341.     public $Username = '';
  342.  
  343.     /**
  344.      * SMTP password.
  345.      *
  346.      * @var string
  347.      */
  348.     public $Password = '';
  349.  
  350.     /**
  351.      * SMTP auth type.
  352.      * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified.
  353.      *
  354.      * @var string
  355.      */
  356.     public $AuthType = '';
  357.  
  358.     /**
  359.      * An instance of the PHPMailer OAuth class.
  360.      *
  361.      * @var OAuth
  362.      */
  363.     protected $oauth;
  364.  
  365.     /**
  366.      * The SMTP server timeout in seconds.
  367.      * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
  368.      *
  369.      * @var int
  370.      */
  371.     public $Timeout = 300;
  372.  
  373.     /**
  374.      * Comma separated list of DSN notifications
  375.      * 'NEVER' under no circumstances a DSN must be returned to the sender.
  376.      *         If you use NEVER all other notifications will be ignored.
  377.      * 'SUCCESS' will notify you when your mail has arrived at its destination.
  378.      * 'FAILURE' will arrive if an error occurred during delivery.
  379.      * 'DELAY'   will notify you if there is an unusual delay in delivery, but the actual
  380.      *           delivery's outcome (success or failure) is not yet decided.
  381.      *
  382.      * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY
  383.      */
  384.     public $dsn = '';
  385.  
  386.     /**
  387.      * SMTP class debug output mode.
  388.      * Debug output level.
  389.      * Options:
  390.      * @see SMTP::DEBUG_OFF: No output
  391.      * @see SMTP::DEBUG_CLIENT: Client messages
  392.      * @see SMTP::DEBUG_SERVER: Client and server messages
  393.      * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status
  394.      * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed
  395.      *
  396.      * @see SMTP::$do_debug
  397.      *
  398.      * @var int
  399.      */
  400.     public $SMTPDebug = 0;
  401.  
  402.     /**
  403.      * How to handle debug output.
  404.      * Options:
  405.      * * `echo` Output plain-text as-is, appropriate for CLI
  406.      * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
  407.      * * `error_log` Output to error log as configured in php.ini
  408.      * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise.
  409.      * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
  410.      *
  411.      * ```php
  412.      * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
  413.      * ```
  414.      *
  415.      * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
  416.      * level output is used:
  417.      *
  418.      * ```php
  419.      * $mail->Debugoutput = new myPsr3Logger;
  420.      * ```
  421.      *
  422.      * @see SMTP::$Debugoutput
  423.      *
  424.      * @var string|callable|\Psr\Log\LoggerInterface
  425.      */
  426.     public $Debugoutput = 'echo';
  427.  
  428.     /**
  429.      * Whether to keep SMTP connection open after each message.
  430.      * If this is set to true then to close the connection
  431.      * requires an explicit call to smtpClose().
  432.      *
  433.      * @var bool
  434.      */
  435.     public $SMTPKeepAlive = false;
  436.  
  437.     /**
  438.      * Whether to split multiple to addresses into multiple messages
  439.      * or send them all in one message.
  440.      * Only supported in `mail` and `sendmail` transports, not in SMTP.
  441.      *
  442.      * @var bool
  443.      *
  444.      * @deprecated 6.0.0 PHPMailer isn't a mailing list manager!
  445.      */
  446.     public $SingleTo = false;
  447.  
  448.     /**
  449.      * Storage for addresses when SingleTo is enabled.
  450.      *
  451.      * @var array
  452.      */
  453.     protected $SingleToArray = [];
  454.  
  455.     /**
  456.      * Whether to generate VERP addresses on send.
  457.      * Only applicable when sending via SMTP.
  458.      *
  459.      * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
  460.      * @see http://www.postfix.org/VERP_README.html Postfix VERP info
  461.      *
  462.      * @var bool
  463.      */
  464.     public $do_verp = false;
  465.  
  466.     /**
  467.      * Whether to allow sending messages with an empty body.
  468.      *
  469.      * @var bool
  470.      */
  471.     public $AllowEmpty = false;
  472.  
  473.     /**
  474.      * DKIM selector.
  475.      *
  476.      * @var string
  477.      */
  478.     public $DKIM_selector = '';
  479.  
  480.     /**
  481.      * DKIM Identity.
  482.      * Usually the email address used as the source of the email.
  483.      *
  484.      * @var string
  485.      */
  486.     public $DKIM_identity = '';
  487.  
  488.     /**
  489.      * DKIM passphrase.
  490.      * Used if your key is encrypted.
  491.      *
  492.      * @var string
  493.      */
  494.     public $DKIM_passphrase = '';
  495.  
  496.     /**
  497.      * DKIM signing domain name.
  498.      *
  499.      * @example 'example.com'
  500.      *
  501.      * @var string
  502.      */
  503.     public $DKIM_domain = '';
  504.  
  505.     /**
  506.      * DKIM Copy header field values for diagnostic use.
  507.      *
  508.      * @var bool
  509.      */
  510.     public $DKIM_copyHeaderFields = true;
  511.  
  512.     /**
  513.      * DKIM Extra signing headers.
  514.      *
  515.      * @example ['List-Unsubscribe', 'List-Help']
  516.      *
  517.      * @var array
  518.      */
  519.     public $DKIM_extraHeaders = [];
  520.  
  521.     /**
  522.      * DKIM private key file path.
  523.      *
  524.      * @var string
  525.      */
  526.     public $DKIM_private = '';
  527.  
  528.     /**
  529.      * DKIM private key string.
  530.      *
  531.      * If set, takes precedence over `$DKIM_private`.
  532.      *
  533.      * @var string
  534.      */
  535.     public $DKIM_private_string = '';
  536.  
  537.     /**
  538.      * Callback Action function name.
  539.      *
  540.      * The function that handles the result of the send email action.
  541.      * It is called out by send() for each email sent.
  542.      *
  543.      * Value can be any php callable: http://www.php.net/is_callable
  544.      *
  545.      * Parameters:
  546.      *   bool $result        result of the send action
  547.      *   array   $to            email addresses of the recipients
  548.      *   array   $cc            cc email addresses
  549.      *   array   $bcc           bcc email addresses
  550.      *   string  $subject       the subject
  551.      *   string  $body          the email body
  552.      *   string  $from          email address of sender
  553.      *   string  $extra         extra information of possible use
  554.      *                          "smtp_transaction_id' => last smtp transaction id
  555.      *
  556.      * @var string
  557.      */
  558.     public $action_function = '';
  559.  
  560.     /**
  561.      * What to put in the X-Mailer header.
  562.      * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use.
  563.      *
  564.      * @var string|null
  565.      */
  566.     public $XMailer = '';
  567.  
  568.     /**
  569.      * Which validator to use by default when validating email addresses.
  570.      * May be a callable to inject your own validator, but there are several built-in validators.
  571.      * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option.
  572.      *
  573.      * @see PHPMailer::validateAddress()
  574.      *
  575.      * @var string|callable
  576.      */
  577.     public static $validator = 'php';
  578.  
  579.     /**
  580.      * An instance of the SMTP sender class.
  581.      *
  582.      * @var SMTP
  583.      */
  584.     protected $smtp;
  585.  
  586.     /**
  587.      * The array of 'to' names and addresses.
  588.      *
  589.      * @var array
  590.      */
  591.     protected $to = [];
  592.  
  593.     /**
  594.      * The array of 'cc' names and addresses.
  595.      *
  596.      * @var array
  597.      */
  598.     protected $cc = [];
  599.  
  600.     /**
  601.      * The array of 'bcc' names and addresses.
  602.      *
  603.      * @var array
  604.      */
  605.     protected $bcc = [];
  606.  
  607.     /**
  608.      * The array of reply-to names and addresses.
  609.      *
  610.      * @var array
  611.      */
  612.     protected $ReplyTo = [];
  613.  
  614.     /**
  615.      * An array of all kinds of addresses.
  616.      * Includes all of $to, $cc, $bcc.
  617.      *
  618.      * @see PHPMailer::$to
  619.      * @see PHPMailer::$cc
  620.      * @see PHPMailer::$bcc
  621.      *
  622.      * @var array
  623.      */
  624.     protected $all_recipients = [];
  625.  
  626.     /**
  627.      * An array of names and addresses queued for validation.
  628.      * In send(), valid and non duplicate entries are moved to $all_recipients
  629.      * and one of $to, $cc, or $bcc.
  630.      * This array is used only for addresses with IDN.
  631.      *
  632.      * @see PHPMailer::$to
  633.      * @see PHPMailer::$cc
  634.      * @see PHPMailer::$bcc
  635.      * @see PHPMailer::$all_recipients
  636.      *
  637.      * @var array
  638.      */
  639.     protected $RecipientsQueue = [];
  640.  
  641.     /**
  642.      * An array of reply-to names and addresses queued for validation.
  643.      * In send(), valid and non duplicate entries are moved to $ReplyTo.
  644.      * This array is used only for addresses with IDN.
  645.      *
  646.      * @see PHPMailer::$ReplyTo
  647.      *
  648.      * @var array
  649.      */
  650.     protected $ReplyToQueue = [];
  651.  
  652.     /**
  653.      * The array of attachments.
  654.      *
  655.      * @var array
  656.      */
  657.     protected $attachment = [];
  658.  
  659.     /**
  660.      * The array of custom headers.
  661.      *
  662.      * @var array
  663.      */
  664.     protected $CustomHeader = [];
  665.  
  666.     /**
  667.      * The most recent Message-ID (including angular brackets).
  668.      *
  669.      * @var string
  670.      */
  671.     protected $lastMessageID = '';
  672.  
  673.     /**
  674.      * The message's MIME type.
  675.      *
  676.      * @var string
  677.      */
  678.     protected $message_type = '';
  679.  
  680.     /**
  681.      * The array of MIME boundary strings.
  682.      *
  683.      * @var array
  684.      */
  685.     protected $boundary = [];
  686.  
  687.     /**
  688.      * The array of available languages.
  689.      *
  690.      * @var array
  691.      */
  692.     protected $language = [];
  693.  
  694.     /**
  695.      * The number of errors encountered.
  696.      *
  697.      * @var int
  698.      */
  699.     protected $error_count = 0;
  700.  
  701.     /**
  702.      * The S/MIME certificate file path.
  703.      *
  704.      * @var string
  705.      */
  706.     protected $sign_cert_file = '';
  707.  
  708.     /**
  709.      * The S/MIME key file path.
  710.      *
  711.      * @var string
  712.      */
  713.     protected $sign_key_file = '';
  714.  
  715.     /**
  716.      * The optional S/MIME extra certificates ("CA Chain") file path.
  717.      *
  718.      * @var string
  719.      */
  720.     protected $sign_extracerts_file = '';
  721.  
  722.     /**
  723.      * The S/MIME password for the key.
  724.      * Used only if the key is encrypted.
  725.      *
  726.      * @var string
  727.      */
  728.     protected $sign_key_pass = '';
  729.  
  730.     /**
  731.      * Whether to throw exceptions for errors.
  732.      *
  733.      * @var bool
  734.      */
  735.     protected $exceptions = false;
  736.  
  737.     /**
  738.      * Unique ID used for message ID and boundaries.
  739.      *
  740.      * @var string
  741.      */
  742.     protected $uniqueid = '';
  743.  
  744.     /**
  745.      * The PHPMailer Version number.
  746.      *
  747.      * @var string
  748.      */
  749.     const VERSION = '6.2.0';
  750.  
  751.     /**
  752.      * Error severity: message only, continue processing.
  753.      *
  754.      * @var int
  755.      */
  756.     const STOP_MESSAGE = 0;
  757.  
  758.     /**
  759.      * Error severity: message, likely ok to continue processing.
  760.      *
  761.      * @var int
  762.      */
  763.     const STOP_CONTINUE = 1;
  764.  
  765.     /**
  766.      * Error severity: message, plus full stop, critical error reached.
  767.      *
  768.      * @var int
  769.      */
  770.     const STOP_CRITICAL = 2;
  771.  
  772.     /**
  773.      * The SMTP standard CRLF line break.
  774.      * If you want to change line break format, change static::$LE, not this.
  775.      */
  776.     const CRLF = "\r\n";
  777.  
  778.     /**
  779.      * "Folding White Space" a white space string used for line folding.
  780.      */
  781.     const FWS = ' ';
  782.  
  783.     /**
  784.      * SMTP RFC standard line ending; Carriage Return, Line Feed.
  785.      *
  786.      * @var string
  787.      */
  788.     protected static $LE = self::CRLF;
  789.  
  790.     /**
  791.      * The maximum line length supported by mail().
  792.      *
  793.      * Background: mail() will sometimes corrupt messages
  794.      * with headers headers longer than 65 chars, see #818.
  795.      *
  796.      * @var int
  797.      */
  798.     const MAIL_MAX_LINE_LENGTH = 63;
  799.  
  800.     /**
  801.      * The maximum line length allowed by RFC 2822 section 2.1.1.
  802.      *
  803.      * @var int
  804.      */
  805.     const MAX_LINE_LENGTH = 998;
  806.  
  807.     /**
  808.      * The lower maximum line length allowed by RFC 2822 section 2.1.1.
  809.      * This length does NOT include the line break
  810.      * 76 means that lines will be 77 or 78 chars depending on whether
  811.      * the line break format is LF or CRLF; both are valid.
  812.      *
  813.      * @var int
  814.      */
  815.     const STD_LINE_LENGTH = 76;
  816.  
  817.     /**
  818.      * Constructor.
  819.      *
  820.      * @param bool $exceptions Should we throw external exceptions?
  821.      */
  822.     public function __construct($exceptions = null)
  823.     {
  824.         if (null !== $exceptions) {
  825.             $this->exceptions = (bool) $exceptions;
  826.         }
  827.         //Pick an appropriate debug output format automatically
  828.         $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
  829.     }
  830.  
  831.     /**
  832.      * Destructor.
  833.      */
  834.     public function __destruct()
  835.     {
  836.         //Close any open SMTP connection nicely
  837.         $this->smtpClose();
  838.     }
  839.  
  840.     /**
  841.      * Call mail() in a safe_mode-aware fashion.
  842.      * Also, unless sendmail_path points to sendmail (or something that
  843.      * claims to be sendmail), don't pass params (not a perfect fix,
  844.      * but it will do).
  845.      *
  846.      * @param string      $to      To
  847.      * @param string      $subject Subject
  848.      * @param string      $body    Message Body
  849.      * @param string      $header  Additional Header(s)
  850.      * @param string|null $params  Params
  851.      *
  852.      * @return bool
  853.      */
  854.     private function mailPassthru($to, $subject, $body, $header, $params)
  855.     {
  856.         //Check overloading of mail function to avoid double-encoding
  857.         if (ini_get('mbstring.func_overload') & 1) {
  858.             $subject = $this->secureHeader($subject);
  859.         } else {
  860.             $subject = $this->encodeHeader($this->secureHeader($subject));
  861.         }
  862.         //Calling mail() with null params breaks
  863.         if (!$this->UseSendmailOptions || null === $params) {
  864.             $result = @mail($to, $subject, $body, $header);
  865.         } else {
  866.             $result = @mail($to, $subject, $body, $header, $params);
  867.         }
  868.  
  869.         return $result;
  870.     }
  871.  
  872.     /**
  873.      * Output debugging info via user-defined method.
  874.      * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
  875.      *
  876.      * @see PHPMailer::$Debugoutput
  877.      * @see PHPMailer::$SMTPDebug
  878.      *
  879.      * @param string $str
  880.      */
  881.     protected function edebug($str)
  882.     {
  883.         if ($this->SMTPDebug <= 0) {
  884.             return;
  885.         }
  886.         //Is this a PSR-3 logger?
  887.         if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
  888.             $this->Debugoutput->debug($str);
  889.  
  890.             return;
  891.         }
  892.         //Avoid clash with built-in function names
  893.         if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
  894.             call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
  895.  
  896.             return;
  897.         }
  898.         switch ($this->Debugoutput) {
  899.             case 'error_log':
  900.                 //Don't output, just log
  901.                 /** @noinspection ForgottenDebugOutputInspection */
  902.                 error_log($str);
  903.                 break;
  904.             case 'html':
  905.                 //Cleans up output a bit for a better looking, HTML-safe output
  906.                 echo htmlentities(
  907.                     preg_replace('/[\r\n]+/', '', $str),
  908.                     ENT_QUOTES,
  909.                     'UTF-8'
  910.                 ), "<br>\n";
  911.                 break;
  912.             case 'echo':
  913.             default:
  914.                 //Normalize line breaks
  915.                 $str = preg_replace('/\r\n|\r/m', "\n", $str);
  916.                 echo gmdate('Y-m-d H:i:s'),
  917.                 "\t",
  918.                     //Trim trailing space
  919.                 trim(
  920.                     //Indent for readability, except for trailing break
  921.                     str_replace(
  922.                         "\n",
  923.                         "\n                   \t                  ",
  924.                         trim($str)
  925.                     )
  926.                 ),
  927.                 "\n";
  928.         }
  929.     }
  930.  
  931.     /**
  932.      * Sets message type to HTML or plain.
  933.      *
  934.      * @param bool $isHtml True for HTML mode
  935.      */
  936.     public function isHTML($isHtml = true)
  937.     {
  938.         if ($isHtml) {
  939.             $this->ContentType = static::CONTENT_TYPE_TEXT_HTML;
  940.         } else {
  941.             $this->ContentType = static::CONTENT_TYPE_PLAINTEXT;
  942.         }
  943.     }
  944.  
  945.     /**
  946.      * Send messages using SMTP.
  947.      */
  948.     public function isSMTP()
  949.     {
  950.         $this->Mailer = 'smtp';
  951.     }
  952.  
  953.     /**
  954.      * Send messages using PHP's mail() function.
  955.      */
  956.     public function isMail()
  957.     {
  958.         $this->Mailer = 'mail';
  959.     }
  960.  
  961.     /**
  962.      * Send messages using $Sendmail.
  963.      */
  964.     public function isSendmail()
  965.     {
  966.         $ini_sendmail_path = ini_get('sendmail_path');
  967.  
  968.         if (false === stripos($ini_sendmail_path, 'sendmail')) {
  969.             $this->Sendmail = '/usr/sbin/sendmail';
  970.         } else {
  971.             $this->Sendmail = $ini_sendmail_path;
  972.         }
  973.         $this->Mailer = 'sendmail';
  974.     }
  975.  
  976.     /**
  977.      * Send messages using qmail.
  978.      */
  979.     public function isQmail()
  980.     {
  981.         $ini_sendmail_path = ini_get('sendmail_path');
  982.  
  983.         if (false === stripos($ini_sendmail_path, 'qmail')) {
  984.             $this->Sendmail = '/var/qmail/bin/qmail-inject';
  985.         } else {
  986.             $this->Sendmail = $ini_sendmail_path;
  987.         }
  988.         $this->Mailer = 'qmail';
  989.     }
  990.  
  991.     /**
  992.      * Add a "To" address.
  993.      *
  994.      * @param string $address The email address to send to
  995.      * @param string $name
  996.      *
  997.      * @throws PHPMailerException
  998.      *
  999.      * @return bool true on success, false if address already used or invalid in some way
  1000.      */
  1001.     public function addAddress($address, $name = '')
  1002.     {
  1003.         return $this->addOrEnqueueAnAddress('to', $address, $name);
  1004.     }
  1005.  
  1006.     /**
  1007.      * Add a "CC" address.
  1008.      *
  1009.      * @param string $address The email address to send to
  1010.      * @param string $name
  1011.      *
  1012.      * @throws PHPMailerException
  1013.      *
  1014.      * @return bool true on success, false if address already used or invalid in some way
  1015.      */
  1016.     public function addCC($address, $name = '')
  1017.     {
  1018.         return $this->addOrEnqueueAnAddress('cc', $address, $name);
  1019.     }
  1020.  
  1021.     /**
  1022.      * Add a "BCC" address.
  1023.      *
  1024.      * @param string $address The email address to send to
  1025.      * @param string $name
  1026.      *
  1027.      * @throws PHPMailerException
  1028.      *
  1029.      * @return bool true on success, false if address already used or invalid in some way
  1030.      */
  1031.     public function addBCC($address, $name = '')
  1032.     {
  1033.         return $this->addOrEnqueueAnAddress('bcc', $address, $name);
  1034.     }
  1035.  
  1036.     /**
  1037.      * Add a "Reply-To" address.
  1038.      *
  1039.      * @param string $address The email address to reply to
  1040.      * @param string $name
  1041.      *
  1042.      * @throws PHPMailerException
  1043.      *
  1044.      * @return bool true on success, false if address already used or invalid in some way
  1045.      */
  1046.     public function addReplyTo($address, $name = '')
  1047.     {
  1048.         return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
  1049.     }
  1050.  
  1051.     /**
  1052.      * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
  1053.      * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
  1054.      * be modified after calling this function), addition of such addresses is delayed until send().
  1055.      * Addresses that have been added already return false, but do not throw exceptions.
  1056.      *
  1057.      * @param string $kind    One of 'to', 'cc', 'bcc', or 'ReplyTo'
  1058.      * @param string $address The email address to send, resp. to reply to
  1059.      * @param string $name
  1060.      *
  1061.      * @throws PHPMailerException
  1062.      *
  1063.      * @return bool true on success, false if address already used or invalid in some way
  1064.      */
  1065.     protected function addOrEnqueueAnAddress($kind, $address, $name)
  1066.     {
  1067.         $address = trim($address);
  1068.         $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
  1069.         $pos = strrpos($address, '@');
  1070.         if (false === $pos) {
  1071.             // At-sign is missing.
  1072.             $error_message = sprintf(
  1073.                 '%s (%s): %s',
  1074.                 $this->lang('invalid_address'),
  1075.                 $kind,
  1076.                 $address
  1077.             );
  1078.             $this->setError($error_message);
  1079.             $this->edebug($error_message);
  1080.             if ($this->exceptions) {
  1081.                 throw new PHPMailerException($error_message);
  1082.             }
  1083.  
  1084.             return false;
  1085.         }
  1086.         $params = [$kind, $address, $name];
  1087.         // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
  1088.         if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) {
  1089.             if ('Reply-To' !== $kind) {
  1090.                 if (!array_key_exists($address, $this->RecipientsQueue)) {
  1091.                     $this->RecipientsQueue[$address] = $params;
  1092.  
  1093.                     return true;
  1094.                 }
  1095.             } elseif (!array_key_exists($address, $this->ReplyToQueue)) {
  1096.                 $this->ReplyToQueue[$address] = $params;
  1097.  
  1098.                 return true;
  1099.             }
  1100.  
  1101.             return false;
  1102.         }
  1103.  
  1104.         // Immediately add standard addresses without IDN.
  1105.         return call_user_func_array([$this, 'addAnAddress'], $params);
  1106.     }
  1107.  
  1108.     /**
  1109.      * Add an address to one of the recipient arrays or to the ReplyTo array.
  1110.      * Addresses that have been added already return false, but do not throw exceptions.
  1111.      *
  1112.      * @param string $kind    One of 'to', 'cc', 'bcc', or 'ReplyTo'
  1113.      * @param string $address The email address to send, resp. to reply to
  1114.      * @param string $name
  1115.      *
  1116.      * @throws PHPMailerException
  1117.      *
  1118.      * @return bool true on success, false if address already used or invalid in some way
  1119.      */
  1120.     protected function addAnAddress($kind, $address, $name = '')
  1121.     {
  1122.         if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
  1123.             $error_message = sprintf(
  1124.                 '%s: %s',
  1125.                 $this->lang('Invalid recipient kind'),
  1126.                 $kind
  1127.             );
  1128.             $this->setError($error_message);
  1129.             $this->edebug($error_message);
  1130.             if ($this->exceptions) {
  1131.                 throw new PHPMailerException($error_message);
  1132.             }
  1133.  
  1134.             return false;
  1135.         }
  1136.         if (!static::validateAddress($address)) {
  1137.             $error_message = sprintf(
  1138.                 '%s (%s): %s',
  1139.                 $this->lang('invalid_address'),
  1140.                 $kind,
  1141.                 $address
  1142.             );
  1143.             $this->setError($error_message);
  1144.             $this->edebug($error_message);
  1145.             if ($this->exceptions) {
  1146.                 throw new PHPMailerException($error_message);
  1147.             }
  1148.  
  1149.             return false;
  1150.         }
  1151.         if ('Reply-To' !== $kind) {
  1152.             if (!array_key_exists(strtolower($address), $this->all_recipients)) {
  1153.                 $this->{$kind}[] = [$address, $name];
  1154.                 $this->all_recipients[strtolower($address)] = true;
  1155.  
  1156.                 return true;
  1157.             }
  1158.         } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
  1159.             $this->ReplyTo[strtolower($address)] = [$address, $name];
  1160.  
  1161.             return true;
  1162.         }
  1163.  
  1164.         return false;
  1165.     }
  1166.  
  1167.     /**
  1168.      * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
  1169.      * of the form "display name <address>" into an array of name/address pairs.
  1170.      * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
  1171.      * Note that quotes in the name part are removed.
  1172.      *
  1173.      * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
  1174.      *
  1175.      * @param string $addrstr The address list string
  1176.      * @param bool   $useimap Whether to use the IMAP extension to parse the list
  1177.      *
  1178.      * @return array
  1179.      */
  1180.     public static function parseAddresses($addrstr, $useimap = true)
  1181.     {
  1182.         $addresses = [];
  1183.         if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
  1184.             //Use this built-in parser if it's available
  1185.             $list = imap_rfc822_parse_adrlist($addrstr, '');
  1186.             foreach ($list as $address) {
  1187.                 if (
  1188.                     ('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress(
  1189.                         $address->mailbox . '@' . $address->host
  1190.                     )
  1191.                 ) {
  1192.                     $addresses[] = [
  1193.                         'name' => (property_exists($address, 'personal') ? $address->personal : ''),
  1194.                         'address' => $address->mailbox . '@' . $address->host,
  1195.                     ];
  1196.                 }
  1197.             }
  1198.         } else {
  1199.             //Use this simpler parser
  1200.             $list = explode(',', $addrstr);
  1201.             foreach ($list as $address) {
  1202.                 $address = trim($address);
  1203.                 //Is there a separate name part?
  1204.                 if (strpos($address, '<') === false) {
  1205.                     //No separate name, just use the whole thing
  1206.                     if (static::validateAddress($address)) {
  1207.                         $addresses[] = [
  1208.                             'name' => '',
  1209.                             'address' => $address,
  1210.                         ];
  1211.                     }
  1212.                 } else {
  1213.                     list($name, $email) = explode('<', $address);
  1214.                     $email = trim(str_replace('>', '', $email));
  1215.                     if (static::validateAddress($email)) {
  1216.                         $addresses[] = [
  1217.                             'name' => trim(str_replace(['"', "'"], '', $name)),
  1218.                             'address' => $email,
  1219.                         ];
  1220.                     }
  1221.                 }
  1222.             }
  1223.         }
  1224.  
  1225.         return $addresses;
  1226.     }
  1227.  
  1228.     /**
  1229.      * Set the From and FromName properties.
  1230.      *
  1231.      * @param string $address
  1232.      * @param string $name
  1233.      * @param bool   $auto    Whether to also set the Sender address, defaults to true
  1234.      *
  1235.      * @throws PHPMailerException
  1236.      *
  1237.      * @return bool
  1238.      */
  1239.     public function setFrom($address, $name = '', $auto = true)
  1240.     {
  1241.         $address = trim($address);
  1242.         $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
  1243.         // Don't validate now addresses with IDN. Will be done in send().
  1244.         $pos = strrpos($address, '@');
  1245.         if (
  1246.             (false === $pos)
  1247.             || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported())
  1248.             && !static::validateAddress($address))
  1249.         ) {
  1250.             $error_message = sprintf(
  1251.                 '%s (From): %s',
  1252.                 $this->lang('invalid_address'),
  1253.                 $address
  1254.             );
  1255.             $this->setError($error_message);
  1256.             $this->edebug($error_message);
  1257.             if ($this->exceptions) {
  1258.                 throw new PHPMailerException($error_message);
  1259.             }
  1260.  
  1261.             return false;
  1262.         }
  1263.         $this->From = $address;
  1264.         $this->FromName = $name;
  1265.         if ($auto && empty($this->Sender)) {
  1266.             $this->Sender = $address;
  1267.         }
  1268.  
  1269.         return true;
  1270.     }
  1271.  
  1272.     /**
  1273.      * Return the Message-ID header of the last email.
  1274.      * Technically this is the value from the last time the headers were created,
  1275.      * but it's also the message ID of the last sent message except in
  1276.      * pathological cases.
  1277.      *
  1278.      * @return string
  1279.      */
  1280.     public function getLastMessageID()
  1281.     {
  1282.         return $this->lastMessageID;
  1283.     }
  1284.  
  1285.     /**
  1286.      * Check that a string looks like an email address.
  1287.      * Validation patterns supported:
  1288.      * * `auto` Pick best pattern automatically;
  1289.      * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0;
  1290.      * * `pcre` Use old PCRE implementation;
  1291.      * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
  1292.      * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
  1293.      * * `noregex` Don't use a regex: super fast, really dumb.
  1294.      * Alternatively you may pass in a callable to inject your own validator, for example:
  1295.      *
  1296.      * ```php
  1297.      * PHPMailer::validateAddress('user@example.com', function($address) {
  1298.      *     return (strpos($address, '@') !== false);
  1299.      * });
  1300.      * ```
  1301.      *
  1302.      * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
  1303.      *
  1304.      * @param string          $address       The email address to check
  1305.      * @param string|callable $patternselect Which pattern to use
  1306.      *
  1307.      * @return bool
  1308.      */
  1309.     public static function validateAddress($address, $patternselect = null)
  1310.     {
  1311.         if (null === $patternselect) {
  1312.             $patternselect = static::$validator;
  1313.         }
  1314.         if (is_callable($patternselect)) {
  1315.             return call_user_func($patternselect, $address);
  1316.         }
  1317.         //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
  1318.         if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) {
  1319.             return false;
  1320.         }
  1321.         switch ($patternselect) {
  1322.             case 'pcre': //Kept for BC
  1323.             case 'pcre8':
  1324.                 /*
  1325.                  * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL
  1326.                  * is based.
  1327.                  * In addition to the addresses allowed by filter_var, also permits:
  1328.                  *  * dotless domains: `a@b`
  1329.                  *  * comments: `1234 @ local(blah) .machine .example`
  1330.                  *  * quoted elements: `'"test blah"@example.org'`
  1331.                  *  * numeric TLDs: `a@b.123`
  1332.                  *  * unbracketed IPv4 literals: `a@192.168.0.1`
  1333.                  *  * IPv6 literals: 'first.last@[IPv6:a1::]'
  1334.                  * Not all of these will necessarily work for sending!
  1335.                  *
  1336.                  * @see       http://squiloople.com/2009/12/20/email-address-validation/
  1337.                  * @copyright 2009-2010 Michael Rushton
  1338.                  * Feel free to use and redistribute this code. But please keep this copyright notice.
  1339.                  */
  1340.                 return (bool) preg_match(
  1341.                     '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
  1342.                     '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
  1343.                     '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
  1344.                     '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
  1345.                     '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
  1346.                     '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
  1347.                     '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
  1348.                     '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
  1349.                     '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
  1350.                     $address
  1351.                 );
  1352.             case 'html5':
  1353.                 /*
  1354.                  * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
  1355.                  *
  1356.                  * @see https://html.spec.whatwg.org/#e-mail-state-(type=email)
  1357.                  */
  1358.                 return (bool) preg_match(
  1359.                     '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
  1360.                     '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
  1361.                     $address
  1362.                 );
  1363.             case 'php':
  1364.             default:
  1365.                 return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
  1366.         }
  1367.     }
  1368.  
  1369.     /**
  1370.      * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
  1371.      * `intl` and `mbstring` PHP extensions.
  1372.      *
  1373.      * @return bool `true` if required functions for IDN support are present
  1374.      */
  1375.     public static function idnSupported()
  1376.     {
  1377.         return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding');
  1378.     }
  1379.  
  1380.     /**
  1381.      * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
  1382.      * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
  1383.      * This function silently returns unmodified address if:
  1384.      * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
  1385.      * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
  1386.      *   or fails for any reason (e.g. domain contains characters not allowed in an IDN).
  1387.      *
  1388.      * @see PHPMailer::$CharSet
  1389.      *
  1390.      * @param string $address The email address to convert
  1391.      *
  1392.      * @return string The encoded address in ASCII form
  1393.      */
  1394.     public function punyencodeAddress($address)
  1395.     {
  1396.         // Verify we have required functions, CharSet, and at-sign.
  1397.         $pos = strrpos($address, '@');
  1398.         if (
  1399.             !empty($this->CharSet) &&
  1400.             false !== $pos &&
  1401.             static::idnSupported()
  1402.         ) {
  1403.             $domain = substr($address, ++$pos);
  1404.             // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
  1405.             if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) {
  1406.                 $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
  1407.                 //Ignore IDE complaints about this line - method signature changed in PHP 5.4
  1408.                 $errorcode = 0;
  1409.                 if (defined('INTL_IDNA_VARIANT_UTS46')) {
  1410.                     $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46);
  1411.                 } elseif (defined('INTL_IDNA_VARIANT_2003')) {
  1412.                     $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003);
  1413.                 } else {
  1414.                     $punycode = idn_to_ascii($domain, $errorcode);
  1415.                 }
  1416.                 if (false !== $punycode) {
  1417.                     return substr($address, 0, $pos) . $punycode;
  1418.                 }
  1419.             }
  1420.         }
  1421.  
  1422.         return $address;
  1423.     }
  1424.  
  1425.     /**
  1426.      * Create a message and send it.
  1427.      * Uses the sending method specified by $Mailer.
  1428.      *
  1429.      * @throws PHPMailerException
  1430.      *
  1431.      * @return bool false on error - See the ErrorInfo property for details of the error
  1432.      */
  1433.     public function send()
  1434.     {
  1435.         try {
  1436.             if (!$this->preSend()) {
  1437.                 return false;
  1438.             }
  1439.  
  1440.             return $this->postSend();
  1441.         } catch (PHPMailerException $exc) {
  1442.             $this->mailHeader = '';
  1443.             $this->setError($exc->getMessage());
  1444.             if ($this->exceptions) {
  1445.                 throw $exc;
  1446.             }
  1447.  
  1448.             return false;
  1449.         }
  1450.     }
  1451.  
  1452.     /**
  1453.      * Prepare a message for sending.
  1454.      *
  1455.      * @throws PHPMailerException
  1456.      *
  1457.      * @return bool
  1458.      */
  1459.     public function preSend()
  1460.     {
  1461.         if (
  1462.             'smtp' === $this->Mailer
  1463.             || ('mail' === $this->Mailer && (PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0))
  1464.         ) {
  1465.             //SMTP mandates RFC-compliant line endings
  1466.             //and it's also used with mail() on Windows
  1467.             static::setLE(self::CRLF);
  1468.         } else {
  1469.             //Maintain backward compatibility with legacy Linux command line mailers
  1470.             static::setLE(PHP_EOL);
  1471.         }
  1472.         //Check for buggy PHP versions that add a header with an incorrect line break
  1473.         if (
  1474.             'mail' === $this->Mailer
  1475.             && ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017)
  1476.                 || (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103))
  1477.             && ini_get('mail.add_x_header') === '1'
  1478.             && stripos(PHP_OS, 'WIN') === 0
  1479.         ) {
  1480.             trigger_error(
  1481.                 'Your version of PHP is affected by a bug that may result in corrupted messages.' .
  1482.                 ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' .
  1483.                 ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
  1484.                 E_USER_WARNING
  1485.             );
  1486.         }
  1487.  
  1488.         try {
  1489.             $this->error_count = 0; // Reset errors
  1490.             $this->mailHeader = '';
  1491.  
  1492.             // Dequeue recipient and Reply-To addresses with IDN
  1493.             foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
  1494.                 $params[1] = $this->punyencodeAddress($params[1]);
  1495.                 call_user_func_array([$this, 'addAnAddress'], $params);
  1496.             }
  1497.             if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
  1498.                 throw new PHPMailerException($this->lang('provide_address'), self::STOP_CRITICAL);
  1499.             }
  1500.  
  1501.             // Validate From, Sender, and ConfirmReadingTo addresses
  1502.             foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
  1503.                 $this->$address_kind = trim($this->$address_kind);
  1504.                 if (empty($this->$address_kind)) {
  1505.                     continue;
  1506.                 }
  1507.                 $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
  1508.                 if (!static::validateAddress($this->$address_kind)) {
  1509.                     $error_message = sprintf(
  1510.                         '%s (%s): %s',
  1511.                         $this->lang('invalid_address'),
  1512.                         $address_kind,
  1513.                         $this->$address_kind
  1514.                     );
  1515.                     $this->setError($error_message);
  1516.                     $this->edebug($error_message);
  1517.                     if ($this->exceptions) {
  1518.                         throw new PHPMailerException($error_message);
  1519.                     }
  1520.  
  1521.                     return false;
  1522.                 }
  1523.             }
  1524.  
  1525.             // Set whether the message is multipart/alternative
  1526.             if ($this->alternativeExists()) {
  1527.                 $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;
  1528.             }
  1529.  
  1530.             $this->setMessageType();
  1531.             // Refuse to send an empty message unless we are specifically allowing it
  1532.             if (!$this->AllowEmpty && empty($this->Body)) {
  1533.                 throw new PHPMailerException($this->lang('empty_message'), self::STOP_CRITICAL);
  1534.             }
  1535.  
  1536.             //Trim subject consistently
  1537.             $this->Subject = trim($this->Subject);
  1538.             // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
  1539.             $this->MIMEHeader = '';
  1540.             $this->MIMEBody = $this->createBody();
  1541.             // createBody may have added some headers, so retain them
  1542.             $tempheaders = $this->MIMEHeader;
  1543.             $this->MIMEHeader = $this->createHeader();
  1544.             $this->MIMEHeader .= $tempheaders;
  1545.  
  1546.             // To capture the complete message when using mail(), create
  1547.             // an extra header list which createHeader() doesn't fold in
  1548.             if ('mail' === $this->Mailer) {
  1549.                 if (count($this->to) > 0) {
  1550.                     $this->mailHeader .= $this->addrAppend('To', $this->to);
  1551.                 } else {
  1552.                     $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
  1553.                 }
  1554.                 $this->mailHeader .= $this->headerLine(
  1555.                     'Subject',
  1556.                     $this->encodeHeader($this->secureHeader($this->Subject))
  1557.                 );
  1558.             }
  1559.  
  1560.             // Sign with DKIM if enabled
  1561.             if (
  1562.                 !empty($this->DKIM_domain)
  1563.                 && !empty($this->DKIM_selector)
  1564.                 && (!empty($this->DKIM_private_string)
  1565.                     || (!empty($this->DKIM_private)
  1566.                         && static::isPermittedPath($this->DKIM_private)
  1567.                         && file_exists($this->DKIM_private)
  1568.                     )
  1569.                 )
  1570.             ) {
  1571.                 $header_dkim = $this->DKIM_Add(
  1572.                     $this->MIMEHeader . $this->mailHeader,
  1573.                     $this->encodeHeader($this->secureHeader($this->Subject)),
  1574.                     $this->MIMEBody
  1575.                 );
  1576.                 $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE .
  1577.                     static::normalizeBreaks($header_dkim) . static::$LE;
  1578.             }
  1579.  
  1580.             return true;
  1581.         } catch (PHPMailerException $exc) {
  1582.             $this->setError($exc->getMessage());
  1583.             if ($this->exceptions) {
  1584.                 throw $exc;
  1585.             }
  1586.  
  1587.             return false;
  1588.         }
  1589.     }
  1590.  
  1591.     /**
  1592.      * Actually send a message via the selected mechanism.
  1593.      *
  1594.      * @throws PHPMailerException
  1595.      *
  1596.      * @return bool
  1597.      */
  1598.     public function postSend()
  1599.     {
  1600.         try {
  1601.             // Choose the mailer and send through it
  1602.             switch ($this->Mailer) {
  1603.                 case 'sendmail':
  1604.                 case 'qmail':
  1605.                     return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
  1606.                 case 'smtp':
  1607.                     return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
  1608.                 case 'mail':
  1609.                     return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
  1610.                 default:
  1611.                     $sendMethod = $this->Mailer . 'Send';
  1612.                     if (method_exists($this, $sendMethod)) {
  1613.                         return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
  1614.                     }
  1615.  
  1616.                     return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
  1617.             }
  1618.         } catch (PHPMailerException $exc) {
  1619.             if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true) {
  1620.                 $this->smtp->reset();
  1621.             }
  1622.             $this->setError($exc->getMessage());
  1623.             $this->edebug($exc->getMessage());
  1624.             if ($this->exceptions) {
  1625.                 throw $exc;
  1626.             }
  1627.         }
  1628.  
  1629.         return false;
  1630.     }
  1631.  
  1632.     /**
  1633.      * Send mail using the $Sendmail program.
  1634.      *
  1635.      * @see PHPMailer::$Sendmail
  1636.      *
  1637.      * @param string $header The message headers
  1638.      * @param string $body   The message body
  1639.      *
  1640.      * @throws PHPMailerException
  1641.      *
  1642.      * @return bool
  1643.      */
  1644.     protected function sendmailSend($header, $body)
  1645.     {
  1646.         $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
  1647.  
  1648.         // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
  1649.         if (!empty($this->Sender) && self::isShellSafe($this->Sender)) {
  1650.             if ('qmail' === $this->Mailer) {
  1651.                 $sendmailFmt = '%s -f%s';
  1652.             } else {
  1653.                 $sendmailFmt = '%s -oi -f%s -t';
  1654.             }
  1655.         } elseif ('qmail' === $this->Mailer) {
  1656.             $sendmailFmt = '%s';
  1657.         } else {
  1658.             $sendmailFmt = '%s -oi -t';
  1659.         }
  1660.  
  1661.         $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
  1662.  
  1663.         if ($this->SingleTo) {
  1664.             foreach ($this->SingleToArray as $toAddr) {
  1665.                 $mail = @popen($sendmail, 'w');
  1666.                 if (!$mail) {
  1667.                     throw new PHPMailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
  1668.                 }
  1669.                 fwrite($mail, 'To: ' . $toAddr . "\n");
  1670.                 fwrite($mail, $header);
  1671.                 fwrite($mail, $body);
  1672.                 $result = pclose($mail);
  1673.                 $this->doCallback(
  1674.                     ($result === 0),
  1675.                     [$toAddr],
  1676.                     $this->cc,
  1677.                     $this->bcc,
  1678.                     $this->Subject,
  1679.                     $body,
  1680.                     $this->From,
  1681.                     []
  1682.                 );
  1683.                 if (0 !== $result) {
  1684.                     throw new PHPMailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
  1685.                 }
  1686.             }
  1687.         } else {
  1688.             $mail = @popen($sendmail, 'w');
  1689.             if (!$mail) {
  1690.                 throw new PHPMailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
  1691.             }
  1692.             fwrite($mail, $header);
  1693.             fwrite($mail, $body);
  1694.             $result = pclose($mail);
  1695.             $this->doCallback(
  1696.                 ($result === 0),
  1697.                 $this->to,
  1698.                 $this->cc,
  1699.                 $this->bcc,
  1700.                 $this->Subject,
  1701.                 $body,
  1702.                 $this->From,
  1703.                 []
  1704.             );
  1705.             if (0 !== $result) {
  1706.                 throw new PHPMailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
  1707.             }
  1708.         }
  1709.  
  1710.         return true;
  1711.     }
  1712.  
  1713.     /**
  1714.      * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
  1715.      * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
  1716.      *
  1717.      * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
  1718.      *
  1719.      * @param string $string The string to be validated
  1720.      *
  1721.      * @return bool
  1722.      */
  1723.     protected static function isShellSafe($string)
  1724.     {
  1725.         // Future-proof
  1726.         if (
  1727.             escapeshellcmd($string) !== $string
  1728.             || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
  1729.         ) {
  1730.             return false;
  1731.         }
  1732.  
  1733.         $length = strlen($string);
  1734.  
  1735.         for ($i = 0; $i < $length; ++$i) {
  1736.             $c = $string[$i];
  1737.  
  1738.             // All other characters have a special meaning in at least one common shell, including = and +.
  1739.             // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
  1740.             // Note that this does permit non-Latin alphanumeric characters based on the current locale.
  1741.             if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
  1742.                 return false;
  1743.             }
  1744.         }
  1745.  
  1746.         return true;
  1747.     }
  1748.  
  1749.     /**
  1750.      * Check whether a file path is of a permitted type.
  1751.      * Used to reject URLs and phar files from functions that access local file paths,
  1752.      * such as addAttachment.
  1753.      *
  1754.      * @param string $path A relative or absolute path to a file
  1755.      *
  1756.      * @return bool
  1757.      */
  1758.     protected static function isPermittedPath($path)
  1759.     {
  1760.         return !preg_match('#^[a-z]+://#i', $path);
  1761.     }
  1762.  
  1763.     /**
  1764.      * Check whether a file path is safe, accessible, and readable.
  1765.      *
  1766.      * @param string $path A relative or absolute path to a file
  1767.      *
  1768.      * @return bool
  1769.      */
  1770.     protected static function fileIsAccessible($path)
  1771.     {
  1772.         $readable = file_exists($path);
  1773.         //If not a UNC path (expected to start with \\), check read permission, see #2069
  1774.         if (strpos($path, '\\\\') !== 0) {
  1775.             $readable = $readable && is_readable($path);
  1776.         }
  1777.         return static::isPermittedPath($path) && $readable;
  1778.     }
  1779.  
  1780.     /**
  1781.      * Send mail using the PHP mail() function.
  1782.      *
  1783.      * @see http://www.php.net/manual/en/book.mail.php
  1784.      *
  1785.      * @param string $header The message headers
  1786.      * @param string $body   The message body
  1787.      *
  1788.      * @throws PHPMailerException
  1789.      *
  1790.      * @return bool
  1791.      */
  1792.     protected function mailSend($header, $body)
  1793.     {
  1794.         $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
  1795.  
  1796.         $toArr = [];
  1797.         foreach ($this->to as $toaddr) {
  1798.             $toArr[] = $this->addrFormat($toaddr);
  1799.         }
  1800.         $to = implode(', ', $toArr);
  1801.  
  1802.         $params = null;
  1803.         //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
  1804.         //A space after `-f` is optional, but there is a long history of its presence
  1805.         //causing problems, so we don't use one
  1806.         //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
  1807.         //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
  1808.         //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
  1809.         //Example problem: https://www.drupal.org/node/1057954
  1810.         // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
  1811.         if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {
  1812.             $params = sprintf('-f%s', $this->Sender);
  1813.         }
  1814.         if (!empty($this->Sender) && static::validateAddress($this->Sender)) {
  1815.             $old_from = ini_get('sendmail_from');
  1816.             ini_set('sendmail_from', $this->Sender);
  1817.         }
  1818.         $result = false;
  1819.         if ($this->SingleTo && count($toArr) > 1) {
  1820.             foreach ($toArr as $toAddr) {
  1821.                 $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
  1822.                 $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
  1823.             }
  1824.         } else {
  1825.             $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
  1826.             $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
  1827.         }
  1828.         if (isset($old_from)) {
  1829.             ini_set('sendmail_from', $old_from);
  1830.         }
  1831.         if (!$result) {
  1832.             throw new PHPMailerException($this->lang('instantiate'), self::STOP_CRITICAL);
  1833.         }
  1834.  
  1835.         return true;
  1836.     }
  1837.  
  1838.     /**
  1839.      * Get an instance to use for SMTP operations.
  1840.      * Override this function to load your own SMTP implementation,
  1841.      * or set one with setSMTPInstance.
  1842.      *
  1843.      * @return SMTP
  1844.      */
  1845.     public function getSMTPInstance()
  1846.     {
  1847.         if (!is_object($this->smtp)) {
  1848.             $this->smtp = new SMTP();
  1849.         }
  1850.  
  1851.         return $this->smtp;
  1852.     }
  1853.  
  1854.     /**
  1855.      * Provide an instance to use for SMTP operations.
  1856.      *
  1857.      * @return SMTP
  1858.      */
  1859.     public function setSMTPInstance(SMTP $smtp)
  1860.     {
  1861.         $this->smtp = $smtp;
  1862.  
  1863.         return $this->smtp;
  1864.     }
  1865.  
  1866.     /**
  1867.      * Send mail via SMTP.
  1868.      * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
  1869.      *
  1870.      * @see PHPMailer::setSMTPInstance() to use a different class.
  1871.      *
  1872.      * @uses \PHPMailer\PHPMailer\SMTP
  1873.      *
  1874.      * @param string $header The message headers
  1875.      * @param string $body   The message body
  1876.      *
  1877.      * @throws PHPMailerException
  1878.      *
  1879.      * @return bool
  1880.      */
  1881.     protected function smtpSend($header, $body)
  1882.     {
  1883.         $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
  1884.         $bad_rcpt = [];
  1885.         if (!$this->smtpConnect($this->SMTPOptions)) {
  1886.             throw new PHPMailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
  1887.         }
  1888.         //Sender already validated in preSend()
  1889.         if ('' === $this->Sender) {
  1890.             $smtp_from = $this->From;
  1891.         } else {
  1892.             $smtp_from = $this->Sender;
  1893.         }
  1894.         if (!$this->smtp->mail($smtp_from)) {
  1895.             $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
  1896.             throw new PHPMailerException($this->ErrorInfo, self::STOP_CRITICAL);
  1897.         }
  1898.  
  1899.         $callbacks = [];
  1900.         // Attempt to send to all recipients
  1901.         foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {
  1902.             foreach ($togroup as $to) {
  1903.                 if (!$this->smtp->recipient($to[0], $this->dsn)) {
  1904.                     $error = $this->smtp->getError();
  1905.                     $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
  1906.                     $isSent = false;
  1907.                 } else {
  1908.                     $isSent = true;
  1909.                 }
  1910.  
  1911.                 $callbacks[] = ['issent' => $isSent, 'to' => $to[0]];
  1912.             }
  1913.         }
  1914.  
  1915.         // Only send the DATA command if we have viable recipients
  1916.         if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
  1917.             throw new PHPMailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
  1918.         }
  1919.  
  1920.         $smtp_transaction_id = $this->smtp->getLastTransactionID();
  1921.  
  1922.         if ($this->SMTPKeepAlive) {
  1923.             $this->smtp->reset();
  1924.         } else {
  1925.             $this->smtp->quit();
  1926.             $this->smtp->close();
  1927.         }
  1928.  
  1929.         foreach ($callbacks as $cb) {
  1930.             $this->doCallback(
  1931.                 $cb['issent'],
  1932.                 [$cb['to']],
  1933.                 [],
  1934.                 [],
  1935.                 $this->Subject,
  1936.                 $body,
  1937.                 $this->From,
  1938.                 ['smtp_transaction_id' => $smtp_transaction_id]
  1939.             );
  1940.         }
  1941.  
  1942.         //Create error message for any bad addresses
  1943.         if (count($bad_rcpt) > 0) {
  1944.             $errstr = '';
  1945.             foreach ($bad_rcpt as $bad) {
  1946.                 $errstr .= $bad['to'] . ': ' . $bad['error'];
  1947.             }
  1948.             throw new PHPMailerException($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
  1949.         }
  1950.  
  1951.         return true;
  1952.     }
  1953.  
  1954.     /**
  1955.      * Initiate a connection to an SMTP server.
  1956.      * Returns false if the operation failed.
  1957.      *
  1958.      * @param array $options An array of options compatible with stream_context_create()
  1959.      *
  1960.      * @throws PHPMailerException
  1961.      *
  1962.      * @uses \PHPMailer\PHPMailer\SMTP
  1963.      *
  1964.      * @return bool
  1965.      */
  1966.     public function smtpConnect($options = null)
  1967.     {
  1968.         if (null === $this->smtp) {
  1969.             $this->smtp = $this->getSMTPInstance();
  1970.         }
  1971.  
  1972.         //If no options are provided, use whatever is set in the instance
  1973.         if (null === $options) {
  1974.             $options = $this->SMTPOptions;
  1975.         }
  1976.  
  1977.         // Already connected?
  1978.         if ($this->smtp->connected()) {
  1979.             return true;
  1980.         }
  1981.  
  1982.         $this->smtp->setTimeout($this->Timeout);
  1983.         $this->smtp->setDebugLevel($this->SMTPDebug);
  1984.         $this->smtp->setDebugOutput($this->Debugoutput);
  1985.         $this->smtp->setVerp($this->do_verp);
  1986.         $hosts = explode(';', $this->Host);
  1987.         $lastexception = null;
  1988.  
  1989.         foreach ($hosts as $hostentry) {
  1990.             $hostinfo = [];
  1991.             if (
  1992.                 !preg_match(
  1993.                     '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/',
  1994.                     trim($hostentry),
  1995.                     $hostinfo
  1996.                 )
  1997.             ) {
  1998.                 $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
  1999.                 // Not a valid host entry
  2000.                 continue;
  2001.             }
  2002.             // $hostinfo[1]: optional ssl or tls prefix
  2003.             // $hostinfo[2]: the hostname
  2004.             // $hostinfo[3]: optional port number
  2005.             // The host string prefix can temporarily override the current setting for SMTPSecure
  2006.             // If it's not specified, the default value is used
  2007.  
  2008.             //Check the host name is a valid name or IP address before trying to use it
  2009.             if (!static::isValidHost($hostinfo[2])) {
  2010.                 $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
  2011.                 continue;
  2012.             }
  2013.             $prefix = '';
  2014.             $secure = $this->SMTPSecure;
  2015.             $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure);
  2016.             if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) {
  2017.                 $prefix = 'ssl://';
  2018.                 $tls = false; // Can't have SSL and TLS at the same time
  2019.                 $secure = static::ENCRYPTION_SMTPS;
  2020.             } elseif ('tls' === $hostinfo[1]) {
  2021.                 $tls = true;
  2022.                 // tls doesn't use a prefix
  2023.                 $secure = static::ENCRYPTION_STARTTLS;
  2024.             }
  2025.             //Do we need the OpenSSL extension?
  2026.             $sslext = defined('OPENSSL_ALGO_SHA256');
  2027.             if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
  2028.                 //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
  2029.                 if (!$sslext) {
  2030.                     throw new PHPMailerException($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
  2031.                 }
  2032.             }
  2033.             $host = $hostinfo[2];
  2034.             $port = $this->Port;
  2035.             if (
  2036.                 array_key_exists(3, $hostinfo) &&
  2037.                 is_numeric($hostinfo[3]) &&
  2038.                 $hostinfo[3] > 0 &&
  2039.                 $hostinfo[3] < 65536
  2040.             ) {
  2041.                 $port = (int) $hostinfo[3];
  2042.             }
  2043.             if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
  2044.                 try {
  2045.                     if ($this->Helo) {
  2046.                         $hello = $this->Helo;
  2047.                     } else {
  2048.                         $hello = $this->serverHostname();
  2049.                     }
  2050.                     $this->smtp->hello($hello);
  2051.                     //Automatically enable TLS encryption if:
  2052.                     // * it's not disabled
  2053.                     // * we have openssl extension
  2054.                     // * we are not already using SSL
  2055.                     // * the server offers STARTTLS
  2056.                     if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) {
  2057.                         $tls = true;
  2058.                     }
  2059.                     if ($tls) {
  2060.                         if (!$this->smtp->startTLS()) {
  2061.                             throw new PHPMailerException($this->lang('connect_host'));
  2062.                         }
  2063.                         // We must resend EHLO after TLS negotiation
  2064.                         $this->smtp->hello($hello);
  2065.                     }
  2066.                     if (
  2067.                         $this->SMTPAuth && !$this->smtp->authenticate(
  2068.                             $this->Username,
  2069.                             $this->Password,
  2070.                             $this->AuthType,
  2071.                             $this->oauth
  2072.                         )
  2073.                     ) {
  2074.                         throw new PHPMailerException($this->lang('authenticate'));
  2075.                     }
  2076.  
  2077.                     return true;
  2078.                 } catch (PHPMailerException $exc) {
  2079.                     $lastexception = $exc;
  2080.                     $this->edebug($exc->getMessage());
  2081.                     // We must have connected, but then failed TLS or Auth, so close connection nicely
  2082.                     $this->smtp->quit();
  2083.                 }
  2084.             }
  2085.         }
  2086.         // If we get here, all connection attempts have failed, so close connection hard
  2087.         $this->smtp->close();
  2088.         // As we've caught all exceptions, just report whatever the last one was
  2089.         if ($this->exceptions && null !== $lastexception) {
  2090.             throw $lastexception;
  2091.         }
  2092.  
  2093.         return false;
  2094.     }
  2095.  
  2096.     /**
  2097.      * Close the active SMTP session if one exists.
  2098.      */
  2099.     public function smtpClose()
  2100.     {
  2101.         if ((null !== $this->smtp) && $this->smtp->connected()) {
  2102.             $this->smtp->quit();
  2103.             $this->smtp->close();
  2104.         }
  2105.     }
  2106.  
  2107.     /**
  2108.      * Set the language for error messages.
  2109.      * Returns false if it cannot load the language file.
  2110.      * The default language is English.
  2111.      *
  2112.      * @param string $langcode  ISO 639-1 2-character language code (e.g. French is "fr")
  2113.      * @param string $lang_path Path to the language file directory, with trailing separator (slash)
  2114.      *
  2115.      * @return bool
  2116.      */
  2117.     public function setLanguage($langcode = 'en', $lang_path = '')
  2118.     {
  2119.         // Backwards compatibility for renamed language codes
  2120.         $renamed_langcodes = [
  2121.             'br' => 'pt_br',
  2122.             'cz' => 'cs',
  2123.             'dk' => 'da',
  2124.             'no' => 'nb',
  2125.             'se' => 'sv',
  2126.             'rs' => 'sr',
  2127.             'tg' => 'tl',
  2128.             'am' => 'hy',
  2129.         ];
  2130.  
  2131.         if (array_key_exists($langcode, $renamed_langcodes)) {
  2132.             $langcode = $renamed_langcodes[$langcode];
  2133.         }
  2134.  
  2135.         // Define full set of translatable strings in English
  2136.         $PHPMAILER_LANG = [
  2137.             'authenticate' => 'SMTP Error: Could not authenticate.',
  2138.             'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
  2139.             'data_not_accepted' => 'SMTP Error: data not accepted.',
  2140.             'empty_message' => 'Message body empty',
  2141.             'encoding' => 'Unknown encoding: ',
  2142.             'execute' => 'Could not execute: ',
  2143.             'file_access' => 'Could not access file: ',
  2144.             'file_open' => 'File Error: Could not open file: ',
  2145.             'from_failed' => 'The following From address failed: ',
  2146.             'instantiate' => 'Could not instantiate mail function.',
  2147.             'invalid_address' => 'Invalid address: ',
  2148.             'invalid_hostentry' => 'Invalid hostentry: ',
  2149.             'invalid_host' => 'Invalid host: ',
  2150.             'mailer_not_supported' => ' mailer is not supported.',
  2151.             'provide_address' => 'You must provide at least one recipient email address.',
  2152.             'recipients_failed' => 'SMTP Error: The following recipients failed: ',
  2153.             'signing' => 'Signing Error: ',
  2154.             'smtp_connect_failed' => 'SMTP connect() failed.',
  2155.             'smtp_error' => 'SMTP server error: ',
  2156.             'variable_set' => 'Cannot set or reset variable: ',
  2157.             'extension_missing' => 'Extension missing: ',
  2158.         ];
  2159.         if (empty($lang_path)) {
  2160.             // Calculate an absolute path so it can work if CWD is not here
  2161.             $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR;
  2162.         }
  2163.         //Validate $langcode
  2164.         if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
  2165.             $langcode = 'en';
  2166.         }
  2167.         $foundlang = true;
  2168.         $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
  2169.         // There is no English translation file
  2170.         if ('en' !== $langcode) {
  2171.             // Make sure language file path is readable
  2172.             if (!static::fileIsAccessible($lang_file)) {
  2173.                 $foundlang = false;
  2174.             } else {
  2175.                 // Overwrite language-specific strings.
  2176.                 // This way we'll never have missing translation keys.
  2177.                 $foundlang = include $lang_file;
  2178.             }
  2179.         }
  2180.         $this->language = $PHPMAILER_LANG;
  2181.  
  2182.         return (bool) $foundlang; // Returns false if language not found
  2183.     }
  2184.  
  2185.     /**
  2186.      * Get the array of strings for the current language.
  2187.      *
  2188.      * @return array
  2189.      */
  2190.     public function getTranslations()
  2191.     {
  2192.         return $this->language;
  2193.     }
  2194.  
  2195.     /**
  2196.      * Create recipient headers.
  2197.      *
  2198.      * @param string $type
  2199.      * @param array  $addr An array of recipients,
  2200.      *                     where each recipient is a 2-element indexed array with element 0 containing an address
  2201.      *                     and element 1 containing a name, like:
  2202.      *                     [['joe@example.com', 'Joe User'], ['zoe@example.com', 'Zoe User']]
  2203.      *
  2204.      * @return string
  2205.      */
  2206.     public function addrAppend($type, $addr)
  2207.     {
  2208.         $addresses = [];
  2209.         foreach ($addr as $address) {
  2210.             $addresses[] = $this->addrFormat($address);
  2211.         }
  2212.  
  2213.         return $type . ': ' . implode(', ', $addresses) . static::$LE;
  2214.     }
  2215.  
  2216.     /**
  2217.      * Format an address for use in a message header.
  2218.      *
  2219.      * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like
  2220.      *                    ['joe@example.com', 'Joe User']
  2221.      *
  2222.      * @return string
  2223.      */
  2224.     public function addrFormat($addr)
  2225.     {
  2226.         if (empty($addr[1])) { // No name provided
  2227.             return $this->secureHeader($addr[0]);
  2228.         }
  2229.  
  2230.         return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') .
  2231.             ' <' . $this->secureHeader($addr[0]) . '>';
  2232.     }
  2233.  
  2234.     /**
  2235.      * Word-wrap message.
  2236.      * For use with mailers that do not automatically perform wrapping
  2237.      * and for quoted-printable encoded messages.
  2238.      * Original written by philippe.
  2239.      *
  2240.      * @param string $message The message to wrap
  2241.      * @param int    $length  The line length to wrap to
  2242.      * @param bool   $qp_mode Whether to run in Quoted-Printable mode
  2243.      *
  2244.      * @return string
  2245.      */
  2246.     public function wrapText($message, $length, $qp_mode = false)
  2247.     {
  2248.         if ($qp_mode) {
  2249.             $soft_break = sprintf(' =%s', static::$LE);
  2250.         } else {
  2251.             $soft_break = static::$LE;
  2252.         }
  2253.         // If utf-8 encoding is used, we will need to make sure we don't
  2254.         // split multibyte characters when we wrap
  2255.         $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);
  2256.         $lelen = strlen(static::$LE);
  2257.         $crlflen = strlen(static::$LE);
  2258.  
  2259.         $message = static::normalizeBreaks($message);
  2260.         //Remove a trailing line break
  2261.         if (substr($message, -$lelen) === static::$LE) {
  2262.             $message = substr($message, 0, -$lelen);
  2263.         }
  2264.  
  2265.         //Split message into lines
  2266.         $lines = explode(static::$LE, $message);
  2267.         //Message will be rebuilt in here
  2268.         $message = '';
  2269.         foreach ($lines as $line) {
  2270.             $words = explode(' ', $line);
  2271.             $buf = '';
  2272.             $firstword = true;
  2273.             foreach ($words as $word) {
  2274.                 if ($qp_mode && (strlen($word) > $length)) {
  2275.                     $space_left = $length - strlen($buf) - $crlflen;
  2276.                     if (!$firstword) {
  2277.                         if ($space_left > 20) {
  2278.                             $len = $space_left;
  2279.                             if ($is_utf8) {
  2280.                                 $len = $this->utf8CharBoundary($word, $len);
  2281.                             } elseif ('=' === substr($word, $len - 1, 1)) {
  2282.                                 --$len;
  2283.                             } elseif ('=' === substr($word, $len - 2, 1)) {
  2284.                                 $len -= 2;
  2285.                             }
  2286.                             $part = substr($word, 0, $len);
  2287.                             $word = substr($word, $len);
  2288.                             $buf .= ' ' . $part;
  2289.                             $message .= $buf . sprintf('=%s', static::$LE);
  2290.                         } else {
  2291.                             $message .= $buf . $soft_break;
  2292.                         }
  2293.                         $buf = '';
  2294.                     }
  2295.                     while ($word !== '') {
  2296.                         if ($length <= 0) {
  2297.                             break;
  2298.                         }
  2299.                         $len = $length;
  2300.                         if ($is_utf8) {
  2301.                             $len = $this->utf8CharBoundary($word, $len);
  2302.                         } elseif ('=' === substr($word, $len - 1, 1)) {
  2303.                             --$len;
  2304.                         } elseif ('=' === substr($word, $len - 2, 1)) {
  2305.                             $len -= 2;
  2306.                         }
  2307.                         $part = substr($word, 0, $len);
  2308.                         $word = (string) substr($word, $len);
  2309.  
  2310.                         if ($word !== '') {
  2311.                             $message .= $part . sprintf('=%s', static::$LE);
  2312.                         } else {
  2313.                             $buf = $part;
  2314.                         }
  2315.                     }
  2316.                 } else {
  2317.                     $buf_o = $buf;
  2318.                     if (!$firstword) {
  2319.                         $buf .= ' ';
  2320.                     }
  2321.                     $buf .= $word;
  2322.  
  2323.                     if ('' !== $buf_o && strlen($buf) > $length) {
  2324.                         $message .= $buf_o . $soft_break;
  2325.                         $buf = $word;
  2326.                     }
  2327.                 }
  2328.                 $firstword = false;
  2329.             }
  2330.             $message .= $buf . static::$LE;
  2331.         }
  2332.  
  2333.         return $message;
  2334.     }
  2335.  
  2336.     /**
  2337.      * Find the last character boundary prior to $maxLength in a utf-8
  2338.      * quoted-printable encoded string.
  2339.      * Original written by Colin Brown.
  2340.      *
  2341.      * @param string $encodedText utf-8 QP text
  2342.      * @param int    $maxLength   Find the last character boundary prior to this length
  2343.      *
  2344.      * @return int
  2345.      */
  2346.     public function utf8CharBoundary($encodedText, $maxLength)
  2347.     {
  2348.         $foundSplitPos = false;
  2349.         $lookBack = 3;
  2350.         while (!$foundSplitPos) {
  2351.             $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
  2352.             $encodedCharPos = strpos($lastChunk, '=');
  2353.             if (false !== $encodedCharPos) {
  2354.                 // Found start of encoded character byte within $lookBack block.
  2355.                 // Check the encoded byte value (the 2 chars after the '=')
  2356.                 $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
  2357.                 $dec = hexdec($hex);
  2358.                 if ($dec < 128) {
  2359.                     // Single byte character.
  2360.                     // If the encoded char was found at pos 0, it will fit
  2361.                     // otherwise reduce maxLength to start of the encoded char
  2362.                     if ($encodedCharPos > 0) {
  2363.                         $maxLength -= $lookBack - $encodedCharPos;
  2364.                     }
  2365.                     $foundSplitPos = true;
  2366.                 } elseif ($dec >= 192) {
  2367.                     // First byte of a multi byte character
  2368.                     // Reduce maxLength to split at start of character
  2369.                     $maxLength -= $lookBack - $encodedCharPos;
  2370.                     $foundSplitPos = true;
  2371.                 } elseif ($dec < 192) {
  2372.                     // Middle byte of a multi byte character, look further back
  2373.                     $lookBack += 3;
  2374.                 }
  2375.             } else {
  2376.                 // No encoded character found
  2377.                 $foundSplitPos = true;
  2378.             }
  2379.         }
  2380.  
  2381.         return $maxLength;
  2382.     }
  2383.  
  2384.     /**
  2385.      * Apply word wrapping to the message body.
  2386.      * Wraps the message body to the number of chars set in the WordWrap property.
  2387.      * You should only do this to plain-text bodies as wrapping HTML tags may break them.
  2388.      * This is called automatically by createBody(), so you don't need to call it yourself.
  2389.      */
  2390.     public function setWordWrap()
  2391.     {
  2392.         if ($this->WordWrap < 1) {
  2393.             return;
  2394.         }
  2395.  
  2396.         switch ($this->message_type) {
  2397.             case 'alt':
  2398.             case 'alt_inline':
  2399.             case 'alt_attach':
  2400.             case 'alt_inline_attach':
  2401.                 $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
  2402.                 break;
  2403.             default:
  2404.                 $this->Body = $this->wrapText($this->Body, $this->WordWrap);
  2405.                 break;
  2406.         }
  2407.     }
  2408.  
  2409.     /**
  2410.      * Assemble message headers.
  2411.      *
  2412.      * @return string The assembled headers
  2413.      */
  2414.     public function createHeader()
  2415.     {
  2416.         $result = '';
  2417.  
  2418.         $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);
  2419.  
  2420.         // The To header is created automatically by mail(), so needs to be omitted here
  2421.         if ('mail' !== $this->Mailer) {
  2422.             if ($this->SingleTo) {
  2423.                 foreach ($this->to as $toaddr) {
  2424.                     $this->SingleToArray[] = $this->addrFormat($toaddr);
  2425.                 }
  2426.             } elseif (count($this->to) > 0) {
  2427.                 $result .= $this->addrAppend('To', $this->to);
  2428.             } elseif (count($this->cc) === 0) {
  2429.                 $result .= $this->headerLine('To', 'undisclosed-recipients:;');
  2430.             }
  2431.         }
  2432.         $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
  2433.  
  2434.         // sendmail and mail() extract Cc from the header before sending
  2435.         if (count($this->cc) > 0) {
  2436.             $result .= $this->addrAppend('Cc', $this->cc);
  2437.         }
  2438.  
  2439.         // sendmail and mail() extract Bcc from the header before sending
  2440.         if (
  2441.             (
  2442.                 'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer
  2443.             )
  2444.             && count($this->bcc) > 0
  2445.         ) {
  2446.             $result .= $this->addrAppend('Bcc', $this->bcc);
  2447.         }
  2448.  
  2449.         if (count($this->ReplyTo) > 0) {
  2450.             $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
  2451.         }
  2452.  
  2453.         // mail() sets the subject itself
  2454.         if ('mail' !== $this->Mailer) {
  2455.             $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
  2456.         }
  2457.  
  2458.         // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
  2459.         // https://tools.ietf.org/html/rfc5322#section-3.6.4
  2460.         if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) {
  2461.             $this->lastMessageID = $this->MessageID;
  2462.         } else {
  2463.             $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
  2464.         }
  2465.         $result .= $this->headerLine('Message-ID', $this->lastMessageID);
  2466.         if (null !== $this->Priority) {
  2467.             $result .= $this->headerLine('X-Priority', $this->Priority);
  2468.         }
  2469.         if ('' === $this->XMailer) {
  2470.             $result .= $this->headerLine(
  2471.                 'X-Mailer',
  2472.                 'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)'
  2473.             );
  2474.         } else {
  2475.             $myXmailer = trim($this->XMailer);
  2476.             if ($myXmailer) {
  2477.                 $result .= $this->headerLine('X-Mailer', $myXmailer);
  2478.             }
  2479.         }
  2480.  
  2481.         if ('' !== $this->ConfirmReadingTo) {
  2482.             $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
  2483.         }
  2484.  
  2485.         // Add custom headers
  2486.         foreach ($this->CustomHeader as $header) {
  2487.             $result .= $this->headerLine(
  2488.                 trim($header[0]),
  2489.                 $this->encodeHeader(trim($header[1]))
  2490.             );
  2491.         }
  2492.         if (!$this->sign_key_file) {
  2493.             $result .= $this->headerLine('MIME-Version', '1.0');
  2494.             $result .= $this->getMailMIME();
  2495.         }
  2496.  
  2497.         return $result;
  2498.     }
  2499.  
  2500.     /**
  2501.      * Get the message MIME type headers.
  2502.      *
  2503.      * @return string
  2504.      */
  2505.     public function getMailMIME()
  2506.     {
  2507.         $result = '';
  2508.         $ismultipart = true;
  2509.         switch ($this->message_type) {
  2510.             case 'inline':
  2511.                 $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
  2512.                 $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
  2513.                 break;
  2514.             case 'attach':
  2515.             case 'inline_attach':
  2516.             case 'alt_attach':
  2517.             case 'alt_inline_attach':
  2518.                 $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';');
  2519.                 $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
  2520.                 break;
  2521.             case 'alt':
  2522.             case 'alt_inline':
  2523.                 $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
  2524.                 $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
  2525.                 break;
  2526.             default:
  2527.                 // Catches case 'plain': and case '':
  2528.                 $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
  2529.                 $ismultipart = false;
  2530.                 break;
  2531.         }
  2532.         // RFC1341 part 5 says 7bit is assumed if not specified
  2533.         if (static::ENCODING_7BIT !== $this->Encoding) {
  2534.             // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
  2535.             if ($ismultipart) {
  2536.                 if (static::ENCODING_8BIT === $this->Encoding) {
  2537.                     $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
  2538.                 }
  2539.                 // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
  2540.             } else {
  2541.                 $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
  2542.             }
  2543.         }
  2544.  
  2545.         if ('mail' !== $this->Mailer) {
  2546. //            $result .= static::$LE;
  2547.         }
  2548.  
  2549.         return $result;
  2550.     }
  2551.  
  2552.     /**
  2553.      * Returns the whole MIME message.
  2554.      * Includes complete headers and body.
  2555.      * Only valid post preSend().
  2556.      *
  2557.      * @see PHPMailer::preSend()
  2558.      *
  2559.      * @return string
  2560.      */
  2561.     public function getSentMIMEMessage()
  2562.     {
  2563.         return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) .
  2564.             static::$LE . static::$LE . $this->MIMEBody;
  2565.     }
  2566.  
  2567.     /**
  2568.      * Create a unique ID to use for boundaries.
  2569.      *
  2570.      * @return string
  2571.      */
  2572.     protected function generateId()
  2573.     {
  2574.         $len = 32; //32 bytes = 256 bits
  2575.         $bytes = '';
  2576.         if (function_exists('random_bytes')) {
  2577.             try {
  2578.                 $bytes = random_bytes($len);
  2579.             } catch (PHPMailerException $e) {
  2580.                 //Do nothing
  2581.             }
  2582.         } elseif (function_exists('openssl_random_pseudo_bytes')) {
  2583.             /** @noinspection CryptographicallySecureRandomnessInspection */
  2584.             $bytes = openssl_random_pseudo_bytes($len);
  2585.         }
  2586.         if ($bytes === '') {
  2587.             //We failed to produce a proper random string, so make do.
  2588.             //Use a hash to force the length to the same as the other methods
  2589.             $bytes = hash('sha256', uniqid((string) mt_rand(), true), true);
  2590.         }
  2591.  
  2592.         //We don't care about messing up base64 format here, just want a random string
  2593.         return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));
  2594.     }
  2595.  
  2596.     /**
  2597.      * Assemble the message body.
  2598.      * Returns an empty string on failure.
  2599.      *
  2600.      * @throws PHPMailerException
  2601.      *
  2602.      * @return string The assembled message body
  2603.      */
  2604.     public function createBody()
  2605.     {
  2606.         $body = '';
  2607.         //Create unique IDs and preset boundaries
  2608.         $this->uniqueid = $this->generateId();
  2609.         $this->boundary[1] = 'b1_' . $this->uniqueid;
  2610.         $this->boundary[2] = 'b2_' . $this->uniqueid;
  2611.         $this->boundary[3] = 'b3_' . $this->uniqueid;
  2612.  
  2613.         if ($this->sign_key_file) {
  2614.             $body .= $this->getMailMIME() . static::$LE;
  2615.         }
  2616.  
  2617.         $this->setWordWrap();
  2618.  
  2619.         $bodyEncoding = $this->Encoding;
  2620.         $bodyCharSet = $this->CharSet;
  2621.         //Can we do a 7-bit downgrade?
  2622.         if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
  2623.             $bodyEncoding = static::ENCODING_7BIT;
  2624.             //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
  2625.             $bodyCharSet = static::CHARSET_ASCII;
  2626.         }
  2627.         //If lines are too long, and we're not already using an encoding that will shorten them,
  2628.         //change to quoted-printable transfer encoding for the body part only
  2629.         if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) {
  2630.             $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
  2631.         }
  2632.  
  2633.         $altBodyEncoding = $this->Encoding;
  2634.         $altBodyCharSet = $this->CharSet;
  2635.         //Can we do a 7-bit downgrade?
  2636.         if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) {
  2637.             $altBodyEncoding = static::ENCODING_7BIT;
  2638.             //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
  2639.             $altBodyCharSet = static::CHARSET_ASCII;
  2640.         }
  2641.         //If lines are too long, and we're not already using an encoding that will shorten them,
  2642.         //change to quoted-printable transfer encoding for the alt body part only
  2643.         if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
  2644.             $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
  2645.         }
  2646.         //Use this as a preamble in all multipart message types
  2647.         $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE;
  2648.         switch ($this->message_type) {
  2649.             case 'inline':
  2650.                 $body .= $mimepre;
  2651.                 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
  2652.                 $body .= $this->encodeString($this->Body, $bodyEncoding);
  2653.                 $body .= static::$LE;
  2654.                 $body .= $this->attachAll('inline', $this->boundary[1]);
  2655.                 break;
  2656.             case 'attach':
  2657.                 $body .= $mimepre;
  2658.                 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
  2659.                 $body .= $this->encodeString($this->Body, $bodyEncoding);
  2660.                 $body .= static::$LE;
  2661.                 $body .= $this->attachAll('attachment', $this->boundary[1]);
  2662.                 break;
  2663.             case 'inline_attach':
  2664.                 $body .= $mimepre;
  2665.                 $body .= $this->textLine('--' . $this->boundary[1]);
  2666.                 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
  2667.                 $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
  2668.                 $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
  2669.                 $body .= static::$LE;
  2670.                 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
  2671.                 $body .= $this->encodeString($this->Body, $bodyEncoding);
  2672.                 $body .= static::$LE;
  2673.                 $body .= $this->attachAll('inline', $this->boundary[2]);
  2674.                 $body .= static::$LE;
  2675.                 $body .= $this->attachAll('attachment', $this->boundary[1]);
  2676.                 break;
  2677.             case 'alt':
  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->getBoundary(
  2688.                     $this->boundary[1],
  2689.                     $bodyCharSet,
  2690.                     static::CONTENT_TYPE_TEXT_HTML,
  2691.                     $bodyEncoding
  2692.                 );
  2693.                 $body .= $this->encodeString($this->Body, $bodyEncoding);
  2694.                 $body .= static::$LE;
  2695.                 if (!empty($this->Ical)) {
  2696.                     $method = static::ICAL_METHOD_REQUEST;
  2697.                     foreach (static::$IcalMethods as $imethod) {
  2698.                         if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
  2699.                             $method = $imethod;
  2700.                             break;
  2701.                         }
  2702.                     }
  2703.                     $body .= $this->getBoundary(
  2704.                         $this->boundary[1],
  2705.                         '',
  2706.                         static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
  2707.                         ''
  2708.                     );
  2709.                     $body .= $this->encodeString($this->Ical, $this->Encoding);
  2710.                     $body .= static::$LE;
  2711.                 }
  2712.                 $body .= $this->endBoundary($this->boundary[1]);
  2713.                 break;
  2714.             case 'alt_inline':
  2715.                 $body .= $mimepre;
  2716.                 $body .= $this->getBoundary(
  2717.                     $this->boundary[1],
  2718.                     $altBodyCharSet,
  2719.                     static::CONTENT_TYPE_PLAINTEXT,
  2720.                     $altBodyEncoding
  2721.                 );
  2722.                 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
  2723.                 $body .= static::$LE;
  2724.                 $body .= $this->textLine('--' . $this->boundary[1]);
  2725.                 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
  2726.                 $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
  2727.                 $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
  2728.                 $body .= static::$LE;
  2729.                 $body .= $this->getBoundary(
  2730.                     $this->boundary[2],
  2731.                     $bodyCharSet,
  2732.                     static::CONTENT_TYPE_TEXT_HTML,
  2733.                     $bodyEncoding
  2734.                 );
  2735.                 $body .= $this->encodeString($this->Body, $bodyEncoding);
  2736.                 $body .= static::$LE;
  2737.                 $body .= $this->attachAll('inline', $this->boundary[2]);
  2738.                 $body .= static::$LE;
  2739.                 $body .= $this->endBoundary($this->boundary[1]);
  2740.                 break;
  2741.             case 'alt_attach':
  2742.                 $body .= $mimepre;
  2743.                 $body .= $this->textLine('--' . $this->boundary[1]);
  2744.                 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
  2745.                 $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
  2746.                 $body .= static::$LE;
  2747.                 $body .= $this->getBoundary(
  2748.                     $this->boundary[2],
  2749.                     $altBodyCharSet,
  2750.                     static::CONTENT_TYPE_PLAINTEXT,
  2751.                     $altBodyEncoding
  2752.                 );
  2753.                 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
  2754.                 $body .= static::$LE;
  2755.                 $body .= $this->getBoundary(
  2756.                     $this->boundary[2],
  2757.                     $bodyCharSet,
  2758.                     static::CONTENT_TYPE_TEXT_HTML,
  2759.                     $bodyEncoding
  2760.                 );
  2761.                 $body .= $this->encodeString($this->Body, $bodyEncoding);
  2762.                 $body .= static::$LE;
  2763.                 if (!empty($this->Ical)) {
  2764.                     $method = static::ICAL_METHOD_REQUEST;
  2765.                     foreach (static::$IcalMethods as $imethod) {
  2766.                         if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
  2767.                             $method = $imethod;
  2768.                             break;
  2769.                         }
  2770.                     }
  2771.                     $body .= $this->getBoundary(
  2772.                         $this->boundary[2],
  2773.                         '',
  2774.                         static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
  2775.                         ''
  2776.                     );
  2777.                     $body .= $this->encodeString($this->Ical, $this->Encoding);
  2778.                 }
  2779.                 $body .= $this->endBoundary($this->boundary[2]);
  2780.                 $body .= static::$LE;
  2781.                 $body .= $this->attachAll('attachment', $this->boundary[1]);
  2782.                 break;
  2783.             case 'alt_inline_attach':
  2784.                 $body .= $mimepre;
  2785.                 $body .= $this->textLine('--' . $this->boundary[1]);
  2786.                 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
  2787.                 $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
  2788.                 $body .= static::$LE;
  2789.                 $body .= $this->getBoundary(
  2790.                     $this->boundary[2],
  2791.                     $altBodyCharSet,
  2792.                     static::CONTENT_TYPE_PLAINTEXT,
  2793.                     $altBodyEncoding
  2794.                 );
  2795.                 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
  2796.                 $body .= static::$LE;
  2797.                 $body .= $this->textLine('--' . $this->boundary[2]);
  2798.                 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
  2799.                 $body .= $this->textLine(' boundary="' . $this->boundary[3] . '";');
  2800.                 $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
  2801.                 $body .= static::$LE;
  2802.                 $body .= $this->getBoundary(
  2803.                     $this->boundary[3],
  2804.                     $bodyCharSet,
  2805.                     static::CONTENT_TYPE_TEXT_HTML,
  2806.                     $bodyEncoding
  2807.                 );
  2808.                 $body .= $this->encodeString($this->Body, $bodyEncoding);
  2809.                 $body .= static::$LE;
  2810.                 $body .= $this->attachAll('inline', $this->boundary[3]);
  2811.                 $body .= static::$LE;
  2812.                 $body .= $this->endBoundary($this->boundary[2]);
  2813.                 $body .= static::$LE;
  2814.                 $body .= $this->attachAll('attachment', $this->boundary[1]);
  2815.                 break;
  2816.             default:
  2817.                 // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
  2818.                 //Reset the `Encoding` property in case we changed it for line length reasons
  2819.                 $this->Encoding = $bodyEncoding;
  2820.                 $body .= $this->encodeString($this->Body, $this->Encoding);
  2821.                 break;
  2822.         }
  2823.  
  2824.         if ($this->isError()) {
  2825.             $body = '';
  2826.             if ($this->exceptions) {
  2827.                 throw new PHPMailerException($this->lang('empty_message'), self::STOP_CRITICAL);
  2828.             }
  2829.         } elseif ($this->sign_key_file) {
  2830.             try {
  2831.                 if (!defined('PKCS7_TEXT')) {
  2832.                     throw new PHPMailerException($this->lang('extension_missing') . 'openssl');
  2833.                 }
  2834.  
  2835.                 $file = tempnam(sys_get_temp_dir(), 'srcsign');
  2836.                 $signed = tempnam(sys_get_temp_dir(), 'mailsign');
  2837.                 file_put_contents($file, $body);
  2838.  
  2839.                 //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
  2840.                 if (empty($this->sign_extracerts_file)) {
  2841.                     $sign = @openssl_pkcs7_sign(
  2842.                         $file,
  2843.                         $signed,
  2844.                         'file://' . realpath($this->sign_cert_file),
  2845.                         ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
  2846.                         []
  2847.                     );
  2848.                 } else {
  2849.                     $sign = @openssl_pkcs7_sign(
  2850.                         $file,
  2851.                         $signed,
  2852.                         'file://' . realpath($this->sign_cert_file),
  2853.                         ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
  2854.                         [],
  2855.                         PKCS7_DETACHED,
  2856.                         $this->sign_extracerts_file
  2857.                     );
  2858.                 }
  2859.  
  2860.                 @unlink($file);
  2861.                 if ($sign) {
  2862.                     $body = file_get_contents($signed);
  2863.                     @unlink($signed);
  2864.                     //The message returned by openssl contains both headers and body, so need to split them up
  2865.                     $parts = explode("\n\n", $body, 2);
  2866.                     $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE;
  2867.                     $body = $parts[1];
  2868.                 } else {
  2869.                     @unlink($signed);
  2870.                     throw new PHPMailerException($this->lang('signing') . openssl_error_string());
  2871.                 }
  2872.             } catch (PHPMailerException $exc) {
  2873.                 $body = '';
  2874.                 if ($this->exceptions) {
  2875.                     throw $exc;
  2876.                 }
  2877.             }
  2878.         }
  2879.  
  2880.         return $body;
  2881.     }
  2882.  
  2883.     /**
  2884.      * Return the start of a message boundary.
  2885.      *
  2886.      * @param string $boundary
  2887.      * @param string $charSet
  2888.      * @param string $contentType
  2889.      * @param string $encoding
  2890.      *
  2891.      * @return string
  2892.      */
  2893.     protected function getBoundary($boundary, $charSet, $contentType, $encoding)
  2894.     {
  2895.         $result = '';
  2896.         if ('' === $charSet) {
  2897.             $charSet = $this->CharSet;
  2898.         }
  2899.         if ('' === $contentType) {
  2900.             $contentType = $this->ContentType;
  2901.         }
  2902.         if ('' === $encoding) {
  2903.             $encoding = $this->Encoding;
  2904.         }
  2905.         $result .= $this->textLine('--' . $boundary);
  2906.         $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
  2907.         $result .= static::$LE;
  2908.         // RFC1341 part 5 says 7bit is assumed if not specified
  2909.         if (static::ENCODING_7BIT !== $encoding) {
  2910.             $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
  2911.         }
  2912.         $result .= static::$LE;
  2913.  
  2914.         return $result;
  2915.     }
  2916.  
  2917.     /**
  2918.      * Return the end of a message boundary.
  2919.      *
  2920.      * @param string $boundary
  2921.      *
  2922.      * @return string
  2923.      */
  2924.     protected function endBoundary($boundary)
  2925.     {
  2926.         return static::$LE . '--' . $boundary . '--' . static::$LE;
  2927.     }
  2928.  
  2929.     /**
  2930.      * Set the message type.
  2931.      * PHPMailer only supports some preset message types, not arbitrary MIME structures.
  2932.      */
  2933.     protected function setMessageType()
  2934.     {
  2935.         $type = [];
  2936.         if ($this->alternativeExists()) {
  2937.             $type[] = 'alt';
  2938.         }
  2939.         if ($this->inlineImageExists()) {
  2940.             $type[] = 'inline';
  2941.         }
  2942.         if ($this->attachmentExists()) {
  2943.             $type[] = 'attach';
  2944.         }
  2945.         $this->message_type = implode('_', $type);
  2946.         if ('' === $this->message_type) {
  2947.             //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
  2948.             $this->message_type = 'plain';
  2949.         }
  2950.     }
  2951.  
  2952.     /**
  2953.      * Format a header line.
  2954.      *
  2955.      * @param string     $name
  2956.      * @param string|int $value
  2957.      *
  2958.      * @return string
  2959.      */
  2960.     public function headerLine($name, $value)
  2961.     {
  2962.         return $name . ': ' . $value . static::$LE;
  2963.     }
  2964.  
  2965.     /**
  2966.      * Return a formatted mail line.
  2967.      *
  2968.      * @param string $value
  2969.      *
  2970.      * @return string
  2971.      */
  2972.     public function textLine($value)
  2973.     {
  2974.         return $value . static::$LE;
  2975.     }
  2976.  
  2977.     /**
  2978.      * Add an attachment from a path on the filesystem.
  2979.      * Never use a user-supplied path to a file!
  2980.      * Returns false if the file could not be found or read.
  2981.      * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
  2982.      * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
  2983.      *
  2984.      * @param string $path        Path to the attachment
  2985.      * @param string $name        Overrides the attachment name
  2986.      * @param string $encoding    File encoding (see $Encoding)
  2987.      * @param string $type        MIME type, e.g. `image/jpeg`; determined automatically from $path if not specified
  2988.      * @param string $disposition Disposition to use
  2989.      *
  2990.      * @throws PHPMailerException
  2991.      *
  2992.      * @return bool
  2993.      */
  2994.     public function addAttachment(
  2995.         $path,
  2996.         $name = '',
  2997.         $encoding = self::ENCODING_BASE64,
  2998.         $type = '',
  2999.         $disposition = 'attachment'
  3000.     ) {
  3001.         try {
  3002.             if (!static::fileIsAccessible($path)) {
  3003.                 throw new PHPMailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
  3004.             }
  3005.  
  3006.             // If a MIME type is not specified, try to work it out from the file name
  3007.             if ('' === $type) {
  3008.                 $type = static::filenameToType($path);
  3009.             }
  3010.  
  3011.             $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
  3012.             if ('' === $name) {
  3013.                 $name = $filename;
  3014.             }
  3015.             if (!$this->validateEncoding($encoding)) {
  3016.                 throw new PHPMailerException($this->lang('encoding') . $encoding);
  3017.             }
  3018.  
  3019.             $this->attachment[] = [
  3020.                 0 => $path,
  3021.                 1 => $filename,
  3022.                 2 => $name,
  3023.                 3 => $encoding,
  3024.                 4 => $type,
  3025.                 5 => false, // isStringAttachment
  3026.                 6 => $disposition,
  3027.                 7 => $name,
  3028.             ];
  3029.         } catch (PHPMailerException $exc) {
  3030.             $this->setError($exc->getMessage());
  3031.             $this->edebug($exc->getMessage());
  3032.             if ($this->exceptions) {
  3033.                 throw $exc;
  3034.             }
  3035.  
  3036.             return false;
  3037.         }
  3038.  
  3039.         return true;
  3040.     }
  3041.  
  3042.     /**
  3043.      * Return the array of attachments.
  3044.      *
  3045.      * @return array
  3046.      */
  3047.     public function getAttachments()
  3048.     {
  3049.         return $this->attachment;
  3050.     }
  3051.  
  3052.     /**
  3053.      * Attach all file, string, and binary attachments to the message.
  3054.      * Returns an empty string on failure.
  3055.      *
  3056.      * @param string $disposition_type
  3057.      * @param string $boundary
  3058.      *
  3059.      * @throws PHPMailerException
  3060.      *
  3061.      * @return string
  3062.      */
  3063.     protected function attachAll($disposition_type, $boundary)
  3064.     {
  3065.         // Return text of body
  3066.         $mime = [];
  3067.         $cidUniq = [];
  3068.         $incl = [];
  3069.  
  3070.         // Add all attachments
  3071.         foreach ($this->attachment as $attachment) {
  3072.             // Check if it is a valid disposition_filter
  3073.             if ($attachment[6] === $disposition_type) {
  3074.                 // Check for string attachment
  3075.                 $string = '';
  3076.                 $path = '';
  3077.                 $bString = $attachment[5];
  3078.                 if ($bString) {
  3079.                     $string = $attachment[0];
  3080.                 } else {
  3081.                     $path = $attachment[0];
  3082.                 }
  3083.  
  3084.                 $inclhash = hash('sha256', serialize($attachment));
  3085.                 if (in_array($inclhash, $incl, true)) {
  3086.                     continue;
  3087.                 }
  3088.                 $incl[] = $inclhash;
  3089.                 $name = $attachment[2];
  3090.                 $encoding = $attachment[3];
  3091.                 $type = $attachment[4];
  3092.                 $disposition = $attachment[6];
  3093.                 $cid = $attachment[7];
  3094.                 if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) {
  3095.                     continue;
  3096.                 }
  3097.                 $cidUniq[$cid] = true;
  3098.  
  3099.                 $mime[] = sprintf('--%s%s', $boundary, static::$LE);
  3100.                 //Only include a filename property if we have one
  3101.                 if (!empty($name)) {
  3102.                     $mime[] = sprintf(
  3103.                         'Content-Type: %s; name=%s%s',
  3104.                         $type,
  3105.                         static::quotedString($this->encodeHeader($this->secureHeader($name))),
  3106.                         static::$LE
  3107.                     );
  3108.                 } else {
  3109.                     $mime[] = sprintf(
  3110.                         'Content-Type: %s%s',
  3111.                         $type,
  3112.                         static::$LE
  3113.                     );
  3114.                 }
  3115.                 // RFC1341 part 5 says 7bit is assumed if not specified
  3116.                 if (static::ENCODING_7BIT !== $encoding) {
  3117.                     $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE);
  3118.                 }
  3119.  
  3120.                 //Only set Content-IDs on inline attachments
  3121.                 if ((string) $cid !== '' && $disposition === 'inline') {
  3122.                     $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE;
  3123.                 }
  3124.  
  3125.                 // Allow for bypassing the Content-Disposition header
  3126.                 if (!empty($disposition)) {
  3127.                     $encoded_name = $this->encodeHeader($this->secureHeader($name));
  3128.                     if (!empty($encoded_name)) {
  3129.                         $mime[] = sprintf(
  3130.                             'Content-Disposition: %s; filename=%s%s',
  3131.                             $disposition,
  3132.                             static::quotedString($encoded_name),
  3133.                             static::$LE . static::$LE
  3134.                         );
  3135.                     } else {
  3136.                         $mime[] = sprintf(
  3137.                             'Content-Disposition: %s%s',
  3138.                             $disposition,
  3139.                             static::$LE . static::$LE
  3140.                         );
  3141.                     }
  3142.                 } else {
  3143.                     $mime[] = static::$LE;
  3144.                 }
  3145.  
  3146.                 // Encode as string attachment
  3147.                 if ($bString) {
  3148.                     $mime[] = $this->encodeString($string, $encoding);
  3149.                 } else {
  3150.                     $mime[] = $this->encodeFile($path, $encoding);
  3151.                 }
  3152.                 if ($this->isError()) {
  3153.                     return '';
  3154.                 }
  3155.                 $mime[] = static::$LE;
  3156.             }
  3157.         }
  3158.  
  3159.         $mime[] = sprintf('--%s--%s', $boundary, static::$LE);
  3160.  
  3161.         return implode('', $mime);
  3162.     }
  3163.  
  3164.     /**
  3165.      * Encode a file attachment in requested format.
  3166.      * Returns an empty string on failure.
  3167.      *
  3168.      * @param string $path     The full path to the file
  3169.      * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
  3170.      *
  3171.      * @return string
  3172.      */
  3173.     protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
  3174.     {
  3175.         try {
  3176.             if (!static::fileIsAccessible($path)) {
  3177.                 throw new PHPMailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
  3178.             }
  3179.             $file_buffer = file_get_contents($path);
  3180.             if (false === $file_buffer) {
  3181.                 throw new PHPMailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
  3182.             }
  3183.             $file_buffer = $this->encodeString($file_buffer, $encoding);
  3184.  
  3185.             return $file_buffer;
  3186.         } catch (PHPMailerException $exc) {
  3187.             $this->setError($exc->getMessage());
  3188.             $this->edebug($exc->getMessage());
  3189.             if ($this->exceptions) {
  3190.                 throw $exc;
  3191.             }
  3192.  
  3193.             return '';
  3194.         }
  3195.     }
  3196.  
  3197.     /**
  3198.      * Encode a string in requested format.
  3199.      * Returns an empty string on failure.
  3200.      *
  3201.      * @param string $str      The text to encode
  3202.      * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
  3203.      *
  3204.      * @throws PHPMailerException
  3205.      *
  3206.      * @return string
  3207.      */
  3208.     public function encodeString($str, $encoding = self::ENCODING_BASE64)
  3209.     {
  3210.         $encoded = '';
  3211.         switch (strtolower($encoding)) {
  3212.             case static::ENCODING_BASE64:
  3213.                 $encoded = chunk_split(
  3214.                     base64_encode($str),
  3215.                     static::STD_LINE_LENGTH,
  3216.                     static::$LE
  3217.                 );
  3218.                 break;
  3219.             case static::ENCODING_7BIT:
  3220.             case static::ENCODING_8BIT:
  3221.                 $encoded = static::normalizeBreaks($str);
  3222.                 // Make sure it ends with a line break
  3223.                 if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) {
  3224.                     $encoded .= static::$LE;
  3225.                 }
  3226.                 break;
  3227.             case static::ENCODING_BINARY:
  3228.                 $encoded = $str;
  3229.                 break;
  3230.             case static::ENCODING_QUOTED_PRINTABLE:
  3231.                 $encoded = $this->encodeQP($str);
  3232.                 break;
  3233.             default:
  3234.                 $this->setError($this->lang('encoding') . $encoding);
  3235.                 if ($this->exceptions) {
  3236.                     throw new PHPMailerException($this->lang('encoding') . $encoding);
  3237.                 }
  3238.                 break;
  3239.         }
  3240.  
  3241.         return $encoded;
  3242.     }
  3243.  
  3244.     /**
  3245.      * Encode a header value (not including its label) optimally.
  3246.      * Picks shortest of Q, B, or none. Result includes folding if needed.
  3247.      * See RFC822 definitions for phrase, comment and text positions.
  3248.      *
  3249.      * @param string $str      The header value to encode
  3250.      * @param string $position What context the string will be used in
  3251.      *
  3252.      * @return string
  3253.      */
  3254.     public function encodeHeader($str, $position = 'text')
  3255.     {
  3256.         $matchcount = 0;
  3257.         switch (strtolower($position)) {
  3258.             case 'phrase':
  3259.                 if (!preg_match('/[\200-\377]/', $str)) {
  3260.                     // Can't use addslashes as we don't know the value of magic_quotes_sybase
  3261.                     $encoded = addcslashes($str, "\0..\37\177\\\"");
  3262.                     if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
  3263.                         return $encoded;
  3264.                     }
  3265.  
  3266.                     return "\"$encoded\"";
  3267.                 }
  3268.                 $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
  3269.                 break;
  3270.             /* @noinspection PhpMissingBreakStatementInspection */
  3271.             case 'comment':
  3272.                 $matchcount = preg_match_all('/[()"]/', $str, $matches);
  3273.             //fallthrough
  3274.             case 'text':
  3275.             default:
  3276.                 $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
  3277.                 break;
  3278.         }
  3279.  
  3280.         if ($this->has8bitChars($str)) {
  3281.             $charset = $this->CharSet;
  3282.         } else {
  3283.             $charset = static::CHARSET_ASCII;
  3284.         }
  3285.  
  3286.         // Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<content>?=`").
  3287.         $overhead = 8 + strlen($charset);
  3288.  
  3289.         if ('mail' === $this->Mailer) {
  3290.             $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead;
  3291.         } else {
  3292.             $maxlen = static::MAX_LINE_LENGTH - $overhead;
  3293.         }
  3294.  
  3295.         // Select the encoding that produces the shortest output and/or prevents corruption.
  3296.         if ($matchcount > strlen($str) / 3) {
  3297.             // More than 1/3 of the content needs encoding, use B-encode.
  3298.             $encoding = 'B';
  3299.         } elseif ($matchcount > 0) {
  3300.             // Less than 1/3 of the content needs encoding, use Q-encode.
  3301.             $encoding = 'Q';
  3302.         } elseif (strlen($str) > $maxlen) {
  3303.             // No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption.
  3304.             $encoding = 'Q';
  3305.         } else {
  3306.             // No reformatting needed
  3307.             $encoding = false;
  3308.         }
  3309.  
  3310.         switch ($encoding) {
  3311.             case 'B':
  3312.                 if ($this->hasMultiBytes($str)) {
  3313.                     // Use a custom function which correctly encodes and wraps long
  3314.                     // multibyte strings without breaking lines within a character
  3315.                     $encoded = $this->base64EncodeWrapMB($str, "\n");
  3316.                 } else {
  3317.                     $encoded = base64_encode($str);
  3318.                     $maxlen -= $maxlen % 4;
  3319.                     $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
  3320.                 }
  3321.                 $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
  3322.                 break;
  3323.             case 'Q':
  3324.                 $encoded = $this->encodeQ($str, $position);
  3325.                 $encoded = $this->wrapText($encoded, $maxlen, true);
  3326.                 $encoded = str_replace('=' . static::$LE, "\n", trim($encoded));
  3327.                 $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
  3328.                 break;
  3329.             default:
  3330.                 return $str;
  3331.         }
  3332.  
  3333.         return trim(static::normalizeBreaks($encoded));
  3334.     }
  3335.  
  3336.     /**
  3337.      * Check if a string contains multi-byte characters.
  3338.      *
  3339.      * @param string $str multi-byte text to wrap encode
  3340.      *
  3341.      * @return bool
  3342.      */
  3343.     public function hasMultiBytes($str)
  3344.     {
  3345.         if (function_exists('mb_strlen')) {
  3346.             return strlen($str) > mb_strlen($str, $this->CharSet);
  3347.         }
  3348.  
  3349.         // Assume no multibytes (we can't handle without mbstring functions anyway)
  3350.         return false;
  3351.     }
  3352.  
  3353.     /**
  3354.      * Does a string contain any 8-bit chars (in any charset)?
  3355.      *
  3356.      * @param string $text
  3357.      *
  3358.      * @return bool
  3359.      */
  3360.     public function has8bitChars($text)
  3361.     {
  3362.         return (bool) preg_match('/[\x80-\xFF]/', $text);
  3363.     }
  3364.  
  3365.     /**
  3366.      * Encode and wrap long multibyte strings for mail headers
  3367.      * without breaking lines within a character.
  3368.      * Adapted from a function by paravoid.
  3369.      *
  3370.      * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
  3371.      *
  3372.      * @param string $str       multi-byte text to wrap encode
  3373.      * @param string $linebreak string to use as linefeed/end-of-line
  3374.      *
  3375.      * @return string
  3376.      */
  3377.     public function base64EncodeWrapMB($str, $linebreak = null)
  3378.     {
  3379.         $start = '=?' . $this->CharSet . '?B?';
  3380.         $end = '?=';
  3381.         $encoded = '';
  3382.         if (null === $linebreak) {
  3383.             $linebreak = static::$LE;
  3384.         }
  3385.  
  3386.         $mb_length = mb_strlen($str, $this->CharSet);
  3387.         // Each line must have length <= 75, including $start and $end
  3388.         $length = 75 - strlen($start) - strlen($end);
  3389.         // Average multi-byte ratio
  3390.         $ratio = $mb_length / strlen($str);
  3391.         // Base64 has a 4:3 ratio
  3392.         $avgLength = floor($length * $ratio * .75);
  3393.  
  3394.         $offset = 0;
  3395.         for ($i = 0; $i < $mb_length; $i += $offset) {
  3396.             $lookBack = 0;
  3397.             do {
  3398.                 $offset = $avgLength - $lookBack;
  3399.                 $chunk = mb_substr($str, $i, $offset, $this->CharSet);
  3400.                 $chunk = base64_encode($chunk);
  3401.                 ++$lookBack;
  3402.             } while (strlen($chunk) > $length);
  3403.             $encoded .= $chunk . $linebreak;
  3404.         }
  3405.  
  3406.         // Chomp the last linefeed
  3407.         return substr($encoded, 0, -strlen($linebreak));
  3408.     }
  3409.  
  3410.     /**
  3411.      * Encode a string in quoted-printable format.
  3412.      * According to RFC2045 section 6.7.
  3413.      *
  3414.      * @param string $string The text to encode
  3415.      *
  3416.      * @return string
  3417.      */
  3418.     public function encodeQP($string)
  3419.     {
  3420.         return static::normalizeBreaks(quoted_printable_encode($string));
  3421.     }
  3422.  
  3423.     /**
  3424.      * Encode a string using Q encoding.
  3425.      *
  3426.      * @see http://tools.ietf.org/html/rfc2047#section-4.2
  3427.      *
  3428.      * @param string $str      the text to encode
  3429.      * @param string $position Where the text is going to be used, see the RFC for what that means
  3430.      *
  3431.      * @return string
  3432.      */
  3433.     public function encodeQ($str, $position = 'text')
  3434.     {
  3435.         // There should not be any EOL in the string
  3436.         $pattern = '';
  3437.         $encoded = str_replace(["\r", "\n"], '', $str);
  3438.         switch (strtolower($position)) {
  3439.             case 'phrase':
  3440.                 // RFC 2047 section 5.3
  3441.                 $pattern = '^A-Za-z0-9!*+\/ -';
  3442.                 break;
  3443.             /*
  3444.              * RFC 2047 section 5.2.
  3445.              * Build $pattern without including delimiters and []
  3446.              */
  3447.             /* @noinspection PhpMissingBreakStatementInspection */
  3448.             case 'comment':
  3449.                 $pattern = '\(\)"';
  3450.             /* Intentional fall through */
  3451.             case 'text':
  3452.             default:
  3453.                 // RFC 2047 section 5.1
  3454.                 // Replace every high ascii, control, =, ? and _ characters
  3455.                 $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
  3456.                 break;
  3457.         }
  3458.         $matches = [];
  3459.         if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
  3460.             // If the string contains an '=', make sure it's the first thing we replace
  3461.             // so as to avoid double-encoding
  3462.             $eqkey = array_search('=', $matches[0], true);
  3463.             if (false !== $eqkey) {
  3464.                 unset($matches[0][$eqkey]);
  3465.                 array_unshift($matches[0], '=');
  3466.             }
  3467.             foreach (array_unique($matches[0]) as $char) {
  3468.                 $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
  3469.             }
  3470.         }
  3471.         // Replace spaces with _ (more readable than =20)
  3472.         // RFC 2047 section 4.2(2)
  3473.         return str_replace(' ', '_', $encoded);
  3474.     }
  3475.  
  3476.     /**
  3477.      * Add a string or binary attachment (non-filesystem).
  3478.      * This method can be used to attach ascii or binary data,
  3479.      * such as a BLOB record from a database.
  3480.      *
  3481.      * @param string $string      String attachment data
  3482.      * @param string $filename    Name of the attachment
  3483.      * @param string $encoding    File encoding (see $Encoding)
  3484.      * @param string $type        File extension (MIME) type
  3485.      * @param string $disposition Disposition to use
  3486.      *
  3487.      * @throws PHPMailerException
  3488.      *
  3489.      * @return bool True on successfully adding an attachment
  3490.      */
  3491.     public function addStringAttachment(
  3492.         $string,
  3493.         $filename,
  3494.         $encoding = self::ENCODING_BASE64,
  3495.         $type = '',
  3496.         $disposition = 'attachment'
  3497.     ) {
  3498.         try {
  3499.             // If a MIME type is not specified, try to work it out from the file name
  3500.             if ('' === $type) {
  3501.                 $type = static::filenameToType($filename);
  3502.             }
  3503.  
  3504.             if (!$this->validateEncoding($encoding)) {
  3505.                 throw new PHPMailerException($this->lang('encoding') . $encoding);
  3506.             }
  3507.  
  3508.             // Append to $attachment array
  3509.             $this->attachment[] = [
  3510.                 0 => $string,
  3511.                 1 => $filename,
  3512.                 2 => static::mb_pathinfo($filename, PATHINFO_BASENAME),
  3513.                 3 => $encoding,
  3514.                 4 => $type,
  3515.                 5 => true, // isStringAttachment
  3516.                 6 => $disposition,
  3517.                 7 => 0,
  3518.             ];
  3519.         } catch (PHPMailerException $exc) {
  3520.             $this->setError($exc->getMessage());
  3521.             $this->edebug($exc->getMessage());
  3522.             if ($this->exceptions) {
  3523.                 throw $exc;
  3524.             }
  3525.  
  3526.             return false;
  3527.         }
  3528.  
  3529.         return true;
  3530.     }
  3531.  
  3532.     /**
  3533.      * Add an embedded (inline) attachment from a file.
  3534.      * This can include images, sounds, and just about any other document type.
  3535.      * These differ from 'regular' attachments in that they are intended to be
  3536.      * displayed inline with the message, not just attached for download.
  3537.      * This is used in HTML messages that embed the images
  3538.      * the HTML refers to using the $cid value.
  3539.      * Never use a user-supplied path to a file!
  3540.      *
  3541.      * @param string $path        Path to the attachment
  3542.      * @param string $cid         Content ID of the attachment; Use this to reference
  3543.      *                            the content when using an embedded image in HTML
  3544.      * @param string $name        Overrides the attachment name
  3545.      * @param string $encoding    File encoding (see $Encoding)
  3546.      * @param string $type        File MIME type
  3547.      * @param string $disposition Disposition to use
  3548.      *
  3549.      * @throws PHPMailerException
  3550.      *
  3551.      * @return bool True on successfully adding an attachment
  3552.      */
  3553.     public function addEmbeddedImage(
  3554.         $path,
  3555.         $cid,
  3556.         $name = '',
  3557.         $encoding = self::ENCODING_BASE64,
  3558.         $type = '',
  3559.         $disposition = 'inline'
  3560.     ) {
  3561.         try {
  3562.             if (!static::fileIsAccessible($path)) {
  3563.                 throw new PHPMailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
  3564.             }
  3565.  
  3566.             // If a MIME type is not specified, try to work it out from the file name
  3567.             if ('' === $type) {
  3568.                 $type = static::filenameToType($path);
  3569.             }
  3570.  
  3571.             if (!$this->validateEncoding($encoding)) {
  3572.                 throw new PHPMailerException($this->lang('encoding') . $encoding);
  3573.             }
  3574.  
  3575.             $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
  3576.             if ('' === $name) {
  3577.                 $name = $filename;
  3578.             }
  3579.  
  3580.             // Append to $attachment array
  3581.             $this->attachment[] = [
  3582.                 0 => $path,
  3583.                 1 => $filename,
  3584.                 2 => $name,
  3585.                 3 => $encoding,
  3586.                 4 => $type,
  3587.                 5 => false, // isStringAttachment
  3588.                 6 => $disposition,
  3589.                 7 => $cid,
  3590.             ];
  3591.         } catch (PHPMailerException $exc) {
  3592.             $this->setError($exc->getMessage());
  3593.             $this->edebug($exc->getMessage());
  3594.             if ($this->exceptions) {
  3595.                 throw $exc;
  3596.             }
  3597.  
  3598.             return false;
  3599.         }
  3600.  
  3601.         return true;
  3602.     }
  3603.  
  3604.     /**
  3605.      * Add an embedded stringified attachment.
  3606.      * This can include images, sounds, and just about any other document type.
  3607.      * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type.
  3608.      *
  3609.      * @param string $string      The attachment binary data
  3610.      * @param string $cid         Content ID of the attachment; Use this to reference
  3611.      *                            the content when using an embedded image in HTML
  3612.      * @param string $name        A filename for the attachment. If this contains an extension,
  3613.      *                            PHPMailer will attempt to set a MIME type for the attachment.
  3614.      *                            For example 'file.jpg' would get an 'image/jpeg' MIME type.
  3615.      * @param string $encoding    File encoding (see $Encoding), defaults to 'base64'
  3616.      * @param string $type        MIME type - will be used in preference to any automatically derived type
  3617.      * @param string $disposition Disposition to use
  3618.      *
  3619.      * @throws PHPMailerException
  3620.      *
  3621.      * @return bool True on successfully adding an attachment
  3622.      */
  3623.     public function addStringEmbeddedImage(
  3624.         $string,
  3625.         $cid,
  3626.         $name = '',
  3627.         $encoding = self::ENCODING_BASE64,
  3628.         $type = '',
  3629.         $disposition = 'inline'
  3630.     ) {
  3631.         try {
  3632.             // If a MIME type is not specified, try to work it out from the name
  3633.             if ('' === $type && !empty($name)) {
  3634.                 $type = static::filenameToType($name);
  3635.             }
  3636.  
  3637.             if (!$this->validateEncoding($encoding)) {
  3638.                 throw new PHPMailerException($this->lang('encoding') . $encoding);
  3639.             }
  3640.  
  3641.             // Append to $attachment array
  3642.             $this->attachment[] = [
  3643.                 0 => $string,
  3644.                 1 => $name,
  3645.                 2 => $name,
  3646.                 3 => $encoding,
  3647.                 4 => $type,
  3648.                 5 => true, // isStringAttachment
  3649.                 6 => $disposition,
  3650.                 7 => $cid,
  3651.             ];
  3652.         } catch (PHPMailerException $exc) {
  3653.             $this->setError($exc->getMessage());
  3654.             $this->edebug($exc->getMessage());
  3655.             if ($this->exceptions) {
  3656.                 throw $exc;
  3657.             }
  3658.  
  3659.             return false;
  3660.         }
  3661.  
  3662.         return true;
  3663.     }
  3664.  
  3665.     /**
  3666.      * Validate encodings.
  3667.      *
  3668.      * @param string $encoding
  3669.      *
  3670.      * @return bool
  3671.      */
  3672.     protected function validateEncoding($encoding)
  3673.     {
  3674.         return in_array(
  3675.             $encoding,
  3676.             [
  3677.                 self::ENCODING_7BIT,
  3678.                 self::ENCODING_QUOTED_PRINTABLE,
  3679.                 self::ENCODING_BASE64,
  3680.                 self::ENCODING_8BIT,
  3681.                 self::ENCODING_BINARY,
  3682.             ],
  3683.             true
  3684.         );
  3685.     }
  3686.  
  3687.     /**
  3688.      * Check if an embedded attachment is present with this cid.
  3689.      *
  3690.      * @param string $cid
  3691.      *
  3692.      * @return bool
  3693.      */
  3694.     protected function cidExists($cid)
  3695.     {
  3696.         foreach ($this->attachment as $attachment) {
  3697.             if ('inline' === $attachment[6] && $cid === $attachment[7]) {
  3698.                 return true;
  3699.             }
  3700.         }
  3701.  
  3702.         return false;
  3703.     }
  3704.  
  3705.     /**
  3706.      * Check if an inline attachment is present.
  3707.      *
  3708.      * @return bool
  3709.      */
  3710.     public function inlineImageExists()
  3711.     {
  3712.         foreach ($this->attachment as $attachment) {
  3713.             if ('inline' === $attachment[6]) {
  3714.                 return true;
  3715.             }
  3716.         }
  3717.  
  3718.         return false;
  3719.     }
  3720.  
  3721.     /**
  3722.      * Check if an attachment (non-inline) is present.
  3723.      *
  3724.      * @return bool
  3725.      */
  3726.     public function attachmentExists()
  3727.     {
  3728.         foreach ($this->attachment as $attachment) {
  3729.             if ('attachment' === $attachment[6]) {
  3730.                 return true;
  3731.             }
  3732.         }
  3733.  
  3734.         return false;
  3735.     }
  3736.  
  3737.     /**
  3738.      * Check if this message has an alternative body set.
  3739.      *
  3740.      * @return bool
  3741.      */
  3742.     public function alternativeExists()
  3743.     {
  3744.         return !empty($this->AltBody);
  3745.     }
  3746.  
  3747.     /**
  3748.      * Clear queued addresses of given kind.
  3749.      *
  3750.      * @param string $kind 'to', 'cc', or 'bcc'
  3751.      */
  3752.     public function clearQueuedAddresses($kind)
  3753.     {
  3754.         $this->RecipientsQueue = array_filter(
  3755.             $this->RecipientsQueue,
  3756.             static function ($params) use ($kind) {
  3757.                 return $params[0] !== $kind;
  3758.             }
  3759.         );
  3760.     }
  3761.  
  3762.     /**
  3763.      * Clear all To recipients.
  3764.      */
  3765.     public function clearAddresses()
  3766.     {
  3767.         foreach ($this->to as $to) {
  3768.             unset($this->all_recipients[strtolower($to[0])]);
  3769.         }
  3770.         $this->to = [];
  3771.         $this->clearQueuedAddresses('to');
  3772.     }
  3773.  
  3774.     /**
  3775.      * Clear all CC recipients.
  3776.      */
  3777.     public function clearCCs()
  3778.     {
  3779.         foreach ($this->cc as $cc) {
  3780.             unset($this->all_recipients[strtolower($cc[0])]);
  3781.         }
  3782.         $this->cc = [];
  3783.         $this->clearQueuedAddresses('cc');
  3784.     }
  3785.  
  3786.     /**
  3787.      * Clear all BCC recipients.
  3788.      */
  3789.     public function clearBCCs()
  3790.     {
  3791.         foreach ($this->bcc as $bcc) {
  3792.             unset($this->all_recipients[strtolower($bcc[0])]);
  3793.         }
  3794.         $this->bcc = [];
  3795.         $this->clearQueuedAddresses('bcc');
  3796.     }
  3797.  
  3798.     /**
  3799.      * Clear all ReplyTo recipients.
  3800.      */
  3801.     public function clearReplyTos()
  3802.     {
  3803.         $this->ReplyTo = [];
  3804.         $this->ReplyToQueue = [];
  3805.     }
  3806.  
  3807.     /**
  3808.      * Clear all recipient types.
  3809.      */
  3810.     public function clearAllRecipients()
  3811.     {
  3812.         $this->to = [];
  3813.         $this->cc = [];
  3814.         $this->bcc = [];
  3815.         $this->all_recipients = [];
  3816.         $this->RecipientsQueue = [];
  3817.     }
  3818.  
  3819.     /**
  3820.      * Clear all filesystem, string, and binary attachments.
  3821.      */
  3822.     public function clearAttachments()
  3823.     {
  3824.         $this->attachment = [];
  3825.     }
  3826.  
  3827.     /**
  3828.      * Clear all custom headers.
  3829.      */
  3830.     public function clearCustomHeaders()
  3831.     {
  3832.         $this->CustomHeader = [];
  3833.     }
  3834.  
  3835.     /**
  3836.      * Add an error message to the error container.
  3837.      *
  3838.      * @param string $msg
  3839.      */
  3840.     protected function setError($msg)
  3841.     {
  3842.         ++$this->error_count;
  3843.         if ('smtp' === $this->Mailer && null !== $this->smtp) {
  3844.             $lasterror = $this->smtp->getError();
  3845.             if (!empty($lasterror['error'])) {
  3846.                 $msg .= $this->lang('smtp_error') . $lasterror['error'];
  3847.                 if (!empty($lasterror['detail'])) {
  3848.                     $msg .= ' Detail: ' . $lasterror['detail'];
  3849.                 }
  3850.                 if (!empty($lasterror['smtp_code'])) {
  3851.                     $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
  3852.                 }
  3853.                 if (!empty($lasterror['smtp_code_ex'])) {
  3854.                     $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
  3855.                 }
  3856.             }
  3857.         }
  3858.         $this->ErrorInfo = $msg;
  3859.     }
  3860.  
  3861.     /**
  3862.      * Return an RFC 822 formatted date.
  3863.      *
  3864.      * @return string
  3865.      */
  3866.     public static function rfcDate()
  3867.     {
  3868.         // Set the time zone to whatever the default is to avoid 500 errors
  3869.         // Will default to UTC if it's not set properly in php.ini
  3870.         date_default_timezone_set(@date_default_timezone_get());
  3871.  
  3872.         return date('D, j M Y H:i:s O');
  3873.     }
  3874.  
  3875.     /**
  3876.      * Get the server hostname.
  3877.      * Returns 'localhost.localdomain' if unknown.
  3878.      *
  3879.      * @return string
  3880.      */
  3881.     protected function serverHostname()
  3882.     {
  3883.         $result = '';
  3884.         if (!empty($this->Hostname)) {
  3885.             $result = $this->Hostname;
  3886.         } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) {
  3887.             $result = $_SERVER['SERVER_NAME'];
  3888.         } elseif (function_exists('gethostname') && gethostname() !== false) {
  3889.             $result = gethostname();
  3890.         } elseif (php_uname('n') !== false) {
  3891.             $result = php_uname('n');
  3892.         }
  3893.         if (!static::isValidHost($result)) {
  3894.             return 'localhost.localdomain';
  3895.         }
  3896.  
  3897.         return $result;
  3898.     }
  3899.  
  3900.     /**
  3901.      * Validate whether a string contains a valid value to use as a hostname or IP address.
  3902.      * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`.
  3903.      *
  3904.      * @param string $host The host name or IP address to check
  3905.      *
  3906.      * @return bool
  3907.      */
  3908.     public static function isValidHost($host)
  3909.     {
  3910.         //Simple syntax limits
  3911.         if (
  3912.             empty($host)
  3913.             || !is_string($host)
  3914.             || strlen($host) > 256
  3915.             || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+])$/', $host)
  3916.         ) {
  3917.             return false;
  3918.         }
  3919.         //Looks like a bracketed IPv6 address
  3920.         if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') {
  3921.             return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
  3922.         }
  3923.         //If removing all the dots results in a numeric string, it must be an IPv4 address.
  3924.         //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names
  3925.         if (is_numeric(str_replace('.', '', $host))) {
  3926.             //Is it a valid IPv4 address?
  3927.             return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
  3928.         }
  3929.         if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) {
  3930.             //Is it a syntactically valid hostname?
  3931.             return true;
  3932.         }
  3933.  
  3934.         return false;
  3935.     }
  3936.  
  3937.     /**
  3938.      * Get an error message in the current language.
  3939.      *
  3940.      * @param string $key
  3941.      *
  3942.      * @return string
  3943.      */
  3944.     protected function lang($key)
  3945.     {
  3946.         if (count($this->language) < 1) {
  3947.             $this->setLanguage(); // set the default language
  3948.         }
  3949.  
  3950.         if (array_key_exists($key, $this->language)) {
  3951.             if ('smtp_connect_failed' === $key) {
  3952.                 //Include a link to troubleshooting docs on SMTP connection failure
  3953.                 //this is by far the biggest cause of support questions
  3954.                 //but it's usually not PHPMailer's fault.
  3955.                 return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
  3956.             }
  3957.  
  3958.             return $this->language[$key];
  3959.         }
  3960.  
  3961.         //Return the key as a fallback
  3962.         return $key;
  3963.     }
  3964.  
  3965.     /**
  3966.      * Check if an error occurred.
  3967.      *
  3968.      * @return bool True if an error did occur
  3969.      */
  3970.     public function isError()
  3971.     {
  3972.         return $this->error_count > 0;
  3973.     }
  3974.  
  3975.     /**
  3976.      * Add a custom header.
  3977.      * $name value can be overloaded to contain
  3978.      * both header name and value (name:value).
  3979.      *
  3980.      * @param string      $name  Custom header name
  3981.      * @param string|null $value Header value
  3982.      *
  3983.      * @throws PHPMailerException
  3984.      */
  3985.     public function addCustomHeader($name, $value = null)
  3986.     {
  3987.         if (null === $value && strpos($name, ':') !== false) {
  3988.             // Value passed in as name:value
  3989.             list($name, $value) = explode(':', $name, 2);
  3990.         }
  3991.         $name = trim($name);
  3992.         $value = trim($value);
  3993.         //Ensure name is not empty, and that neither name nor value contain line breaks
  3994.         if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
  3995.             if ($this->exceptions) {
  3996.                 throw new PHPMailerException('Invalid header name or value');
  3997.             }
  3998.  
  3999.             return false;
  4000.         }
  4001.         $this->CustomHeader[] = [$name, $value];
  4002.  
  4003.         return true;
  4004.     }
  4005.  
  4006.     /**
  4007.      * Returns all custom headers.
  4008.      *
  4009.      * @return array
  4010.      */
  4011.     public function getCustomHeaders()
  4012.     {
  4013.         return $this->CustomHeader;
  4014.     }
  4015.  
  4016.     /**
  4017.      * Create a message body from an HTML string.
  4018.      * Automatically inlines images and creates a plain-text version by converting the HTML,
  4019.      * overwriting any existing values in Body and AltBody.
  4020.      * Do not source $message content from user input!
  4021.      * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
  4022.      * will look for an image file in $basedir/images/a.png and convert it to inline.
  4023.      * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
  4024.      * Converts data-uri images into embedded attachments.
  4025.      * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
  4026.      *
  4027.      * @param string        $message  HTML message string
  4028.      * @param string        $basedir  Absolute path to a base directory to prepend to relative paths to images
  4029.      * @param bool|callable $advanced Whether to use the internal HTML to text converter
  4030.      *                                or your own custom converter
  4031.      * @return string The transformed message body
  4032.      *
  4033.      * @throws PHPMailerException
  4034.      *
  4035.      * @see PHPMailer::html2text()
  4036.      */
  4037.     public function msgHTML($message, $basedir = '', $advanced = false)
  4038.     {
  4039.         preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $images);
  4040.         if (array_key_exists(2, $images)) {
  4041.             if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
  4042.                 // Ensure $basedir has a trailing /
  4043.                 $basedir .= '/';
  4044.             }
  4045.             foreach ($images[2] as $imgindex => $url) {
  4046.                 // Convert data URIs into embedded images
  4047.                 //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
  4048.                 $match = [];
  4049.                 if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
  4050.                     if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) {
  4051.                         $data = base64_decode($match[3]);
  4052.                     } elseif ('' === $match[2]) {
  4053.                         $data = rawurldecode($match[3]);
  4054.                     } else {
  4055.                         //Not recognised so leave it alone
  4056.                         continue;
  4057.                     }
  4058.                     //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places
  4059.                     //will only be embedded once, even if it used a different encoding
  4060.                     $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // RFC2392 S 2
  4061.  
  4062.                     if (!$this->cidExists($cid)) {
  4063.                         $this->addStringEmbeddedImage(
  4064.                             $data,
  4065.                             $cid,
  4066.                             'embed' . $imgindex,
  4067.                             static::ENCODING_BASE64,
  4068.                             $match[1]
  4069.                         );
  4070.                     }
  4071.                     $message = str_replace(
  4072.                         $images[0][$imgindex],
  4073.                         $images[1][$imgindex] . '="cid:' . $cid . '"',
  4074.                         $message
  4075.                     );
  4076.                     continue;
  4077.                 }
  4078.                 if (
  4079.                     // Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
  4080.                     !empty($basedir)
  4081.                     // Ignore URLs containing parent dir traversal (..)
  4082.                     && (strpos($url, '..') === false)
  4083.                     // Do not change urls that are already inline images
  4084.                     && 0 !== strpos($url, 'cid:')
  4085.                     // Do not change absolute URLs, including anonymous protocol
  4086.                     && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
  4087.                 ) {
  4088.                     $filename = static::mb_pathinfo($url, PATHINFO_BASENAME);
  4089.                     $directory = dirname($url);
  4090.                     if ('.' === $directory) {
  4091.                         $directory = '';
  4092.                     }
  4093.                     // RFC2392 S 2
  4094.                     $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0';
  4095.                     if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
  4096.                         $basedir .= '/';
  4097.                     }
  4098.                     if (strlen($directory) > 1 && '/' !== substr($directory, -1)) {
  4099.                         $directory .= '/';
  4100.                     }
  4101.                     if (
  4102.                         $this->addEmbeddedImage(
  4103.                             $basedir . $directory . $filename,
  4104.                             $cid,
  4105.                             $filename,
  4106.                             static::ENCODING_BASE64,
  4107.                             static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION))
  4108.                         )
  4109.                     ) {
  4110.                         $message = preg_replace(
  4111.                             '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
  4112.                             $images[1][$imgindex] . '="cid:' . $cid . '"',
  4113.                             $message
  4114.                         );
  4115.                     }
  4116.                 }
  4117.             }
  4118.         }
  4119.         $this->isHTML();
  4120.         // Convert all message body line breaks to LE, makes quoted-printable encoding work much better
  4121.         $this->Body = static::normalizeBreaks($message);
  4122.         $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));
  4123.         if (!$this->alternativeExists()) {
  4124.             $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'
  4125.                 . static::$LE;
  4126.         }
  4127.  
  4128.         return $this->Body;
  4129.     }
  4130.  
  4131.     /**
  4132.      * Convert an HTML string into plain text.
  4133.      * This is used by msgHTML().
  4134.      * Note - older versions of this function used a bundled advanced converter
  4135.      * which was removed for license reasons in #232.
  4136.      * Example usage:
  4137.      *
  4138.      * ```php
  4139.      * // Use default conversion
  4140.      * $plain = $mail->html2text($html);
  4141.      * // Use your own custom converter
  4142.      * $plain = $mail->html2text($html, function($html) {
  4143.      *     $converter = new MyHtml2text($html);
  4144.      *     return $converter->get_text();
  4145.      * });
  4146.      * ```
  4147.      *
  4148.      * @param string        $html     The HTML text to convert
  4149.      * @param bool|callable $advanced Any boolean value to use the internal converter,
  4150.      *                                or provide your own callable for custom conversion
  4151.      *
  4152.      * @return string
  4153.      */
  4154.     public function html2text($html, $advanced = false)
  4155.     {
  4156.         if (is_callable($advanced)) {
  4157.             return call_user_func($advanced, $html);
  4158.         }
  4159.  
  4160.         return html_entity_decode(
  4161.             trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
  4162.             ENT_QUOTES,
  4163.             $this->CharSet
  4164.         );
  4165.     }
  4166.  
  4167.     /**
  4168.      * Get the MIME type for a file extension.
  4169.      *
  4170.      * @param string $ext File extension
  4171.      *
  4172.      * @return string MIME type of file
  4173.      */
  4174.     public static function _mime_types($ext = '')
  4175.     {
  4176.         $mimes = [
  4177.             'xl' => 'application/excel',
  4178.             'js' => 'application/javascript',
  4179.             'hqx' => 'application/mac-binhex40',
  4180.             'cpt' => 'application/mac-compactpro',
  4181.             'bin' => 'application/macbinary',
  4182.             'doc' => 'application/msword',
  4183.             'word' => 'application/msword',
  4184.             'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  4185.             'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
  4186.             'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
  4187.             'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
  4188.             'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  4189.             'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
  4190.             'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  4191.             'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
  4192.             'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
  4193.             'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
  4194.             'class' => 'application/octet-stream',
  4195.             'dll' => 'application/octet-stream',
  4196.             'dms' => 'application/octet-stream',
  4197.             'exe' => 'application/octet-stream',
  4198.             'lha' => 'application/octet-stream',
  4199.             'lzh' => 'application/octet-stream',
  4200.             'psd' => 'application/octet-stream',
  4201.             'sea' => 'application/octet-stream',
  4202.             'so' => 'application/octet-stream',
  4203.             'oda' => 'application/oda',
  4204.             'pdf' => 'application/pdf',
  4205.             'ai' => 'application/postscript',
  4206.             'eps' => 'application/postscript',
  4207.             'ps' => 'application/postscript',
  4208.             'smi' => 'application/smil',
  4209.             'smil' => 'application/smil',
  4210.             'mif' => 'application/vnd.mif',
  4211.             'xls' => 'application/vnd.ms-excel',
  4212.             'ppt' => 'application/vnd.ms-powerpoint',
  4213.             'wbxml' => 'application/vnd.wap.wbxml',
  4214.             'wmlc' => 'application/vnd.wap.wmlc',
  4215.             'dcr' => 'application/x-director',
  4216.             'dir' => 'application/x-director',
  4217.             'dxr' => 'application/x-director',
  4218.             'dvi' => 'application/x-dvi',
  4219.             'gtar' => 'application/x-gtar',
  4220.             'php3' => 'application/x-httpd-php',
  4221.             'php4' => 'application/x-httpd-php',
  4222.             'php' => 'application/x-httpd-php',
  4223.             'phtml' => 'application/x-httpd-php',
  4224.             'phps' => 'application/x-httpd-php-source',
  4225.             'swf' => 'application/x-shockwave-flash',
  4226.             'sit' => 'application/x-stuffit',
  4227.             'tar' => 'application/x-tar',
  4228.             'tgz' => 'application/x-tar',
  4229.             'xht' => 'application/xhtml+xml',
  4230.             'xhtml' => 'application/xhtml+xml',
  4231.             'zip' => 'application/zip',
  4232.             'mid' => 'audio/midi',
  4233.             'midi' => 'audio/midi',
  4234.             'mp2' => 'audio/mpeg',
  4235.             'mp3' => 'audio/mpeg',
  4236.             'm4a' => 'audio/mp4',
  4237.             'mpga' => 'audio/mpeg',
  4238.             'aif' => 'audio/x-aiff',
  4239.             'aifc' => 'audio/x-aiff',
  4240.             'aiff' => 'audio/x-aiff',
  4241.             'ram' => 'audio/x-pn-realaudio',
  4242.             'rm' => 'audio/x-pn-realaudio',
  4243.             'rpm' => 'audio/x-pn-realaudio-plugin',
  4244.             'ra' => 'audio/x-realaudio',
  4245.             'wav' => 'audio/x-wav',
  4246.             'mka' => 'audio/x-matroska',
  4247.             'bmp' => 'image/bmp',
  4248.             'gif' => 'image/gif',
  4249.             'jpeg' => 'image/jpeg',
  4250.             'jpe' => 'image/jpeg',
  4251.             'jpg' => 'image/jpeg',
  4252.             'png' => 'image/png',
  4253.             'tiff' => 'image/tiff',
  4254.             'tif' => 'image/tiff',
  4255.             'webp' => 'image/webp',
  4256.             'avif' => 'image/avif',
  4257.             'heif' => 'image/heif',
  4258.             'heifs' => 'image/heif-sequence',
  4259.             'heic' => 'image/heic',
  4260.             'heics' => 'image/heic-sequence',
  4261.             'eml' => 'message/rfc822',
  4262.             'css' => 'text/css',
  4263.             'html' => 'text/html',
  4264.             'htm' => 'text/html',
  4265.             'shtml' => 'text/html',
  4266.             'log' => 'text/plain',
  4267.             'text' => 'text/plain',
  4268.             'txt' => 'text/plain',
  4269.             'rtx' => 'text/richtext',
  4270.             'rtf' => 'text/rtf',
  4271.             'vcf' => 'text/vcard',
  4272.             'vcard' => 'text/vcard',
  4273.             'ics' => 'text/calendar',
  4274.             'xml' => 'text/xml',
  4275.             'xsl' => 'text/xml',
  4276.             'wmv' => 'video/x-ms-wmv',
  4277.             'mpeg' => 'video/mpeg',
  4278.             'mpe' => 'video/mpeg',
  4279.             'mpg' => 'video/mpeg',
  4280.             'mp4' => 'video/mp4',
  4281.             'm4v' => 'video/mp4',
  4282.             'mov' => 'video/quicktime',
  4283.             'qt' => 'video/quicktime',
  4284.             'rv' => 'video/vnd.rn-realvideo',
  4285.             'avi' => 'video/x-msvideo',
  4286.             'movie' => 'video/x-sgi-movie',
  4287.             'webm' => 'video/webm',
  4288.             'mkv' => 'video/x-matroska',
  4289.         ];
  4290.         $ext = strtolower($ext);
  4291.         if (array_key_exists($ext, $mimes)) {
  4292.             return $mimes[$ext];
  4293.         }
  4294.  
  4295.         return 'application/octet-stream';
  4296.     }
  4297.  
  4298.     /**
  4299.      * Map a file name to a MIME type.
  4300.      * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
  4301.      *
  4302.      * @param string $filename A file name or full path, does not need to exist as a file
  4303.      *
  4304.      * @return string
  4305.      */
  4306.     public static function filenameToType($filename)
  4307.     {
  4308.         // In case the path is a URL, strip any query string before getting extension
  4309.         $qpos = strpos($filename, '?');
  4310.         if (false !== $qpos) {
  4311.             $filename = substr($filename, 0, $qpos);
  4312.         }
  4313.         $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);
  4314.  
  4315.         return static::_mime_types($ext);
  4316.     }
  4317.  
  4318.     /**
  4319.      * Multi-byte-safe pathinfo replacement.
  4320.      * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
  4321.      *
  4322.      * @see http://www.php.net/manual/en/function.pathinfo.php#107461
  4323.      *
  4324.      * @param string     $path    A filename or path, does not need to exist as a file
  4325.      * @param int|string $options Either a PATHINFO_* constant,
  4326.      *                            or a string name to return only the specified piece
  4327.      *
  4328.      * @return string|array
  4329.      */
  4330.     public static function mb_pathinfo($path, $options = null)
  4331.     {
  4332.         $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
  4333.         $pathinfo = [];
  4334.         if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {
  4335.             if (array_key_exists(1, $pathinfo)) {
  4336.                 $ret['dirname'] = $pathinfo[1];
  4337.             }
  4338.             if (array_key_exists(2, $pathinfo)) {
  4339.                 $ret['basename'] = $pathinfo[2];
  4340.             }
  4341.             if (array_key_exists(5, $pathinfo)) {
  4342.                 $ret['extension'] = $pathinfo[5];
  4343.             }
  4344.             if (array_key_exists(3, $pathinfo)) {
  4345.                 $ret['filename'] = $pathinfo[3];
  4346.             }
  4347.         }
  4348.         switch ($options) {
  4349.             case PATHINFO_DIRNAME:
  4350.             case 'dirname':
  4351.                 return $ret['dirname'];
  4352.             case PATHINFO_BASENAME:
  4353.             case 'basename':
  4354.                 return $ret['basename'];
  4355.             case PATHINFO_EXTENSION:
  4356.             case 'extension':
  4357.                 return $ret['extension'];
  4358.             case PATHINFO_FILENAME:
  4359.             case 'filename':
  4360.                 return $ret['filename'];
  4361.             default:
  4362.                 return $ret;
  4363.         }
  4364.     }
  4365.  
  4366.     /**
  4367.      * Set or reset instance properties.
  4368.      * You should avoid this function - it's more verbose, less efficient, more error-prone and
  4369.      * harder to debug than setting properties directly.
  4370.      * Usage Example:
  4371.      * `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);`
  4372.      *   is the same as:
  4373.      * `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`.
  4374.      *
  4375.      * @param string $name  The property name to set
  4376.      * @param mixed  $value The value to set the property to
  4377.      *
  4378.      * @return bool
  4379.      */
  4380.     public function set($name, $value = '')
  4381.     {
  4382.         if (property_exists($this, $name)) {
  4383.             $this->$name = $value;
  4384.  
  4385.             return true;
  4386.         }
  4387.         $this->setError($this->lang('variable_set') . $name);
  4388.  
  4389.         return false;
  4390.     }
  4391.  
  4392.     /**
  4393.      * Strip newlines to prevent header injection.
  4394.      *
  4395.      * @param string $str
  4396.      *
  4397.      * @return string
  4398.      */
  4399.     public function secureHeader($str)
  4400.     {
  4401.         return trim(str_replace(["\r", "\n"], '', $str));
  4402.     }
  4403.  
  4404.     /**
  4405.      * Normalize line breaks in a string.
  4406.      * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
  4407.      * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
  4408.      *
  4409.      * @param string $text
  4410.      * @param string $breaktype What kind of line break to use; defaults to static::$LE
  4411.      *
  4412.      * @return string
  4413.      */
  4414.     public static function normalizeBreaks($text, $breaktype = null)
  4415.     {
  4416.         if (null === $breaktype) {
  4417.             $breaktype = static::$LE;
  4418.         }
  4419.         // Normalise to \n
  4420.         $text = str_replace([self::CRLF, "\r"], "\n", $text);
  4421.         // Now convert LE as needed
  4422.         if ("\n" !== $breaktype) {
  4423.             $text = str_replace("\n", $breaktype, $text);
  4424.         }
  4425.  
  4426.         return $text;
  4427.     }
  4428.  
  4429.     /**
  4430.      * Remove trailing breaks from a string.
  4431.      *
  4432.      * @param string $text
  4433.      *
  4434.      * @return string The text to remove breaks from
  4435.      */
  4436.     public static function stripTrailingWSP($text)
  4437.     {
  4438.         return rtrim($text, " \r\n\t");
  4439.     }
  4440.  
  4441.     /**
  4442.      * Return the current line break format string.
  4443.      *
  4444.      * @return string
  4445.      */
  4446.     public static function getLE()
  4447.     {
  4448.         return static::$LE;
  4449.     }
  4450.  
  4451.     /**
  4452.      * Set the line break format string, e.g. "\r\n".
  4453.      *
  4454.      * @param string $le
  4455.      */
  4456.     protected static function setLE($le)
  4457.     {
  4458.         static::$LE = $le;
  4459.     }
  4460.  
  4461.     /**
  4462.      * Set the public and private key files and password for S/MIME signing.
  4463.      *
  4464.      * @param string $cert_filename
  4465.      * @param string $key_filename
  4466.      * @param string $key_pass            Password for private key
  4467.      * @param string $extracerts_filename Optional path to chain certificate
  4468.      */
  4469.     public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
  4470.     {
  4471.         $this->sign_cert_file = $cert_filename;
  4472.         $this->sign_key_file = $key_filename;
  4473.         $this->sign_key_pass = $key_pass;
  4474.         $this->sign_extracerts_file = $extracerts_filename;
  4475.     }
  4476.  
  4477.     /**
  4478.      * Quoted-Printable-encode a DKIM header.
  4479.      *
  4480.      * @param string $txt
  4481.      *
  4482.      * @return string
  4483.      */
  4484.     public function DKIM_QP($txt)
  4485.     {
  4486.         $line = '';
  4487.         $len = strlen($txt);
  4488.         for ($i = 0; $i < $len; ++$i) {
  4489.             $ord = ord($txt[$i]);
  4490.             if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
  4491.                 $line .= $txt[$i];
  4492.             } else {
  4493.                 $line .= '=' . sprintf('%02X', $ord);
  4494.             }
  4495.         }
  4496.  
  4497.         return $line;
  4498.     }
  4499.  
  4500.     /**
  4501.      * Generate a DKIM signature.
  4502.      *
  4503.      * @param string $signHeader
  4504.      *
  4505.      * @throws PHPMailerException
  4506.      *
  4507.      * @return string The DKIM signature value
  4508.      */
  4509.     public function DKIM_Sign($signHeader)
  4510.     {
  4511.         if (!defined('PKCS7_TEXT')) {
  4512.             if ($this->exceptions) {
  4513.                 throw new PHPMailerException($this->lang('extension_missing') . 'openssl');
  4514.             }
  4515.  
  4516.             return '';
  4517.         }
  4518.         $privKeyStr = !empty($this->DKIM_private_string) ?
  4519.             $this->DKIM_private_string :
  4520.             file_get_contents($this->DKIM_private);
  4521.         if ('' !== $this->DKIM_passphrase) {
  4522.             $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
  4523.         } else {
  4524.             $privKey = openssl_pkey_get_private($privKeyStr);
  4525.         }
  4526.         if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
  4527.             if (PHP_MAJOR_VERSION < 8) {
  4528.                 openssl_pkey_free($privKey);
  4529.             }
  4530.  
  4531.             return base64_encode($signature);
  4532.         }
  4533.         if (PHP_MAJOR_VERSION < 8) {
  4534.             openssl_pkey_free($privKey);
  4535.         }
  4536.  
  4537.         return '';
  4538.     }
  4539.  
  4540.     /**
  4541.      * Generate a DKIM canonicalization header.
  4542.      * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
  4543.      * Canonicalized headers should *always* use CRLF, regardless of mailer setting.
  4544.      *
  4545.      * @see https://tools.ietf.org/html/rfc6376#section-3.4.2
  4546.      *
  4547.      * @param string $signHeader Header
  4548.      *
  4549.      * @return string
  4550.      */
  4551.     public function DKIM_HeaderC($signHeader)
  4552.     {
  4553.         //Normalize breaks to CRLF (regardless of the mailer)
  4554.         $signHeader = static::normalizeBreaks($signHeader, self::CRLF);
  4555.         //Unfold header lines
  4556.         //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
  4557.         //@see https://tools.ietf.org/html/rfc5322#section-2.2
  4558.         //That means this may break if you do something daft like put vertical tabs in your headers.
  4559.         $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
  4560.         //Break headers out into an array
  4561.         $lines = explode(self::CRLF, $signHeader);
  4562.         foreach ($lines as $key => $line) {
  4563.             //If the header is missing a :, skip it as it's invalid
  4564.             //This is likely to happen because the explode() above will also split
  4565.             //on the trailing LE, leaving an empty line
  4566.             if (strpos($line, ':') === false) {
  4567.                 continue;
  4568.             }
  4569.             list($heading, $value) = explode(':', $line, 2);
  4570.             //Lower-case header name
  4571.             $heading = strtolower($heading);
  4572.             //Collapse white space within the value, also convert WSP to space
  4573.             $value = preg_replace('/[ \t]+/', ' ', $value);
  4574.             //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value
  4575.             //But then says to delete space before and after the colon.
  4576.             //Net result is the same as trimming both ends of the value.
  4577.             //By elimination, the same applies to the field name
  4578.             $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");
  4579.         }
  4580.  
  4581.         return implode(self::CRLF, $lines);
  4582.     }
  4583.  
  4584.     /**
  4585.      * Generate a DKIM canonicalization body.
  4586.      * Uses the 'simple' algorithm from RFC6376 section 3.4.3.
  4587.      * Canonicalized bodies should *always* use CRLF, regardless of mailer setting.
  4588.      *
  4589.      * @see https://tools.ietf.org/html/rfc6376#section-3.4.3
  4590.      *
  4591.      * @param string $body Message Body
  4592.      *
  4593.      * @return string
  4594.      */
  4595.     public function DKIM_BodyC($body)
  4596.     {
  4597.         if (empty($body)) {
  4598.             return self::CRLF;
  4599.         }
  4600.         // Normalize line endings to CRLF
  4601.         $body = static::normalizeBreaks($body, self::CRLF);
  4602.  
  4603.         //Reduce multiple trailing line breaks to a single one
  4604.         return static::stripTrailingWSP($body) . self::CRLF;
  4605.     }
  4606.  
  4607.     /**
  4608.      * Create the DKIM header and body in a new message header.
  4609.      *
  4610.      * @param string $headers_line Header lines
  4611.      * @param string $subject      Subject
  4612.      * @param string $body         Body
  4613.      *
  4614.      * @throws PHPMailerException
  4615.      *
  4616.      * @return string
  4617.      */
  4618.     public function DKIM_Add($headers_line, $subject, $body)
  4619.     {
  4620.         $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
  4621.         $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of header & body
  4622.         $DKIMquery = 'dns/txt'; // Query method
  4623.         $DKIMtime = time();
  4624.         //Always sign these headers without being asked
  4625.         //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1
  4626.         $autoSignHeaders = [
  4627.             'from',
  4628.             'to',
  4629.             'cc',
  4630.             'date',
  4631.             'subject',
  4632.             'reply-to',
  4633.             'message-id',
  4634.             'content-type',
  4635.             'mime-version',
  4636.             'x-mailer',
  4637.         ];
  4638.         if (stripos($headers_line, 'Subject') === false) {
  4639.             $headers_line .= 'Subject: ' . $subject . static::$LE;
  4640.         }
  4641.         $headerLines = explode(static::$LE, $headers_line);
  4642.         $currentHeaderLabel = '';
  4643.         $currentHeaderValue = '';
  4644.         $parsedHeaders = [];
  4645.         $headerLineIndex = 0;
  4646.         $headerLineCount = count($headerLines);
  4647.         foreach ($headerLines as $headerLine) {
  4648.             $matches = [];
  4649.             if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) {
  4650.                 if ($currentHeaderLabel !== '') {
  4651.                     //We were previously in another header; This is the start of a new header, so save the previous one
  4652.                     $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
  4653.                 }
  4654.                 $currentHeaderLabel = $matches[1];
  4655.                 $currentHeaderValue = $matches[2];
  4656.             } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) {
  4657.                 //This is a folded continuation of the current header, so unfold it
  4658.                 $currentHeaderValue .= ' ' . $matches[1];
  4659.             }
  4660.             ++$headerLineIndex;
  4661.             if ($headerLineIndex >= $headerLineCount) {
  4662.                 //This was the last line, so finish off this header
  4663.                 $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
  4664.             }
  4665.         }
  4666.         $copiedHeaders = [];
  4667.         $headersToSignKeys = [];
  4668.         $headersToSign = [];
  4669.         foreach ($parsedHeaders as $header) {
  4670.             //Is this header one that must be included in the DKIM signature?
  4671.             if (in_array(strtolower($header['label']), $autoSignHeaders, true)) {
  4672.                 $headersToSignKeys[] = $header['label'];
  4673.                 $headersToSign[] = $header['label'] . ': ' . $header['value'];
  4674.                 if ($this->DKIM_copyHeaderFields) {
  4675.                     $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
  4676.                         str_replace('|', '=7C', $this->DKIM_QP($header['value']));
  4677.                 }
  4678.                 continue;
  4679.             }
  4680.             //Is this an extra custom header we've been asked to sign?
  4681.             if (in_array($header['label'], $this->DKIM_extraHeaders, true)) {
  4682.                 //Find its value in custom headers
  4683.                 foreach ($this->CustomHeader as $customHeader) {
  4684.                     if ($customHeader[0] === $header['label']) {
  4685.                         $headersToSignKeys[] = $header['label'];
  4686.                         $headersToSign[] = $header['label'] . ': ' . $header['value'];
  4687.                         if ($this->DKIM_copyHeaderFields) {
  4688.                             $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
  4689.                                 str_replace('|', '=7C', $this->DKIM_QP($header['value']));
  4690.                         }
  4691.                         //Skip straight to the next header
  4692.                         continue 2;
  4693.                     }
  4694.                 }
  4695.             }
  4696.         }
  4697.         $copiedHeaderFields = '';
  4698.         if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) {
  4699.             //Assemble a DKIM 'z' tag
  4700.             $copiedHeaderFields = ' z=';
  4701.             $first = true;
  4702.             foreach ($copiedHeaders as $copiedHeader) {
  4703.                 if (!$first) {
  4704.                     $copiedHeaderFields .= static::$LE . ' |';
  4705.                 }
  4706.                 //Fold long values
  4707.                 if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {
  4708.                     $copiedHeaderFields .= substr(
  4709.                         chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS),
  4710.                         0,
  4711.                         -strlen(static::$LE . self::FWS)
  4712.                     );
  4713.                 } else {
  4714.                     $copiedHeaderFields .= $copiedHeader;
  4715.                 }
  4716.                 $first = false;
  4717.             }
  4718.             $copiedHeaderFields .= ';' . static::$LE;
  4719.         }
  4720.         $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE;
  4721.         $headerValues = implode(static::$LE, $headersToSign);
  4722.         $body = $this->DKIM_BodyC($body);
  4723.         $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
  4724.         $ident = '';
  4725.         if ('' !== $this->DKIM_identity) {
  4726.             $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE;
  4727.         }
  4728.         //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag
  4729.         //which is appended after calculating the signature
  4730.         //https://tools.ietf.org/html/rfc6376#section-3.5
  4731.         $dkimSignatureHeader = 'DKIM-Signature: v=1;' .
  4732.             ' d=' . $this->DKIM_domain . ';' .
  4733.             ' s=' . $this->DKIM_selector . ';' . static::$LE .
  4734.             ' a=' . $DKIMsignatureType . ';' .
  4735.             ' q=' . $DKIMquery . ';' .
  4736.             ' t=' . $DKIMtime . ';' .
  4737.             ' c=' . $DKIMcanonicalization . ';' . static::$LE .
  4738.             $headerKeys .
  4739.             $ident .
  4740.             $copiedHeaderFields .
  4741.             ' bh=' . $DKIMb64 . ';' . static::$LE .
  4742.             ' b=';
  4743.         //Canonicalize the set of headers
  4744.         $canonicalizedHeaders = $this->DKIM_HeaderC(
  4745.             $headerValues . static::$LE . $dkimSignatureHeader
  4746.         );
  4747.         $signature = $this->DKIM_Sign($canonicalizedHeaders);
  4748.         $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS));
  4749.  
  4750.         return static::normalizeBreaks($dkimSignatureHeader . $signature);
  4751.     }
  4752.  
  4753.     /**
  4754.      * Detect if a string contains a line longer than the maximum line length
  4755.      * allowed by RFC 2822 section 2.1.1.
  4756.      *
  4757.      * @param string $str
  4758.      *
  4759.      * @return bool
  4760.      */
  4761.     public static function hasLineLongerThanMax($str)
  4762.     {
  4763.         return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
  4764.     }
  4765.  
  4766.     /**
  4767.      * If a string contains any "special" characters, double-quote the name,
  4768.      * and escape any double quotes with a backslash.
  4769.      *
  4770.      * @param string $str
  4771.      *
  4772.      * @return string
  4773.      *
  4774.      * @see RFC822 3.4.1
  4775.      */
  4776.     public static function quotedString($str)
  4777.     {
  4778.         if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) {
  4779.             //If the string contains any of these chars, it must be double-quoted
  4780.             //and any double quotes must be escaped with a backslash
  4781.             return '"' . str_replace('"', '\\"', $str) . '"';
  4782.         }
  4783.  
  4784.         //Return the string untouched, it doesn't need quoting
  4785.         return $str;
  4786.     }
  4787.  
  4788.     /**
  4789.      * Allows for public read access to 'to' property.
  4790.      * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4791.      *
  4792.      * @return array
  4793.      */
  4794.     public function getToAddresses()
  4795.     {
  4796.         return $this->to;
  4797.     }
  4798.  
  4799.     /**
  4800.      * Allows for public read access to 'cc' property.
  4801.      * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4802.      *
  4803.      * @return array
  4804.      */
  4805.     public function getCcAddresses()
  4806.     {
  4807.         return $this->cc;
  4808.     }
  4809.  
  4810.     /**
  4811.      * Allows for public read access to 'bcc' property.
  4812.      * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4813.      *
  4814.      * @return array
  4815.      */
  4816.     public function getBccAddresses()
  4817.     {
  4818.         return $this->bcc;
  4819.     }
  4820.  
  4821.     /**
  4822.      * Allows for public read access to 'ReplyTo' property.
  4823.      * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4824.      *
  4825.      * @return array
  4826.      */
  4827.     public function getReplyToAddresses()
  4828.     {
  4829.         return $this->ReplyTo;
  4830.     }
  4831.  
  4832.     /**
  4833.      * Allows for public read access to 'all_recipients' property.
  4834.      * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4835.      *
  4836.      * @return array
  4837.      */
  4838.     public function getAllRecipientAddresses()
  4839.     {
  4840.         return $this->all_recipients;
  4841.     }
  4842.  
  4843.     /**
  4844.      * Perform a callback.
  4845.      *
  4846.      * @param bool   $isSent
  4847.      * @param array  $to
  4848.      * @param array  $cc
  4849.      * @param array  $bcc
  4850.      * @param string $subject
  4851.      * @param string $body
  4852.      * @param string $from
  4853.      * @param array  $extra
  4854.      */
  4855.     protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
  4856.     {
  4857.         if (!empty($this->action_function) && is_callable($this->action_function)) {
  4858.             call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);
  4859.         }
  4860.     }
  4861.  
  4862.     /**
  4863.      * Get the OAuth instance.
  4864.      *
  4865.      * @return OAuth
  4866.      */
  4867.     public function getOAuth()
  4868.     {
  4869.         return $this->oauth;
  4870.     }
  4871.  
  4872.     /**
  4873.      * Set an OAuth instance.
  4874.      */
  4875.     public function setOAuth(OAuth $oauth)
  4876.     {
  4877.         $this->oauth = $oauth;
  4878.     }
  4879. }
  4880.  
  4881.  
  4882. /**
  4883.  * PHPMailer RFC821 SMTP email transport class.
  4884.  * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
  4885.  *
  4886.  * @author Chris Ryan
  4887.  * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
  4888.  */
  4889. class SMTP
  4890. {
  4891.     /**
  4892.      * The PHPMailer SMTP version number.
  4893.      *
  4894.      * @var string
  4895.      */
  4896.     const VERSION = '6.2.0';
  4897.  
  4898.     /**
  4899.      * SMTP line break constant.
  4900.      *
  4901.      * @var string
  4902.      */
  4903.     const LE = "\r\n";
  4904.  
  4905.     /**
  4906.      * The SMTP port to use if one is not specified.
  4907.      *
  4908.      * @var int
  4909.      */
  4910.     const DEFAULT_PORT = 25;
  4911.  
  4912.     /**
  4913.      * The maximum line length allowed by RFC 5321 section 4.5.3.1.6,
  4914.      * *excluding* a trailing CRLF break.
  4915.      *
  4916.      * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6
  4917.      *
  4918.      * @var int
  4919.      */
  4920.     const MAX_LINE_LENGTH = 998;
  4921.  
  4922.     /**
  4923.      * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5,
  4924.      * *including* a trailing CRLF line break.
  4925.      *
  4926.      * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5
  4927.      *
  4928.      * @var int
  4929.      */
  4930.     const MAX_REPLY_LENGTH = 512;
  4931.  
  4932.     /**
  4933.      * Debug level for no output.
  4934.      *
  4935.      * @var int
  4936.      */
  4937.     const DEBUG_OFF = 0;
  4938.  
  4939.     /**
  4940.      * Debug level to show client -> server messages.
  4941.      *
  4942.      * @var int
  4943.      */
  4944.     const DEBUG_CLIENT = 1;
  4945.  
  4946.     /**
  4947.      * Debug level to show client -> server and server -> client messages.
  4948.      *
  4949.      * @var int
  4950.      */
  4951.     const DEBUG_SERVER = 2;
  4952.  
  4953.     /**
  4954.      * Debug level to show connection status, client -> server and server -> client messages.
  4955.      *
  4956.      * @var int
  4957.      */
  4958.     const DEBUG_CONNECTION = 3;
  4959.  
  4960.     /**
  4961.      * Debug level to show all messages.
  4962.      *
  4963.      * @var int
  4964.      */
  4965.     const DEBUG_LOWLEVEL = 4;
  4966.  
  4967.     /**
  4968.      * Debug output level.
  4969.      * Options:
  4970.      * * self::DEBUG_OFF (`0`) No debug output, default
  4971.      * * self::DEBUG_CLIENT (`1`) Client commands
  4972.      * * self::DEBUG_SERVER (`2`) Client commands and server responses
  4973.      * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
  4974.      * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages.
  4975.      *
  4976.      * @var int
  4977.      */
  4978.     public $do_debug = self::DEBUG_OFF;
  4979.  
  4980.     /**
  4981.      * How to handle debug output.
  4982.      * Options:
  4983.      * * `echo` Output plain-text as-is, appropriate for CLI
  4984.      * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
  4985.      * * `error_log` Output to error log as configured in php.ini
  4986.      * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
  4987.      *
  4988.      * ```php
  4989.      * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
  4990.      * ```
  4991.      *
  4992.      * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
  4993.      * level output is used:
  4994.      *
  4995.      * ```php
  4996.      * $mail->Debugoutput = new myPsr3Logger;
  4997.      * ```
  4998.      *
  4999.      * @var string|callable|\Psr\Log\LoggerInterface
  5000.      */
  5001.     public $Debugoutput = 'echo';
  5002.  
  5003.     /**
  5004.      * Whether to use VERP.
  5005.      *
  5006.      * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path
  5007.      * @see http://www.postfix.org/VERP_README.html Info on VERP
  5008.      *
  5009.      * @var bool
  5010.      */
  5011.     public $do_verp = false;
  5012.  
  5013.     /**
  5014.      * The timeout value for connection, in seconds.
  5015.      * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
  5016.      * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
  5017.      *
  5018.      * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2
  5019.      *
  5020.      * @var int
  5021.      */
  5022.     public $Timeout = 300;
  5023.  
  5024.     /**
  5025.      * How long to wait for commands to complete, in seconds.
  5026.      * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
  5027.      *
  5028.      * @var int
  5029.      */
  5030.     public $Timelimit = 300;
  5031.  
  5032.     /**
  5033.      * Patterns to extract an SMTP transaction id from reply to a DATA command.
  5034.      * The first capture group in each regex will be used as the ID.
  5035.      * MS ESMTP returns the message ID, which may not be correct for internal tracking.
  5036.      *
  5037.      * @var string[]
  5038.      */
  5039.     protected $smtp_transaction_id_patterns = [
  5040.         'exim' => '/[\d]{3} OK id=(.*)/',
  5041.         'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/',
  5042.         'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/',
  5043.         'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/',
  5044.         'Amazon_SES' => '/[\d]{3} Ok (.*)/',
  5045.         'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
  5046.         'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
  5047.     ];
  5048.  
  5049.     /**
  5050.      * The last transaction ID issued in response to a DATA command,
  5051.      * if one was detected.
  5052.      *
  5053.      * @var string|bool|null
  5054.      */
  5055.     protected $last_smtp_transaction_id;
  5056.  
  5057.     /**
  5058.      * The socket for the server connection.
  5059.      *
  5060.      * @var ?resource
  5061.      */
  5062.     protected $smtp_conn;
  5063.  
  5064.     /**
  5065.      * Error information, if any, for the last SMTP command.
  5066.      *
  5067.      * @var array
  5068.      */
  5069.     protected $error = [
  5070.         'error' => '',
  5071.         'detail' => '',
  5072.         'smtp_code' => '',
  5073.         'smtp_code_ex' => '',
  5074.     ];
  5075.  
  5076.     /**
  5077.      * The reply the server sent to us for HELO.
  5078.      * If null, no HELO string has yet been received.
  5079.      *
  5080.      * @var string|null
  5081.      */
  5082.     protected $helo_rply;
  5083.  
  5084.     /**
  5085.      * The set of SMTP extensions sent in reply to EHLO command.
  5086.      * Indexes of the array are extension names.
  5087.      * Value at index 'HELO' or 'EHLO' (according to command that was sent)
  5088.      * represents the server name. In case of HELO it is the only element of the array.
  5089.      * Other values can be boolean TRUE or an array containing extension options.
  5090.      * If null, no HELO/EHLO string has yet been received.
  5091.      *
  5092.      * @var array|null
  5093.      */
  5094.     protected $server_caps;
  5095.  
  5096.     /**
  5097.      * The most recent reply received from the server.
  5098.      *
  5099.      * @var string
  5100.      */
  5101.     protected $last_reply = '';
  5102.  
  5103.     /**
  5104.      * Output debugging info via a user-selected method.
  5105.      *
  5106.      * @param string $str   Debug string to output
  5107.      * @param int    $level The debug level of this message; see DEBUG_* constants
  5108.      *
  5109.      * @see SMTP::$Debugoutput
  5110.      * @see SMTP::$do_debug
  5111.      */
  5112.     protected function edebug($str, $level = 0)
  5113.     {
  5114.         if ($level > $this->do_debug) {
  5115.             return;
  5116.         }
  5117.         //Is this a PSR-3 logger?
  5118.         if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
  5119.             $this->Debugoutput->debug($str);
  5120.  
  5121.             return;
  5122.         }
  5123.         //Avoid clash with built-in function names
  5124.         if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
  5125.             call_user_func($this->Debugoutput, $str, $level);
  5126.  
  5127.             return;
  5128.         }
  5129.         switch ($this->Debugoutput) {
  5130.             case 'error_log':
  5131.                 //Don't output, just log
  5132.                 error_log($str);
  5133.                 break;
  5134.             case 'html':
  5135.                 //Cleans up output a bit for a better looking, HTML-safe output
  5136.                 echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
  5137.                     preg_replace('/[\r\n]+/', '', $str),
  5138.                     ENT_QUOTES,
  5139.                     'UTF-8'
  5140.                 ), "<br>\n";
  5141.                 break;
  5142.             case 'echo':
  5143.             default:
  5144.                 //Normalize line breaks
  5145.                 $str = preg_replace('/\r\n|\r/m', "\n", $str);
  5146.                 echo gmdate('Y-m-d H:i:s'),
  5147.                 "\t",
  5148.                     //Trim trailing space
  5149.                 trim(
  5150.                     //Indent for readability, except for trailing break
  5151.                     str_replace(
  5152.                         "\n",
  5153.                         "\n                   \t                  ",
  5154.                         trim($str)
  5155.                     )
  5156.                 ),
  5157.                 "\n";
  5158.         }
  5159.     }
  5160.  
  5161.     /**
  5162.      * Connect to an SMTP server.
  5163.      *
  5164.      * @param string $host    SMTP server IP or host name
  5165.      * @param int    $port    The port number to connect to
  5166.      * @param int    $timeout How long to wait for the connection to open
  5167.      * @param array  $options An array of options for stream_context_create()
  5168.      *
  5169.      * @return bool
  5170.      */
  5171.     public function connect($host, $port = null, $timeout = 30, $options = [])
  5172.     {
  5173.         // Clear errors to avoid confusion
  5174.         $this->setError('');
  5175.         // Make sure we are __not__ connected
  5176.         if ($this->connected()) {
  5177.             // Already connected, generate error
  5178.             $this->setError('Already connected to a server');
  5179.  
  5180.             return false;
  5181.         }
  5182.         if (empty($port)) {
  5183.             $port = self::DEFAULT_PORT;
  5184.         }
  5185.         // Connect to the SMTP server
  5186.         $this->edebug(
  5187.             "Connection: opening to $host:$port, timeout=$timeout, options=" .
  5188.             (count($options) > 0 ? var_export($options, true) : 'array()'),
  5189.             self::DEBUG_CONNECTION
  5190.         );
  5191.  
  5192.         $this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options);
  5193.  
  5194.         if ($this->smtp_conn === false) {
  5195.             //Error info already set inside `getSMTPConnection()`
  5196.             return false;
  5197.         }
  5198.  
  5199.         $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
  5200.  
  5201.         // Get any announcement
  5202.         $this->last_reply = $this->get_lines();
  5203.         $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
  5204.  
  5205.         return true;
  5206.     }
  5207.  
  5208.     /**
  5209.      * Create connection to the SMTP server.
  5210.      *
  5211.      * @param string $host    SMTP server IP or host name
  5212.      * @param int    $port    The port number to connect to
  5213.      * @param int    $timeout How long to wait for the connection to open
  5214.      * @param array  $options An array of options for stream_context_create()
  5215.      *
  5216.      * @return false|resource
  5217.      */
  5218.     protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = [])
  5219.     {
  5220.         static $streamok;
  5221.         //This is enabled by default since 5.0.0 but some providers disable it
  5222.         //Check this once and cache the result
  5223.         if (null === $streamok) {
  5224.             $streamok = function_exists('stream_socket_client');
  5225.         }
  5226.  
  5227.         $errno = 0;
  5228.         $errstr = '';
  5229.         if ($streamok) {
  5230.             $socket_context = stream_context_create($options);
  5231.             set_error_handler([$this, 'errorHandler']);
  5232.             $connection = stream_socket_client(
  5233.                 $host . ':' . $port,
  5234.                 $errno,
  5235.                 $errstr,
  5236.                 $timeout,
  5237.                 STREAM_CLIENT_CONNECT,
  5238.                 $socket_context
  5239.             );
  5240.             restore_error_handler();
  5241.         } else {
  5242.             //Fall back to fsockopen which should work in more places, but is missing some features
  5243.             $this->edebug(
  5244.                 'Connection: stream_socket_client not available, falling back to fsockopen',
  5245.                 self::DEBUG_CONNECTION
  5246.             );
  5247.             set_error_handler([$this, 'errorHandler']);
  5248.             $connection = fsockopen(
  5249.                 $host,
  5250.                 $port,
  5251.                 $errno,
  5252.                 $errstr,
  5253.                 $timeout
  5254.             );
  5255.             restore_error_handler();
  5256.         }
  5257.  
  5258.         // Verify we connected properly
  5259.         if (!is_resource($connection)) {
  5260.             $this->setError(
  5261.                 'Failed to connect to server',
  5262.                 '',
  5263.                 (string) $errno,
  5264.                 $errstr
  5265.             );
  5266.             $this->edebug(
  5267.                 'SMTP ERROR: ' . $this->error['error']
  5268.                 . ": $errstr ($errno)",
  5269.                 self::DEBUG_CLIENT
  5270.             );
  5271.  
  5272.             return false;
  5273.         }
  5274.  
  5275.         // SMTP server can take longer to respond, give longer timeout for first read
  5276.         // Windows does not have support for this timeout function
  5277.         if (strpos(PHP_OS, 'WIN') !== 0) {
  5278.             $max = (int)ini_get('max_execution_time');
  5279.             // Don't bother if unlimited, or if set_time_limit is disabled
  5280.             if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
  5281.                 @set_time_limit($timeout);
  5282.             }
  5283.             stream_set_timeout($connection, $timeout, 0);
  5284.         }
  5285.  
  5286.         return $connection;
  5287.     }
  5288.  
  5289.     /**
  5290.      * Initiate a TLS (encrypted) session.
  5291.      *
  5292.      * @return bool
  5293.      */
  5294.     public function startTLS()
  5295.     {
  5296.         if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
  5297.             return false;
  5298.         }
  5299.  
  5300.         //Allow the best TLS version(s) we can
  5301.         $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
  5302.  
  5303.         //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
  5304.         //so add them back in manually if we can
  5305.         if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
  5306.             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
  5307.             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
  5308.         }
  5309.  
  5310.         // Begin encrypted connection
  5311.         set_error_handler([$this, 'errorHandler']);
  5312.         $crypto_ok = stream_socket_enable_crypto(
  5313.             $this->smtp_conn,
  5314.             true,
  5315.             $crypto_method
  5316.         );
  5317.         restore_error_handler();
  5318.  
  5319.         return (bool) $crypto_ok;
  5320.     }
  5321.  
  5322.     /**
  5323.      * Perform SMTP authentication.
  5324.      * Must be run after hello().
  5325.      *
  5326.      * @see    hello()
  5327.      *
  5328.      * @param string $username The user name
  5329.      * @param string $password The password
  5330.      * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
  5331.      * @param OAuth  $OAuth    An optional OAuth instance for XOAUTH2 authentication
  5332.      *
  5333.      * @return bool True if successfully authenticated
  5334.      */
  5335.     public function authenticate(
  5336.         $username,
  5337.         $password,
  5338.         $authtype = null,
  5339.         $OAuth = null
  5340.     ) {
  5341.         if (!$this->server_caps) {
  5342.             $this->setError('Authentication is not allowed before HELO/EHLO');
  5343.  
  5344.             return false;
  5345.         }
  5346.  
  5347.         if (array_key_exists('EHLO', $this->server_caps)) {
  5348.             // SMTP extensions are available; try to find a proper authentication method
  5349.             if (!array_key_exists('AUTH', $this->server_caps)) {
  5350.                 $this->setError('Authentication is not allowed at this stage');
  5351.                 // 'at this stage' means that auth may be allowed after the stage changes
  5352.                 // e.g. after STARTTLS
  5353.  
  5354.                 return false;
  5355.             }
  5356.  
  5357.             $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
  5358.             $this->edebug(
  5359.                 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
  5360.                 self::DEBUG_LOWLEVEL
  5361.             );
  5362.  
  5363.             //If we have requested a specific auth type, check the server supports it before trying others
  5364.             if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) {
  5365.                 $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
  5366.                 $authtype = null;
  5367.             }
  5368.  
  5369.             if (empty($authtype)) {
  5370.                 //If no auth mechanism is specified, attempt to use these, in this order
  5371.                 //Try CRAM-MD5 first as it's more secure than the others
  5372.                 foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
  5373.                     if (in_array($method, $this->server_caps['AUTH'], true)) {
  5374.                         $authtype = $method;
  5375.                         break;
  5376.                     }
  5377.                 }
  5378.                 if (empty($authtype)) {
  5379.                     $this->setError('No supported authentication methods found');
  5380.  
  5381.                     return false;
  5382.                 }
  5383.                 $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
  5384.             }
  5385.  
  5386.             if (!in_array($authtype, $this->server_caps['AUTH'], true)) {
  5387.                 $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
  5388.  
  5389.                 return false;
  5390.             }
  5391.         } elseif (empty($authtype)) {
  5392.             $authtype = 'LOGIN';
  5393.         }
  5394.         switch ($authtype) {
  5395.             case 'PLAIN':
  5396.                 // Start authentication
  5397.                 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
  5398.                     return false;
  5399.                 }
  5400.                 // Send encoded username and password
  5401.                 if (
  5402.                     !$this->sendCommand(
  5403.                         'User & Password',
  5404.                         base64_encode("\0" . $username . "\0" . $password),
  5405.                         235
  5406.                     )
  5407.                 ) {
  5408.                     return false;
  5409.                 }
  5410.                 break;
  5411.             case 'LOGIN':
  5412.                 // Start authentication
  5413.                 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
  5414.                     return false;
  5415.                 }
  5416.                 if (!$this->sendCommand('Username', base64_encode($username), 334)) {
  5417.                     return false;
  5418.                 }
  5419.                 if (!$this->sendCommand('Password', base64_encode($password), 235)) {
  5420.                     return false;
  5421.                 }
  5422.                 break;
  5423.             case 'CRAM-MD5':
  5424.                 // Start authentication
  5425.                 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
  5426.                     return false;
  5427.                 }
  5428.                 // Get the challenge
  5429.                 $challenge = base64_decode(substr($this->last_reply, 4));
  5430.  
  5431.                 // Build the response
  5432.                 $response = $username . ' ' . $this->hmac($challenge, $password);
  5433.  
  5434.                 // send encoded credentials
  5435.                 return $this->sendCommand('Username', base64_encode($response), 235);
  5436.             case 'XOAUTH2':
  5437.                 //The OAuth instance must be set up prior to requesting auth.
  5438.                 if (null === $OAuth) {
  5439.                     return false;
  5440.                 }
  5441.                 $oauth = $OAuth->getOauth64();
  5442.  
  5443.                 // Start authentication
  5444.                 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
  5445.                     return false;
  5446.                 }
  5447.                 break;
  5448.             default:
  5449.                 $this->setError("Authentication method \"$authtype\" is not supported");
  5450.  
  5451.                 return false;
  5452.         }
  5453.  
  5454.         return true;
  5455.     }
  5456.  
  5457.     /**
  5458.      * Calculate an MD5 HMAC hash.
  5459.      * Works like hash_hmac('md5', $data, $key)
  5460.      * in case that function is not available.
  5461.      *
  5462.      * @param string $data The data to hash
  5463.      * @param string $key  The key to hash with
  5464.      *
  5465.      * @return string
  5466.      */
  5467.     protected function hmac($data, $key)
  5468.     {
  5469.         if (function_exists('hash_hmac')) {
  5470.             return hash_hmac('md5', $data, $key);
  5471.         }
  5472.  
  5473.         // The following borrowed from
  5474.         // http://php.net/manual/en/function.mhash.php#27225
  5475.  
  5476.         // RFC 2104 HMAC implementation for php.
  5477.         // Creates an md5 HMAC.
  5478.         // Eliminates the need to install mhash to compute a HMAC
  5479.         // by Lance Rushing
  5480.  
  5481.         $bytelen = 64; // byte length for md5
  5482.         if (strlen($key) > $bytelen) {
  5483.             $key = pack('H*', md5($key));
  5484.         }
  5485.         $key = str_pad($key, $bytelen, chr(0x00));
  5486.         $ipad = str_pad('', $bytelen, chr(0x36));
  5487.         $opad = str_pad('', $bytelen, chr(0x5c));
  5488.         $k_ipad = $key ^ $ipad;
  5489.         $k_opad = $key ^ $opad;
  5490.  
  5491.         return md5($k_opad . pack('H*', md5($k_ipad . $data)));
  5492.     }
  5493.  
  5494.     /**
  5495.      * Check connection state.
  5496.      *
  5497.      * @return bool True if connected
  5498.      */
  5499.     public function connected()
  5500.     {
  5501.         if (is_resource($this->smtp_conn)) {
  5502.             $sock_status = stream_get_meta_data($this->smtp_conn);
  5503.             if ($sock_status['eof']) {
  5504.                 // The socket is valid but we are not connected
  5505.                 $this->edebug(
  5506.                     'SMTP NOTICE: EOF caught while checking if connected',
  5507.                     self::DEBUG_CLIENT
  5508.                 );
  5509.                 $this->close();
  5510.  
  5511.                 return false;
  5512.             }
  5513.  
  5514.             return true; // everything looks good
  5515.         }
  5516.  
  5517.         return false;
  5518.     }
  5519.  
  5520.     /**
  5521.      * Close the socket and clean up the state of the class.
  5522.      * Don't use this function without first trying to use QUIT.
  5523.      *
  5524.      * @see quit()
  5525.      */
  5526.     public function close()
  5527.     {
  5528.         $this->setError('');
  5529.         $this->server_caps = null;
  5530.         $this->helo_rply = null;
  5531.         if (is_resource($this->smtp_conn)) {
  5532.             // close the connection and cleanup
  5533.             fclose($this->smtp_conn);
  5534.             $this->smtp_conn = null; //Makes for cleaner serialization
  5535.             $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
  5536.         }
  5537.     }
  5538.  
  5539.     /**
  5540.      * Send an SMTP DATA command.
  5541.      * Issues a data command and sends the msg_data to the server,
  5542.      * finializing the mail transaction. $msg_data is the message
  5543.      * that is to be send with the headers. Each header needs to be
  5544.      * on a single line followed by a <CRLF> with the message headers
  5545.      * and the message body being separated by an additional <CRLF>.
  5546.      * Implements RFC 821: DATA <CRLF>.
  5547.      *
  5548.      * @param string $msg_data Message data to send
  5549.      *
  5550.      * @return bool
  5551.      */
  5552.     public function data($msg_data)
  5553.     {
  5554.         //This will use the standard timelimit
  5555.         if (!$this->sendCommand('DATA', 'DATA', 354)) {
  5556.             return false;
  5557.         }
  5558.  
  5559.         /* The server is ready to accept data!
  5560.          * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
  5561.          * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
  5562.          * smaller lines to fit within the limit.
  5563.          * We will also look for lines that start with a '.' and prepend an additional '.'.
  5564.          * NOTE: this does not count towards line-length limit.
  5565.          */
  5566.  
  5567.         // Normalize line breaks before exploding
  5568.         $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
  5569.  
  5570.         /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
  5571.          * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
  5572.          * process all lines before a blank line as headers.
  5573.          */
  5574.  
  5575.         $field = substr($lines[0], 0, strpos($lines[0], ':'));
  5576.         $in_headers = false;
  5577.         if (!empty($field) && strpos($field, ' ') === false) {
  5578.             $in_headers = true;
  5579.         }
  5580.  
  5581.         foreach ($lines as $line) {
  5582.             $lines_out = [];
  5583.             if ($in_headers && $line === '') {
  5584.                 $in_headers = false;
  5585.             }
  5586.             //Break this line up into several smaller lines if it's too long
  5587.             //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
  5588.             while (isset($line[self::MAX_LINE_LENGTH])) {
  5589.                 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
  5590.                 //so as to avoid breaking in the middle of a word
  5591.                 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
  5592.                 //Deliberately matches both false and 0
  5593.                 if (!$pos) {
  5594.                     //No nice break found, add a hard break
  5595.                     $pos = self::MAX_LINE_LENGTH - 1;
  5596.                     $lines_out[] = substr($line, 0, $pos);
  5597.                     $line = substr($line, $pos);
  5598.                 } else {
  5599.                     //Break at the found point
  5600.                     $lines_out[] = substr($line, 0, $pos);
  5601.                     //Move along by the amount we dealt with
  5602.                     $line = substr($line, $pos + 1);
  5603.                 }
  5604.                 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
  5605.                 if ($in_headers) {
  5606.                     $line = "\t" . $line;
  5607.                 }
  5608.             }
  5609.             $lines_out[] = $line;
  5610.  
  5611.             //Send the lines to the server
  5612.             foreach ($lines_out as $line_out) {
  5613.                 //RFC2821 section 4.5.2
  5614.                 if (!empty($line_out) && $line_out[0] === '.') {
  5615.                     $line_out = '.' . $line_out;
  5616.                 }
  5617.                 $this->client_send($line_out . static::LE, 'DATA');
  5618.             }
  5619.         }
  5620.  
  5621.         //Message data has been sent, complete the command
  5622.         //Increase timelimit for end of DATA command
  5623.         $savetimelimit = $this->Timelimit;
  5624.         $this->Timelimit *= 2;
  5625.         $result = $this->sendCommand('DATA END', '.', 250);
  5626.         $this->recordLastTransactionID();
  5627.         //Restore timelimit
  5628.         $this->Timelimit = $savetimelimit;
  5629.  
  5630.         return $result;
  5631.     }
  5632.  
  5633.     /**
  5634.      * Send an SMTP HELO or EHLO command.
  5635.      * Used to identify the sending server to the receiving server.
  5636.      * This makes sure that client and server are in a known state.
  5637.      * Implements RFC 821: HELO <SP> <domain> <CRLF>
  5638.      * and RFC 2821 EHLO.
  5639.      *
  5640.      * @param string $host The host name or IP to connect to
  5641.      *
  5642.      * @return bool
  5643.      */
  5644.     public function hello($host = '')
  5645.     {
  5646.         //Try extended hello first (RFC 2821)
  5647.         return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host);
  5648.     }
  5649.  
  5650.     /**
  5651.      * Send an SMTP HELO or EHLO command.
  5652.      * Low-level implementation used by hello().
  5653.      *
  5654.      * @param string $hello The HELO string
  5655.      * @param string $host  The hostname to say we are
  5656.      *
  5657.      * @return bool
  5658.      *
  5659.      * @see hello()
  5660.      */
  5661.     protected function sendHello($hello, $host)
  5662.     {
  5663.         $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
  5664.         $this->helo_rply = $this->last_reply;
  5665.         if ($noerror) {
  5666.             $this->parseHelloFields($hello);
  5667.         } else {
  5668.             $this->server_caps = null;
  5669.         }
  5670.  
  5671.         return $noerror;
  5672.     }
  5673.  
  5674.     /**
  5675.      * Parse a reply to HELO/EHLO command to discover server extensions.
  5676.      * In case of HELO, the only parameter that can be discovered is a server name.
  5677.      *
  5678.      * @param string $type `HELO` or `EHLO`
  5679.      */
  5680.     protected function parseHelloFields($type)
  5681.     {
  5682.         $this->server_caps = [];
  5683.         $lines = explode("\n", $this->helo_rply);
  5684.  
  5685.         foreach ($lines as $n => $s) {
  5686.             //First 4 chars contain response code followed by - or space
  5687.             $s = trim(substr($s, 4));
  5688.             if (empty($s)) {
  5689.                 continue;
  5690.             }
  5691.             $fields = explode(' ', $s);
  5692.             if (!empty($fields)) {
  5693.                 if (!$n) {
  5694.                     $name = $type;
  5695.                     $fields = $fields[0];
  5696.                 } else {
  5697.                     $name = array_shift($fields);
  5698.                     switch ($name) {
  5699.                         case 'SIZE':
  5700.                             $fields = ($fields ? $fields[0] : 0);
  5701.                             break;
  5702.                         case 'AUTH':
  5703.                             if (!is_array($fields)) {
  5704.                                 $fields = [];
  5705.                             }
  5706.                             break;
  5707.                         default:
  5708.                             $fields = true;
  5709.                     }
  5710.                 }
  5711.                 $this->server_caps[$name] = $fields;
  5712.             }
  5713.         }
  5714.     }
  5715.  
  5716.     /**
  5717.      * Send an SMTP MAIL command.
  5718.      * Starts a mail transaction from the email address specified in
  5719.      * $from. Returns true if successful or false otherwise. If True
  5720.      * the mail transaction is started and then one or more recipient
  5721.      * commands may be called followed by a data command.
  5722.      * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
  5723.      *
  5724.      * @param string $from Source address of this message
  5725.      *
  5726.      * @return bool
  5727.      */
  5728.     public function mail($from)
  5729.     {
  5730.         $useVerp = ($this->do_verp ? ' XVERP' : '');
  5731.  
  5732.         return $this->sendCommand(
  5733.             'MAIL FROM',
  5734.             'MAIL FROM:<' . $from . '>' . $useVerp,
  5735.             250
  5736.         );
  5737.     }
  5738.  
  5739.     /**
  5740.      * Send an SMTP QUIT command.
  5741.      * Closes the socket if there is no error or the $close_on_error argument is true.
  5742.      * Implements from RFC 821: QUIT <CRLF>.
  5743.      *
  5744.      * @param bool $close_on_error Should the connection close if an error occurs?
  5745.      *
  5746.      * @return bool
  5747.      */
  5748.     public function quit($close_on_error = true)
  5749.     {
  5750.         $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
  5751.         $err = $this->error; //Save any error
  5752.         if ($noerror || $close_on_error) {
  5753.             $this->close();
  5754.             $this->error = $err; //Restore any error from the quit command
  5755.         }
  5756.  
  5757.         return $noerror;
  5758.     }
  5759.  
  5760.     /**
  5761.      * Send an SMTP RCPT command.
  5762.      * Sets the TO argument to $toaddr.
  5763.      * Returns true if the recipient was accepted false if it was rejected.
  5764.      * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
  5765.      *
  5766.      * @param string $address The address the message is being sent to
  5767.      * @param string $dsn     Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE
  5768.      *                        or DELAY. If you specify NEVER all other notifications are ignored.
  5769.      *
  5770.      * @return bool
  5771.      */
  5772.     public function recipient($address, $dsn = '')
  5773.     {
  5774.         if (empty($dsn)) {
  5775.             $rcpt = 'RCPT TO:<' . $address . '>';
  5776.         } else {
  5777.             $dsn = strtoupper($dsn);
  5778.             $notify = [];
  5779.  
  5780.             if (strpos($dsn, 'NEVER') !== false) {
  5781.                 $notify[] = 'NEVER';
  5782.             } else {
  5783.                 foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) {
  5784.                     if (strpos($dsn, $value) !== false) {
  5785.                         $notify[] = $value;
  5786.                     }
  5787.                 }
  5788.             }
  5789.  
  5790.             $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify);
  5791.         }
  5792.  
  5793.         return $this->sendCommand(
  5794.             'RCPT TO',
  5795.             $rcpt,
  5796.             [250, 251]
  5797.         );
  5798.     }
  5799.  
  5800.     /**
  5801.      * Send an SMTP RSET command.
  5802.      * Abort any transaction that is currently in progress.
  5803.      * Implements RFC 821: RSET <CRLF>.
  5804.      *
  5805.      * @return bool True on success
  5806.      */
  5807.     public function reset()
  5808.     {
  5809.         return $this->sendCommand('RSET', 'RSET', 250);
  5810.     }
  5811.  
  5812.     /**
  5813.      * Send a command to an SMTP server and check its return code.
  5814.      *
  5815.      * @param string    $command       The command name - not sent to the server
  5816.      * @param string    $commandstring The actual command to send
  5817.      * @param int|array $expect        One or more expected integer success codes
  5818.      *
  5819.      * @return bool True on success
  5820.      */
  5821.     protected function sendCommand($command, $commandstring, $expect)
  5822.     {
  5823.         if (!$this->connected()) {
  5824.             $this->setError("Called $command without being connected");
  5825.  
  5826.             return false;
  5827.         }
  5828.         //Reject line breaks in all commands
  5829.         if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) {
  5830.             $this->setError("Command '$command' contained line breaks");
  5831.  
  5832.             return false;
  5833.         }
  5834.         $this->client_send($commandstring . static::LE, $command);
  5835.  
  5836.         $this->last_reply = $this->get_lines();
  5837.         // Fetch SMTP code and possible error code explanation
  5838.         $matches = [];
  5839.         if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
  5840.             $code = (int) $matches[1];
  5841.             $code_ex = (count($matches) > 2 ? $matches[2] : null);
  5842.             // Cut off error code from each response line
  5843.             $detail = preg_replace(
  5844.                 "/{$code}[ -]" .
  5845.                 ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
  5846.                 '',
  5847.                 $this->last_reply
  5848.             );
  5849.         } else {
  5850.             // Fall back to simple parsing if regex fails
  5851.             $code = (int) substr($this->last_reply, 0, 3);
  5852.             $code_ex = null;
  5853.             $detail = substr($this->last_reply, 4);
  5854.         }
  5855.  
  5856.         $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
  5857.  
  5858.         if (!in_array($code, (array) $expect, true)) {
  5859.             $this->setError(
  5860.                 "$command command failed",
  5861.                 $detail,
  5862.                 $code,
  5863.                 $code_ex
  5864.             );
  5865.             $this->edebug(
  5866.                 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
  5867.                 self::DEBUG_CLIENT
  5868.             );
  5869.  
  5870.             return false;
  5871.         }
  5872.  
  5873.         $this->setError('');
  5874.  
  5875.         return true;
  5876.     }
  5877.  
  5878.     /**
  5879.      * Send an SMTP SAML command.
  5880.      * Starts a mail transaction from the email address specified in $from.
  5881.      * Returns true if successful or false otherwise. If True
  5882.      * the mail transaction is started and then one or more recipient
  5883.      * commands may be called followed by a data command. This command
  5884.      * will send the message to the users terminal if they are logged
  5885.      * in and send them an email.
  5886.      * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
  5887.      *
  5888.      * @param string $from The address the message is from
  5889.      *
  5890.      * @return bool
  5891.      */
  5892.     public function sendAndMail($from)
  5893.     {
  5894.         return $this->sendCommand('SAML', "SAML FROM:$from", 250);
  5895.     }
  5896.  
  5897.     /**
  5898.      * Send an SMTP VRFY command.
  5899.      *
  5900.      * @param string $name The name to verify
  5901.      *
  5902.      * @return bool
  5903.      */
  5904.     public function verify($name)
  5905.     {
  5906.         return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
  5907.     }
  5908.  
  5909.     /**
  5910.      * Send an SMTP NOOP command.
  5911.      * Used to keep keep-alives alive, doesn't actually do anything.
  5912.      *
  5913.      * @return bool
  5914.      */
  5915.     public function noop()
  5916.     {
  5917.         return $this->sendCommand('NOOP', 'NOOP', 250);
  5918.     }
  5919.  
  5920.     /**
  5921.      * Send an SMTP TURN command.
  5922.      * This is an optional command for SMTP that this class does not support.
  5923.      * This method is here to make the RFC821 Definition complete for this class
  5924.      * and _may_ be implemented in future.
  5925.      * Implements from RFC 821: TURN <CRLF>.
  5926.      *
  5927.      * @return bool
  5928.      */
  5929.     public function turn()
  5930.     {
  5931.         $this->setError('The SMTP TURN command is not implemented');
  5932.         $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
  5933.  
  5934.         return false;
  5935.     }
  5936.  
  5937.     /**
  5938.      * Send raw data to the server.
  5939.      *
  5940.      * @param string $data    The data to send
  5941.      * @param string $command Optionally, the command this is part of, used only for controlling debug output
  5942.      *
  5943.      * @return int|bool The number of bytes sent to the server or false on error
  5944.      */
  5945.     public function client_send($data, $command = '')
  5946.     {
  5947.         //If SMTP transcripts are left enabled, or debug output is posted online
  5948.         //it can leak credentials, so hide credentials in all but lowest level
  5949.         if (
  5950.             self::DEBUG_LOWLEVEL > $this->do_debug &&
  5951.             in_array($command, ['User & Password', 'Username', 'Password'], true)
  5952.         ) {
  5953.             $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
  5954.         } else {
  5955.             $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
  5956.         }
  5957.         set_error_handler([$this, 'errorHandler']);
  5958.         $result = fwrite($this->smtp_conn, $data);
  5959.         restore_error_handler();
  5960.  
  5961.         return $result;
  5962.     }
  5963.  
  5964.     /**
  5965.      * Get the latest error.
  5966.      *
  5967.      * @return array
  5968.      */
  5969.     public function getError()
  5970.     {
  5971.         return $this->error;
  5972.     }
  5973.  
  5974.     /**
  5975.      * Get SMTP extensions available on the server.
  5976.      *
  5977.      * @return array|null
  5978.      */
  5979.     public function getServerExtList()
  5980.     {
  5981.         return $this->server_caps;
  5982.     }
  5983.  
  5984.     /**
  5985.      * Get metadata about the SMTP server from its HELO/EHLO response.
  5986.      * The method works in three ways, dependent on argument value and current state:
  5987.      *   1. HELO/EHLO has not been sent - returns null and populates $this->error.
  5988.      *   2. HELO has been sent -
  5989.      *     $name == 'HELO': returns server name
  5990.      *     $name == 'EHLO': returns boolean false
  5991.      *     $name == any other string: returns null and populates $this->error
  5992.      *   3. EHLO has been sent -
  5993.      *     $name == 'HELO'|'EHLO': returns the server name
  5994.      *     $name == any other string: if extension $name exists, returns True
  5995.      *       or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
  5996.      *
  5997.      * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
  5998.      *
  5999.      * @return string|bool|null
  6000.      */
  6001.     public function getServerExt($name)
  6002.     {
  6003.         if (!$this->server_caps) {
  6004.             $this->setError('No HELO/EHLO was sent');
  6005.  
  6006.             return;
  6007.         }
  6008.  
  6009.         if (!array_key_exists($name, $this->server_caps)) {
  6010.             if ('HELO' === $name) {
  6011.                 return $this->server_caps['EHLO'];
  6012.             }
  6013.             if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) {
  6014.                 return false;
  6015.             }
  6016.             $this->setError('HELO handshake was used; No information about server extensions available');
  6017.  
  6018.             return;
  6019.         }
  6020.  
  6021.         return $this->server_caps[$name];
  6022.     }
  6023.  
  6024.     /**
  6025.      * Get the last reply from the server.
  6026.      *
  6027.      * @return string
  6028.      */
  6029.     public function getLastReply()
  6030.     {
  6031.         return $this->last_reply;
  6032.     }
  6033.  
  6034.     /**
  6035.      * Read the SMTP server's response.
  6036.      * Either before eof or socket timeout occurs on the operation.
  6037.      * With SMTP we can tell if we have more lines to read if the
  6038.      * 4th character is '-' symbol. If it is a space then we don't
  6039.      * need to read anything else.
  6040.      *
  6041.      * @return string
  6042.      */
  6043.     protected function get_lines()
  6044.     {
  6045.         // If the connection is bad, give up straight away
  6046.         if (!is_resource($this->smtp_conn)) {
  6047.             return '';
  6048.         }
  6049.         $data = '';
  6050.         $endtime = 0;
  6051.         stream_set_timeout($this->smtp_conn, $this->Timeout);
  6052.         if ($this->Timelimit > 0) {
  6053.             $endtime = time() + $this->Timelimit;
  6054.         }
  6055.         $selR = [$this->smtp_conn];
  6056.         $selW = null;
  6057.         while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
  6058.             //Must pass vars in here as params are by reference
  6059.             //solution for signals inspired by https://github.com/symfony/symfony/pull/6540
  6060.             set_error_handler([$this, 'errorHandler']);
  6061.             $n = stream_select($selR, $selW, $selW, $this->Timelimit);
  6062.             restore_error_handler();
  6063.  
  6064.             if ($n === false) {
  6065.                 $message = $this->getError()['detail'];
  6066.  
  6067.                 $this->edebug(
  6068.                     'SMTP -> get_lines(): select failed (' . $message . ')',
  6069.                     self::DEBUG_LOWLEVEL
  6070.                 );
  6071.  
  6072.                 //stream_select returns false when the `select` system call is interrupted
  6073.                 //by an incoming signal, try the select again
  6074.                 if (stripos($message, 'interrupted system call') !== false) {
  6075.                     $this->edebug(
  6076.                         'SMTP -> get_lines(): retrying stream_select',
  6077.                         self::DEBUG_LOWLEVEL
  6078.                     );
  6079.                     $this->setError('');
  6080.                     continue;
  6081.                 }
  6082.  
  6083.                 break;
  6084.             }
  6085.  
  6086.             if (!$n) {
  6087.                 $this->edebug(
  6088.                     'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
  6089.                     self::DEBUG_LOWLEVEL
  6090.                 );
  6091.                 break;
  6092.             }
  6093.  
  6094.             //Deliberate noise suppression - errors are handled afterwards
  6095.             $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
  6096.             $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
  6097.             $data .= $str;
  6098.             // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
  6099.             // or 4th character is a space or a line break char, we are done reading, break the loop.
  6100.             // String array access is a significant micro-optimisation over strlen
  6101.             if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
  6102.                 break;
  6103.             }
  6104.             // Timed-out? Log and break
  6105.             $info = stream_get_meta_data($this->smtp_conn);
  6106.             if ($info['timed_out']) {
  6107.                 $this->edebug(
  6108.                     'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
  6109.                     self::DEBUG_LOWLEVEL
  6110.                 );
  6111.                 break;
  6112.             }
  6113.             // Now check if reads took too long
  6114.             if ($endtime && time() > $endtime) {
  6115.                 $this->edebug(
  6116.                     'SMTP -> get_lines(): timelimit reached (' .
  6117.                     $this->Timelimit . ' sec)',
  6118.                     self::DEBUG_LOWLEVEL
  6119.                 );
  6120.                 break;
  6121.             }
  6122.         }
  6123.  
  6124.         return $data;
  6125.     }
  6126.  
  6127.     /**
  6128.      * Enable or disable VERP address generation.
  6129.      *
  6130.      * @param bool $enabled
  6131.      */
  6132.     public function setVerp($enabled = false)
  6133.     {
  6134.         $this->do_verp = $enabled;
  6135.     }
  6136.  
  6137.     /**
  6138.      * Get VERP address generation mode.
  6139.      *
  6140.      * @return bool
  6141.      */
  6142.     public function getVerp()
  6143.     {
  6144.         return $this->do_verp;
  6145.     }
  6146.  
  6147.     /**
  6148.      * Set error messages and codes.
  6149.      *
  6150.      * @param string $message      The error message
  6151.      * @param string $detail       Further detail on the error
  6152.      * @param string $smtp_code    An associated SMTP error code
  6153.      * @param string $smtp_code_ex Extended SMTP code
  6154.      */
  6155.     protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
  6156.     {
  6157.         $this->error = [
  6158.             'error' => $message,
  6159.             'detail' => $detail,
  6160.             'smtp_code' => $smtp_code,
  6161.             'smtp_code_ex' => $smtp_code_ex,
  6162.         ];
  6163.     }
  6164.  
  6165.     /**
  6166.      * Set debug output method.
  6167.      *
  6168.      * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
  6169.      */
  6170.     public function setDebugOutput($method = 'echo')
  6171.     {
  6172.         $this->Debugoutput = $method;
  6173.     }
  6174.  
  6175.     /**
  6176.      * Get debug output method.
  6177.      *
  6178.      * @return string
  6179.      */
  6180.     public function getDebugOutput()
  6181.     {
  6182.         return $this->Debugoutput;
  6183.     }
  6184.  
  6185.     /**
  6186.      * Set debug output level.
  6187.      *
  6188.      * @param int $level
  6189.      */
  6190.     public function setDebugLevel($level = 0)
  6191.     {
  6192.         $this->do_debug = $level;
  6193.     }
  6194.  
  6195.     /**
  6196.      * Get debug output level.
  6197.      *
  6198.      * @return int
  6199.      */
  6200.     public function getDebugLevel()
  6201.     {
  6202.         return $this->do_debug;
  6203.     }
  6204.  
  6205.     /**
  6206.      * Set SMTP timeout.
  6207.      *
  6208.      * @param int $timeout The timeout duration in seconds
  6209.      */
  6210.     public function setTimeout($timeout = 0)
  6211.     {
  6212.         $this->Timeout = $timeout;
  6213.     }
  6214.  
  6215.     /**
  6216.      * Get SMTP timeout.
  6217.      *
  6218.      * @return int
  6219.      */
  6220.     public function getTimeout()
  6221.     {
  6222.         return $this->Timeout;
  6223.     }
  6224.  
  6225.     /**
  6226.      * Reports an error number and string.
  6227.      *
  6228.      * @param int    $errno   The error number returned by PHP
  6229.      * @param string $errmsg  The error message returned by PHP
  6230.      * @param string $errfile The file the error occurred in
  6231.      * @param int    $errline The line number the error occurred on
  6232.      */
  6233.     protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
  6234.     {
  6235.         $notice = 'Connection failed.';
  6236.         $this->setError(
  6237.             $notice,
  6238.             $errmsg,
  6239.             (string) $errno
  6240.         );
  6241.         $this->edebug(
  6242.             "$notice Error #$errno: $errmsg [$errfile line $errline]",
  6243.             self::DEBUG_CONNECTION
  6244.         );
  6245.     }
  6246.  
  6247.     /**
  6248.      * Extract and return the ID of the last SMTP transaction based on
  6249.      * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
  6250.      * Relies on the host providing the ID in response to a DATA command.
  6251.      * If no reply has been received yet, it will return null.
  6252.      * If no pattern was matched, it will return false.
  6253.      *
  6254.      * @return bool|string|null
  6255.      */
  6256.     protected function recordLastTransactionID()
  6257.     {
  6258.         $reply = $this->getLastReply();
  6259.  
  6260.         if (empty($reply)) {
  6261.             $this->last_smtp_transaction_id = null;
  6262.         } else {
  6263.             $this->last_smtp_transaction_id = false;
  6264.             foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
  6265.                 $matches = [];
  6266.                 if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
  6267.                     $this->last_smtp_transaction_id = trim($matches[1]);
  6268.                     break;
  6269.                 }
  6270.             }
  6271.         }
  6272.  
  6273.         return $this->last_smtp_transaction_id;
  6274.     }
  6275.  
  6276.     /**
  6277.      * Get the queue/transaction ID of the last SMTP transaction
  6278.      * If no reply has been received yet, it will return null.
  6279.      * If no pattern was matched, it will return false.
  6280.      *
  6281.      * @return bool|string|null
  6282.      *
  6283.      * @see recordLastTransactionID()
  6284.      */
  6285.     public function getLastTransactionID()
  6286.     {
  6287.         return $this->last_smtp_transaction_id;
  6288.     }
  6289. }
  6290.  
  6291. /**
  6292.  * PHPMailer POP-Before-SMTP Authentication Class.
  6293.  * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication.
  6294.  * 1) This class does not support APOP authentication.
  6295.  * 2) Opening and closing lots of POP3 connections can be quite slow. If you need
  6296.  *   to send a batch of emails then just perform the authentication once at the start,
  6297.  *   and then loop through your mail sending script. Providing this process doesn't
  6298.  *   take longer than the verification period lasts on your POP3 server, you should be fine.
  6299.  * 3) This is really ancient technology; you should only need to use it to talk to very old systems.
  6300.  * 4) This POP3 class is deliberately lightweight and incomplete, implementing just
  6301.  *   enough to do authentication.
  6302.  *   If you want a more complete class there are other POP3 classes for PHP available.
  6303.  *
  6304.  * @author Richard Davey (original author) <rich@corephp.co.uk>
  6305.  * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
  6306.  * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
  6307.  * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
  6308.  */
  6309. class POP3
  6310. {
  6311.     /**
  6312.      * The POP3 PHPMailer Version number.
  6313.      *
  6314.      * @var string
  6315.      */
  6316.     const VERSION = '6.2.0';
  6317.  
  6318.     /**
  6319.      * Default POP3 port number.
  6320.      *
  6321.      * @var int
  6322.      */
  6323.     const DEFAULT_PORT = 110;
  6324.  
  6325.     /**
  6326.      * Default timeout in seconds.
  6327.      *
  6328.      * @var int
  6329.      */
  6330.     const DEFAULT_TIMEOUT = 30;
  6331.  
  6332.     /**
  6333.      * POP3 class debug output mode.
  6334.      * Debug output level.
  6335.      * Options:
  6336.      * @see POP3::DEBUG_OFF: No output
  6337.      * @see POP3::DEBUG_SERVER: Server messages, connection/server errors
  6338.      * @see POP3::DEBUG_CLIENT: Client and Server messages, connection/server errors
  6339.      *
  6340.      * @var int
  6341.      */
  6342.     public $do_debug = self::DEBUG_OFF;
  6343.  
  6344.     /**
  6345.      * POP3 mail server hostname.
  6346.      *
  6347.      * @var string
  6348.      */
  6349.     public $host;
  6350.  
  6351.     /**
  6352.      * POP3 port number.
  6353.      *
  6354.      * @var int
  6355.      */
  6356.     public $port;
  6357.  
  6358.     /**
  6359.      * POP3 Timeout Value in seconds.
  6360.      *
  6361.      * @var int
  6362.      */
  6363.     public $tval;
  6364.  
  6365.     /**
  6366.      * POP3 username.
  6367.      *
  6368.      * @var string
  6369.      */
  6370.     public $username;
  6371.  
  6372.     /**
  6373.      * POP3 password.
  6374.      *
  6375.      * @var string
  6376.      */
  6377.     public $password;
  6378.  
  6379.     /**
  6380.      * Resource handle for the POP3 connection socket.
  6381.      *
  6382.      * @var resource
  6383.      */
  6384.     protected $pop_conn;
  6385.  
  6386.     /**
  6387.      * Are we connected?
  6388.      *
  6389.      * @var bool
  6390.      */
  6391.     protected $connected = false;
  6392.  
  6393.     /**
  6394.      * Error container.
  6395.      *
  6396.      * @var array
  6397.      */
  6398.     protected $errors = [];
  6399.  
  6400.     /**
  6401.      * Line break constant.
  6402.      */
  6403.     const LE = "\r\n";
  6404.  
  6405.     /**
  6406.      * Debug level for no output.
  6407.      *
  6408.      * @var int
  6409.      */
  6410.     const DEBUG_OFF = 0;
  6411.  
  6412.     /**
  6413.      * Debug level to show server -> client messages
  6414.      * also shows clients connection errors or errors from server
  6415.      *
  6416.      * @var int
  6417.      */
  6418.     const DEBUG_SERVER = 1;
  6419.  
  6420.     /**
  6421.      * Debug level to show client -> server and server -> client messages.
  6422.      *
  6423.      * @var int
  6424.      */
  6425.     const DEBUG_CLIENT = 2;
  6426.  
  6427.     /**
  6428.      * Simple static wrapper for all-in-one POP before SMTP.
  6429.      *
  6430.      * @param string   $host        The hostname to connect to
  6431.      * @param int|bool $port        The port number to connect to
  6432.      * @param int|bool $timeout     The timeout value
  6433.      * @param string   $username
  6434.      * @param string   $password
  6435.      * @param int      $debug_level
  6436.      *
  6437.      * @return bool
  6438.      */
  6439.     public static function popBeforeSmtp(
  6440.         $host,
  6441.         $port = false,
  6442.         $timeout = false,
  6443.         $username = '',
  6444.         $password = '',
  6445.         $debug_level = 0
  6446.     ) {
  6447.         $pop = new self();
  6448.  
  6449.         return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level);
  6450.     }
  6451.  
  6452.     /**
  6453.      * Authenticate with a POP3 server.
  6454.      * A connect, login, disconnect sequence
  6455.      * appropriate for POP-before SMTP authorisation.
  6456.      *
  6457.      * @param string   $host        The hostname to connect to
  6458.      * @param int|bool $port        The port number to connect to
  6459.      * @param int|bool $timeout     The timeout value
  6460.      * @param string   $username
  6461.      * @param string   $password
  6462.      * @param int      $debug_level
  6463.      *
  6464.      * @return bool
  6465.      */
  6466.     public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0)
  6467.     {
  6468.         $this->host = $host;
  6469.         // If no port value provided, use default
  6470.         if (false === $port) {
  6471.             $this->port = static::DEFAULT_PORT;
  6472.         } else {
  6473.             $this->port = (int) $port;
  6474.         }
  6475.         // If no timeout value provided, use default
  6476.         if (false === $timeout) {
  6477.             $this->tval = static::DEFAULT_TIMEOUT;
  6478.         } else {
  6479.             $this->tval = (int) $timeout;
  6480.         }
  6481.         $this->do_debug = $debug_level;
  6482.         $this->username = $username;
  6483.         $this->password = $password;
  6484.         //  Reset the error log
  6485.         $this->errors = [];
  6486.         //  connect
  6487.         $result = $this->connect($this->host, $this->port, $this->tval);
  6488.         if ($result) {
  6489.             $login_result = $this->login($this->username, $this->password);
  6490.             if ($login_result) {
  6491.                 $this->disconnect();
  6492.  
  6493.                 return true;
  6494.             }
  6495.         }
  6496.         // We need to disconnect regardless of whether the login succeeded
  6497.         $this->disconnect();
  6498.  
  6499.         return false;
  6500.     }
  6501.  
  6502.     /**
  6503.      * Connect to a POP3 server.
  6504.      *
  6505.      * @param string   $host
  6506.      * @param int|bool $port
  6507.      * @param int      $tval
  6508.      *
  6509.      * @return bool
  6510.      */
  6511.     public function connect($host, $port = false, $tval = 30)
  6512.     {
  6513.         //  Are we already connected?
  6514.         if ($this->connected) {
  6515.             return true;
  6516.         }
  6517.  
  6518.         //On Windows this will raise a PHP Warning error if the hostname doesn't exist.
  6519.         //Rather than suppress it with @fsockopen, capture it cleanly instead
  6520.         set_error_handler([$this, 'catchWarning']);
  6521.  
  6522.         if (false === $port) {
  6523.             $port = static::DEFAULT_PORT;
  6524.         }
  6525.  
  6526.         //  connect to the POP3 server
  6527.         $errno = 0;
  6528.         $errstr = '';
  6529.         $this->pop_conn = fsockopen(
  6530.             $host, //  POP3 Host
  6531.             $port, //  Port #
  6532.             $errno, //  Error Number
  6533.             $errstr, //  Error Message
  6534.             $tval
  6535.         ); //  Timeout (seconds)
  6536.         //  Restore the error handler
  6537.         restore_error_handler();
  6538.  
  6539.         //  Did we connect?
  6540.         if (false === $this->pop_conn) {
  6541.             //  It would appear not...
  6542.             $this->setError(
  6543.                 "Failed to connect to server $host on port $port. errno: $errno; errstr: $errstr"
  6544.             );
  6545.  
  6546.             return false;
  6547.         }
  6548.  
  6549.         //  Increase the stream time-out
  6550.         stream_set_timeout($this->pop_conn, $tval, 0);
  6551.  
  6552.         //  Get the POP3 server response
  6553.         $pop3_response = $this->getResponse();
  6554.         //  Check for the +OK
  6555.         if ($this->checkResponse($pop3_response)) {
  6556.             //  The connection is established and the POP3 server is talking
  6557.             $this->connected = true;
  6558.  
  6559.             return true;
  6560.         }
  6561.  
  6562.         return false;
  6563.     }
  6564.  
  6565.     /**
  6566.      * Log in to the POP3 server.
  6567.      * Does not support APOP (RFC 2828, 4949).
  6568.      *
  6569.      * @param string $username
  6570.      * @param string $password
  6571.      *
  6572.      * @return bool
  6573.      */
  6574.     public function login($username = '', $password = '')
  6575.     {
  6576.         if (!$this->connected) {
  6577.             $this->setError('Not connected to POP3 server');
  6578.         }
  6579.         if (empty($username)) {
  6580.             $username = $this->username;
  6581.         }
  6582.         if (empty($password)) {
  6583.             $password = $this->password;
  6584.         }
  6585.  
  6586.         // Send the Username
  6587.         $this->sendString("USER $username" . static::LE);
  6588.         $pop3_response = $this->getResponse();
  6589.         if ($this->checkResponse($pop3_response)) {
  6590.             // Send the Password
  6591.             $this->sendString("PASS $password" . static::LE);
  6592.             $pop3_response = $this->getResponse();
  6593.             if ($this->checkResponse($pop3_response)) {
  6594.                 return true;
  6595.             }
  6596.         }
  6597.  
  6598.         return false;
  6599.     }
  6600.  
  6601.     /**
  6602.      * Disconnect from the POP3 server.
  6603.      */
  6604.     public function disconnect()
  6605.     {
  6606.         $this->sendString('QUIT');
  6607.         //The QUIT command may cause the daemon to exit, which will kill our connection
  6608.         //So ignore errors here
  6609.         try {
  6610.             @fclose($this->pop_conn);
  6611.         } catch (Exception $e) {
  6612.             //Do nothing
  6613.         }
  6614.     }
  6615.  
  6616.     /**
  6617.      * Get a response from the POP3 server.
  6618.      *
  6619.      * @param int $size The maximum number of bytes to retrieve
  6620.      *
  6621.      * @return string
  6622.      */
  6623.     protected function getResponse($size = 128)
  6624.     {
  6625.         $response = fgets($this->pop_conn, $size);
  6626.         if ($this->do_debug >= self::DEBUG_SERVER) {
  6627.             echo 'Server -> Client: ', $response;
  6628.         }
  6629.  
  6630.         return $response;
  6631.     }
  6632.  
  6633.     /**
  6634.      * Send raw data to the POP3 server.
  6635.      *
  6636.      * @param string $string
  6637.      *
  6638.      * @return int
  6639.      */
  6640.     protected function sendString($string)
  6641.     {
  6642.         if ($this->pop_conn) {
  6643.             if ($this->do_debug >= self::DEBUG_CLIENT) { //Show client messages when debug >= 2
  6644.                 echo 'Client -> Server: ', $string;
  6645.             }
  6646.  
  6647.             return fwrite($this->pop_conn, $string, strlen($string));
  6648.         }
  6649.  
  6650.         return 0;
  6651.     }
  6652.  
  6653.     /**
  6654.      * Checks the POP3 server response.
  6655.      * Looks for for +OK or -ERR.
  6656.      *
  6657.      * @param string $string
  6658.      *
  6659.      * @return bool
  6660.      */
  6661.     protected function checkResponse($string)
  6662.     {
  6663.         if (strpos($string, '+OK') !== 0) {
  6664.             $this->setError("Server reported an error: $string");
  6665.  
  6666.             return false;
  6667.         }
  6668.  
  6669.         return true;
  6670.     }
  6671.  
  6672.     /**
  6673.      * Add an error to the internal error store.
  6674.      * Also display debug output if it's enabled.
  6675.      *
  6676.      * @param string $error
  6677.      */
  6678.     protected function setError($error)
  6679.     {
  6680.         $this->errors[] = $error;
  6681.         if ($this->do_debug >= self::DEBUG_SERVER) {
  6682.             echo '<pre>';
  6683.             foreach ($this->errors as $e) {
  6684.                 print_r($e);
  6685.             }
  6686.             echo '</pre>';
  6687.         }
  6688.     }
  6689.  
  6690.     /**
  6691.      * Get an array of error messages, if any.
  6692.      *
  6693.      * @return array
  6694.      */
  6695.     public function getErrors()
  6696.     {
  6697.         return $this->errors;
  6698.     }
  6699.  
  6700.     /**
  6701.      * POP3 connection error handler.
  6702.      *
  6703.      * @param int    $errno
  6704.      * @param string $errstr
  6705.      * @param string $errfile
  6706.      * @param int    $errline
  6707.      */
  6708.     protected function catchWarning($errno, $errstr, $errfile, $errline)
  6709.     {
  6710.         $this->setError(
  6711.             'Connecting to the POP3 server raised a PHP warning:' .
  6712.             "errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline"
  6713.         );
  6714.     }
  6715. }
  6716.  
  6717. /**
  6718.  * PHPMailer exception handler.
  6719.  *
  6720.  * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
  6721.  */
  6722. class PHPMailerException extends Exception
  6723. {
  6724.     /**
  6725.      * Prettify error message output.
  6726.      *
  6727.      * @return string
  6728.      */
  6729.     public function errorMessage()
  6730.     {
  6731.         return '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
  6732.     }
  6733. }
  6734.  
Add Comment
Please, Sign In to add comment