MDL-53465 lib: Upgrade PHPMailer to 5.2.14
[moodle.git] / lib / phpmailer / class.phpmailer.php
1 <?php
2 /**
3  * PHPMailer - PHP email creation and transport class.
4  * PHP Version 5
5  * @package PHPMailer
6  * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
7  * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
8  * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
9  * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
10  * @author Brent R. Matzelle (original founder)
11  * @copyright 2012 - 2014 Marcus Bointon
12  * @copyright 2010 - 2012 Jim Jagielski
13  * @copyright 2004 - 2009 Andy Prevost
14  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
15  * @note This program is distributed in the hope that it will be useful - WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  * FITNESS FOR A PARTICULAR PURPOSE.
18  */
20 /**
21  * PHPMailer - PHP email creation and transport class.
22  * @package PHPMailer
23  * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
24  * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
25  * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
26  * @author Brent R. Matzelle (original founder)
27  */
28 class PHPMailer
29 {
30     /**
31      * The PHPMailer Version number.
32      * @var string
33      */
34     public $Version = '5.2.14';
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;
44     /**
45      * The character set of the message.
46      * @var string
47      */
48     public $CharSet = 'iso-8859-1';
50     /**
51      * The MIME Content-type of the message.
52      * @var string
53      */
54     public $ContentType = 'text/plain';
56     /**
57      * The message encoding.
58      * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
59      * @var string
60      */
61     public $Encoding = '8bit';
63     /**
64      * Holds the most recent mailer error message.
65      * @var string
66      */
67     public $ErrorInfo = '';
69     /**
70      * The From email address for the message.
71      * @var string
72      */
73     public $From = 'root@localhost';
75     /**
76      * The From name of the message.
77      * @var string
78      */
79     public $FromName = 'Root User';
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 = '';
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 = '';
98     /**
99      * The Subject of the message.
100      * @var string
101      */
102     public $Subject = '';
104     /**
105      * An HTML or plain text message body.
106      * If HTML then call isHTML(true).
107      * @var string
108      */
109     public $Body = '';
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 = '';
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 = '';
130     /**
131      * The complete compiled MIME message body.
132      * @access protected
133      * @var string
134      */
135     protected $MIMEBody = '';
137     /**
138      * The complete compiled MIME message headers.
139      * @var string
140      * @access protected
141      */
142     protected $MIMEHeader = '';
144     /**
145      * Extra headers that createHeader() doesn't fold in.
146      * @var string
147      * @access protected
148      */
149     protected $mailHeader = '';
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;
158     /**
159      * Which method to use to send mail.
160      * Options: "mail", "sendmail", or "smtp".
161      * @var string
162      */
163     public $Mailer = 'mail';
165     /**
166      * The path to the sendmail program.
167      * @var string
168      */
169     public $Sendmail = '/usr/sbin/sendmail';
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;
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 = '';
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 = '';
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 = '';
201     /**
202      * An ID to be used in the Message-ID header.
203      * If empty, a unique id will be generated.
204      * @var string
205      */
206     public $MessageID = '';
208     /**
209      * The message Date to be used in the Date header.
210      * If empty, the current date will be added.
211      * @var string
212      */
213     public $MessageDate = '';
215     /**
216      * SMTP hosts.
217      * Either a single hostname or multiple semicolon-delimited hostnames.
218      * You can also specify a different port
219      * for each host by using this format: [hostname:port]
220      * (e.g. "smtp1.example.com:25;smtp2.example.com").
221      * You can also specify encryption type, for example:
222      * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
223      * Hosts will be tried in order.
224      * @var string
225      */
226     public $Host = 'localhost';
228     /**
229      * The default SMTP server port.
230      * @var integer
231      * @TODO Why is this needed when the SMTP class takes care of it?
232      */
233     public $Port = 25;
235     /**
236      * The SMTP HELO of the message.
237      * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
238      * one with the same method described above for $Hostname.
239      * @var string
240      * @see PHPMailer::$Hostname
241      */
242     public $Helo = '';
244     /**
245      * What kind of encryption to use on the SMTP connection.
246      * Options: '', 'ssl' or 'tls'
247      * @var string
248      */
249     public $SMTPSecure = '';
251     /**
252      * Whether to enable TLS encryption automatically if a server supports it,
253      * even if `SMTPSecure` is not set to 'tls'.
254      * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
255      * @var boolean
256      */
257     public $SMTPAutoTLS = true;
259     /**
260      * Whether to use SMTP authentication.
261      * Uses the Username and Password properties.
262      * @var boolean
263      * @see PHPMailer::$Username
264      * @see PHPMailer::$Password
265      */
266     public $SMTPAuth = false;
268     /**
269      * Options array passed to stream_context_create when connecting via SMTP.
270      * @var array
271      */
272     public $SMTPOptions = array();
274     /**
275      * SMTP username.
276      * @var string
277      */
278     public $Username = '';
280     /**
281      * SMTP password.
282      * @var string
283      */
284     public $Password = '';
286     /**
287      * SMTP auth type.
288      * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5
289      * @var string
290      */
291     public $AuthType = '';
293     /**
294      * SMTP realm.
295      * Used for NTLM auth
296      * @var string
297      */
298     public $Realm = '';
300     /**
301      * SMTP workstation.
302      * Used for NTLM auth
303      * @var string
304      */
305     public $Workstation = '';
307     /**
308      * The SMTP server timeout in seconds.
309      * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
310      * @var integer
311      */
312     public $Timeout = 300;
314     /**
315      * SMTP class debug output mode.
316      * Debug output level.
317      * Options:
318      * * `0` No output
319      * * `1` Commands
320      * * `2` Data and commands
321      * * `3` As 2 plus connection status
322      * * `4` Low-level data output
323      * @var integer
324      * @see SMTP::$do_debug
325      */
326     public $SMTPDebug = 0;
328     /**
329      * How to handle debug output.
330      * Options:
331      * * `echo` Output plain-text as-is, appropriate for CLI
332      * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
333      * * `error_log` Output to error log as configured in php.ini
334      *
335      * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
336      * <code>
337      * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
338      * </code>
339      * @var string|callable
340      * @see SMTP::$Debugoutput
341      */
342     public $Debugoutput = 'echo';
344     /**
345      * Whether to keep SMTP connection open after each message.
346      * If this is set to true then to close the connection
347      * requires an explicit call to smtpClose().
348      * @var boolean
349      */
350     public $SMTPKeepAlive = false;
352     /**
353      * Whether to split multiple to addresses into multiple messages
354      * or send them all in one message.
355      * @var boolean
356      */
357     public $SingleTo = false;
359     /**
360      * Storage for addresses when SingleTo is enabled.
361      * @var array
362      * @TODO This should really not be public
363      */
364     public $SingleToArray = array();
366     /**
367      * Whether to generate VERP addresses on send.
368      * Only applicable when sending via SMTP.
369      * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path
370      * @link http://www.postfix.org/VERP_README.html Postfix VERP info
371      * @var boolean
372      */
373     public $do_verp = false;
375     /**
376      * Whether to allow sending messages with an empty body.
377      * @var boolean
378      */
379     public $AllowEmpty = false;
381     /**
382      * The default line ending.
383      * @note The default remains "\n". We force CRLF where we know
384      *        it must be used via self::CRLF.
385      * @var string
386      */
387     public $LE = "\n";
389     /**
390      * DKIM selector.
391      * @var string
392      */
393     public $DKIM_selector = '';
395     /**
396      * DKIM Identity.
397      * Usually the email address used as the source of the email
398      * @var string
399      */
400     public $DKIM_identity = '';
402     /**
403      * DKIM passphrase.
404      * Used if your key is encrypted.
405      * @var string
406      */
407     public $DKIM_passphrase = '';
409     /**
410      * DKIM signing domain name.
411      * @example 'example.com'
412      * @var string
413      */
414     public $DKIM_domain = '';
416     /**
417      * DKIM private key file path.
418      * @var string
419      */
420     public $DKIM_private = '';
422     /**
423      * Callback Action function name.
424      *
425      * The function that handles the result of the send email action.
426      * It is called out by send() for each email sent.
427      *
428      * Value can be any php callable: http://www.php.net/is_callable
429      *
430      * Parameters:
431      *   boolean $result        result of the send action
432      *   string  $to            email address of the recipient
433      *   string  $cc            cc email addresses
434      *   string  $bcc           bcc email addresses
435      *   string  $subject       the subject
436      *   string  $body          the email body
437      *   string  $from          email address of sender
438      * @var string
439      */
440     public $action_function = '';
442     /**
443      * What to put in the X-Mailer header.
444      * Options: An empty string for PHPMailer default, whitespace for none, or a string to use
445      * @var string
446      */
447     public $XMailer = '';
449     /**
450      * An instance of the SMTP sender class.
451      * @var SMTP
452      * @access protected
453      */
454     protected $smtp = null;
456     /**
457      * The array of 'to' names and addresses.
458      * @var array
459      * @access protected
460      */
461     protected $to = array();
463     /**
464      * The array of 'cc' names and addresses.
465      * @var array
466      * @access protected
467      */
468     protected $cc = array();
470     /**
471      * The array of 'bcc' names and addresses.
472      * @var array
473      * @access protected
474      */
475     protected $bcc = array();
477     /**
478      * The array of reply-to names and addresses.
479      * @var array
480      * @access protected
481      */
482     protected $ReplyTo = array();
484     /**
485      * An array of all kinds of addresses.
486      * Includes all of $to, $cc, $bcc
487      * @var array
488      * @access protected
489      * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
490      */
491     protected $all_recipients = array();
493     /**
494      * An array of names and addresses queued for validation.
495      * In send(), valid and non duplicate entries are moved to $all_recipients
496      * and one of $to, $cc, or $bcc.
497      * This array is used only for addresses with IDN.
498      * @var array
499      * @access protected
500      * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
501      * @see PHPMailer::$all_recipients
502      */
503     protected $RecipientsQueue = array();
505     /**
506      * An array of reply-to names and addresses queued for validation.
507      * In send(), valid and non duplicate entries are moved to $ReplyTo.
508      * This array is used only for addresses with IDN.
509      * @var array
510      * @access protected
511      * @see PHPMailer::$ReplyTo
512      */
513     protected $ReplyToQueue = array();
515     /**
516      * The array of attachments.
517      * @var array
518      * @access protected
519      */
520     protected $attachment = array();
522     /**
523      * The array of custom headers.
524      * @var array
525      * @access protected
526      */
527     protected $CustomHeader = array();
529     /**
530      * The most recent Message-ID (including angular brackets).
531      * @var string
532      * @access protected
533      */
534     protected $lastMessageID = '';
536     /**
537      * The message's MIME type.
538      * @var string
539      * @access protected
540      */
541     protected $message_type = '';
543     /**
544      * The array of MIME boundary strings.
545      * @var array
546      * @access protected
547      */
548     protected $boundary = array();
550     /**
551      * The array of available languages.
552      * @var array
553      * @access protected
554      */
555     protected $language = array();
557     /**
558      * The number of errors encountered.
559      * @var integer
560      * @access protected
561      */
562     protected $error_count = 0;
564     /**
565      * The S/MIME certificate file path.
566      * @var string
567      * @access protected
568      */
569     protected $sign_cert_file = '';
571     /**
572      * The S/MIME key file path.
573      * @var string
574      * @access protected
575      */
576     protected $sign_key_file = '';
578     /**
579      * The optional S/MIME extra certificates ("CA Chain") file path.
580      * @var string
581      * @access protected
582      */
583     protected $sign_extracerts_file = '';
585     /**
586      * The S/MIME password for the key.
587      * Used only if the key is encrypted.
588      * @var string
589      * @access protected
590      */
591     protected $sign_key_pass = '';
593     /**
594      * Whether to throw exceptions for errors.
595      * @var boolean
596      * @access protected
597      */
598     protected $exceptions = false;
600     /**
601      * Unique ID used for message ID and boundaries.
602      * @var string
603      * @access protected
604      */
605     protected $uniqueid = '';
607     /**
608      * Error severity: message only, continue processing.
609      */
610     const STOP_MESSAGE = 0;
612     /**
613      * Error severity: message, likely ok to continue processing.
614      */
615     const STOP_CONTINUE = 1;
617     /**
618      * Error severity: message, plus full stop, critical error reached.
619      */
620     const STOP_CRITICAL = 2;
622     /**
623      * SMTP RFC standard line ending.
624      */
625     const CRLF = "\r\n";
627     /**
628      * The maximum line length allowed by RFC 2822 section 2.1.1
629      * @var integer
630      */
631     const MAX_LINE_LENGTH = 998;
633     /**
634      * Constructor.
635      * @param boolean $exceptions Should we throw external exceptions?
636      */
637     public function __construct($exceptions = false)
638     {
639         $this->exceptions = (boolean)$exceptions;
640     }
642     /**
643      * Destructor.
644      */
645     public function __destruct()
646     {
647         //Close any open SMTP connection nicely
648         if ($this->Mailer == 'smtp') {
649             $this->smtpClose();
650         }
651     }
653     /**
654      * Call mail() in a safe_mode-aware fashion.
655      * Also, unless sendmail_path points to sendmail (or something that
656      * claims to be sendmail), don't pass params (not a perfect fix,
657      * but it will do)
658      * @param string $to To
659      * @param string $subject Subject
660      * @param string $body Message Body
661      * @param string $header Additional Header(s)
662      * @param string $params Params
663      * @access private
664      * @return boolean
665      */
666     private function mailPassthru($to, $subject, $body, $header, $params)
667     {
668         //Check overloading of mail function to avoid double-encoding
669         if (ini_get('mbstring.func_overload') & 1) {
670             $subject = $this->secureHeader($subject);
671         } else {
672             $subject = $this->encodeHeader($this->secureHeader($subject));
673         }
674         if (ini_get('safe_mode') || !($this->UseSendmailOptions)) {
675             $result = @mail($to, $subject, $body, $header);
676         } else {
677             $result = @mail($to, $subject, $body, $header, $params);
678         }
679         return $result;
680     }
682     /**
683      * Output debugging info via user-defined method.
684      * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
685      * @see PHPMailer::$Debugoutput
686      * @see PHPMailer::$SMTPDebug
687      * @param string $str
688      */
689     protected function edebug($str)
690     {
691         if ($this->SMTPDebug <= 0) {
692             return;
693         }
694         //Avoid clash with built-in function names
695         if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
696             call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
697             return;
698         }
699         switch ($this->Debugoutput) {
700             case 'error_log':
701                 //Don't output, just log
702                 error_log($str);
703                 break;
704             case 'html':
705                 //Cleans up output a bit for a better looking, HTML-safe output
706                 echo htmlentities(
707                     preg_replace('/[\r\n]+/', '', $str),
708                     ENT_QUOTES,
709                     'UTF-8'
710                 )
711                 . "<br>\n";
712                 break;
713             case 'echo':
714             default:
715                 //Normalize line breaks
716                 $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
717                 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
718                     "\n",
719                     "\n                   \t                  ",
720                     trim($str)
721                 ) . "\n";
722         }
723     }
725     /**
726      * Sets message type to HTML or plain.
727      * @param boolean $isHtml True for HTML mode.
728      * @return void
729      */
730     public function isHTML($isHtml = true)
731     {
732         if ($isHtml) {
733             $this->ContentType = 'text/html';
734         } else {
735             $this->ContentType = 'text/plain';
736         }
737     }
739     /**
740      * Send messages using SMTP.
741      * @return void
742      */
743     public function isSMTP()
744     {
745         $this->Mailer = 'smtp';
746     }
748     /**
749      * Send messages using PHP's mail() function.
750      * @return void
751      */
752     public function isMail()
753     {
754         $this->Mailer = 'mail';
755     }
757     /**
758      * Send messages using $Sendmail.
759      * @return void
760      */
761     public function isSendmail()
762     {
763         $ini_sendmail_path = ini_get('sendmail_path');
765         if (!stristr($ini_sendmail_path, 'sendmail')) {
766             $this->Sendmail = '/usr/sbin/sendmail';
767         } else {
768             $this->Sendmail = $ini_sendmail_path;
769         }
770         $this->Mailer = 'sendmail';
771     }
773     /**
774      * Send messages using qmail.
775      * @return void
776      */
777     public function isQmail()
778     {
779         $ini_sendmail_path = ini_get('sendmail_path');
781         if (!stristr($ini_sendmail_path, 'qmail')) {
782             $this->Sendmail = '/var/qmail/bin/qmail-inject';
783         } else {
784             $this->Sendmail = $ini_sendmail_path;
785         }
786         $this->Mailer = 'qmail';
787     }
789     /**
790      * Add a "To" address.
791      * @param string $address The email address to send to
792      * @param string $name
793      * @return boolean true on success, false if address already used or invalid in some way
794      */
795     public function addAddress($address, $name = '')
796     {
797         return $this->addOrEnqueueAnAddress('to', $address, $name);
798     }
800     /**
801      * Add a "CC" address.
802      * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
803      * @param string $address The email address to send to
804      * @param string $name
805      * @return boolean true on success, false if address already used or invalid in some way
806      */
807     public function addCC($address, $name = '')
808     {
809         return $this->addOrEnqueueAnAddress('cc', $address, $name);
810     }
812     /**
813      * Add a "BCC" address.
814      * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
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 addBCC($address, $name = '')
820     {
821         return $this->addOrEnqueueAnAddress('bcc', $address, $name);
822     }
824     /**
825      * Add a "Reply-To" address.
826      * @param string $address The email address to reply to
827      * @param string $name
828      * @return boolean true on success, false if address already used or invalid in some way
829      */
830     public function addReplyTo($address, $name = '')
831     {
832         return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
833     }
835     /**
836      * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
837      * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
838      * be modified after calling this function), addition of such addresses is delayed until send().
839      * Addresses that have been added already return false, but do not throw exceptions.
840      * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
841      * @param string $address The email address to send, resp. to reply to
842      * @param string $name
843      * @throws phpmailerException
844      * @return boolean true on success, false if address already used or invalid in some way
845      * @access protected
846      */
847     protected function addOrEnqueueAnAddress($kind, $address, $name)
848     {
849         $address = trim($address);
850         $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
851         if (($pos = strrpos($address, '@')) === false) {
852             // At-sign is misssing.
853             $error_message = $this->lang('invalid_address') . $address;
854             $this->setError($error_message);
855             $this->edebug($error_message);
856             if ($this->exceptions) {
857                 throw new phpmailerException($error_message);
858             }
859             return false;
860         }
861         $params = array($kind, $address, $name);
862         // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
863         if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) {
864             if ($kind != 'Reply-To') {
865                 if (!array_key_exists($address, $this->RecipientsQueue)) {
866                     $this->RecipientsQueue[$address] = $params;
867                     return true;
868                 }
869             } else {
870                 if (!array_key_exists($address, $this->ReplyToQueue)) {
871                     $this->ReplyToQueue[$address] = $params;
872                     return true;
873                 }
874             }
875             return false;
876         }
877         // Immediately add standard addresses without IDN.
878         return call_user_func_array(array($this, 'addAnAddress'), $params);
879     }
881     /**
882      * Add an address to one of the recipient arrays or to the ReplyTo array.
883      * Addresses that have been added already return false, but do not throw exceptions.
884      * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
885      * @param string $address The email address to send, resp. to reply to
886      * @param string $name
887      * @throws phpmailerException
888      * @return boolean true on success, false if address already used or invalid in some way
889      * @access protected
890      */
891     protected function addAnAddress($kind, $address, $name = '')
892     {
893         if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) {
894             $error_message = $this->lang('Invalid recipient kind: ') . $kind;
895             $this->setError($error_message);
896             $this->edebug($error_message);
897             if ($this->exceptions) {
898                 throw new phpmailerException($error_message);
899             }
900             return false;
901         }
902         if (!$this->validateAddress($address)) {
903             $error_message = $this->lang('invalid_address') . $address;
904             $this->setError($error_message);
905             $this->edebug($error_message);
906             if ($this->exceptions) {
907                 throw new phpmailerException($error_message);
908             }
909             return false;
910         }
911         if ($kind != 'Reply-To') {
912             if (!array_key_exists(strtolower($address), $this->all_recipients)) {
913                 array_push($this->$kind, array($address, $name));
914                 $this->all_recipients[strtolower($address)] = true;
915                 return true;
916             }
917         } else {
918             if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
919                 $this->ReplyTo[strtolower($address)] = array($address, $name);
920                 return true;
921             }
922         }
923         return false;
924     }
926     /**
927      * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
928      * of the form "display name <address>" into an array of name/address pairs.
929      * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
930      * Note that quotes in the name part are removed.
931      * @param string $addrstr The address list string
932      * @param bool $useimap Whether to use the IMAP extension to parse the list
933      * @return array
934      * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
935      */
936     public function parseAddresses($addrstr, $useimap = true)
937     {
938         $addresses = array();
939         if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
940             //Use this built-in parser if it's available
941             $list = imap_rfc822_parse_adrlist($addrstr, '');
942             foreach ($list as $address) {
943                 if ($address->host != '.SYNTAX-ERROR.') {
944                     if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
945                         $addresses[] = array(
946                             'name' => (property_exists($address, 'personal') ? $address->personal : ''),
947                             'address' => $address->mailbox . '@' . $address->host
948                         );
949                     }
950                 }
951             }
952         } else {
953             //Use this simpler parser
954             $list = explode(',', $addrstr);
955             foreach ($list as $address) {
956                 $address = trim($address);
957                 //Is there a separate name part?
958                 if (strpos($address, '<') === false) {
959                     //No separate name, just use the whole thing
960                     if ($this->validateAddress($address)) {
961                         $addresses[] = array(
962                             'name' => '',
963                             'address' => $address
964                         );
965                     }
966                 } else {
967                     list($name, $email) = explode('<', $address);
968                     $email = trim(str_replace('>', '', $email));
969                     if ($this->validateAddress($email)) {
970                         $addresses[] = array(
971                             'name' => trim(str_replace(array('"', "'"), '', $name)),
972                             'address' => $email
973                         );
974                     }
975                 }
976             }
977         }
978         return $addresses;
979     }
981     /**
982      * Set the From and FromName properties.
983      * @param string $address
984      * @param string $name
985      * @param boolean $auto Whether to also set the Sender address, defaults to true
986      * @throws phpmailerException
987      * @return boolean
988      */
989     public function setFrom($address, $name = '', $auto = true)
990     {
991         $address = trim($address);
992         $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
993         // Don't validate now addresses with IDN. Will be done in send().
994         if (($pos = strrpos($address, '@')) === false or
995             (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
996             !$this->validateAddress($address)) {
997             $error_message = $this->lang('invalid_address') . $address;
998             $this->setError($error_message);
999             $this->edebug($error_message);
1000             if ($this->exceptions) {
1001                 throw new phpmailerException($error_message);
1002             }
1003             return false;
1004         }
1005         $this->From = $address;
1006         $this->FromName = $name;
1007         if ($auto) {
1008             if (empty($this->Sender)) {
1009                 $this->Sender = $address;
1010             }
1011         }
1012         return true;
1013     }
1015     /**
1016      * Return the Message-ID header of the last email.
1017      * Technically this is the value from the last time the headers were created,
1018      * but it's also the message ID of the last sent message except in
1019      * pathological cases.
1020      * @return string
1021      */
1022     public function getLastMessageID()
1023     {
1024         return $this->lastMessageID;
1025     }
1027     /**
1028      * Check that a string looks like an email address.
1029      * @param string $address The email address to check
1030      * @param string $patternselect A selector for the validation pattern to use :
1031      * * `auto` Pick best pattern automatically;
1032      * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
1033      * * `pcre` Use old PCRE implementation;
1034      * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
1035      * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
1036      * * `noregex` Don't use a regex: super fast, really dumb.
1037      * @return boolean
1038      * @static
1039      * @access public
1040      */
1041     public static function validateAddress($address, $patternselect = 'auto')
1042     {
1043         //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1044         if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
1045             return false;
1046         }
1047         if (!$patternselect or $patternselect == 'auto') {
1048             //Check this constant first so it works when extension_loaded() is disabled by safe mode
1049             //Constant was added in PHP 5.2.4
1050             if (defined('PCRE_VERSION')) {
1051                 //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
1052                 if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
1053                     $patternselect = 'pcre8';
1054                 } else {
1055                     $patternselect = 'pcre';
1056                 }
1057             } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
1058                 //Fall back to older PCRE
1059                 $patternselect = 'pcre';
1060             } else {
1061                 //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
1062                 if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
1063                     $patternselect = 'php';
1064                 } else {
1065                     $patternselect = 'noregex';
1066                 }
1067             }
1068         }
1069         switch ($patternselect) {
1070             case 'pcre8':
1071                 /**
1072                  * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
1073                  * @link http://squiloople.com/2009/12/20/email-address-validation/
1074                  * @copyright 2009-2010 Michael Rushton
1075                  * Feel free to use and redistribute this code. But please keep this copyright notice.
1076                  */
1077                 return (boolean)preg_match(
1078                     '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1079                     '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
1080                     '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
1081                     '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1082                     '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1083                     '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1084                     '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1085                     '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1086                     '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1087                     $address
1088                 );
1089             case 'pcre':
1090                 //An older regex that doesn't need a recent PCRE
1091                 return (boolean)preg_match(
1092                     '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
1093                     '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
1094                     '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
1095                     '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
1096                     '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
1097                     '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
1098                     '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
1099                     '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
1100                     '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1101                     '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
1102                     $address
1103                 );
1104             case 'html5':
1105                 /**
1106                  * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1107                  * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
1108                  */
1109                 return (boolean)preg_match(
1110                     '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1111                     '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1112                     $address
1113                 );
1114             case 'noregex':
1115                 //No PCRE! Do something _very_ approximate!
1116                 //Check the address is 3 chars or longer and contains an @ that's not the first or last char
1117                 return (strlen($address) >= 3
1118                     and strpos($address, '@') >= 1
1119                     and strpos($address, '@') != strlen($address) - 1);
1120             case 'php':
1121             default:
1122                 return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
1123         }
1124     }
1126     /**
1127      * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
1128      * "intl" and "mbstring" PHP extensions.
1129      * @return bool "true" if required functions for IDN support are present
1130      */
1131     public function idnSupported()
1132     {
1133         // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
1134         return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
1135     }
1137     /**
1138      * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
1139      * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
1140      * This function silently returns unmodified address if:
1141      * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
1142      * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
1143      *   or fails for any reason (e.g. domain has characters not allowed in an IDN)
1144      * @see PHPMailer::$CharSet
1145      * @param string $address The email address to convert
1146      * @return string The encoded address in ASCII form
1147      */
1148     public function punyencodeAddress($address)
1149     {
1150         // Verify we have required functions, CharSet, and at-sign.
1151         if ($this->idnSupported() and
1152             !empty($this->CharSet) and
1153             ($pos = strrpos($address, '@')) !== false) {
1154             $domain = substr($address, ++$pos);
1155             // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1156             if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
1157                 $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
1158                 if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
1159                     idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
1160                     idn_to_ascii($domain)) !== false) {
1161                     return substr($address, 0, $pos) . $punycode;
1162                 }
1163             }
1164         }
1165         return $address;
1166     }
1168     /**
1169      * Create a message and send it.
1170      * Uses the sending method specified by $Mailer.
1171      * @throws phpmailerException
1172      * @return boolean false on error - See the ErrorInfo property for details of the error.
1173      */
1174     public function send()
1175     {
1176         try {
1177             if (!$this->preSend()) {
1178                 return false;
1179             }
1180             return $this->postSend();
1181         } catch (phpmailerException $exc) {
1182             $this->mailHeader = '';
1183             $this->setError($exc->getMessage());
1184             if ($this->exceptions) {
1185                 throw $exc;
1186             }
1187             return false;
1188         }
1189     }
1191     /**
1192      * Prepare a message for sending.
1193      * @throws phpmailerException
1194      * @return boolean
1195      */
1196     public function preSend()
1197     {
1198         try {
1199             $this->error_count = 0; // Reset errors
1200             $this->mailHeader = '';
1202             // Dequeue recipient and Reply-To addresses with IDN
1203             foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1204                 $params[1] = $this->punyencodeAddress($params[1]);
1205                 call_user_func_array(array($this, 'addAnAddress'), $params);
1206             }
1207             if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
1208                 throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
1209             }
1211             // Validate From, Sender, and ConfirmReadingTo addresses
1212             foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
1213                 $this->$address_kind = trim($this->$address_kind);
1214                 if (empty($this->$address_kind)) {
1215                     continue;
1216                 }
1217                 $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
1218                 if (!$this->validateAddress($this->$address_kind)) {
1219                     $error_message = $this->lang('invalid_address') . $this->$address_kind;
1220                     $this->setError($error_message);
1221                     $this->edebug($error_message);
1222                     if ($this->exceptions) {
1223                         throw new phpmailerException($error_message);
1224                     }
1225                     return false;
1226                 }
1227             }
1229             // Set whether the message is multipart/alternative
1230             if (!empty($this->AltBody)) {
1231                 $this->ContentType = 'multipart/alternative';
1232             }
1234             $this->setMessageType();
1235             // Refuse to send an empty message unless we are specifically allowing it
1236             if (!$this->AllowEmpty and empty($this->Body)) {
1237                 throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
1238             }
1240             // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1241             $this->MIMEHeader = '';
1242             $this->MIMEBody = $this->createBody();
1243             // createBody may have added some headers, so retain them
1244             $tempheaders = $this->MIMEHeader;
1245             $this->MIMEHeader = $this->createHeader();
1246             $this->MIMEHeader .= $tempheaders;
1248             // To capture the complete message when using mail(), create
1249             // an extra header list which createHeader() doesn't fold in
1250             if ($this->Mailer == 'mail') {
1251                 if (count($this->to) > 0) {
1252                     $this->mailHeader .= $this->addrAppend('To', $this->to);
1253                 } else {
1254                     $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1255                 }
1256                 $this->mailHeader .= $this->headerLine(
1257                     'Subject',
1258                     $this->encodeHeader($this->secureHeader(trim($this->Subject)))
1259                 );
1260             }
1262             // Sign with DKIM if enabled
1263             if (!empty($this->DKIM_domain)
1264                 && !empty($this->DKIM_private)
1265                 && !empty($this->DKIM_selector)
1266                 && file_exists($this->DKIM_private)) {
1267                 $header_dkim = $this->DKIM_Add(
1268                     $this->MIMEHeader . $this->mailHeader,
1269                     $this->encodeHeader($this->secureHeader($this->Subject)),
1270                     $this->MIMEBody
1271                 );
1272                 $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
1273                     str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
1274             }
1275             return true;
1276         } catch (phpmailerException $exc) {
1277             $this->setError($exc->getMessage());
1278             if ($this->exceptions) {
1279                 throw $exc;
1280             }
1281             return false;
1282         }
1283     }
1285     /**
1286      * Actually send a message.
1287      * Send the email via the selected mechanism
1288      * @throws phpmailerException
1289      * @return boolean
1290      */
1291     public function postSend()
1292     {
1293         try {
1294             // Choose the mailer and send through it
1295             switch ($this->Mailer) {
1296                 case 'sendmail':
1297                 case 'qmail':
1298                     return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1299                 case 'smtp':
1300                     return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1301                 case 'mail':
1302                     return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1303                 default:
1304                     $sendMethod = $this->Mailer.'Send';
1305                     if (method_exists($this, $sendMethod)) {
1306                         return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1307                     }
1309                     return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1310             }
1311         } catch (phpmailerException $exc) {
1312             $this->setError($exc->getMessage());
1313             $this->edebug($exc->getMessage());
1314             if ($this->exceptions) {
1315                 throw $exc;
1316             }
1317         }
1318         return false;
1319     }
1321     /**
1322      * Send mail using the $Sendmail program.
1323      * @param string $header The message headers
1324      * @param string $body The message body
1325      * @see PHPMailer::$Sendmail
1326      * @throws phpmailerException
1327      * @access protected
1328      * @return boolean
1329      */
1330     protected function sendmailSend($header, $body)
1331     {
1332         if ($this->Sender != '') {
1333             if ($this->Mailer == 'qmail') {
1334                 $sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
1335             } else {
1336                 $sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
1337             }
1338         } else {
1339             if ($this->Mailer == 'qmail') {
1340                 $sendmail = sprintf('%s', escapeshellcmd($this->Sendmail));
1341             } else {
1342                 $sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail));
1343             }
1344         }
1345         if ($this->SingleTo) {
1346             foreach ($this->SingleToArray as $toAddr) {
1347                 if (!@$mail = popen($sendmail, 'w')) {
1348                     throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1349                 }
1350                 fputs($mail, 'To: ' . $toAddr . "\n");
1351                 fputs($mail, $header);
1352                 fputs($mail, $body);
1353                 $result = pclose($mail);
1354                 $this->doCallback(
1355                     ($result == 0),
1356                     array($toAddr),
1357                     $this->cc,
1358                     $this->bcc,
1359                     $this->Subject,
1360                     $body,
1361                     $this->From
1362                 );
1363                 if ($result != 0) {
1364                     throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1365                 }
1366             }
1367         } else {
1368             if (!@$mail = popen($sendmail, 'w')) {
1369                 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1370             }
1371             fputs($mail, $header);
1372             fputs($mail, $body);
1373             $result = pclose($mail);
1374             $this->doCallback(
1375                 ($result == 0),
1376                 $this->to,
1377                 $this->cc,
1378                 $this->bcc,
1379                 $this->Subject,
1380                 $body,
1381                 $this->From
1382             );
1383             if ($result != 0) {
1384                 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1385             }
1386         }
1387         return true;
1388     }
1390     /**
1391      * Send mail using the PHP mail() function.
1392      * @param string $header The message headers
1393      * @param string $body The message body
1394      * @link http://www.php.net/manual/en/book.mail.php
1395      * @throws phpmailerException
1396      * @access protected
1397      * @return boolean
1398      */
1399     protected function mailSend($header, $body)
1400     {
1401         $toArr = array();
1402         foreach ($this->to as $toaddr) {
1403             $toArr[] = $this->addrFormat($toaddr);
1404         }
1405         $to = implode(', ', $toArr);
1407         if (empty($this->Sender)) {
1408             $params = ' ';
1409         } else {
1410             $params = sprintf('-f%s', $this->Sender);
1411         }
1412         if ($this->Sender != '' and !ini_get('safe_mode')) {
1413             $old_from = ini_get('sendmail_from');
1414             ini_set('sendmail_from', $this->Sender);
1415         }
1416         $result = false;
1417         if ($this->SingleTo && count($toArr) > 1) {
1418             foreach ($toArr as $toAddr) {
1419                 $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1420                 $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1421             }
1422         } else {
1423             $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1424             $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1425         }
1426         if (isset($old_from)) {
1427             ini_set('sendmail_from', $old_from);
1428         }
1429         if (!$result) {
1430             throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
1431         }
1432         return true;
1433     }
1435     /**
1436      * Get an instance to use for SMTP operations.
1437      * Override this function to load your own SMTP implementation
1438      * @return SMTP
1439      */
1440     public function getSMTPInstance()
1441     {
1442         if (!is_object($this->smtp)) {
1443             $this->smtp = new SMTP;
1444         }
1445         return $this->smtp;
1446     }
1448     /**
1449      * Send mail via SMTP.
1450      * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
1451      * Uses the PHPMailerSMTP class by default.
1452      * @see PHPMailer::getSMTPInstance() to use a different class.
1453      * @param string $header The message headers
1454      * @param string $body The message body
1455      * @throws phpmailerException
1456      * @uses SMTP
1457      * @access protected
1458      * @return boolean
1459      */
1460     protected function smtpSend($header, $body)
1461     {
1462         $bad_rcpt = array();
1463         if (!$this->smtpConnect($this->SMTPOptions)) {
1464             throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1465         }
1466         if ('' == $this->Sender) {
1467             $smtp_from = $this->From;
1468         } else {
1469             $smtp_from = $this->Sender;
1470         }
1471         if (!$this->smtp->mail($smtp_from)) {
1472             $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1473             throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
1474         }
1476         // Attempt to send to all recipients
1477         foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
1478             foreach ($togroup as $to) {
1479                 if (!$this->smtp->recipient($to[0])) {
1480                     $error = $this->smtp->getError();
1481                     $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
1482                     $isSent = false;
1483                 } else {
1484                     $isSent = true;
1485                 }
1486                 $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
1487             }
1488         }
1490         // Only send the DATA command if we have viable recipients
1491         if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
1492             throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1493         }
1494         if ($this->SMTPKeepAlive) {
1495             $this->smtp->reset();
1496         } else {
1497             $this->smtp->quit();
1498             $this->smtp->close();
1499         }
1500         //Create error message for any bad addresses
1501         if (count($bad_rcpt) > 0) {
1502             $errstr = '';
1503             foreach ($bad_rcpt as $bad) {
1504                 $errstr .= $bad['to'] . ': ' . $bad['error'];
1505             }
1506             throw new phpmailerException(
1507                 $this->lang('recipients_failed') . $errstr,
1508                 self::STOP_CONTINUE
1509             );
1510         }
1511         return true;
1512     }
1514     /**
1515      * Initiate a connection to an SMTP server.
1516      * Returns false if the operation failed.
1517      * @param array $options An array of options compatible with stream_context_create()
1518      * @uses SMTP
1519      * @access public
1520      * @throws phpmailerException
1521      * @return boolean
1522      */
1523     public function smtpConnect($options = array())
1524     {
1525         if (is_null($this->smtp)) {
1526             $this->smtp = $this->getSMTPInstance();
1527         }
1529         // Already connected?
1530         if ($this->smtp->connected()) {
1531             return true;
1532         }
1534         $this->smtp->setTimeout($this->Timeout);
1535         $this->smtp->setDebugLevel($this->SMTPDebug);
1536         $this->smtp->setDebugOutput($this->Debugoutput);
1537         $this->smtp->setVerp($this->do_verp);
1538         $hosts = explode(';', $this->Host);
1539         $lastexception = null;
1541         foreach ($hosts as $hostentry) {
1542             $hostinfo = array();
1543             if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
1544                 // Not a valid host entry
1545                 continue;
1546             }
1547             // $hostinfo[2]: optional ssl or tls prefix
1548             // $hostinfo[3]: the hostname
1549             // $hostinfo[4]: optional port number
1550             // The host string prefix can temporarily override the current setting for SMTPSecure
1551             // If it's not specified, the default value is used
1552             $prefix = '';
1553             $secure = $this->SMTPSecure;
1554             $tls = ($this->SMTPSecure == 'tls');
1555             if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1556                 $prefix = 'ssl://';
1557                 $tls = false; // Can't have SSL and TLS at the same time
1558                 $secure = 'ssl';
1559             } elseif ($hostinfo[2] == 'tls') {
1560                 $tls = true;
1561                 // tls doesn't use a prefix
1562                 $secure = 'tls';
1563             }
1564             //Do we need the OpenSSL extension?
1565             $sslext = defined('OPENSSL_ALGO_SHA1');
1566             if ('tls' === $secure or 'ssl' === $secure) {
1567                 //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1568                 if (!$sslext) {
1569                     throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
1570                 }
1571             }
1572             $host = $hostinfo[3];
1573             $port = $this->Port;
1574             $tport = (integer)$hostinfo[4];
1575             if ($tport > 0 and $tport < 65536) {
1576                 $port = $tport;
1577             }
1578             if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1579                 try {
1580                     if ($this->Helo) {
1581                         $hello = $this->Helo;
1582                     } else {
1583                         $hello = $this->serverHostname();
1584                     }
1585                     $this->smtp->hello($hello);
1586                     //Automatically enable TLS encryption if:
1587                     // * it's not disabled
1588                     // * we have openssl extension
1589                     // * we are not already using SSL
1590                     // * the server offers STARTTLS
1591                     if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
1592                         $tls = true;
1593                     }
1594                     if ($tls) {
1595                         if (!$this->smtp->startTLS()) {
1596                             throw new phpmailerException($this->lang('connect_host'));
1597                         }
1598                         // We must resend HELO after tls negotiation
1599                         $this->smtp->hello($hello);
1600                     }
1601                     if ($this->SMTPAuth) {
1602                         if (!$this->smtp->authenticate(
1603                             $this->Username,
1604                             $this->Password,
1605                             $this->AuthType,
1606                             $this->Realm,
1607                             $this->Workstation
1608                         )
1609                         ) {
1610                             throw new phpmailerException($this->lang('authenticate'));
1611                         }
1612                     }
1613                     return true;
1614                 } catch (phpmailerException $exc) {
1615                     $lastexception = $exc;
1616                     $this->edebug($exc->getMessage());
1617                     // We must have connected, but then failed TLS or Auth, so close connection nicely
1618                     $this->smtp->quit();
1619                 }
1620             }
1621         }
1622         // If we get here, all connection attempts have failed, so close connection hard
1623         $this->smtp->close();
1624         // As we've caught all exceptions, just report whatever the last one was
1625         if ($this->exceptions and !is_null($lastexception)) {
1626             throw $lastexception;
1627         }
1628         return false;
1629     }
1631     /**
1632      * Close the active SMTP session if one exists.
1633      * @return void
1634      */
1635     public function smtpClose()
1636     {
1637         if ($this->smtp !== null) {
1638             if ($this->smtp->connected()) {
1639                 $this->smtp->quit();
1640                 $this->smtp->close();
1641             }
1642         }
1643     }
1645     /**
1646      * Set the language for error messages.
1647      * Returns false if it cannot load the language file.
1648      * The default language is English.
1649      * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
1650      * @param string $lang_path Path to the language file directory, with trailing separator (slash)
1651      * @return boolean
1652      * @access public
1653      */
1654     public function setLanguage($langcode = 'en', $lang_path = '')
1655     {
1656         // Define full set of translatable strings in English
1657         $PHPMAILER_LANG = array(
1658             'authenticate' => 'SMTP Error: Could not authenticate.',
1659             'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
1660             'data_not_accepted' => 'SMTP Error: data not accepted.',
1661             'empty_message' => 'Message body empty',
1662             'encoding' => 'Unknown encoding: ',
1663             'execute' => 'Could not execute: ',
1664             'file_access' => 'Could not access file: ',
1665             'file_open' => 'File Error: Could not open file: ',
1666             'from_failed' => 'The following From address failed: ',
1667             'instantiate' => 'Could not instantiate mail function.',
1668             'invalid_address' => 'Invalid address: ',
1669             'mailer_not_supported' => ' mailer is not supported.',
1670             'provide_address' => 'You must provide at least one recipient email address.',
1671             'recipients_failed' => 'SMTP Error: The following recipients failed: ',
1672             'signing' => 'Signing Error: ',
1673             'smtp_connect_failed' => 'SMTP connect() failed.',
1674             'smtp_error' => 'SMTP server error: ',
1675             'variable_set' => 'Cannot set or reset variable: ',
1676             'extension_missing' => 'Extension missing: '
1677         );
1678         if (empty($lang_path)) {
1679             // Calculate an absolute path so it can work if CWD is not here
1680             $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
1681         }
1682         $foundlang = true;
1683         $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1684         // There is no English translation file
1685         if ($langcode != 'en') {
1686             // Make sure language file path is readable
1687             if (!is_readable($lang_file)) {
1688                 $foundlang = false;
1689             } else {
1690                 // Overwrite language-specific strings.
1691                 // This way we'll never have missing translation keys.
1692                 $foundlang = include $lang_file;
1693             }
1694         }
1695         $this->language = $PHPMAILER_LANG;
1696         return (boolean)$foundlang; // Returns false if language not found
1697     }
1699     /**
1700      * Get the array of strings for the current language.
1701      * @return array
1702      */
1703     public function getTranslations()
1704     {
1705         return $this->language;
1706     }
1708     /**
1709      * Create recipient headers.
1710      * @access public
1711      * @param string $type
1712      * @param array $addr An array of recipient,
1713      * where each recipient is a 2-element indexed array with element 0 containing an address
1714      * and element 1 containing a name, like:
1715      * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User'))
1716      * @return string
1717      */
1718     public function addrAppend($type, $addr)
1719     {
1720         $addresses = array();
1721         foreach ($addr as $address) {
1722             $addresses[] = $this->addrFormat($address);
1723         }
1724         return $type . ': ' . implode(', ', $addresses) . $this->LE;
1725     }
1727     /**
1728      * Format an address for use in a message header.
1729      * @access public
1730      * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
1731      *      like array('joe@example.com', 'Joe User')
1732      * @return string
1733      */
1734     public function addrFormat($addr)
1735     {
1736         if (empty($addr[1])) { // No name provided
1737             return $this->secureHeader($addr[0]);
1738         } else {
1739             return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
1740                 $addr[0]
1741             ) . '>';
1742         }
1743     }
1745     /**
1746      * Word-wrap message.
1747      * For use with mailers that do not automatically perform wrapping
1748      * and for quoted-printable encoded messages.
1749      * Original written by philippe.
1750      * @param string $message The message to wrap
1751      * @param integer $length The line length to wrap to
1752      * @param boolean $qp_mode Whether to run in Quoted-Printable mode
1753      * @access public
1754      * @return string
1755      */
1756     public function wrapText($message, $length, $qp_mode = false)
1757     {
1758         if ($qp_mode) {
1759             $soft_break = sprintf(' =%s', $this->LE);
1760         } else {
1761             $soft_break = $this->LE;
1762         }
1763         // If utf-8 encoding is used, we will need to make sure we don't
1764         // split multibyte characters when we wrap
1765         $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
1766         $lelen = strlen($this->LE);
1767         $crlflen = strlen(self::CRLF);
1769         $message = $this->fixEOL($message);
1770         //Remove a trailing line break
1771         if (substr($message, -$lelen) == $this->LE) {
1772             $message = substr($message, 0, -$lelen);
1773         }
1775         //Split message into lines
1776         $lines = explode($this->LE, $message);
1777         //Message will be rebuilt in here
1778         $message = '';
1779         foreach ($lines as $line) {
1780             $words = explode(' ', $line);
1781             $buf = '';
1782             $firstword = true;
1783             foreach ($words as $word) {
1784                 if ($qp_mode and (strlen($word) > $length)) {
1785                     $space_left = $length - strlen($buf) - $crlflen;
1786                     if (!$firstword) {
1787                         if ($space_left > 20) {
1788                             $len = $space_left;
1789                             if ($is_utf8) {
1790                                 $len = $this->utf8CharBoundary($word, $len);
1791                             } elseif (substr($word, $len - 1, 1) == '=') {
1792                                 $len--;
1793                             } elseif (substr($word, $len - 2, 1) == '=') {
1794                                 $len -= 2;
1795                             }
1796                             $part = substr($word, 0, $len);
1797                             $word = substr($word, $len);
1798                             $buf .= ' ' . $part;
1799                             $message .= $buf . sprintf('=%s', self::CRLF);
1800                         } else {
1801                             $message .= $buf . $soft_break;
1802                         }
1803                         $buf = '';
1804                     }
1805                     while (strlen($word) > 0) {
1806                         if ($length <= 0) {
1807                             break;
1808                         }
1809                         $len = $length;
1810                         if ($is_utf8) {
1811                             $len = $this->utf8CharBoundary($word, $len);
1812                         } elseif (substr($word, $len - 1, 1) == '=') {
1813                             $len--;
1814                         } elseif (substr($word, $len - 2, 1) == '=') {
1815                             $len -= 2;
1816                         }
1817                         $part = substr($word, 0, $len);
1818                         $word = substr($word, $len);
1820                         if (strlen($word) > 0) {
1821                             $message .= $part . sprintf('=%s', self::CRLF);
1822                         } else {
1823                             $buf = $part;
1824                         }
1825                     }
1826                 } else {
1827                     $buf_o = $buf;
1828                     if (!$firstword) {
1829                         $buf .= ' ';
1830                     }
1831                     $buf .= $word;
1833                     if (strlen($buf) > $length and $buf_o != '') {
1834                         $message .= $buf_o . $soft_break;
1835                         $buf = $word;
1836                     }
1837                 }
1838                 $firstword = false;
1839             }
1840             $message .= $buf . self::CRLF;
1841         }
1843         return $message;
1844     }
1846     /**
1847      * Find the last character boundary prior to $maxLength in a utf-8
1848      * quoted-printable encoded string.
1849      * Original written by Colin Brown.
1850      * @access public
1851      * @param string $encodedText utf-8 QP text
1852      * @param integer $maxLength Find the last character boundary prior to this length
1853      * @return integer
1854      */
1855     public function utf8CharBoundary($encodedText, $maxLength)
1856     {
1857         $foundSplitPos = false;
1858         $lookBack = 3;
1859         while (!$foundSplitPos) {
1860             $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
1861             $encodedCharPos = strpos($lastChunk, '=');
1862             if (false !== $encodedCharPos) {
1863                 // Found start of encoded character byte within $lookBack block.
1864                 // Check the encoded byte value (the 2 chars after the '=')
1865                 $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1866                 $dec = hexdec($hex);
1867                 if ($dec < 128) {
1868                     // Single byte character.
1869                     // If the encoded char was found at pos 0, it will fit
1870                     // otherwise reduce maxLength to start of the encoded char
1871                     if ($encodedCharPos > 0) {
1872                         $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1873                     }
1874                     $foundSplitPos = true;
1875                 } elseif ($dec >= 192) {
1876                     // First byte of a multi byte character
1877                     // Reduce maxLength to split at start of character
1878                     $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1879                     $foundSplitPos = true;
1880                 } elseif ($dec < 192) {
1881                     // Middle byte of a multi byte character, look further back
1882                     $lookBack += 3;
1883                 }
1884             } else {
1885                 // No encoded character found
1886                 $foundSplitPos = true;
1887             }
1888         }
1889         return $maxLength;
1890     }
1892     /**
1893      * Apply word wrapping to the message body.
1894      * Wraps the message body to the number of chars set in the WordWrap property.
1895      * You should only do this to plain-text bodies as wrapping HTML tags may break them.
1896      * This is called automatically by createBody(), so you don't need to call it yourself.
1897      * @access public
1898      * @return void
1899      */
1900     public function setWordWrap()
1901     {
1902         if ($this->WordWrap < 1) {
1903             return;
1904         }
1906         switch ($this->message_type) {
1907             case 'alt':
1908             case 'alt_inline':
1909             case 'alt_attach':
1910             case 'alt_inline_attach':
1911                 $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
1912                 break;
1913             default:
1914                 $this->Body = $this->wrapText($this->Body, $this->WordWrap);
1915                 break;
1916         }
1917     }
1919     /**
1920      * Assemble message headers.
1921      * @access public
1922      * @return string The assembled headers
1923      */
1924     public function createHeader()
1925     {
1926         $result = '';
1928         if ($this->MessageDate == '') {
1929             $this->MessageDate = self::rfcDate();
1930         }
1931         $result .= $this->headerLine('Date', $this->MessageDate);
1933         // To be created automatically by mail()
1934         if ($this->SingleTo) {
1935             if ($this->Mailer != 'mail') {
1936                 foreach ($this->to as $toaddr) {
1937                     $this->SingleToArray[] = $this->addrFormat($toaddr);
1938                 }
1939             }
1940         } else {
1941             if (count($this->to) > 0) {
1942                 if ($this->Mailer != 'mail') {
1943                     $result .= $this->addrAppend('To', $this->to);
1944                 }
1945             } elseif (count($this->cc) == 0) {
1946                 $result .= $this->headerLine('To', 'undisclosed-recipients:;');
1947             }
1948         }
1950         $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
1952         // sendmail and mail() extract Cc from the header before sending
1953         if (count($this->cc) > 0) {
1954             $result .= $this->addrAppend('Cc', $this->cc);
1955         }
1957         // sendmail and mail() extract Bcc from the header before sending
1958         if ((
1959                 $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
1960             )
1961             and count($this->bcc) > 0
1962         ) {
1963             $result .= $this->addrAppend('Bcc', $this->bcc);
1964         }
1966         if (count($this->ReplyTo) > 0) {
1967             $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
1968         }
1970         // mail() sets the subject itself
1971         if ($this->Mailer != 'mail') {
1972             $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
1973         }
1975         if ($this->MessageID != '') {
1976             $this->lastMessageID = $this->MessageID;
1977         } else {
1978             $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
1979         }
1980         $result .= $this->headerLine('Message-ID', $this->lastMessageID);
1981         if (!is_null($this->Priority)) {
1982             $result .= $this->headerLine('X-Priority', $this->Priority);
1983         }
1984         if ($this->XMailer == '') {
1985             $result .= $this->headerLine(
1986                 'X-Mailer',
1987                 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
1988             );
1989         } else {
1990             $myXmailer = trim($this->XMailer);
1991             if ($myXmailer) {
1992                 $result .= $this->headerLine('X-Mailer', $myXmailer);
1993             }
1994         }
1996         if ($this->ConfirmReadingTo != '') {
1997             $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
1998         }
2000         // Add custom headers
2001         foreach ($this->CustomHeader as $header) {
2002             $result .= $this->headerLine(
2003                 trim($header[0]),
2004                 $this->encodeHeader(trim($header[1]))
2005             );
2006         }
2007         if (!$this->sign_key_file) {
2008             $result .= $this->headerLine('MIME-Version', '1.0');
2009             $result .= $this->getMailMIME();
2010         }
2012         return $result;
2013     }
2015     /**
2016      * Get the message MIME type headers.
2017      * @access public
2018      * @return string
2019      */
2020     public function getMailMIME()
2021     {
2022         $result = '';
2023         $ismultipart = true;
2024         switch ($this->message_type) {
2025             case 'inline':
2026                 $result .= $this->headerLine('Content-Type', 'multipart/related;');
2027                 $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2028                 break;
2029             case 'attach':
2030             case 'inline_attach':
2031             case 'alt_attach':
2032             case 'alt_inline_attach':
2033                 $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
2034                 $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2035                 break;
2036             case 'alt':
2037             case 'alt_inline':
2038                 $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
2039                 $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2040                 break;
2041             default:
2042                 // Catches case 'plain': and case '':
2043                 $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2044                 $ismultipart = false;
2045                 break;
2046         }
2047         // RFC1341 part 5 says 7bit is assumed if not specified
2048         if ($this->Encoding != '7bit') {
2049             // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2050             if ($ismultipart) {
2051                 if ($this->Encoding == '8bit') {
2052                     $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
2053                 }
2054                 // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2055             } else {
2056                 $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2057             }
2058         }
2060         if ($this->Mailer != 'mail') {
2061             $result .= $this->LE;
2062         }
2064         return $result;
2065     }
2067     /**
2068      * Returns the whole MIME message.
2069      * Includes complete headers and body.
2070      * Only valid post preSend().
2071      * @see PHPMailer::preSend()
2072      * @access public
2073      * @return string
2074      */
2075     public function getSentMIMEMessage()
2076     {
2077         return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody;
2078     }
2080     /**
2081      * Assemble the message body.
2082      * Returns an empty string on failure.
2083      * @access public
2084      * @throws phpmailerException
2085      * @return string The assembled message body
2086      */
2087     public function createBody()
2088     {
2089         $body = '';
2090         //Create unique IDs and preset boundaries
2091         $this->uniqueid = md5(uniqid(time()));
2092         $this->boundary[1] = 'b1_' . $this->uniqueid;
2093         $this->boundary[2] = 'b2_' . $this->uniqueid;
2094         $this->boundary[3] = 'b3_' . $this->uniqueid;
2096         if ($this->sign_key_file) {
2097             $body .= $this->getMailMIME() . $this->LE;
2098         }
2100         $this->setWordWrap();
2102         $bodyEncoding = $this->Encoding;
2103         $bodyCharSet = $this->CharSet;
2104         //Can we do a 7-bit downgrade?
2105         if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
2106             $bodyEncoding = '7bit';
2107             $bodyCharSet = 'us-ascii';
2108         }
2109         //If lines are too long, and we're not already using an encoding that will shorten them,
2110         //change to quoted-printable transfer encoding
2111         if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
2112             $this->Encoding = 'quoted-printable';
2113             $bodyEncoding = 'quoted-printable';
2114         }
2116         $altBodyEncoding = $this->Encoding;
2117         $altBodyCharSet = $this->CharSet;
2118         //Can we do a 7-bit downgrade?
2119         if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
2120             $altBodyEncoding = '7bit';
2121             $altBodyCharSet = 'us-ascii';
2122         }
2123         //If lines are too long, change to quoted-printable transfer encoding
2124         if (self::hasLineLongerThanMax($this->AltBody)) {
2125             $altBodyEncoding = 'quoted-printable';
2126         }
2127         //Use this as a preamble in all multipart message types
2128         $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
2129         switch ($this->message_type) {
2130             case 'inline':
2131                 $body .= $mimepre;
2132                 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2133                 $body .= $this->encodeString($this->Body, $bodyEncoding);
2134                 $body .= $this->LE . $this->LE;
2135                 $body .= $this->attachAll('inline', $this->boundary[1]);
2136                 break;
2137             case 'attach':
2138                 $body .= $mimepre;
2139                 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2140                 $body .= $this->encodeString($this->Body, $bodyEncoding);
2141                 $body .= $this->LE . $this->LE;
2142                 $body .= $this->attachAll('attachment', $this->boundary[1]);
2143                 break;
2144             case 'inline_attach':
2145                 $body .= $mimepre;
2146                 $body .= $this->textLine('--' . $this->boundary[1]);
2147                 $body .= $this->headerLine('Content-Type', 'multipart/related;');
2148                 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2149                 $body .= $this->LE;
2150                 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2151                 $body .= $this->encodeString($this->Body, $bodyEncoding);
2152                 $body .= $this->LE . $this->LE;
2153                 $body .= $this->attachAll('inline', $this->boundary[2]);
2154                 $body .= $this->LE;
2155                 $body .= $this->attachAll('attachment', $this->boundary[1]);
2156                 break;
2157             case 'alt':
2158                 $body .= $mimepre;
2159                 $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2160                 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2161                 $body .= $this->LE . $this->LE;
2162                 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2163                 $body .= $this->encodeString($this->Body, $bodyEncoding);
2164                 $body .= $this->LE . $this->LE;
2165                 if (!empty($this->Ical)) {
2166                     $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2167                     $body .= $this->encodeString($this->Ical, $this->Encoding);
2168                     $body .= $this->LE . $this->LE;
2169                 }
2170                 $body .= $this->endBoundary($this->boundary[1]);
2171                 break;
2172             case 'alt_inline':
2173                 $body .= $mimepre;
2174                 $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2175                 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2176                 $body .= $this->LE . $this->LE;
2177                 $body .= $this->textLine('--' . $this->boundary[1]);
2178                 $body .= $this->headerLine('Content-Type', 'multipart/related;');
2179                 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2180                 $body .= $this->LE;
2181                 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2182                 $body .= $this->encodeString($this->Body, $bodyEncoding);
2183                 $body .= $this->LE . $this->LE;
2184                 $body .= $this->attachAll('inline', $this->boundary[2]);
2185                 $body .= $this->LE;
2186                 $body .= $this->endBoundary($this->boundary[1]);
2187                 break;
2188             case 'alt_attach':
2189                 $body .= $mimepre;
2190                 $body .= $this->textLine('--' . $this->boundary[1]);
2191                 $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2192                 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2193                 $body .= $this->LE;
2194                 $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2195                 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2196                 $body .= $this->LE . $this->LE;
2197                 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2198                 $body .= $this->encodeString($this->Body, $bodyEncoding);
2199                 $body .= $this->LE . $this->LE;
2200                 $body .= $this->endBoundary($this->boundary[2]);
2201                 $body .= $this->LE;
2202                 $body .= $this->attachAll('attachment', $this->boundary[1]);
2203                 break;
2204             case 'alt_inline_attach':
2205                 $body .= $mimepre;
2206                 $body .= $this->textLine('--' . $this->boundary[1]);
2207                 $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2208                 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2209                 $body .= $this->LE;
2210                 $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2211                 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2212                 $body .= $this->LE . $this->LE;
2213                 $body .= $this->textLine('--' . $this->boundary[2]);
2214                 $body .= $this->headerLine('Content-Type', 'multipart/related;');
2215                 $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2216                 $body .= $this->LE;
2217                 $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2218                 $body .= $this->encodeString($this->Body, $bodyEncoding);
2219                 $body .= $this->LE . $this->LE;
2220                 $body .= $this->attachAll('inline', $this->boundary[3]);
2221                 $body .= $this->LE;
2222                 $body .= $this->endBoundary($this->boundary[2]);
2223                 $body .= $this->LE;
2224                 $body .= $this->attachAll('attachment', $this->boundary[1]);
2225                 break;
2226             default:
2227                 // catch case 'plain' and case ''
2228                 $body .= $this->encodeString($this->Body, $bodyEncoding);
2229                 break;
2230         }
2232         if ($this->isError()) {
2233             $body = '';
2234         } elseif ($this->sign_key_file) {
2235             try {
2236                 if (!defined('PKCS7_TEXT')) {
2237                     throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2238                 }
2239                 // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2240                 $file = tempnam(sys_get_temp_dir(), 'mail');
2241                 if (false === file_put_contents($file, $body)) {
2242                     throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2243                 }
2244                 $signed = tempnam(sys_get_temp_dir(), 'signed');
2245                 //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2246                 if (empty($this->sign_extracerts_file)) {
2247                     $sign = @openssl_pkcs7_sign(
2248                         $file,
2249                         $signed,
2250                         'file://' . realpath($this->sign_cert_file),
2251                         array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2252                         null
2253                     );
2254                 } else {
2255                     $sign = @openssl_pkcs7_sign(
2256                         $file,
2257                         $signed,
2258                         'file://' . realpath($this->sign_cert_file),
2259                         array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2260                         null,
2261                         PKCS7_DETACHED,
2262                         $this->sign_extracerts_file
2263                     );
2264                 }
2265                 if ($sign) {
2266                     @unlink($file);
2267                     $body = file_get_contents($signed);
2268                     @unlink($signed);
2269                     //The message returned by openssl contains both headers and body, so need to split them up
2270                     $parts = explode("\n\n", $body, 2);
2271                     $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2272                     $body = $parts[1];
2273                 } else {
2274                     @unlink($file);
2275                     @unlink($signed);
2276                     throw new phpmailerException($this->lang('signing') . openssl_error_string());
2277                 }
2278             } catch (phpmailerException $exc) {
2279                 $body = '';
2280                 if ($this->exceptions) {
2281                     throw $exc;
2282                 }
2283             }
2284         }
2285         return $body;
2286     }
2288     /**
2289      * Return the start of a message boundary.
2290      * @access protected
2291      * @param string $boundary
2292      * @param string $charSet
2293      * @param string $contentType
2294      * @param string $encoding
2295      * @return string
2296      */
2297     protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2298     {
2299         $result = '';
2300         if ($charSet == '') {
2301             $charSet = $this->CharSet;
2302         }
2303         if ($contentType == '') {
2304             $contentType = $this->ContentType;
2305         }
2306         if ($encoding == '') {
2307             $encoding = $this->Encoding;
2308         }
2309         $result .= $this->textLine('--' . $boundary);
2310         $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2311         $result .= $this->LE;
2312         // RFC1341 part 5 says 7bit is assumed if not specified
2313         if ($encoding != '7bit') {
2314             $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2315         }
2316         $result .= $this->LE;
2318         return $result;
2319     }
2321     /**
2322      * Return the end of a message boundary.
2323      * @access protected
2324      * @param string $boundary
2325      * @return string
2326      */
2327     protected function endBoundary($boundary)
2328     {
2329         return $this->LE . '--' . $boundary . '--' . $this->LE;
2330     }
2332     /**
2333      * Set the message type.
2334      * PHPMailer only supports some preset message types,
2335      * not arbitrary MIME structures.
2336      * @access protected
2337      * @return void
2338      */
2339     protected function setMessageType()
2340     {
2341         $type = array();
2342         if ($this->alternativeExists()) {
2343             $type[] = 'alt';
2344         }
2345         if ($this->inlineImageExists()) {
2346             $type[] = 'inline';
2347         }
2348         if ($this->attachmentExists()) {
2349             $type[] = 'attach';
2350         }
2351         $this->message_type = implode('_', $type);
2352         if ($this->message_type == '') {
2353             $this->message_type = 'plain';
2354         }
2355     }
2357     /**
2358      * Format a header line.
2359      * @access public
2360      * @param string $name
2361      * @param string $value
2362      * @return string
2363      */
2364     public function headerLine($name, $value)
2365     {
2366         return $name . ': ' . $value . $this->LE;
2367     }
2369     /**
2370      * Return a formatted mail line.
2371      * @access public
2372      * @param string $value
2373      * @return string
2374      */
2375     public function textLine($value)
2376     {
2377         return $value . $this->LE;
2378     }
2380     /**
2381      * Add an attachment from a path on the filesystem.
2382      * Returns false if the file could not be found or read.
2383      * @param string $path Path to the attachment.
2384      * @param string $name Overrides the attachment name.
2385      * @param string $encoding File encoding (see $Encoding).
2386      * @param string $type File extension (MIME) type.
2387      * @param string $disposition Disposition to use
2388      * @throws phpmailerException
2389      * @return boolean
2390      */
2391     public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2392     {
2393         try {
2394             if (!@is_file($path)) {
2395                 throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2396             }
2398             // If a MIME type is not specified, try to work it out from the file name
2399             if ($type == '') {
2400                 $type = self::filenameToType($path);
2401             }
2403             $filename = basename($path);
2404             if ($name == '') {
2405                 $name = $filename;
2406             }
2408             $this->attachment[] = array(
2409                 0 => $path,
2410                 1 => $filename,
2411                 2 => $name,
2412                 3 => $encoding,
2413                 4 => $type,
2414                 5 => false, // isStringAttachment
2415                 6 => $disposition,
2416                 7 => 0
2417             );
2419         } catch (phpmailerException $exc) {
2420             $this->setError($exc->getMessage());
2421             $this->edebug($exc->getMessage());
2422             if ($this->exceptions) {
2423                 throw $exc;
2424             }
2425             return false;
2426         }
2427         return true;
2428     }
2430     /**
2431      * Return the array of attachments.
2432      * @return array
2433      */
2434     public function getAttachments()
2435     {
2436         return $this->attachment;
2437     }
2439     /**
2440      * Attach all file, string, and binary attachments to the message.
2441      * Returns an empty string on failure.
2442      * @access protected
2443      * @param string $disposition_type
2444      * @param string $boundary
2445      * @return string
2446      */
2447     protected function attachAll($disposition_type, $boundary)
2448     {
2449         // Return text of body
2450         $mime = array();
2451         $cidUniq = array();
2452         $incl = array();
2454         // Add all attachments
2455         foreach ($this->attachment as $attachment) {
2456             // Check if it is a valid disposition_filter
2457             if ($attachment[6] == $disposition_type) {
2458                 // Check for string attachment
2459                 $string = '';
2460                 $path = '';
2461                 $bString = $attachment[5];
2462                 if ($bString) {
2463                     $string = $attachment[0];
2464                 } else {
2465                     $path = $attachment[0];
2466                 }
2468                 $inclhash = md5(serialize($attachment));
2469                 if (in_array($inclhash, $incl)) {
2470                     continue;
2471                 }
2472                 $incl[] = $inclhash;
2473                 $name = $attachment[2];
2474                 $encoding = $attachment[3];
2475                 $type = $attachment[4];
2476                 $disposition = $attachment[6];
2477                 $cid = $attachment[7];
2478                 if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
2479                     continue;
2480                 }
2481                 $cidUniq[$cid] = true;
2483                 $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2484                 //Only include a filename property if we have one
2485                 if (!empty($name)) {
2486                     $mime[] = sprintf(
2487                         'Content-Type: %s; name="%s"%s',
2488                         $type,
2489                         $this->encodeHeader($this->secureHeader($name)),
2490                         $this->LE
2491                     );
2492                 } else {
2493                     $mime[] = sprintf(
2494                         'Content-Type: %s%s',
2495                         $type,
2496                         $this->LE
2497                     );
2498                 }
2499                 // RFC1341 part 5 says 7bit is assumed if not specified
2500                 if ($encoding != '7bit') {
2501                     $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2502                 }
2504                 if ($disposition == 'inline') {
2505                     $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2506                 }
2508                 // If a filename contains any of these chars, it should be quoted,
2509                 // but not otherwise: RFC2183 & RFC2045 5.1
2510                 // Fixes a warning in IETF's msglint MIME checker
2511                 // Allow for bypassing the Content-Disposition header totally
2512                 if (!(empty($disposition))) {
2513                     $encoded_name = $this->encodeHeader($this->secureHeader($name));
2514                     if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2515                         $mime[] = sprintf(
2516                             'Content-Disposition: %s; filename="%s"%s',
2517                             $disposition,
2518                             $encoded_name,
2519                             $this->LE . $this->LE
2520                         );
2521                     } else {
2522                         if (!empty($encoded_name)) {
2523                             $mime[] = sprintf(
2524                                 'Content-Disposition: %s; filename=%s%s',
2525                                 $disposition,
2526                                 $encoded_name,
2527                                 $this->LE . $this->LE
2528                             );
2529                         } else {
2530                             $mime[] = sprintf(
2531                                 'Content-Disposition: %s%s',
2532                                 $disposition,
2533                                 $this->LE . $this->LE
2534                             );
2535                         }
2536                     }
2537                 } else {
2538                     $mime[] = $this->LE;
2539                 }
2541                 // Encode as string attachment
2542                 if ($bString) {
2543                     $mime[] = $this->encodeString($string, $encoding);
2544                     if ($this->isError()) {
2545                         return '';
2546                     }
2547                     $mime[] = $this->LE . $this->LE;
2548                 } else {
2549                     $mime[] = $this->encodeFile($path, $encoding);
2550                     if ($this->isError()) {
2551                         return '';
2552                     }
2553                     $mime[] = $this->LE . $this->LE;
2554                 }
2555             }
2556         }
2558         $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2560         return implode('', $mime);
2561     }
2563     /**
2564      * Encode a file attachment in requested format.
2565      * Returns an empty string on failure.
2566      * @param string $path The full path to the file
2567      * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2568      * @throws phpmailerException
2569      * @access protected
2570      * @return string
2571      */
2572     protected function encodeFile($path, $encoding = 'base64')
2573     {
2574         try {
2575             if (!is_readable($path)) {
2576                 throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2577             }
2578             $magic_quotes = get_magic_quotes_runtime();
2579             if ($magic_quotes) {
2580                 if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2581                     set_magic_quotes_runtime(false);
2582                 } else {
2583                     //Doesn't exist in PHP 5.4, but we don't need to check because
2584                     //get_magic_quotes_runtime always returns false in 5.4+
2585                     //so it will never get here
2586                     ini_set('magic_quotes_runtime', false);
2587                 }
2588             }
2589             $file_buffer = file_get_contents($path);
2590             $file_buffer = $this->encodeString($file_buffer, $encoding);
2591             if ($magic_quotes) {
2592                 if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2593                     set_magic_quotes_runtime($magic_quotes);
2594                 } else {
2595                     ini_set('magic_quotes_runtime', $magic_quotes);
2596                 }
2597             }
2598             return $file_buffer;
2599         } catch (Exception $exc) {
2600             $this->setError($exc->getMessage());
2601             return '';
2602         }
2603     }
2605     /**
2606      * Encode a string in requested format.
2607      * Returns an empty string on failure.
2608      * @param string $str The text to encode
2609      * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2610      * @access public
2611      * @return string
2612      */
2613     public function encodeString($str, $encoding = 'base64')
2614     {
2615         $encoded = '';
2616         switch (strtolower($encoding)) {
2617             case 'base64':
2618                 $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2619                 break;
2620             case '7bit':
2621             case '8bit':
2622                 $encoded = $this->fixEOL($str);
2623                 // Make sure it ends with a line break
2624                 if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
2625                     $encoded .= $this->LE;
2626                 }
2627                 break;
2628             case 'binary':
2629                 $encoded = $str;
2630                 break;
2631             case 'quoted-printable':
2632                 $encoded = $this->encodeQP($str);
2633                 break;
2634             default:
2635                 $this->setError($this->lang('encoding') . $encoding);
2636                 break;
2637         }
2638         return $encoded;
2639     }
2641     /**
2642      * Encode a header string optimally.
2643      * Picks shortest of Q, B, quoted-printable or none.
2644      * @access public
2645      * @param string $str
2646      * @param string $position
2647      * @return string
2648      */
2649     public function encodeHeader($str, $position = 'text')
2650     {
2651         $matchcount = 0;
2652         switch (strtolower($position)) {
2653             case 'phrase':
2654                 if (!preg_match('/[\200-\377]/', $str)) {
2655                     // Can't use addslashes as we don't know the value of magic_quotes_sybase
2656                     $encoded = addcslashes($str, "\0..\37\177\\\"");
2657                     if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2658                         return ($encoded);
2659                     } else {
2660                         return ("\"$encoded\"");
2661                     }
2662                 }
2663                 $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2664                 break;
2665             /** @noinspection PhpMissingBreakStatementInspection */
2666             case 'comment':
2667                 $matchcount = preg_match_all('/[()"]/', $str, $matches);
2668                 // Intentional fall-through
2669             case 'text':
2670             default:
2671                 $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2672                 break;
2673         }
2675         //There are no chars that need encoding
2676         if ($matchcount == 0) {
2677             return ($str);
2678         }
2680         $maxlen = 75 - 7 - strlen($this->CharSet);
2681         // Try to select the encoding which should produce the shortest output
2682         if ($matchcount > strlen($str) / 3) {
2683             // More than a third of the content will need encoding, so B encoding will be most efficient
2684             $encoding = 'B';
2685             if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2686                 // Use a custom function which correctly encodes and wraps long
2687                 // multibyte strings without breaking lines within a character
2688                 $encoded = $this->base64EncodeWrapMB($str, "\n");
2689             } else {
2690                 $encoded = base64_encode($str);
2691                 $maxlen -= $maxlen % 4;
2692                 $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2693             }
2694         } else {
2695             $encoding = 'Q';
2696             $encoded = $this->encodeQ($str, $position);
2697             $encoded = $this->wrapText($encoded, $maxlen, true);
2698             $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
2699         }
2701         $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2702         $encoded = trim(str_replace("\n", $this->LE, $encoded));
2704         return $encoded;
2705     }
2707     /**
2708      * Check if a string contains multi-byte characters.
2709      * @access public
2710      * @param string $str multi-byte text to wrap encode
2711      * @return boolean
2712      */
2713     public function hasMultiBytes($str)
2714     {
2715         if (function_exists('mb_strlen')) {
2716             return (strlen($str) > mb_strlen($str, $this->CharSet));
2717         } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
2718             return false;
2719         }
2720     }
2722     /**
2723      * Does a string contain any 8-bit chars (in any charset)?
2724      * @param string $text
2725      * @return boolean
2726      */
2727     public function has8bitChars($text)
2728     {
2729         return (boolean)preg_match('/[\x80-\xFF]/', $text);
2730     }
2732     /**
2733      * Encode and wrap long multibyte strings for mail headers
2734      * without breaking lines within a character.
2735      * Adapted from a function by paravoid
2736      * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
2737      * @access public
2738      * @param string $str multi-byte text to wrap encode
2739      * @param string $linebreak string to use as linefeed/end-of-line
2740      * @return string
2741      */
2742     public function base64EncodeWrapMB($str, $linebreak = null)
2743     {
2744         $start = '=?' . $this->CharSet . '?B?';
2745         $end = '?=';
2746         $encoded = '';
2747         if ($linebreak === null) {
2748             $linebreak = $this->LE;
2749         }
2751         $mb_length = mb_strlen($str, $this->CharSet);
2752         // Each line must have length <= 75, including $start and $end
2753         $length = 75 - strlen($start) - strlen($end);
2754         // Average multi-byte ratio
2755         $ratio = $mb_length / strlen($str);
2756         // Base64 has a 4:3 ratio
2757         $avgLength = floor($length * $ratio * .75);
2759         for ($i = 0; $i < $mb_length; $i += $offset) {
2760             $lookBack = 0;
2761             do {
2762                 $offset = $avgLength - $lookBack;
2763                 $chunk = mb_substr($str, $i, $offset, $this->CharSet);
2764                 $chunk = base64_encode($chunk);
2765                 $lookBack++;
2766             } while (strlen($chunk) > $length);
2767             $encoded .= $chunk . $linebreak;
2768         }
2770         // Chomp the last linefeed
2771         $encoded = substr($encoded, 0, -strlen($linebreak));
2772         return $encoded;
2773     }
2775     /**
2776      * Encode a string in quoted-printable format.
2777      * According to RFC2045 section 6.7.
2778      * @access public
2779      * @param string $string The text to encode
2780      * @param integer $line_max Number of chars allowed on a line before wrapping
2781      * @return string
2782      * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
2783      */
2784     public function encodeQP($string, $line_max = 76)
2785     {
2786         // Use native function if it's available (>= PHP5.3)
2787         if (function_exists('quoted_printable_encode')) {
2788             return quoted_printable_encode($string);
2789         }
2790         // Fall back to a pure PHP implementation
2791         $string = str_replace(
2792             array('%20', '%0D%0A.', '%0D%0A', '%'),
2793             array(' ', "\r\n=2E", "\r\n", '='),
2794             rawurlencode($string)
2795         );
2796         return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2797     }
2799     /**
2800      * Backward compatibility wrapper for an old QP encoding function that was removed.
2801      * @see PHPMailer::encodeQP()
2802      * @access public
2803      * @param string $string
2804      * @param integer $line_max
2805      * @param boolean $space_conv
2806      * @return string
2807      * @deprecated Use encodeQP instead.
2808      */
2809     public function encodeQPphp(
2810         $string,
2811         $line_max = 76,
2812         /** @noinspection PhpUnusedParameterInspection */ $space_conv = false
2813     ) {
2814         return $this->encodeQP($string, $line_max);
2815     }
2817     /**
2818      * Encode a string using Q encoding.
2819      * @link http://tools.ietf.org/html/rfc2047
2820      * @param string $str the text to encode
2821      * @param string $position Where the text is going to be used, see the RFC for what that means
2822      * @access public
2823      * @return string
2824      */
2825     public function encodeQ($str, $position = 'text')
2826     {
2827         // There should not be any EOL in the string
2828         $pattern = '';
2829         $encoded = str_replace(array("\r", "\n"), '', $str);
2830         switch (strtolower($position)) {
2831             case 'phrase':
2832                 // RFC 2047 section 5.3
2833                 $pattern = '^A-Za-z0-9!*+\/ -';
2834                 break;
2835             /** @noinspection PhpMissingBreakStatementInspection */
2836             case 'comment':
2837                 // RFC 2047 section 5.2
2838                 $pattern = '\(\)"';
2839                 // intentional fall-through
2840                 // for this reason we build the $pattern without including delimiters and []
2841             case 'text':
2842             default:
2843                 // RFC 2047 section 5.1
2844                 // Replace every high ascii, control, =, ? and _ characters
2845                 $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2846                 break;
2847         }
2848         $matches = array();
2849         if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2850             // If the string contains an '=', make sure it's the first thing we replace
2851             // so as to avoid double-encoding
2852             $eqkey = array_search('=', $matches[0]);
2853             if (false !== $eqkey) {
2854                 unset($matches[0][$eqkey]);
2855                 array_unshift($matches[0], '=');
2856             }
2857             foreach (array_unique($matches[0]) as $char) {
2858                 $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2859             }
2860         }
2861         // Replace every spaces to _ (more readable than =20)
2862         return str_replace(' ', '_', $encoded);
2863     }
2865     /**
2866      * Add a string or binary attachment (non-filesystem).
2867      * This method can be used to attach ascii or binary data,
2868      * such as a BLOB record from a database.
2869      * @param string $string String attachment data.
2870      * @param string $filename Name of the attachment.
2871      * @param string $encoding File encoding (see $Encoding).
2872      * @param string $type File extension (MIME) type.
2873      * @param string $disposition Disposition to use
2874      * @return void
2875      */
2876     public function addStringAttachment(
2877         $string,
2878         $filename,
2879         $encoding = 'base64',
2880         $type = '',
2881         $disposition = 'attachment'
2882     ) {
2883         // If a MIME type is not specified, try to work it out from the file name
2884         if ($type == '') {
2885             $type = self::filenameToType($filename);
2886         }
2887         // Append to $attachment array
2888         $this->attachment[] = array(
2889             0 => $string,
2890             1 => $filename,
2891             2 => basename($filename),
2892             3 => $encoding,
2893             4 => $type,
2894             5 => true, // isStringAttachment
2895             6 => $disposition,
2896             7 => 0
2897         );
2898     }
2900     /**
2901      * Add an embedded (inline) attachment from a file.
2902      * This can include images, sounds, and just about any other document type.
2903      * These differ from 'regular' attachments in that they are intended to be
2904      * displayed inline with the message, not just attached for download.
2905      * This is used in HTML messages that embed the images
2906      * the HTML refers to using the $cid value.
2907      * @param string $path Path to the attachment.
2908      * @param string $cid Content ID of the attachment; Use this to reference
2909      *        the content when using an embedded image in HTML.
2910      * @param string $name Overrides the attachment name.
2911      * @param string $encoding File encoding (see $Encoding).
2912      * @param string $type File MIME type.
2913      * @param string $disposition Disposition to use
2914      * @return boolean True on successfully adding an attachment
2915      */
2916     public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
2917     {
2918         if (!@is_file($path)) {
2919             $this->setError($this->lang('file_access') . $path);
2920             return false;
2921         }
2923         // If a MIME type is not specified, try to work it out from the file name
2924         if ($type == '') {
2925             $type = self::filenameToType($path);
2926         }
2928         $filename = basename($path);
2929         if ($name == '') {
2930             $name = $filename;
2931         }
2933         // Append to $attachment array
2934         $this->attachment[] = array(
2935             0 => $path,
2936             1 => $filename,
2937             2 => $name,
2938             3 => $encoding,
2939             4 => $type,
2940             5 => false, // isStringAttachment
2941             6 => $disposition,
2942             7 => $cid
2943         );
2944         return true;
2945     }
2947     /**
2948      * Add an embedded stringified attachment.
2949      * This can include images, sounds, and just about any other document type.
2950      * Be sure to set the $type to an image type for images:
2951      * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
2952      * @param string $string The attachment binary data.
2953      * @param string $cid Content ID of the attachment; Use this to reference
2954      *        the content when using an embedded image in HTML.
2955      * @param string $name
2956      * @param string $encoding File encoding (see $Encoding).
2957      * @param string $type MIME type.
2958      * @param string $disposition Disposition to use
2959      * @return boolean True on successfully adding an attachment
2960      */
2961     public function addStringEmbeddedImage(
2962         $string,
2963         $cid,
2964         $name = '',
2965         $encoding = 'base64',
2966         $type = '',
2967         $disposition = 'inline'
2968     ) {
2969         // If a MIME type is not specified, try to work it out from the name
2970         if ($type == '' and !empty($name)) {
2971             $type = self::filenameToType($name);
2972         }
2974         // Append to $attachment array
2975         $this->attachment[] = array(
2976             0 => $string,
2977             1 => $name,
2978             2 => $name,
2979             3 => $encoding,
2980             4 => $type,
2981             5 => true, // isStringAttachment
2982             6 => $disposition,
2983             7 => $cid
2984         );
2985         return true;
2986     }
2988     /**
2989      * Check if an inline attachment is present.
2990      * @access public
2991      * @return boolean
2992      */
2993     public function inlineImageExists()
2994     {
2995         foreach ($this->attachment as $attachment) {
2996             if ($attachment[6] == 'inline') {
2997                 return true;
2998             }
2999         }
3000         return false;
3001     }
3003     /**
3004      * Check if an attachment (non-inline) is present.
3005      * @return boolean
3006      */
3007     public function attachmentExists()
3008     {
3009         foreach ($this->attachment as $attachment) {
3010             if ($attachment[6] == 'attachment') {
3011                 return true;
3012             }
3013         }
3014         return false;
3015     }
3017     /**
3018      * Check if this message has an alternative body set.
3019      * @return boolean
3020      */
3021     public function alternativeExists()
3022     {
3023         return !empty($this->AltBody);
3024     }
3026     /**
3027      * Clear queued addresses of given kind.
3028      * @access protected
3029      * @param string $kind 'to', 'cc', or 'bcc'
3030      * @return void
3031      */
3032     public function clearQueuedAddresses($kind)
3033     {
3034         $RecipientsQueue = $this->RecipientsQueue;
3035         foreach ($RecipientsQueue as $address => $params) {
3036             if ($params[0] == $kind) {
3037                 unset($this->RecipientsQueue[$address]);
3038             }
3039         }
3040     }
3042     /**
3043      * Clear all To recipients.
3044      * @return void
3045      */
3046     public function clearAddresses()
3047     {
3048         foreach ($this->to as $to) {
3049             unset($this->all_recipients[strtolower($to[0])]);
3050         }
3051         $this->to = array();
3052         $this->clearQueuedAddresses('to');
3053     }
3055     /**
3056      * Clear all CC recipients.
3057      * @return void
3058      */
3059     public function clearCCs()
3060     {
3061         foreach ($this->cc as $cc) {
3062             unset($this->all_recipients[strtolower($cc[0])]);
3063         }
3064         $this->cc = array();
3065         $this->clearQueuedAddresses('cc');
3066     }
3068     /**
3069      * Clear all BCC recipients.
3070      * @return void
3071      */
3072     public function clearBCCs()
3073     {
3074         foreach ($this->bcc as $bcc) {
3075             unset($this->all_recipients[strtolower($bcc[0])]);
3076         }
3077         $this->bcc = array();
3078         $this->clearQueuedAddresses('bcc');
3079     }
3081     /**
3082      * Clear all ReplyTo recipients.
3083      * @return void
3084      */
3085     public function clearReplyTos()
3086     {
3087         $this->ReplyTo = array();
3088         $this->ReplyToQueue = array();
3089     }
3091     /**
3092      * Clear all recipient types.
3093      * @return void
3094      */
3095     public function clearAllRecipients()
3096     {
3097         $this->to = array();
3098         $this->cc = array();
3099         $this->bcc = array();
3100         $this->all_recipients = array();
3101         $this->RecipientsQueue = array();
3102     }
3104     /**
3105      * Clear all filesystem, string, and binary attachments.
3106      * @return void
3107      */
3108     public function clearAttachments()
3109     {
3110         $this->attachment = array();
3111     }
3113     /**
3114      * Clear all custom headers.
3115      * @return void
3116      */
3117     public function clearCustomHeaders()
3118     {
3119         $this->CustomHeader = array();
3120     }
3122     /**
3123      * Add an error message to the error container.
3124      * @access protected
3125      * @param string $msg
3126      * @return void
3127      */
3128     protected function setError($msg)
3129     {
3130         $this->error_count++;
3131         if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
3132             $lasterror = $this->smtp->getError();
3133             if (!empty($lasterror['error'])) {
3134                 $msg .= $this->lang('smtp_error') . $lasterror['error'];
3135                 if (!empty($lasterror['detail'])) {
3136                     $msg .= ' Detail: '. $lasterror['detail'];
3137                 }
3138                 if (!empty($lasterror['smtp_code'])) {
3139                     $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3140                 }
3141                 if (!empty($lasterror['smtp_code_ex'])) {
3142                     $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3143                 }
3144             }
3145         }
3146         $this->ErrorInfo = $msg;
3147     }
3149     /**
3150      * Return an RFC 822 formatted date.
3151      * @access public
3152      * @return string
3153      * @static
3154      */
3155     public static function rfcDate()
3156     {
3157         // Set the time zone to whatever the default is to avoid 500 errors
3158         // Will default to UTC if it's not set properly in php.ini
3159         date_default_timezone_set(@date_default_timezone_get());
3160         return date('D, j M Y H:i:s O');
3161     }
3163     /**
3164      * Get the server hostname.
3165      * Returns 'localhost.localdomain' if unknown.
3166      * @access protected
3167      * @return string
3168      */
3169     protected function serverHostname()
3170     {
3171         $result = 'localhost.localdomain';
3172         if (!empty($this->Hostname)) {
3173             $result = $this->Hostname;
3174         } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
3175             $result = $_SERVER['SERVER_NAME'];
3176         } elseif (function_exists('gethostname') && gethostname() !== false) {
3177             $result = gethostname();
3178         } elseif (php_uname('n') !== false) {
3179             $result = php_uname('n');
3180         }
3181         return $result;
3182     }
3184     /**
3185      * Get an error message in the current language.
3186      * @access protected
3187      * @param string $key
3188      * @return string
3189      */
3190     protected function lang($key)
3191     {
3192         if (count($this->language) < 1) {
3193             $this->setLanguage('en'); // set the default language
3194         }
3196         if (array_key_exists($key, $this->language)) {
3197             if ($key == 'smtp_connect_failed') {
3198                 //Include a link to troubleshooting docs on SMTP connection failure
3199                 //this is by far the biggest cause of support questions
3200                 //but it's usually not PHPMailer's fault.
3201                 return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3202             }
3203             return $this->language[$key];
3204         } else {
3205             //Return the key as a fallback
3206             return $key;
3207         }
3208     }
3210     /**
3211      * Check if an error occurred.
3212      * @access public
3213      * @return boolean True if an error did occur.
3214      */
3215     public function isError()
3216     {
3217         return ($this->error_count > 0);
3218     }
3220     /**
3221      * Ensure consistent line endings in a string.
3222      * Changes every end of line from CRLF, CR or LF to $this->LE.
3223      * @access public
3224      * @param string $str String to fixEOL
3225      * @return string
3226      */
3227     public function fixEOL($str)
3228     {
3229         // Normalise to \n
3230         $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
3231         // Now convert LE as needed
3232         if ($this->LE !== "\n") {
3233             $nstr = str_replace("\n", $this->LE, $nstr);
3234         }
3235         return $nstr;
3236     }
3238     /**
3239      * Add a custom header.
3240      * $name value can be overloaded to contain
3241      * both header name and value (name:value)
3242      * @access public
3243      * @param string $name Custom header name
3244      * @param string $value Header value
3245      * @return void
3246      */
3247     public function addCustomHeader($name, $value = null)
3248     {
3249         if ($value === null) {
3250             // Value passed in as name:value
3251             $this->CustomHeader[] = explode(':', $name, 2);
3252         } else {
3253             $this->CustomHeader[] = array($name, $value);
3254         }
3255     }
3257     /**
3258      * Returns all custom headers.
3259      * @return array
3260      */
3261     public function getCustomHeaders()
3262     {
3263         return $this->CustomHeader;
3264     }
3266     /**
3267      * Create a message from an HTML string.
3268      * Automatically makes modifications for inline images and backgrounds
3269      * and creates a plain-text version by converting the HTML.
3270      * Overwrites any existing values in $this->Body and $this->AltBody
3271      * @access public
3272      * @param string $message HTML message string
3273      * @param string $basedir baseline directory for path
3274      * @param boolean|callable $advanced Whether to use the internal HTML to text converter
3275      *    or your own custom converter @see PHPMailer::html2text()
3276      * @return string $message
3277      */
3278     public function msgHTML($message, $basedir = '', $advanced = false)
3279     {
3280         preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3281         if (array_key_exists(2, $images)) {
3282             foreach ($images[2] as $imgindex => $url) {
3283                 // Convert data URIs into embedded images
3284                 if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {