Make WordPress Core

Ticket #40472: 40472v5.diff

File 40472v5.diff, 338.1 KB (added by MattyRob, 6 years ago)
  • src/wp-includes/class-phpmailer.php

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

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