source: trunk/include/phpmailer/class.phpmailer.php @ 24953

Last change on this file since 24953 was 24953, checked in by plg, 11 years ago

feature 2965: move phpmailer files in a specific directory

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