Commit 08f3629d9c629765efec29169e924b9ed0b886d7

Authored by Leandro Arndt
1 parent 7c20e0a2
Exists in master

Inclusão do PHP Mailer no repositório.

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