Changeset 39727
- Timestamp:
- 01/06/2017 05:53:59 AM (8 years ago)
- Location:
- branches/4.1
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/4.1
- Property svn:mergeinfo changed
/trunk merged: 33124,33142,36083,39645
- Property svn:mergeinfo changed
-
branches/4.1/src/wp-includes/class-phpmailer.php
r27385 r39727 2 2 /** 3 3 * PHPMailer - PHP email creation and transport class. 4 * PHP Version 5.0.0 5 * Version 5.2.7 4 * PHP Version 5 6 5 * @package PHPMailer 7 * @link https://github.com/PHPMailer/PHPMailer/ 8 * @author Marcus Bointon ( coolbru) <phpmailer@synchromedia.co.uk>6 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project 7 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk> 9 8 * @author Jim Jagielski (jimjag) <jimjag@gmail.com> 10 9 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net> 11 10 * @author Brent R. Matzelle (original founder) 12 * @copyright 201 3Marcus Bointon11 * @copyright 2012 - 2014 Marcus Bointon 13 12 * @copyright 2010 - 2012 Jim Jagielski 14 13 * @copyright 2004 - 2009 Andy Prevost … … 19 18 */ 20 19 21 if (version_compare(PHP_VERSION, '5.0.0', '<')) {22 exit("Sorry, PHPMailer will only run on PHP version 5 or greater!\n");23 }24 25 20 /** 26 21 * PHPMailer - PHP email creation and transport class. 27 * PHP Version 5.0.028 22 * @package PHPMailer 29 * @author Marcus Bointon ( coolbru) <phpmailer@synchromedia.co.uk>23 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk> 30 24 * @author Jim Jagielski (jimjag) <jimjag@gmail.com> 31 25 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net> 32 26 * @author Brent R. Matzelle (original founder) 33 * @copyright 2013 Marcus Bointon34 * @copyright 2010 - 2012 Jim Jagielski35 * @copyright 2004 - 2009 Andy Prevost36 27 */ 37 28 class PHPMailer … … 39 30 /** 40 31 * The PHPMailer Version number. 41 * @ typestring42 */ 43 public $Version = '5.2. 7';32 * @var string 33 */ 34 public $Version = '5.2.21'; 44 35 45 36 /** 46 37 * Email priority. 47 * Options: 1 = High, 3 = Normal, 5 = low. 48 * @type int 49 */ 50 public $Priority = 3; 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; 51 43 52 44 /** 53 45 * The character set of the message. 54 * @ typestring46 * @var string 55 47 */ 56 48 public $CharSet = 'iso-8859-1'; … … 58 50 /** 59 51 * The MIME Content-type of the message. 60 * @ typestring52 * @var string 61 53 */ 62 54 public $ContentType = 'text/plain'; … … 65 57 * The message encoding. 66 58 * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". 67 * @ typestring59 * @var string 68 60 */ 69 61 public $Encoding = '8bit'; … … 71 63 /** 72 64 * Holds the most recent mailer error message. 73 * @ typestring65 * @var string 74 66 */ 75 67 public $ErrorInfo = ''; … … 77 69 /** 78 70 * The From email address for the message. 79 * @ typestring71 * @var string 80 72 */ 81 73 public $From = 'root@localhost'; … … 83 75 /** 84 76 * The From name of the message. 85 * @ typestring77 * @var string 86 78 */ 87 79 public $FromName = 'Root User'; … … 90 82 * The Sender email (Return-Path) of the message. 91 83 * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. 92 * @ typestring84 * @var string 93 85 */ 94 86 public $Sender = ''; … … 97 89 * The Return-Path of the message. 98 90 * If empty, it will be set to either From or Sender. 99 * @type string 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 100 95 */ 101 96 public $ReturnPath = ''; … … 103 98 /** 104 99 * The Subject of the message. 105 * @ typestring100 * @var string 106 101 */ 107 102 public $Subject = ''; … … 110 105 * An HTML or plain text message body. 111 106 * If HTML then call isHTML(true). 112 * @ typestring107 * @var string 113 108 */ 114 109 public $Body = ''; … … 119 114 * capability such as mutt & Eudora. 120 115 * Clients that can read HTML will view the normal Body. 121 * @ typestring116 * @var string 122 117 */ 123 118 public $AltBody = ''; … … 129 124 * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ 130 125 * @link http://kigkonsult.se/iCalcreator/ 131 * @ typestring126 * @var string 132 127 */ 133 128 public $Ical = ''; … … 136 131 * The complete compiled MIME message body. 137 132 * @access protected 138 * @ typestring133 * @var string 139 134 */ 140 135 protected $MIMEBody = ''; … … 142 137 /** 143 138 * The complete compiled MIME message headers. 144 * @ typestring139 * @var string 145 140 * @access protected 146 141 */ … … 149 144 /** 150 145 * Extra headers that createHeader() doesn't fold in. 151 * @ typestring146 * @var string 152 147 * @access protected 153 148 */ … … 156 151 /** 157 152 * Word-wrap the message body to this number of chars. 158 * @type int 153 * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. 154 * @var integer 159 155 */ 160 156 public $WordWrap = 0; … … 163 159 * Which method to use to send mail. 164 160 * Options: "mail", "sendmail", or "smtp". 165 * @ typestring161 * @var string 166 162 */ 167 163 public $Mailer = 'mail'; … … 169 165 /** 170 166 * The path to the sendmail program. 171 * @ typestring167 * @var string 172 168 */ 173 169 public $Sendmail = '/usr/sbin/sendmail'; … … 176 172 * Whether mail() uses a fully sendmail-compatible MTA. 177 173 * One which supports sendmail's "-oi -f" options. 178 * @ type bool174 * @var boolean 179 175 */ 180 176 public $UseSendmailOptions = true; … … 183 179 * Path to PHPMailer plugins. 184 180 * Useful if the SMTP class is not in the PHP include path. 185 * @ typestring181 * @var string 186 182 * @deprecated Should not be needed now there is an autoloader. 187 183 */ … … 189 185 190 186 /** 191 * The email address that a reading confirmation should be sent to .192 * @ typestring187 * The email address that a reading confirmation should be sent to, also known as read receipt. 188 * @var string 193 189 */ 194 190 public $ConfirmReadingTo = ''; 195 191 196 192 /** 197 * The hostname to use in Message-Id and Received headers198 * and as default HELO string.199 * If empty, the value returned200 * by SERVER_NAME is used or'localhost.localdomain'.201 * @ typestring193 * 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 202 198 */ 203 199 public $Hostname = ''; 204 200 205 201 /** 206 * An ID to be used in the Message-I dheader.202 * An ID to be used in the Message-ID header. 207 203 * If empty, a unique id will be generated. 208 * @type string 204 * You can set your own, but it must be in the format "<id@domain>", 205 * as defined in RFC5322 section 3.6.4 or it will be ignored. 206 * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 207 * @var string 209 208 */ 210 209 public $MessageID = ''; … … 213 212 * The message Date to be used in the Date header. 214 213 * If empty, the current date will be added. 215 * @ typestring214 * @var string 216 215 */ 217 216 public $MessageDate = ''; … … 223 222 * for each host by using this format: [hostname:port] 224 223 * (e.g. "smtp1.example.com:25;smtp2.example.com"). 224 * You can also specify encryption type, for example: 225 * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). 225 226 * Hosts will be tried in order. 226 * @ typestring227 * @var string 227 228 */ 228 229 public $Host = 'localhost'; … … 230 231 /** 231 232 * The default SMTP server port. 232 * @ type int233 * @T odoWhy is this needed when the SMTP class takes care of it?233 * @var integer 234 * @TODO Why is this needed when the SMTP class takes care of it? 234 235 */ 235 236 public $Port = 25; … … 237 238 /** 238 239 * The SMTP HELO of the message. 239 * Default is $Hostname. 240 * @type string 240 * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find 241 * one with the same method described above for $Hostname. 242 * @var string 241 243 * @see PHPMailer::$Hostname 242 244 */ … … 244 246 245 247 /** 246 * The secure connection prefix.247 * Options: "", "ssl" or "tls"248 * @ typestring248 * What kind of encryption to use on the SMTP connection. 249 * Options: '', 'ssl' or 'tls' 250 * @var string 249 251 */ 250 252 public $SMTPSecure = ''; 253 254 /** 255 * Whether to enable TLS encryption automatically if a server supports it, 256 * even if `SMTPSecure` is not set to 'tls'. 257 * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. 258 * @var boolean 259 */ 260 public $SMTPAutoTLS = true; 251 261 252 262 /** 253 263 * Whether to use SMTP authentication. 254 264 * Uses the Username and Password properties. 255 * @ type bool265 * @var boolean 256 266 * @see PHPMailer::$Username 257 267 * @see PHPMailer::$Password … … 260 270 261 271 /** 272 * Options array passed to stream_context_create when connecting via SMTP. 273 * @var array 274 */ 275 public $SMTPOptions = array(); 276 277 /** 262 278 * SMTP username. 263 * @ typestring279 * @var string 264 280 */ 265 281 public $Username = ''; … … 267 283 /** 268 284 * SMTP password. 269 * @ typestring285 * @var string 270 286 */ 271 287 public $Password = ''; … … 273 289 /** 274 290 * SMTP auth type. 275 * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5276 * @ typestring291 * Options are CRAM-MD5, LOGIN, PLAIN, attempted in that order if not specified 292 * @var string 277 293 */ 278 294 public $AuthType = ''; … … 281 297 * SMTP realm. 282 298 * Used for NTLM auth 283 * @ typestring299 * @var string 284 300 */ 285 301 public $Realm = ''; … … 288 304 * SMTP workstation. 289 305 * Used for NTLM auth 290 * @ typestring306 * @var string 291 307 */ 292 308 public $Workstation = ''; … … 294 310 /** 295 311 * The SMTP server timeout in seconds. 296 * @type int 297 */ 298 public $Timeout = 10; 312 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 313 * @var integer 314 */ 315 public $Timeout = 300; 299 316 300 317 /** 301 318 * SMTP class debug output mode. 302 * Options: 0 = off, 1 = commands, 2 = commands and data 303 * @type int 319 * Debug output level. 320 * Options: 321 * * `0` No output 322 * * `1` Commands 323 * * `2` Data and commands 324 * * `3` As 2 plus connection status 325 * * `4` Low-level data output 326 * @var integer 304 327 * @see SMTP::$do_debug 305 328 */ … … 307 330 308 331 /** 309 * The function/method to use for debugging output. 310 * Options: "echo" or "error_log" 311 * @type string 332 * How to handle debug output. 333 * Options: 334 * * `echo` Output plain-text as-is, appropriate for CLI 335 * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output 336 * * `error_log` Output to error log as configured in php.ini 337 * 338 * Alternatively, you can provide a callable expecting two params: a message string and the debug level: 339 * <code> 340 * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; 341 * </code> 342 * @var string|callable 312 343 * @see SMTP::$Debugoutput 313 344 */ 314 public $Debugoutput = "echo";345 public $Debugoutput = 'echo'; 315 346 316 347 /** … … 318 349 * If this is set to true then to close the connection 319 350 * requires an explicit call to smtpClose(). 320 * @ type bool351 * @var boolean 321 352 */ 322 353 public $SMTPKeepAlive = false; … … 325 356 * Whether to split multiple to addresses into multiple messages 326 357 * or send them all in one message. 327 * @type bool 358 * Only supported in `mail` and `sendmail` transports, not in SMTP. 359 * @var boolean 328 360 */ 329 361 public $SingleTo = false; … … 331 363 /** 332 364 * Storage for addresses when SingleTo is enabled. 333 * @ typearray334 * @ todoThis should really not be public365 * @var array 366 * @TODO This should really not be public 335 367 */ 336 368 public $SingleToArray = array(); … … 339 371 * Whether to generate VERP addresses on send. 340 372 * Only applicable when sending via SMTP. 341 * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path 342 * @type bool 373 * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path 374 * @link http://www.postfix.org/VERP_README.html Postfix VERP info 375 * @var boolean 343 376 */ 344 377 public $do_verp = false; … … 346 379 /** 347 380 * Whether to allow sending messages with an empty body. 348 * @ type bool381 * @var boolean 349 382 */ 350 383 public $AllowEmpty = false; … … 354 387 * @note The default remains "\n". We force CRLF where we know 355 388 * it must be used via self::CRLF. 356 * @ typestring389 * @var string 357 390 */ 358 391 public $LE = "\n"; … … 360 393 /** 361 394 * DKIM selector. 362 * @ typestring395 * @var string 363 396 */ 364 397 public $DKIM_selector = ''; … … 366 399 /** 367 400 * DKIM Identity. 368 * Usually the email address used as the source of the email 369 * @ typestring401 * Usually the email address used as the source of the email. 402 * @var string 370 403 */ 371 404 public $DKIM_identity = ''; … … 374 407 * DKIM passphrase. 375 408 * Used if your key is encrypted. 376 * @ typestring409 * @var string 377 410 */ 378 411 public $DKIM_passphrase = ''; … … 381 414 * DKIM signing domain name. 382 415 * @example 'example.com' 383 * @ typestring416 * @var string 384 417 */ 385 418 public $DKIM_domain = ''; … … 387 420 /** 388 421 * DKIM private key file path. 389 * @ typestring422 * @var string 390 423 */ 391 424 public $DKIM_private = ''; 425 426 /** 427 * DKIM private key string. 428 * If set, takes precedence over `$DKIM_private`. 429 * @var string 430 */ 431 public $DKIM_private_string = ''; 392 432 393 433 /** … … 397 437 * It is called out by send() for each email sent. 398 438 * 399 * Value can be: 400 * - 'function_name' for function names 401 * - 'Class::Method' for static method calls 402 * - array($object, 'Method') for calling methods on $object 403 * See http://php.net/is_callable manual page for more details. 439 * Value can be any php callable: http://www.php.net/is_callable 404 440 * 405 441 * Parameters: 406 * bool 442 * boolean $result result of the send action 407 443 * string $to email address of the recipient 408 444 * string $cc cc email addresses … … 411 447 * string $body the email body 412 448 * string $from email address of sender 413 * 414 * @type string 449 * @var string 415 450 */ 416 451 public $action_function = ''; 417 452 418 453 /** 419 * What to usein the X-Mailer header.420 * Options: null for default, whitespace for none, or a string to use421 * @ typestring454 * What to put in the X-Mailer header. 455 * Options: An empty string for PHPMailer default, whitespace for none, or a string to use 456 * @var string 422 457 */ 423 458 public $XMailer = ''; 424 459 425 460 /** 461 * Which validator to use by default when validating email addresses. 462 * May be a callable to inject your own validator, but there are several built-in validators. 463 * @see PHPMailer::validateAddress() 464 * @var string|callable 465 * @static 466 */ 467 public static $validator = 'auto'; 468 469 /** 426 470 * An instance of the SMTP sender class. 427 * @ typeSMTP471 * @var SMTP 428 472 * @access protected 429 473 */ … … 431 475 432 476 /** 433 * The array of 'to' addresses.434 * @ typearray477 * The array of 'to' names and addresses. 478 * @var array 435 479 * @access protected 436 480 */ … … 438 482 439 483 /** 440 * The array of 'cc' addresses.441 * @ typearray484 * The array of 'cc' names and addresses. 485 * @var array 442 486 * @access protected 443 487 */ … … 445 489 446 490 /** 447 * The array of 'bcc' addresses.448 * @ typearray491 * The array of 'bcc' names and addresses. 492 * @var array 449 493 * @access protected 450 494 */ … … 453 497 /** 454 498 * The array of reply-to names and addresses. 455 * @ typearray499 * @var array 456 500 * @access protected 457 501 */ … … 460 504 /** 461 505 * An array of all kinds of addresses. 462 * Includes all of $to, $cc, $bcc , $replyto463 * @ typearray506 * Includes all of $to, $cc, $bcc 507 * @var array 464 508 * @access protected 509 * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc 465 510 */ 466 511 protected $all_recipients = array(); 467 512 468 513 /** 514 * An array of names and addresses queued for validation. 515 * In send(), valid and non duplicate entries are moved to $all_recipients 516 * and one of $to, $cc, or $bcc. 517 * This array is used only for addresses with IDN. 518 * @var array 519 * @access protected 520 * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc 521 * @see PHPMailer::$all_recipients 522 */ 523 protected $RecipientsQueue = array(); 524 525 /** 526 * An array of reply-to names and addresses queued for validation. 527 * In send(), valid and non duplicate entries are moved to $ReplyTo. 528 * This array is used only for addresses with IDN. 529 * @var array 530 * @access protected 531 * @see PHPMailer::$ReplyTo 532 */ 533 protected $ReplyToQueue = array(); 534 535 /** 469 536 * The array of attachments. 470 * @ typearray537 * @var array 471 538 * @access protected 472 539 */ … … 475 542 /** 476 543 * The array of custom headers. 477 * @ typearray544 * @var array 478 545 * @access protected 479 546 */ … … 482 549 /** 483 550 * The most recent Message-ID (including angular brackets). 484 * @ typestring551 * @var string 485 552 * @access protected 486 553 */ … … 489 556 /** 490 557 * The message's MIME type. 491 * @ typestring558 * @var string 492 559 * @access protected 493 560 */ … … 496 563 /** 497 564 * The array of MIME boundary strings. 498 * @ typearray565 * @var array 499 566 * @access protected 500 567 */ … … 503 570 /** 504 571 * The array of available languages. 505 * @ typearray572 * @var array 506 573 * @access protected 507 574 */ … … 510 577 /** 511 578 * The number of errors encountered. 512 * @ typeinteger579 * @var integer 513 580 * @access protected 514 581 */ … … 517 584 /** 518 585 * The S/MIME certificate file path. 519 * @ typestring586 * @var string 520 587 * @access protected 521 588 */ … … 524 591 /** 525 592 * The S/MIME key file path. 526 * @ typestring593 * @var string 527 594 * @access protected 528 595 */ 529 596 protected $sign_key_file = ''; 597 598 /** 599 * The optional S/MIME extra certificates ("CA Chain") file path. 600 * @var string 601 * @access protected 602 */ 603 protected $sign_extracerts_file = ''; 530 604 531 605 /** 532 606 * The S/MIME password for the key. 533 607 * Used only if the key is encrypted. 534 * @ typestring608 * @var string 535 609 * @access protected 536 610 */ … … 539 613 /** 540 614 * Whether to throw exceptions for errors. 541 * @ type bool615 * @var boolean 542 616 * @access protected 543 617 */ … … 545 619 546 620 /** 547 * Error severity: message only, continue processing 621 * Unique ID used for message ID and boundaries. 622 * @var string 623 * @access protected 624 */ 625 protected $uniqueid = ''; 626 627 /** 628 * Error severity: message only, continue processing. 548 629 */ 549 630 const STOP_MESSAGE = 0; 550 631 551 632 /** 552 * Error severity: message, likely ok to continue processing 633 * Error severity: message, likely ok to continue processing. 553 634 */ 554 635 const STOP_CONTINUE = 1; 555 636 556 637 /** 557 * Error severity: message, plus full stop, critical error reached 638 * Error severity: message, plus full stop, critical error reached. 558 639 */ 559 640 const STOP_CRITICAL = 2; 560 641 561 642 /** 562 * SMTP RFC standard line ending 643 * SMTP RFC standard line ending. 563 644 */ 564 645 const CRLF = "\r\n"; 565 646 566 647 /** 567 * Constructor 568 * @param bool $exceptions Should we throw external exceptions? 569 */ 570 public function __construct($exceptions = false) 571 { 572 $this->exceptions = ($exceptions == true); 648 * The maximum line length allowed by RFC 2822 section 2.1.1 649 * @var integer 650 */ 651 const MAX_LINE_LENGTH = 998; 652 653 /** 654 * Constructor. 655 * @param boolean $exceptions Should we throw external exceptions? 656 */ 657 public function __construct($exceptions = null) 658 { 659 if ($exceptions !== null) { 660 $this->exceptions = (boolean)$exceptions; 661 } 573 662 } 574 663 … … 578 667 public function __destruct() 579 668 { 580 if ($this->Mailer == 'smtp') { //close any open SMTP connection nicely 581 $this->smtpClose(); 582 } 669 //Close any open SMTP connection nicely 670 $this->smtpClose(); 583 671 } 584 672 … … 594 682 * @param string $params Params 595 683 * @access private 596 * @return bool 684 * @return boolean 597 685 */ 598 686 private function mailPassthru($to, $subject, $body, $header, $params) 599 687 { 600 if (ini_get('safe_mode') || !($this->UseSendmailOptions)) { 601 $rt = @mail($to, $this->encodeHeader($this->secureHeader($subject)), $body, $header); 688 //Check overloading of mail function to avoid double-encoding 689 if (ini_get('mbstring.func_overload') & 1) { 690 $subject = $this->secureHeader($subject); 602 691 } else { 603 $rt = @mail($to, $this->encodeHeader($this->secureHeader($subject)), $body, $header, $params); 604 } 605 return $rt; 606 } 607 692 $subject = $this->encodeHeader($this->secureHeader($subject)); 693 } 694 695 //Can't use additional_parameters in safe_mode, calling mail() with null params breaks 696 //@link http://php.net/manual/en/function.mail.php 697 if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) { 698 $result = @mail($to, $subject, $body, $header); 699 } else { 700 $result = @mail($to, $subject, $body, $header, $params); 701 } 702 return $result; 703 } 608 704 /** 609 705 * Output debugging info via user-defined method. 610 * Only if debug output is enabled.706 * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). 611 707 * @see PHPMailer::$Debugoutput 612 708 * @see PHPMailer::$SMTPDebug … … 615 711 protected function edebug($str) 616 712 { 617 if (!$this->SMTPDebug) { 713 if ($this->SMTPDebug <= 0) { 714 return; 715 } 716 //Avoid clash with built-in function names 717 if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { 718 call_user_func($this->Debugoutput, $str, $this->SMTPDebug); 618 719 return; 619 720 } 620 721 switch ($this->Debugoutput) { 621 722 case 'error_log': 723 //Don't output, just log 622 724 error_log($str); 623 725 break; 624 726 case 'html': 625 //Cleans up output a bit for a better looking display that's HTML-safe 626 echo htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, $this->CharSet) . "<br>\n"; 727 //Cleans up output a bit for a better looking, HTML-safe output 728 echo htmlentities( 729 preg_replace('/[\r\n]+/', '', $str), 730 ENT_QUOTES, 731 'UTF-8' 732 ) 733 . "<br>\n"; 627 734 break; 628 735 case 'echo': 629 736 default: 630 //Just echoes exactly what was received 631 echo $str; 737 //Normalize line breaks 738 $str = preg_replace('/\r\n?/ms', "\n", $str); 739 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 740 "\n", 741 "\n \t ", 742 trim($str) 743 ) . "\n"; 632 744 } 633 745 } … … 635 747 /** 636 748 * Sets message type to HTML or plain. 637 * @param bool $ishtml True for HTML mode.749 * @param boolean $isHtml True for HTML mode. 638 750 * @return void 639 751 */ 640 public function isHTML($is html = true)641 { 642 if ($is html) {752 public function isHTML($isHtml = true) 753 { 754 if ($isHtml) { 643 755 $this->ContentType = 'text/html'; 644 756 } else { … … 671 783 public function isSendmail() 672 784 { 673 if (!stristr(ini_get('sendmail_path'), 'sendmail')) { 674 $this->Sendmail = '/var/qmail/bin/sendmail'; 785 $ini_sendmail_path = ini_get('sendmail_path'); 786 787 if (!stristr($ini_sendmail_path, 'sendmail')) { 788 $this->Sendmail = '/usr/sbin/sendmail'; 789 } else { 790 $this->Sendmail = $ini_sendmail_path; 675 791 } 676 792 $this->Mailer = 'sendmail'; … … 683 799 public function isQmail() 684 800 { 685 if (stristr(ini_get('sendmail_path'), 'qmail')) { 686 $this->Sendmail = '/var/qmail/bin/sendmail'; 687 } 688 $this->Mailer = 'sendmail'; 801 $ini_sendmail_path = ini_get('sendmail_path'); 802 803 if (!stristr($ini_sendmail_path, 'qmail')) { 804 $this->Sendmail = '/var/qmail/bin/qmail-inject'; 805 } else { 806 $this->Sendmail = $ini_sendmail_path; 807 } 808 $this->Mailer = 'qmail'; 689 809 } 690 810 691 811 /** 692 812 * Add a "To" address. 693 * @param string $address 813 * @param string $address The email address to send to 694 814 * @param string $name 695 * @return bool true on success, false if address already used815 * @return boolean true on success, false if address already used or invalid in some way 696 816 */ 697 817 public function addAddress($address, $name = '') 698 818 { 699 return $this->add AnAddress('to', $address, $name);819 return $this->addOrEnqueueAnAddress('to', $address, $name); 700 820 } 701 821 … … 703 823 * Add a "CC" address. 704 824 * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. 705 * @param string $address 825 * @param string $address The email address to send to 706 826 * @param string $name 707 * @return bool true on success, false if address already used827 * @return boolean true on success, false if address already used or invalid in some way 708 828 */ 709 829 public function addCC($address, $name = '') 710 830 { 711 return $this->add AnAddress('cc', $address, $name);831 return $this->addOrEnqueueAnAddress('cc', $address, $name); 712 832 } 713 833 … … 715 835 * Add a "BCC" address. 716 836 * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. 717 * @param string $address718 * @param string $name719 * @return bool true on success, false if address already used720 */721 public function addBCC($address, $name = '')722 {723 return $this->addAnAddress('bcc', $address, $name);724 }725 726 /**727 * Add a "Reply-to" address.728 * @param string $address729 * @param string $name730 * @return bool731 */732 public function addReplyTo($address, $name = '')733 {734 return $this->addAnAddress('Reply-To', $address, $name);735 }736 737 /**738 * Add an address to one of the recipient arrays.739 * Addresses that have been added already return false, but do not throw exceptions740 * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo'741 837 * @param string $address The email address to send to 742 838 * @param string $name 839 * @return boolean true on success, false if address already used or invalid in some way 840 */ 841 public function addBCC($address, $name = '') 842 { 843 return $this->addOrEnqueueAnAddress('bcc', $address, $name); 844 } 845 846 /** 847 * Add a "Reply-To" address. 848 * @param string $address The email address to reply to 849 * @param string $name 850 * @return boolean true on success, false if address already used or invalid in some way 851 */ 852 public function addReplyTo($address, $name = '') 853 { 854 return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); 855 } 856 857 /** 858 * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer 859 * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still 860 * be modified after calling this function), addition of such addresses is delayed until send(). 861 * Addresses that have been added already return false, but do not throw exceptions. 862 * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' 863 * @param string $address The email address to send, resp. to reply to 864 * @param string $name 743 865 * @throws phpmailerException 744 * @return bool true on success, false if address already used or invalid in some way866 * @return boolean true on success, false if address already used or invalid in some way 745 867 * @access protected 746 868 */ 747 protected function addAnAddress($kind, $address, $name = '') 748 { 749 if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) { 750 $this->setError($this->lang('Invalid recipient array') . ': ' . $kind); 751 if ($this->exceptions) { 752 throw new phpmailerException('Invalid recipient array: ' . $kind); 753 } 754 $this->edebug($this->lang('Invalid recipient array') . ': ' . $kind); 755 return false; 756 } 869 protected function addOrEnqueueAnAddress($kind, $address, $name) 870 { 757 871 $address = trim($address); 758 872 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim 873 if (($pos = strrpos($address, '@')) === false) { 874 // At-sign is misssing. 875 $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; 876 $this->setError($error_message); 877 $this->edebug($error_message); 878 if ($this->exceptions) { 879 throw new phpmailerException($error_message); 880 } 881 return false; 882 } 883 $params = array($kind, $address, $name); 884 // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. 885 if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) { 886 if ($kind != 'Reply-To') { 887 if (!array_key_exists($address, $this->RecipientsQueue)) { 888 $this->RecipientsQueue[$address] = $params; 889 return true; 890 } 891 } else { 892 if (!array_key_exists($address, $this->ReplyToQueue)) { 893 $this->ReplyToQueue[$address] = $params; 894 return true; 895 } 896 } 897 return false; 898 } 899 // Immediately add standard addresses without IDN. 900 return call_user_func_array(array($this, 'addAnAddress'), $params); 901 } 902 903 /** 904 * Add an address to one of the recipient arrays or to the ReplyTo array. 905 * Addresses that have been added already return false, but do not throw exceptions. 906 * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' 907 * @param string $address The email address to send, resp. to reply to 908 * @param string $name 909 * @throws phpmailerException 910 * @return boolean true on success, false if address already used or invalid in some way 911 * @access protected 912 */ 913 protected function addAnAddress($kind, $address, $name = '') 914 { 915 if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) { 916 $error_message = $this->lang('Invalid recipient kind: ') . $kind; 917 $this->setError($error_message); 918 $this->edebug($error_message); 919 if ($this->exceptions) { 920 throw new phpmailerException($error_message); 921 } 922 return false; 923 } 759 924 if (!$this->validateAddress($address)) { 760 $this->setError($this->lang('invalid_address') . ': ' . $address); 925 $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; 926 $this->setError($error_message); 927 $this->edebug($error_message); 761 928 if ($this->exceptions) { 762 throw new phpmailerException($this->lang('invalid_address') . ': ' . $address); 763 } 764 $this->edebug($this->lang('invalid_address') . ': ' . $address); 929 throw new phpmailerException($error_message); 930 } 765 931 return false; 766 932 } 767 933 if ($kind != 'Reply-To') { 768 if (! isset($this->all_recipients[strtolower($address)])) {934 if (!array_key_exists(strtolower($address), $this->all_recipients)) { 769 935 array_push($this->$kind, array($address, $name)); 770 936 $this->all_recipients[strtolower($address)] = true; … … 781 947 782 948 /** 949 * Parse and validate a string containing one or more RFC822-style comma-separated email addresses 950 * of the form "display name <address>" into an array of name/address pairs. 951 * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. 952 * Note that quotes in the name part are removed. 953 * @param string $addrstr The address list string 954 * @param bool $useimap Whether to use the IMAP extension to parse the list 955 * @return array 956 * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation 957 */ 958 public function parseAddresses($addrstr, $useimap = true) 959 { 960 $addresses = array(); 961 if ($useimap and function_exists('imap_rfc822_parse_adrlist')) { 962 //Use this built-in parser if it's available 963 $list = imap_rfc822_parse_adrlist($addrstr, ''); 964 foreach ($list as $address) { 965 if ($address->host != '.SYNTAX-ERROR.') { 966 if ($this->validateAddress($address->mailbox . '@' . $address->host)) { 967 $addresses[] = array( 968 'name' => (property_exists($address, 'personal') ? $address->personal : ''), 969 'address' => $address->mailbox . '@' . $address->host 970 ); 971 } 972 } 973 } 974 } else { 975 //Use this simpler parser 976 $list = explode(',', $addrstr); 977 foreach ($list as $address) { 978 $address = trim($address); 979 //Is there a separate name part? 980 if (strpos($address, '<') === false) { 981 //No separate name, just use the whole thing 982 if ($this->validateAddress($address)) { 983 $addresses[] = array( 984 'name' => '', 985 'address' => $address 986 ); 987 } 988 } else { 989 list($name, $email) = explode('<', $address); 990 $email = trim(str_replace('>', '', $email)); 991 if ($this->validateAddress($email)) { 992 $addresses[] = array( 993 'name' => trim(str_replace(array('"', "'"), '', $name)), 994 'address' => $email 995 ); 996 } 997 } 998 } 999 } 1000 return $addresses; 1001 } 1002 1003 /** 783 1004 * Set the From and FromName properties. 784 1005 * @param string $address 785 1006 * @param string $name 786 * @param bool $auto Whether to also set the Sender address, defaults to true1007 * @param boolean $auto Whether to also set the Sender address, defaults to true 787 1008 * @throws phpmailerException 788 * @return bool 1009 * @return boolean 789 1010 */ 790 1011 public function setFrom($address, $name = '', $auto = true) … … 792 1013 $address = trim($address); 793 1014 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim 794 if (!$this->validateAddress($address)) { 795 $this->setError($this->lang('invalid_address') . ': ' . $address); 1015 // Don't validate now addresses with IDN. Will be done in send(). 1016 if (($pos = strrpos($address, '@')) === false or 1017 (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and 1018 !$this->validateAddress($address)) { 1019 $error_message = $this->lang('invalid_address') . " (setFrom) $address"; 1020 $this->setError($error_message); 1021 $this->edebug($error_message); 796 1022 if ($this->exceptions) { 797 throw new phpmailerException($this->lang('invalid_address') . ': ' . $address); 798 } 799 $this->edebug($this->lang('invalid_address') . ': ' . $address); 1023 throw new phpmailerException($error_message); 1024 } 800 1025 return false; 801 1026 } … … 825 1050 * Check that a string looks like an email address. 826 1051 * @param string $address The email address to check 827 * @param string $patternselect A selector for the validation pattern to use : 828 * 'auto' - pick best one automatically; 829 * 'pcre8' - use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; 830 * 'pcre' - use old PCRE implementation; 831 * 'php' - use PHP built-in FILTER_VALIDATE_EMAIL; faster, less thorough; 832 * 'noregex' - super fast, really dumb. 833 * @return bool 1052 * @param string|callable $patternselect A selector for the validation pattern to use : 1053 * * `auto` Pick best pattern automatically; 1054 * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; 1055 * * `pcre` Use old PCRE implementation; 1056 * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; 1057 * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. 1058 * * `noregex` Don't use a regex: super fast, really dumb. 1059 * Alternatively you may pass in a callable to inject your own validator, for example: 1060 * PHPMailer::validateAddress('user@example.com', function($address) { 1061 * return (strpos($address, '@') !== false); 1062 * }); 1063 * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. 1064 * @return boolean 834 1065 * @static 835 1066 * @access public 836 1067 */ 837 public static function validateAddress($address, $patternselect = 'auto') 838 { 839 if ($patternselect == 'auto') { 840 if (defined( 841 'PCRE_VERSION' 842 ) 843 ) { //Check this instead of extension_loaded so it works when that function is disabled 844 if (version_compare(PCRE_VERSION, '8.0') >= 0) { 1068 public static function validateAddress($address, $patternselect = null) 1069 { 1070 if (is_null($patternselect)) { 1071 $patternselect = self::$validator; 1072 } 1073 if (is_callable($patternselect)) { 1074 return call_user_func($patternselect, $address); 1075 } 1076 //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 1077 if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) { 1078 return false; 1079 } 1080 if (!$patternselect or $patternselect == 'auto') { 1081 //Check this constant first so it works when extension_loaded() is disabled by safe mode 1082 //Constant was added in PHP 5.2.4 1083 if (defined('PCRE_VERSION')) { 1084 //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2 1085 if (version_compare(PCRE_VERSION, '8.0.3') >= 0) { 845 1086 $patternselect = 'pcre8'; 846 1087 } else { 847 1088 $patternselect = 'pcre'; 848 1089 } 1090 } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) { 1091 //Fall back to older PCRE 1092 $patternselect = 'pcre'; 849 1093 } else { 850 1094 //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension … … 859 1103 case 'pcre8': 860 1104 /** 861 * Conforms to RFC5322: Uses *correct* regex on which FILTER_VALIDATE_EMAIL is 862 * based; So why not use FILTER_VALIDATE_EMAIL? Because it was broken to 863 * not allow a@b type valid addresses :( 1105 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains. 864 1106 * @link http://squiloople.com/2009/12/20/email-address-validation/ 865 1107 * @copyright 2009-2010 Michael Rushton 866 1108 * Feel free to use and redistribute this code. But please keep this copyright notice. 867 1109 */ 868 return (bool )preg_match(1110 return (boolean)preg_match( 869 1111 '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . 870 1112 '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . … … 878 1120 $address 879 1121 ); 880 break;881 1122 case 'pcre': 882 1123 //An older regex that doesn't need a recent PCRE 883 return (bool )preg_match(1124 return (boolean)preg_match( 884 1125 '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' . 885 1126 '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' . … … 894 1135 $address 895 1136 ); 896 break; 897 case 'php': 898 default: 899 return (bool)filter_var($address, FILTER_VALIDATE_EMAIL); 900 break; 1137 case 'html5': 1138 /** 1139 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. 1140 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) 1141 */ 1142 return (boolean)preg_match( 1143 '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . 1144 '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', 1145 $address 1146 ); 901 1147 case 'noregex': 902 1148 //No PCRE! Do something _very_ approximate! … … 905 1151 and strpos($address, '@') >= 1 906 1152 and strpos($address, '@') != strlen($address) - 1); 907 break; 908 } 1153 case 'php': 1154 default: 1155 return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL); 1156 } 1157 } 1158 1159 /** 1160 * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the 1161 * "intl" and "mbstring" PHP extensions. 1162 * @return bool "true" if required functions for IDN support are present 1163 */ 1164 public function idnSupported() 1165 { 1166 // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2. 1167 return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding'); 1168 } 1169 1170 /** 1171 * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. 1172 * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. 1173 * This function silently returns unmodified address if: 1174 * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) 1175 * - Conversion to punycode is impossible (e.g. required PHP functions are not available) 1176 * or fails for any reason (e.g. domain has characters not allowed in an IDN) 1177 * @see PHPMailer::$CharSet 1178 * @param string $address The email address to convert 1179 * @return string The encoded address in ASCII form 1180 */ 1181 public function punyencodeAddress($address) 1182 { 1183 // Verify we have required functions, CharSet, and at-sign. 1184 if ($this->idnSupported() and 1185 !empty($this->CharSet) and 1186 ($pos = strrpos($address, '@')) !== false) { 1187 $domain = substr($address, ++$pos); 1188 // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. 1189 if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) { 1190 $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); 1191 if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ? 1192 idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) : 1193 idn_to_ascii($domain)) !== false) { 1194 return substr($address, 0, $pos) . $punycode; 1195 } 1196 } 1197 } 1198 return $address; 909 1199 } 910 1200 … … 912 1202 * Create a message and send it. 913 1203 * Uses the sending method specified by $Mailer. 914 * Returns false on error - Use the ErrorInfo variable to view description of the error.915 1204 * @throws phpmailerException 916 * @return bool 1205 * @return boolean false on error - See the ErrorInfo property for details of the error. 917 1206 */ 918 1207 public function send() … … 923 1212 } 924 1213 return $this->postSend(); 925 } catch (phpmailerException $e ) {1214 } catch (phpmailerException $exc) { 926 1215 $this->mailHeader = ''; 927 $this->setError($e ->getMessage());1216 $this->setError($exc->getMessage()); 928 1217 if ($this->exceptions) { 929 throw $e ;1218 throw $exc; 930 1219 } 931 1220 return false; … … 936 1225 * Prepare a message for sending. 937 1226 * @throws phpmailerException 938 * @return bool 1227 * @return boolean 939 1228 */ 940 1229 public function preSend() 941 1230 { 942 1231 try { 943 $this->mailHeader = ""; 1232 $this->error_count = 0; // Reset errors 1233 $this->mailHeader = ''; 1234 1235 // Dequeue recipient and Reply-To addresses with IDN 1236 foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { 1237 $params[1] = $this->punyencodeAddress($params[1]); 1238 call_user_func_array(array($this, 'addAnAddress'), $params); 1239 } 944 1240 if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { 945 1241 throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL); 946 1242 } 947 1243 1244 // Validate From, Sender, and ConfirmReadingTo addresses 1245 foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) { 1246 $this->$address_kind = trim($this->$address_kind); 1247 if (empty($this->$address_kind)) { 1248 continue; 1249 } 1250 $this->$address_kind = $this->punyencodeAddress($this->$address_kind); 1251 if (!$this->validateAddress($this->$address_kind)) { 1252 $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind; 1253 $this->setError($error_message); 1254 $this->edebug($error_message); 1255 if ($this->exceptions) { 1256 throw new phpmailerException($error_message); 1257 } 1258 return false; 1259 } 1260 } 1261 948 1262 // Set whether the message is multipart/alternative 949 if ( !empty($this->AltBody)) {1263 if ($this->alternativeExists()) { 950 1264 $this->ContentType = 'multipart/alternative'; 951 1265 } 952 1266 953 $this->error_count = 0; // reset errors954 1267 $this->setMessageType(); 955 1268 // Refuse to send an empty message unless we are specifically allowing it … … 958 1271 } 959 1272 1273 // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) 1274 $this->MIMEHeader = ''; 1275 $this->MIMEBody = $this->createBody(); 1276 // createBody may have added some headers, so retain them 1277 $tempheaders = $this->MIMEHeader; 960 1278 $this->MIMEHeader = $this->createHeader(); 961 $this->MIME Body = $this->createBody();1279 $this->MIMEHeader .= $tempheaders; 962 1280 963 1281 // To capture the complete message when using mail(), create … … 965 1283 if ($this->Mailer == 'mail') { 966 1284 if (count($this->to) > 0) { 967 $this->mailHeader .= $this->addrAppend( "To", $this->to);1285 $this->mailHeader .= $this->addrAppend('To', $this->to); 968 1286 } else { 969 $this->mailHeader .= $this->headerLine( "To", "undisclosed-recipients:;");1287 $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); 970 1288 } 971 1289 $this->mailHeader .= $this->headerLine( … … 977 1295 // Sign with DKIM if enabled 978 1296 if (!empty($this->DKIM_domain) 979 && !empty($this->DKIM_private)980 1297 && !empty($this->DKIM_selector) 981 && !empty($this->DKIM_domain) 982 && file_exists($this->DKIM_private)) { 1298 && (!empty($this->DKIM_private_string) 1299 || (!empty($this->DKIM_private) && file_exists($this->DKIM_private)) 1300 ) 1301 ) { 983 1302 $header_dkim = $this->DKIM_Add( 984 1303 $this->MIMEHeader . $this->mailHeader, … … 990 1309 } 991 1310 return true; 992 993 } catch (phpmailerException $e) { 994 $this->setError($e->getMessage()); 1311 } catch (phpmailerException $exc) { 1312 $this->setError($exc->getMessage()); 995 1313 if ($this->exceptions) { 996 throw $e ;1314 throw $exc; 997 1315 } 998 1316 return false; … … 1004 1322 * Send the email via the selected mechanism 1005 1323 * @throws phpmailerException 1006 * @return bool 1324 * @return boolean 1007 1325 */ 1008 1326 public function postSend() … … 1012 1330 switch ($this->Mailer) { 1013 1331 case 'sendmail': 1332 case 'qmail': 1014 1333 return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); 1015 1334 case 'smtp': … … 1018 1337 return $this->mailSend($this->MIMEHeader, $this->MIMEBody); 1019 1338 default: 1339 $sendMethod = $this->Mailer.'Send'; 1340 if (method_exists($this, $sendMethod)) { 1341 return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); 1342 } 1343 1020 1344 return $this->mailSend($this->MIMEHeader, $this->MIMEBody); 1021 1345 } 1022 } catch (phpmailerException $e) { 1023 $this->setError($e->getMessage()); 1346 } catch (phpmailerException $exc) { 1347 $this->setError($exc->getMessage()); 1348 $this->edebug($exc->getMessage()); 1024 1349 if ($this->exceptions) { 1025 throw $e; 1026 } 1027 $this->edebug($e->getMessage() . "\n"); 1350 throw $exc; 1351 } 1028 1352 } 1029 1353 return false; … … 1037 1361 * @throws phpmailerException 1038 1362 * @access protected 1039 * @return bool 1363 * @return boolean 1040 1364 */ 1041 1365 protected function sendmailSend($header, $body) 1042 1366 { 1043 if ($this->Sender != '') { 1044 $sendmail = sprintf("%s -oi -f%s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); 1367 // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. 1368 if (!empty($this->Sender) and self::isShellSafe($this->Sender)) { 1369 if ($this->Mailer == 'qmail') { 1370 $sendmailFmt = '%s -f%s'; 1371 } else { 1372 $sendmailFmt = '%s -oi -f%s -t'; 1373 } 1045 1374 } else { 1046 $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail)); 1047 } 1048 if ($this->SingleTo === true) { 1049 foreach ($this->SingleToArray as $val) { 1375 if ($this->Mailer == 'qmail') { 1376 $sendmailFmt = '%s'; 1377 } else { 1378 $sendmailFmt = '%s -oi -t'; 1379 } 1380 } 1381 1382 // TODO: If possible, this should be changed to escapeshellarg. Needs thorough testing. 1383 $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); 1384 1385 if ($this->SingleTo) { 1386 foreach ($this->SingleToArray as $toAddr) { 1050 1387 if (!@$mail = popen($sendmail, 'w')) { 1051 1388 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1052 1389 } 1053 fputs($mail, "To: " . $val. "\n");1390 fputs($mail, 'To: ' . $toAddr . "\n"); 1054 1391 fputs($mail, $header); 1055 1392 fputs($mail, $body); 1056 1393 $result = pclose($mail); 1057 // implement call back function if it exists 1058 $isSent = ($result == 0) ? 1 : 0; 1059 $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1394 $this->doCallback( 1395 ($result == 0), 1396 array($toAddr), 1397 $this->cc, 1398 $this->bcc, 1399 $this->Subject, 1400 $body, 1401 $this->From 1402 ); 1060 1403 if ($result != 0) { 1061 1404 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); … … 1069 1412 fputs($mail, $body); 1070 1413 $result = pclose($mail); 1071 // implement call back function if it exists 1072 $isSent = ($result == 0) ? 1 : 0; 1073 $this->doCallback($isSent, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1414 $this->doCallback( 1415 ($result == 0), 1416 $this->to, 1417 $this->cc, 1418 $this->bcc, 1419 $this->Subject, 1420 $body, 1421 $this->From 1422 ); 1074 1423 if ($result != 0) { 1075 1424 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1076 1425 } 1077 1426 } 1427 return true; 1428 } 1429 1430 /** 1431 * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. 1432 * 1433 * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. 1434 * @param string $string The string to be validated 1435 * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report 1436 * @access protected 1437 * @return boolean 1438 */ 1439 protected static function isShellSafe($string) 1440 { 1441 // Future-proof 1442 if (escapeshellcmd($string) !== $string 1443 or !in_array(escapeshellarg($string), array("'$string'", "\"$string\"")) 1444 ) { 1445 return false; 1446 } 1447 1448 $length = strlen($string); 1449 1450 for ($i = 0; $i < $length; $i++) { 1451 $c = $string[$i]; 1452 1453 // All other characters have a special meaning in at least one common shell, including = and +. 1454 // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. 1455 // Note that this does permit non-Latin alphanumeric characters based on the current locale. 1456 if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { 1457 return false; 1458 } 1459 } 1460 1078 1461 return true; 1079 1462 } … … 1086 1469 * @throws phpmailerException 1087 1470 * @access protected 1088 * @return bool 1471 * @return boolean 1089 1472 */ 1090 1473 protected function mailSend($header, $body) 1091 1474 { 1092 1475 $toArr = array(); 1093 foreach ($this->to as $t ) {1094 $toArr[] = $this->addrFormat($t );1476 foreach ($this->to as $toaddr) { 1477 $toArr[] = $this->addrFormat($toaddr); 1095 1478 } 1096 1479 $to = implode(', ', $toArr); 1097 1480 1098 if (empty($this->Sender)) { 1099 $params = " "; 1100 } else { 1101 $params = sprintf("-f%s", $this->Sender); 1102 } 1103 if ($this->Sender != '' and !ini_get('safe_mode')) { 1481 $params = null; 1482 //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver 1483 if (!empty($this->Sender) and $this->validateAddress($this->Sender)) { 1484 // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. 1485 if (self::isShellSafe($this->Sender)) { 1486 $params = sprintf('-f%s', $this->Sender); 1487 } 1488 } 1489 if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) { 1104 1490 $old_from = ini_get('sendmail_from'); 1105 1491 ini_set('sendmail_from', $this->Sender); 1106 1492 } 1107 $rt = false; 1108 if ($this->SingleTo === true && count($toArr) > 1) { 1109 foreach ($toArr as $val) { 1110 $rt = $this->mailPassthru($val, $this->Subject, $body, $header, $params); 1111 // implement call back function if it exists 1112 $isSent = ($rt == 1) ? 1 : 0; 1113 $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1493 $result = false; 1494 if ($this->SingleTo and count($toArr) > 1) { 1495 foreach ($toArr as $toAddr) { 1496 $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); 1497 $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1114 1498 } 1115 1499 } else { 1116 $rt = $this->mailPassthru($to, $this->Subject, $body, $header, $params); 1117 // implement call back function if it exists 1118 $isSent = ($rt == 1) ? 1 : 0; 1119 $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1500 $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); 1501 $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1120 1502 } 1121 1503 if (isset($old_from)) { 1122 1504 ini_set('sendmail_from', $old_from); 1123 1505 } 1124 if (!$r t) {1506 if (!$result) { 1125 1507 throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL); 1126 1508 } … … 1136 1518 { 1137 1519 if (!is_object($this->smtp)) { 1138 require_once 'class-smtp.php';1520 require_once( 'class-smtp.php' ); 1139 1521 $this->smtp = new SMTP; 1140 1522 } … … 1152 1534 * @uses SMTP 1153 1535 * @access protected 1154 * @return bool 1536 * @return boolean 1155 1537 */ 1156 1538 protected function smtpSend($header, $body) 1157 1539 { 1158 1540 $bad_rcpt = array(); 1159 1160 if (!$this->smtpConnect()) { 1541 if (!$this->smtpConnect($this->SMTPOptions)) { 1161 1542 throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); 1162 1543 } 1163 $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; 1544 if (!empty($this->Sender) and $this->validateAddress($this->Sender)) { 1545 $smtp_from = $this->Sender; 1546 } else { 1547 $smtp_from = $this->From; 1548 } 1164 1549 if (!$this->smtp->mail($smtp_from)) { 1165 1550 $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); … … 1167 1552 } 1168 1553 1169 // Attempt to send attach all recipients 1170 foreach ($this->to as $to) { 1171 if (!$this->smtp->recipient($to[0])) { 1172 $bad_rcpt[] = $to[0]; 1173 $isSent = 0; 1174 } else { 1175 $isSent = 1; 1176 } 1177 $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body, $this->From); 1178 } 1179 foreach ($this->cc as $cc) { 1180 if (!$this->smtp->recipient($cc[0])) { 1181 $bad_rcpt[] = $cc[0]; 1182 $isSent = 0; 1183 } else { 1184 $isSent = 1; 1185 } 1186 $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body, $this->From); 1187 } 1188 foreach ($this->bcc as $bcc) { 1189 if (!$this->smtp->recipient($bcc[0])) { 1190 $bad_rcpt[] = $bcc[0]; 1191 $isSent = 0; 1192 } else { 1193 $isSent = 1; 1194 } 1195 $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body, $this->From); 1196 } 1197 1198 if (count($bad_rcpt) > 0) { //Create error message for any bad addresses 1199 throw new phpmailerException($this->lang('recipients_failed') . implode(', ', $bad_rcpt)); 1200 } 1201 if (!$this->smtp->data($header . $body)) { 1554 // Attempt to send to all recipients 1555 foreach (array($this->to, $this->cc, $this->bcc) as $togroup) { 1556 foreach ($togroup as $to) { 1557 if (!$this->smtp->recipient($to[0])) { 1558 $error = $this->smtp->getError(); 1559 $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']); 1560 $isSent = false; 1561 } else { 1562 $isSent = true; 1563 } 1564 $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From); 1565 } 1566 } 1567 1568 // Only send the DATA command if we have viable recipients 1569 if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) { 1202 1570 throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL); 1203 1571 } 1204 if ($this->SMTPKeepAlive == true) {1572 if ($this->SMTPKeepAlive) { 1205 1573 $this->smtp->reset(); 1206 1574 } else { … … 1208 1576 $this->smtp->close(); 1209 1577 } 1578 //Create error message for any bad addresses 1579 if (count($bad_rcpt) > 0) { 1580 $errstr = ''; 1581 foreach ($bad_rcpt as $bad) { 1582 $errstr .= $bad['to'] . ': ' . $bad['error']; 1583 } 1584 throw new phpmailerException( 1585 $this->lang('recipients_failed') . $errstr, 1586 self::STOP_CONTINUE 1587 ); 1588 } 1210 1589 return true; 1211 1590 } … … 1218 1597 * @access public 1219 1598 * @throws phpmailerException 1220 * @return bool 1221 */ 1222 public function smtpConnect($options = array())1599 * @return boolean 1600 */ 1601 public function smtpConnect($options = null) 1223 1602 { 1224 1603 if (is_null($this->smtp)) { … … 1226 1605 } 1227 1606 1228 //Already connected? 1607 //If no options are provided, use whatever is set in the instance 1608 if (is_null($options)) { 1609 $options = $this->SMTPOptions; 1610 } 1611 1612 // Already connected? 1229 1613 if ($this->smtp->connected()) { 1230 1614 return true; … … 1235 1619 $this->smtp->setDebugOutput($this->Debugoutput); 1236 1620 $this->smtp->setVerp($this->do_verp); 1237 $tls = ($this->SMTPSecure == 'tls');1238 $ssl = ($this->SMTPSecure == 'ssl');1239 1621 $hosts = explode(';', $this->Host); 1240 1622 $lastexception = null; … … 1242 1624 foreach ($hosts as $hostentry) { 1243 1625 $hostinfo = array(); 1244 $host = $hostentry; 1626 if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { 1627 // Not a valid host entry 1628 continue; 1629 } 1630 // $hostinfo[2]: optional ssl or tls prefix 1631 // $hostinfo[3]: the hostname 1632 // $hostinfo[4]: optional port number 1633 // The host string prefix can temporarily override the current setting for SMTPSecure 1634 // If it's not specified, the default value is used 1635 $prefix = ''; 1636 $secure = $this->SMTPSecure; 1637 $tls = ($this->SMTPSecure == 'tls'); 1638 if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { 1639 $prefix = 'ssl://'; 1640 $tls = false; // Can't have SSL and TLS at the same time 1641 $secure = 'ssl'; 1642 } elseif ($hostinfo[2] == 'tls') { 1643 $tls = true; 1644 // tls doesn't use a prefix 1645 $secure = 'tls'; 1646 } 1647 //Do we need the OpenSSL extension? 1648 $sslext = defined('OPENSSL_ALGO_SHA1'); 1649 if ('tls' === $secure or 'ssl' === $secure) { 1650 //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled 1651 if (!$sslext) { 1652 throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL); 1653 } 1654 } 1655 $host = $hostinfo[3]; 1245 1656 $port = $this->Port; 1246 if (preg_match( 1247 '/^(.+):([0-9]+)$/', 1248 $hostentry, 1249 $hostinfo 1250 ) 1251 ) { //If $hostentry contains 'address:port', override default 1252 $host = $hostinfo[1]; 1253 $port = $hostinfo[2]; 1254 } 1255 if ($this->smtp->connect(($ssl ? 'ssl://' : '') . $host, $port, $this->Timeout, $options)) { 1657 $tport = (integer)$hostinfo[4]; 1658 if ($tport > 0 and $tport < 65536) { 1659 $port = $tport; 1660 } 1661 if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { 1256 1662 try { 1257 1663 if ($this->Helo) { … … 1261 1667 } 1262 1668 $this->smtp->hello($hello); 1263 1669 //Automatically enable TLS encryption if: 1670 // * it's not disabled 1671 // * we have openssl extension 1672 // * we are not already using SSL 1673 // * the server offers STARTTLS 1674 if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) { 1675 $tls = true; 1676 } 1264 1677 if ($tls) { 1265 1678 if (!$this->smtp->startTLS()) { 1266 1679 throw new phpmailerException($this->lang('connect_host')); 1267 1680 } 1268 // We must resend HELO after tlsnegotiation1681 // We must resend EHLO after TLS negotiation 1269 1682 $this->smtp->hello($hello); 1270 1683 } … … 1282 1695 } 1283 1696 return true; 1284 } catch (phpmailerException $e) { 1285 $lastexception = $e; 1286 //We must have connected, but then failed TLS or Auth, so close connection nicely 1697 } catch (phpmailerException $exc) { 1698 $lastexception = $exc; 1699 $this->edebug($exc->getMessage()); 1700 // We must have connected, but then failed TLS or Auth, so close connection nicely 1287 1701 $this->smtp->quit(); 1288 1702 } 1289 1703 } 1290 1704 } 1291 // If we get here, all connection attempts have failed, so close connection hard1705 // If we get here, all connection attempts have failed, so close connection hard 1292 1706 $this->smtp->close(); 1293 // As we've caught all exceptions, just report whatever the last one was1707 // As we've caught all exceptions, just report whatever the last one was 1294 1708 if ($this->exceptions and !is_null($lastexception)) { 1295 1709 throw $lastexception; … … 1304 1718 public function smtpClose() 1305 1719 { 1306 if ( $this->smtp !== null) {1720 if (is_a($this->smtp, 'SMTP')) { 1307 1721 if ($this->smtp->connected()) { 1308 1722 $this->smtp->quit(); … … 1318 1732 * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") 1319 1733 * @param string $lang_path Path to the language file directory, with trailing separator (slash) 1320 * @return bool 1321 * @access public 1322 */ 1323 public function setLanguage($langcode = 'en', $lang_path = 'language/') 1324 { 1325 //Define full set of translatable strings 1734 * @return boolean 1735 * @access public 1736 */ 1737 public function setLanguage($langcode = 'en', $lang_path = '') 1738 { 1739 // Backwards compatibility for renamed language codes 1740 $renamed_langcodes = array( 1741 'br' => 'pt_br', 1742 'cz' => 'cs', 1743 'dk' => 'da', 1744 'no' => 'nb', 1745 'se' => 'sv', 1746 ); 1747 1748 if (isset($renamed_langcodes[$langcode])) { 1749 $langcode = $renamed_langcodes[$langcode]; 1750 } 1751 1752 // Define full set of translatable strings in English 1326 1753 $PHPMAILER_LANG = array( 1327 1754 'authenticate' => 'SMTP Error: Could not authenticate.', … … 1335 1762 'from_failed' => 'The following From address failed: ', 1336 1763 'instantiate' => 'Could not instantiate mail function.', 1337 'invalid_address' => 'Invalid address ',1764 'invalid_address' => 'Invalid address: ', 1338 1765 'mailer_not_supported' => ' mailer is not supported.', 1339 1766 'provide_address' => 'You must provide at least one recipient email address.', … … 1342 1769 'smtp_connect_failed' => 'SMTP connect() failed.', 1343 1770 'smtp_error' => 'SMTP server error: ', 1344 'variable_set' => 'Cannot set or reset variable: ' 1771 'variable_set' => 'Cannot set or reset variable: ', 1772 'extension_missing' => 'Extension missing: ' 1345 1773 ); 1346 //Overwrite language-specific strings. 1347 //This way we'll never have missing translations - no more "language string failed to load"! 1348 $l = true; 1349 if ($langcode != 'en') { //There is no English translation file 1350 $l = @include $lang_path . 'phpmailer.lang-' . $langcode . '.php'; 1774 if (empty($lang_path)) { 1775 // Calculate an absolute path so it can work if CWD is not here 1776 $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR; 1777 } 1778 //Validate $langcode 1779 if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { 1780 $langcode = 'en'; 1781 } 1782 $foundlang = true; 1783 $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; 1784 // There is no English translation file 1785 if ($langcode != 'en') { 1786 // Make sure language file path is readable 1787 if (!is_readable($lang_file)) { 1788 $foundlang = false; 1789 } else { 1790 // Overwrite language-specific strings. 1791 // This way we'll never have missing translation keys. 1792 $foundlang = include $lang_file; 1793 } 1351 1794 } 1352 1795 $this->language = $PHPMAILER_LANG; 1353 return ( $l == true); //Returns false if language not found1796 return (boolean)$foundlang; // Returns false if language not found 1354 1797 } 1355 1798 … … 1376 1819 { 1377 1820 $addresses = array(); 1378 foreach ($addr as $a ) {1379 $addresses[] = $this->addrFormat($a );1821 foreach ($addr as $address) { 1822 $addresses[] = $this->addrFormat($address); 1380 1823 } 1381 1824 return $type . ': ' . implode(', ', $addresses) . $this->LE; … … 1394 1837 return $this->secureHeader($addr[0]); 1395 1838 } else { 1396 return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . " <". $this->secureHeader(1839 return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader( 1397 1840 $addr[0] 1398 ) . ">";1841 ) . '>'; 1399 1842 } 1400 1843 } … … 1407 1850 * @param string $message The message to wrap 1408 1851 * @param integer $length The line length to wrap to 1409 * @param bool $qp_mode Whether to run in Quoted-Printable mode1852 * @param boolean $qp_mode Whether to run in Quoted-Printable mode 1410 1853 * @access public 1411 1854 * @return string … … 1413 1856 public function wrapText($message, $length, $qp_mode = false) 1414 1857 { 1415 $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE; 1858 if ($qp_mode) { 1859 $soft_break = sprintf(' =%s', $this->LE); 1860 } else { 1861 $soft_break = $this->LE; 1862 } 1416 1863 // If utf-8 encoding is used, we will need to make sure we don't 1417 1864 // split multibyte characters when we wrap 1418 $is_utf8 = (strtolower($this->CharSet) == "utf-8");1865 $is_utf8 = (strtolower($this->CharSet) == 'utf-8'); 1419 1866 $lelen = strlen($this->LE); 1420 1867 $crlflen = strlen(self::CRLF); 1421 1868 1422 1869 $message = $this->fixEOL($message); 1870 //Remove a trailing line break 1423 1871 if (substr($message, -$lelen) == $this->LE) { 1424 1872 $message = substr($message, 0, -$lelen); 1425 1873 } 1426 1874 1427 $line = explode($this->LE, $message); // Magic. We know fixEOL uses $LE 1875 //Split message into lines 1876 $lines = explode($this->LE, $message); 1877 //Message will be rebuilt in here 1428 1878 $message = ''; 1429 for ($i = 0; $i < count($line); $i++) {1430 $ line_part = explode(' ', $line[$i]);1879 foreach ($lines as $line) { 1880 $words = explode(' ', $line); 1431 1881 $buf = ''; 1432 for ($e = 0; $e < count($line_part); $e++) {1433 $word = $line_part[$e];1882 $firstword = true; 1883 foreach ($words as $word) { 1434 1884 if ($qp_mode and (strlen($word) > $length)) { 1435 1885 $space_left = $length - strlen($buf) - $crlflen; 1436 if ( $e != 0) {1886 if (!$firstword) { 1437 1887 if ($space_left > 20) { 1438 1888 $len = $space_left; 1439 1889 if ($is_utf8) { 1440 1890 $len = $this->utf8CharBoundary($word, $len); 1441 } elseif (substr($word, $len - 1, 1) == "=") {1891 } elseif (substr($word, $len - 1, 1) == '=') { 1442 1892 $len--; 1443 } elseif (substr($word, $len - 2, 1) == "=") {1893 } elseif (substr($word, $len - 2, 1) == '=') { 1444 1894 $len -= 2; 1445 1895 } … … 1447 1897 $word = substr($word, $len); 1448 1898 $buf .= ' ' . $part; 1449 $message .= $buf . sprintf( "=%s", self::CRLF);1899 $message .= $buf . sprintf('=%s', self::CRLF); 1450 1900 } else { 1451 1901 $message .= $buf . $soft_break; … … 1460 1910 if ($is_utf8) { 1461 1911 $len = $this->utf8CharBoundary($word, $len); 1462 } elseif (substr($word, $len - 1, 1) == "=") {1912 } elseif (substr($word, $len - 1, 1) == '=') { 1463 1913 $len--; 1464 } elseif (substr($word, $len - 2, 1) == "=") {1914 } elseif (substr($word, $len - 2, 1) == '=') { 1465 1915 $len -= 2; 1466 1916 } … … 1469 1919 1470 1920 if (strlen($word) > 0) { 1471 $message .= $part . sprintf( "=%s", self::CRLF);1921 $message .= $part . sprintf('=%s', self::CRLF); 1472 1922 } else { 1473 1923 $buf = $part; … … 1476 1926 } else { 1477 1927 $buf_o = $buf; 1478 $buf .= ($e == 0) ? $word : (' ' . $word); 1928 if (!$firstword) { 1929 $buf .= ' '; 1930 } 1931 $buf .= $word; 1479 1932 1480 1933 if (strlen($buf) > $length and $buf_o != '') { … … 1483 1936 } 1484 1937 } 1938 $firstword = false; 1485 1939 } 1486 1940 $message .= $buf . self::CRLF; … … 1492 1946 /** 1493 1947 * Find the last character boundary prior to $maxLength in a utf-8 1494 * quoted (printable)encoded string.1948 * quoted-printable encoded string. 1495 1949 * Original written by Colin Brown. 1496 1950 * @access public 1497 1951 * @param string $encodedText utf-8 QP text 1498 * @param int $maxLength findlast character boundary prior to this length1499 * @return int 1952 * @param integer $maxLength Find the last character boundary prior to this length 1953 * @return integer 1500 1954 */ 1501 1955 public function utf8CharBoundary($encodedText, $maxLength) … … 1505 1959 while (!$foundSplitPos) { 1506 1960 $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); 1507 $encodedCharPos = strpos($lastChunk, "=");1508 if ( $encodedCharPos !== false) {1961 $encodedCharPos = strpos($lastChunk, '='); 1962 if (false !== $encodedCharPos) { 1509 1963 // Found start of encoded character byte within $lookBack block. 1510 1964 // Check the encoded byte value (the 2 chars after the '=') 1511 1965 $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); 1512 1966 $dec = hexdec($hex); 1513 if ($dec < 128) { // Single byte character. 1967 if ($dec < 128) { 1968 // Single byte character. 1514 1969 // If the encoded char was found at pos 0, it will fit 1515 1970 // otherwise reduce maxLength to start of the encoded char 1516 $maxLength = ($encodedCharPos == 0) ? $maxLength : 1517 $maxLength - ($lookBack - $encodedCharPos); 1971 if ($encodedCharPos > 0) { 1972 $maxLength = $maxLength - ($lookBack - $encodedCharPos); 1973 } 1518 1974 $foundSplitPos = true; 1519 } elseif ($dec >= 192) { // First byte of a multi byte character 1975 } elseif ($dec >= 192) { 1976 // First byte of a multi byte character 1520 1977 // Reduce maxLength to split at start of character 1521 1978 $maxLength = $maxLength - ($lookBack - $encodedCharPos); 1522 1979 $foundSplitPos = true; 1523 } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back 1980 } elseif ($dec < 192) { 1981 // Middle byte of a multi byte character, look further back 1524 1982 $lookBack += 3; 1525 1983 } … … 1532 1990 } 1533 1991 1534 1535 /** 1536 * Set the body wrapping. 1992 /** 1993 * Apply word wrapping to the message body. 1994 * Wraps the message body to the number of chars set in the WordWrap property. 1995 * You should only do this to plain-text bodies as wrapping HTML tags may break them. 1996 * This is called automatically by createBody(), so you don't need to call it yourself. 1537 1997 * @access public 1538 1998 * @return void … … 1566 2026 $result = ''; 1567 2027 1568 // Set the boundaries1569 $uniq_id = md5(uniqid(time()));1570 $this->boundary[1] = 'b1_' . $uniq_id;1571 $this->boundary[2] = 'b2_' . $uniq_id;1572 $this->boundary[3] = 'b3_' . $uniq_id;1573 1574 2028 if ($this->MessageDate == '') { 1575 $result .= $this->headerLine('Date', self::rfcDate()); 2029 $this->MessageDate = self::rfcDate(); 2030 } 2031 $result .= $this->headerLine('Date', $this->MessageDate); 2032 2033 // To be created automatically by mail() 2034 if ($this->SingleTo) { 2035 if ($this->Mailer != 'mail') { 2036 foreach ($this->to as $toaddr) { 2037 $this->SingleToArray[] = $this->addrFormat($toaddr); 2038 } 2039 } 1576 2040 } else { 1577 $result .= $this->headerLine('Date', $this->MessageDate); 1578 } 1579 1580 if ($this->ReturnPath) { 1581 $result .= $this->headerLine('Return-Path', '<' . trim($this->ReturnPath) . '>'); 1582 } elseif ($this->Sender == '') { 1583 $result .= $this->headerLine('Return-Path', '<' . trim($this->From) . '>'); 1584 } else { 1585 $result .= $this->headerLine('Return-Path', '<' . trim($this->Sender) . '>'); 1586 } 1587 1588 // To be created automatically by mail() 1589 if ($this->Mailer != 'mail') { 1590 if ($this->SingleTo === true) { 1591 foreach ($this->to as $t) { 1592 $this->SingleToArray[] = $this->addrFormat($t); 1593 } 1594 } else { 1595 if (count($this->to) > 0) { 2041 if (count($this->to) > 0) { 2042 if ($this->Mailer != 'mail') { 1596 2043 $result .= $this->addrAppend('To', $this->to); 1597 } elseif (count($this->cc) == 0) {1598 $result .= $this->headerLine('To', 'undisclosed-recipients:;');1599 }2044 } 2045 } elseif (count($this->cc) == 0) { 2046 $result .= $this->headerLine('To', 'undisclosed-recipients:;'); 1600 2047 } 1601 2048 } … … 1609 2056 1610 2057 // sendmail and mail() extract Bcc from the header before sending 1611 if ((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) { 2058 if (( 2059 $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail' 2060 ) 2061 and count($this->bcc) > 0 2062 ) { 1612 2063 $result .= $this->addrAppend('Bcc', $this->bcc); 1613 2064 } … … 1622 2073 } 1623 2074 1624 if ($this->MessageID != '') { 2075 // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 2076 // https://tools.ietf.org/html/rfc5322#section-3.6.4 2077 if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) { 1625 2078 $this->lastMessageID = $this->MessageID; 1626 2079 } else { 1627 $this->lastMessageID = sprintf("<%s@%s>", $uniq_id, $this->ServerHostname()); 1628 } 1629 $result .= $this->HeaderLine('Message-ID', $this->lastMessageID); 1630 $result .= $this->headerLine('X-Priority', $this->Priority); 2080 $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); 2081 } 2082 $result .= $this->headerLine('Message-ID', $this->lastMessageID); 2083 if (!is_null($this->Priority)) { 2084 $result .= $this->headerLine('X-Priority', $this->Priority); 2085 } 1631 2086 if ($this->XMailer == '') { 1632 2087 $result .= $this->headerLine( 1633 2088 'X-Mailer', 1634 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer /)'2089 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)' 1635 2090 ); 1636 2091 } else { … … 1642 2097 1643 2098 if ($this->ConfirmReadingTo != '') { 1644 $result .= $this->headerLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo). '>');2099 $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); 1645 2100 } 1646 2101 1647 2102 // Add custom headers 1648 for ($index = 0; $index < count($this->CustomHeader); $index++) {2103 foreach ($this->CustomHeader as $header) { 1649 2104 $result .= $this->headerLine( 1650 trim($ this->CustomHeader[$index][0]),1651 $this->encodeHeader(trim($ this->CustomHeader[$index][1]))2105 trim($header[0]), 2106 $this->encodeHeader(trim($header[1])) 1652 2107 ); 1653 2108 } … … 1668 2123 { 1669 2124 $result = ''; 2125 $ismultipart = true; 1670 2126 switch ($this->message_type) { 1671 2127 case 'inline': … … 1688 2144 // Catches case 'plain': and case '': 1689 2145 $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); 2146 $ismultipart = false; 1690 2147 break; 1691 2148 } 1692 // RFC1341 part 5 says 7bit is assumed if not specified2149 // RFC1341 part 5 says 7bit is assumed if not specified 1693 2150 if ($this->Encoding != '7bit') { 1694 $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); 2151 // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE 2152 if ($ismultipart) { 2153 if ($this->Encoding == '8bit') { 2154 $result .= $this->headerLine('Content-Transfer-Encoding', '8bit'); 2155 } 2156 // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible 2157 } else { 2158 $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); 2159 } 1695 2160 } 1696 2161 … … 1705 2170 * Returns the whole MIME message. 1706 2171 * Includes complete headers and body. 1707 * Only valid post PreSend().1708 * @see PHPMailer:: PreSend()2172 * Only valid post preSend(). 2173 * @see PHPMailer::preSend() 1709 2174 * @access public 1710 2175 * @return string … … 1712 2177 public function getSentMIMEMessage() 1713 2178 { 1714 return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody; 1715 } 1716 2179 return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody; 2180 } 2181 2182 /** 2183 * Create unique ID 2184 * @return string 2185 */ 2186 protected function generateId() { 2187 return md5(uniqid(time())); 2188 } 1717 2189 1718 2190 /** … … 1726 2198 { 1727 2199 $body = ''; 2200 //Create unique IDs and preset boundaries 2201 $this->uniqueid = $this->generateId(); 2202 $this->boundary[1] = 'b1_' . $this->uniqueid; 2203 $this->boundary[2] = 'b2_' . $this->uniqueid; 2204 $this->boundary[3] = 'b3_' . $this->uniqueid; 1728 2205 1729 2206 if ($this->sign_key_file) { … … 1733 2210 $this->setWordWrap(); 1734 2211 2212 $bodyEncoding = $this->Encoding; 2213 $bodyCharSet = $this->CharSet; 2214 //Can we do a 7-bit downgrade? 2215 if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) { 2216 $bodyEncoding = '7bit'; 2217 //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit 2218 $bodyCharSet = 'us-ascii'; 2219 } 2220 //If lines are too long, and we're not already using an encoding that will shorten them, 2221 //change to quoted-printable transfer encoding for the body part only 2222 if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) { 2223 $bodyEncoding = 'quoted-printable'; 2224 } 2225 2226 $altBodyEncoding = $this->Encoding; 2227 $altBodyCharSet = $this->CharSet; 2228 //Can we do a 7-bit downgrade? 2229 if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) { 2230 $altBodyEncoding = '7bit'; 2231 //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit 2232 $altBodyCharSet = 'us-ascii'; 2233 } 2234 //If lines are too long, and we're not already using an encoding that will shorten them, 2235 //change to quoted-printable transfer encoding for the alt body part only 2236 if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) { 2237 $altBodyEncoding = 'quoted-printable'; 2238 } 2239 //Use this as a preamble in all multipart message types 2240 $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE; 1735 2241 switch ($this->message_type) { 1736 2242 case 'inline': 1737 $body .= $this->getBoundary($this->boundary[1], '', '', ''); 1738 $body .= $this->encodeString($this->Body, $this->Encoding); 2243 $body .= $mimepre; 2244 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); 2245 $body .= $this->encodeString($this->Body, $bodyEncoding); 1739 2246 $body .= $this->LE . $this->LE; 1740 2247 $body .= $this->attachAll('inline', $this->boundary[1]); 1741 2248 break; 1742 2249 case 'attach': 1743 $body .= $this->getBoundary($this->boundary[1], '', '', ''); 1744 $body .= $this->encodeString($this->Body, $this->Encoding); 2250 $body .= $mimepre; 2251 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); 2252 $body .= $this->encodeString($this->Body, $bodyEncoding); 1745 2253 $body .= $this->LE . $this->LE; 1746 2254 $body .= $this->attachAll('attachment', $this->boundary[1]); 1747 2255 break; 1748 2256 case 'inline_attach': 2257 $body .= $mimepre; 1749 2258 $body .= $this->textLine('--' . $this->boundary[1]); 1750 2259 $body .= $this->headerLine('Content-Type', 'multipart/related;'); 1751 2260 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 1752 2261 $body .= $this->LE; 1753 $body .= $this->getBoundary($this->boundary[2], '', '', '');1754 $body .= $this->encodeString($this->Body, $ this->Encoding);2262 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); 2263 $body .= $this->encodeString($this->Body, $bodyEncoding); 1755 2264 $body .= $this->LE . $this->LE; 1756 2265 $body .= $this->attachAll('inline', $this->boundary[2]); … … 1759 2268 break; 1760 2269 case 'alt': 1761 $body .= $this->getBoundary($this->boundary[1], '', 'text/plain', ''); 1762 $body .= $this->encodeString($this->AltBody, $this->Encoding); 2270 $body .= $mimepre; 2271 $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); 2272 $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 1763 2273 $body .= $this->LE . $this->LE; 1764 $body .= $this->getBoundary($this->boundary[1], '', 'text/html', '');1765 $body .= $this->encodeString($this->Body, $ this->Encoding);2274 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding); 2275 $body .= $this->encodeString($this->Body, $bodyEncoding); 1766 2276 $body .= $this->LE . $this->LE; 1767 2277 if (!empty($this->Ical)) { … … 1773 2283 break; 1774 2284 case 'alt_inline': 1775 $body .= $this->getBoundary($this->boundary[1], '', 'text/plain', ''); 1776 $body .= $this->encodeString($this->AltBody, $this->Encoding); 2285 $body .= $mimepre; 2286 $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); 2287 $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 1777 2288 $body .= $this->LE . $this->LE; 1778 2289 $body .= $this->textLine('--' . $this->boundary[1]); … … 1780 2291 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 1781 2292 $body .= $this->LE; 1782 $body .= $this->getBoundary($this->boundary[2], '', 'text/html', '');1783 $body .= $this->encodeString($this->Body, $ this->Encoding);2293 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); 2294 $body .= $this->encodeString($this->Body, $bodyEncoding); 1784 2295 $body .= $this->LE . $this->LE; 1785 2296 $body .= $this->attachAll('inline', $this->boundary[2]); … … 1788 2299 break; 1789 2300 case 'alt_attach': 2301 $body .= $mimepre; 1790 2302 $body .= $this->textLine('--' . $this->boundary[1]); 1791 2303 $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); 1792 2304 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 1793 2305 $body .= $this->LE; 1794 $body .= $this->getBoundary($this->boundary[2], '', 'text/plain', '');1795 $body .= $this->encodeString($this->AltBody, $ this->Encoding);2306 $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); 2307 $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 1796 2308 $body .= $this->LE . $this->LE; 1797 $body .= $this->getBoundary($this->boundary[2], '', 'text/html', '');1798 $body .= $this->encodeString($this->Body, $ this->Encoding);2309 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); 2310 $body .= $this->encodeString($this->Body, $bodyEncoding); 1799 2311 $body .= $this->LE . $this->LE; 1800 2312 $body .= $this->endBoundary($this->boundary[2]); … … 1803 2315 break; 1804 2316 case 'alt_inline_attach': 2317 $body .= $mimepre; 1805 2318 $body .= $this->textLine('--' . $this->boundary[1]); 1806 2319 $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); 1807 2320 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 1808 2321 $body .= $this->LE; 1809 $body .= $this->getBoundary($this->boundary[2], '', 'text/plain', '');1810 $body .= $this->encodeString($this->AltBody, $ this->Encoding);2322 $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); 2323 $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 1811 2324 $body .= $this->LE . $this->LE; 1812 2325 $body .= $this->textLine('--' . $this->boundary[2]); … … 1814 2327 $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"'); 1815 2328 $body .= $this->LE; 1816 $body .= $this->getBoundary($this->boundary[3], '', 'text/html', '');1817 $body .= $this->encodeString($this->Body, $ this->Encoding);2329 $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding); 2330 $body .= $this->encodeString($this->Body, $bodyEncoding); 1818 2331 $body .= $this->LE . $this->LE; 1819 2332 $body .= $this->attachAll('inline', $this->boundary[3]); … … 1824 2337 break; 1825 2338 default: 1826 // catch case 'plain' and case '' 2339 // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types 2340 //Reset the `Encoding` property in case we changed it for line length reasons 2341 $this->Encoding = $bodyEncoding; 1827 2342 $body .= $this->encodeString($this->Body, $this->Encoding); 1828 2343 break; … … 1834 2349 try { 1835 2350 if (!defined('PKCS7_TEXT')) { 1836 throw new phpmailerException($this->lang('signing') . ' OpenSSL extension missing.'); 1837 } 2351 throw new phpmailerException($this->lang('extension_missing') . 'openssl'); 2352 } 2353 // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1 1838 2354 $file = tempnam(sys_get_temp_dir(), 'mail'); 1839 file_put_contents($file, $body); //TODO check this worked 2355 if (false === file_put_contents($file, $body)) { 2356 throw new phpmailerException($this->lang('signing') . ' Could not write temp file'); 2357 } 1840 2358 $signed = tempnam(sys_get_temp_dir(), 'signed'); 1841 if (@openssl_pkcs7_sign( 1842 $file, 1843 $signed, 1844 'file://' . realpath($this->sign_cert_file), 1845 array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), 1846 null 1847 ) 1848 ) { 2359 //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 2360 if (empty($this->sign_extracerts_file)) { 2361 $sign = @openssl_pkcs7_sign( 2362 $file, 2363 $signed, 2364 'file://' . realpath($this->sign_cert_file), 2365 array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), 2366 null 2367 ); 2368 } else { 2369 $sign = @openssl_pkcs7_sign( 2370 $file, 2371 $signed, 2372 'file://' . realpath($this->sign_cert_file), 2373 array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), 2374 null, 2375 PKCS7_DETACHED, 2376 $this->sign_extracerts_file 2377 ); 2378 } 2379 if ($sign) { 1849 2380 @unlink($file); 1850 2381 $body = file_get_contents($signed); 1851 2382 @unlink($signed); 2383 //The message returned by openssl contains both headers and body, so need to split them up 2384 $parts = explode("\n\n", $body, 2); 2385 $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE; 2386 $body = $parts[1]; 1852 2387 } else { 1853 2388 @unlink($file); … … 1855 2390 throw new phpmailerException($this->lang('signing') . openssl_error_string()); 1856 2391 } 1857 } catch (phpmailerException $e ) {2392 } catch (phpmailerException $exc) { 1858 2393 $body = ''; 1859 2394 if ($this->exceptions) { 1860 throw $e ;2395 throw $exc; 1861 2396 } 1862 2397 } … … 1887 2422 } 1888 2423 $result .= $this->textLine('--' . $boundary); 1889 $result .= sprintf( "Content-Type: %s; charset=%s", $contentType, $charSet);2424 $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); 1890 2425 $result .= $this->LE; 1891 $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); 2426 // RFC1341 part 5 says 7bit is assumed if not specified 2427 if ($encoding != '7bit') { 2428 $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); 2429 } 1892 2430 $result .= $this->LE; 1893 2431 … … 1908 2446 /** 1909 2447 * Set the message type. 1910 * PHPMailer only supports some preset message types, 1911 * not arbitrary MIME structures. 2448 * PHPMailer only supports some preset message types, not arbitrary MIME structures. 1912 2449 * @access protected 1913 2450 * @return void … … 1915 2452 protected function setMessageType() 1916 2453 { 1917 $t his->message_type = array();2454 $type = array(); 1918 2455 if ($this->alternativeExists()) { 1919 $t his->message_type[] = "alt";2456 $type[] = 'alt'; 1920 2457 } 1921 2458 if ($this->inlineImageExists()) { 1922 $t his->message_type[] = "inline";2459 $type[] = 'inline'; 1923 2460 } 1924 2461 if ($this->attachmentExists()) { 1925 $this->message_type[] = "attach"; 1926 } 1927 $this->message_type = implode("_", $this->message_type); 1928 if ($this->message_type == "") { 1929 $this->message_type = "plain"; 2462 $type[] = 'attach'; 2463 } 2464 $this->message_type = implode('_', $type); 2465 if ($this->message_type == '') { 2466 //The 'plain' message_type refers to the message having a single body element, not that it is plain-text 2467 $this->message_type = 'plain'; 1930 2468 } 1931 2469 } … … 1963 2501 * @param string $disposition Disposition to use 1964 2502 * @throws phpmailerException 1965 * @return bool 2503 * @return boolean 1966 2504 */ 1967 2505 public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') … … 1972 2510 } 1973 2511 1974 // If a MIME type is not specified, try to work it out from the file name2512 // If a MIME type is not specified, try to work it out from the file name 1975 2513 if ($type == '') { 1976 2514 $type = self::filenameToType($path); … … 1993 2531 ); 1994 2532 1995 } catch (phpmailerException $e) { 1996 $this->setError($e->getMessage()); 2533 } catch (phpmailerException $exc) { 2534 $this->setError($exc->getMessage()); 2535 $this->edebug($exc->getMessage()); 1997 2536 if ($this->exceptions) { 1998 throw $e; 1999 } 2000 $this->edebug($e->getMessage() . "\n"); 2537 throw $exc; 2538 } 2001 2539 return false; 2002 2540 } … … 2052 2590 $disposition = $attachment[6]; 2053 2591 $cid = $attachment[7]; 2054 if ($disposition == 'inline' && isset($cidUniq[$cid])) {2592 if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) { 2055 2593 continue; 2056 2594 } 2057 2595 $cidUniq[$cid] = true; 2058 2596 2059 $mime[] = sprintf("--%s%s", $boundary, $this->LE); 2060 $mime[] = sprintf( 2061 "Content-Type: %s; name=\"%s\"%s", 2062 $type, 2063 $this->encodeHeader($this->secureHeader($name)), 2064 $this->LE 2065 ); 2066 $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE); 2597 $mime[] = sprintf('--%s%s', $boundary, $this->LE); 2598 //Only include a filename property if we have one 2599 if (!empty($name)) { 2600 $mime[] = sprintf( 2601 'Content-Type: %s; name="%s"%s', 2602 $type, 2603 $this->encodeHeader($this->secureHeader($name)), 2604 $this->LE 2605 ); 2606 } else { 2607 $mime[] = sprintf( 2608 'Content-Type: %s%s', 2609 $type, 2610 $this->LE 2611 ); 2612 } 2613 // RFC1341 part 5 says 7bit is assumed if not specified 2614 if ($encoding != '7bit') { 2615 $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE); 2616 } 2067 2617 2068 2618 if ($disposition == 'inline') { 2069 $mime[] = sprintf( "Content-ID: <%s>%s", $cid, $this->LE);2619 $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE); 2070 2620 } 2071 2621 … … 2075 2625 // Allow for bypassing the Content-Disposition header totally 2076 2626 if (!(empty($disposition))) { 2077 if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $name)) { 2627 $encoded_name = $this->encodeHeader($this->secureHeader($name)); 2628 if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) { 2078 2629 $mime[] = sprintf( 2079 "Content-Disposition: %s; filename=\"%s\"%s",2630 'Content-Disposition: %s; filename="%s"%s', 2080 2631 $disposition, 2081 $ this->encodeHeader($this->secureHeader($name)),2632 $encoded_name, 2082 2633 $this->LE . $this->LE 2083 2634 ); 2084 2635 } else { 2085 $mime[] = sprintf( 2086 "Content-Disposition: %s; filename=%s%s", 2087 $disposition, 2088 $this->encodeHeader($this->secureHeader($name)), 2089 $this->LE . $this->LE 2090 ); 2636 if (!empty($encoded_name)) { 2637 $mime[] = sprintf( 2638 'Content-Disposition: %s; filename=%s%s', 2639 $disposition, 2640 $encoded_name, 2641 $this->LE . $this->LE 2642 ); 2643 } else { 2644 $mime[] = sprintf( 2645 'Content-Disposition: %s%s', 2646 $disposition, 2647 $this->LE . $this->LE 2648 ); 2649 } 2091 2650 } 2092 2651 } else { … … 2111 2670 } 2112 2671 2113 $mime[] = sprintf( "--%s--%s", $boundary, $this->LE);2114 2115 return implode( "", $mime);2672 $mime[] = sprintf('--%s--%s', $boundary, $this->LE); 2673 2674 return implode('', $mime); 2116 2675 } 2117 2676 … … 2122 2681 * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' 2123 2682 * @throws phpmailerException 2124 * @see EncodeFile(encodeFile2125 2683 * @access protected 2126 2684 * @return string … … 2135 2693 if ($magic_quotes) { 2136 2694 if (version_compare(PHP_VERSION, '5.3.0', '<')) { 2137 set_magic_quotes_runtime( 0);2695 set_magic_quotes_runtime(false); 2138 2696 } else { 2139 ini_set('magic_quotes_runtime', 0); 2697 //Doesn't exist in PHP 5.4, but we don't need to check because 2698 //get_magic_quotes_runtime always returns false in 5.4+ 2699 //so it will never get here 2700 ini_set('magic_quotes_runtime', false); 2140 2701 } 2141 2702 } … … 2150 2711 } 2151 2712 return $file_buffer; 2152 } catch (Exception $e ) {2153 $this->setError($e ->getMessage());2713 } catch (Exception $exc) { 2714 $this->setError($exc->getMessage()); 2154 2715 return ''; 2155 2716 } … … 2174 2735 case '8bit': 2175 2736 $encoded = $this->fixEOL($str); 2176 // Make sure it ends with a line break2737 // Make sure it ends with a line break 2177 2738 if (substr($encoded, -(strlen($this->LE))) != $this->LE) { 2178 2739 $encoded .= $this->LE; … … 2202 2763 public function encodeHeader($str, $position = 'text') 2203 2764 { 2204 $ x= 0;2765 $matchcount = 0; 2205 2766 switch (strtolower($position)) { 2206 2767 case 'phrase': 2207 2768 if (!preg_match('/[\200-\377]/', $str)) { 2208 // Can't use addslashes as we don't know what value hasmagic_quotes_sybase2769 // Can't use addslashes as we don't know the value of magic_quotes_sybase 2209 2770 $encoded = addcslashes($str, "\0..\37\177\\\""); 2210 2771 if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { … … 2214 2775 } 2215 2776 } 2216 $ x= preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);2777 $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); 2217 2778 break; 2218 2779 /** @noinspection PhpMissingBreakStatementInspection */ 2219 2780 case 'comment': 2220 $ x= preg_match_all('/[()"]/', $str, $matches);2781 $matchcount = preg_match_all('/[()"]/', $str, $matches); 2221 2782 // Intentional fall-through 2222 2783 case 'text': 2223 2784 default: 2224 $ x+= preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);2785 $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); 2225 2786 break; 2226 2787 } 2227 2788 2228 if ($x == 0) { //There are no chars that need encoding 2789 //There are no chars that need encoding 2790 if ($matchcount == 0) { 2229 2791 return ($str); 2230 2792 } … … 2232 2794 $maxlen = 75 - 7 - strlen($this->CharSet); 2233 2795 // Try to select the encoding which should produce the shortest output 2234 if ($ x> strlen($str) / 3) {2235 // More than a third of the content will need encoding, so B encoding will be most efficient2796 if ($matchcount > strlen($str) / 3) { 2797 // More than a third of the content will need encoding, so B encoding will be most efficient 2236 2798 $encoding = 'B'; 2237 2799 if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) { … … 2251 2813 } 2252 2814 2253 $encoded = preg_replace('/^(.*)$/m', " =?". $this->CharSet . "?$encoding?\\1?=", $encoded);2815 $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); 2254 2816 $encoded = trim(str_replace("\n", $this->LE, $encoded)); 2255 2817 … … 2261 2823 * @access public 2262 2824 * @param string $str multi-byte text to wrap encode 2263 * @return bool 2825 * @return boolean 2264 2826 */ 2265 2827 public function hasMultiBytes($str) … … 2273 2835 2274 2836 /** 2837 * Does a string contain any 8-bit chars (in any charset)? 2838 * @param string $text 2839 * @return boolean 2840 */ 2841 public function has8bitChars($text) 2842 { 2843 return (boolean)preg_match('/[\x80-\xFF]/', $text); 2844 } 2845 2846 /** 2275 2847 * Encode and wrap long multibyte strings for mail headers 2276 2848 * without breaking lines within a character. 2277 * Adapted from a function by paravoid at http://uk.php.net/manual/en/function.mb-encode-mimeheader.php 2849 * Adapted from a function by paravoid 2850 * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 2278 2851 * @access public 2279 2852 * @param string $str multi-byte text to wrap encode 2280 * @param string $l fstring to use as linefeed/end-of-line2853 * @param string $linebreak string to use as linefeed/end-of-line 2281 2854 * @return string 2282 2855 */ 2283 public function base64EncodeWrapMB($str, $l f= null)2284 { 2285 $start = "=?" . $this->CharSet . "?B?";2286 $end = "?=";2287 $encoded = "";2288 if ($l f=== null) {2289 $l f= $this->LE;2856 public function base64EncodeWrapMB($str, $linebreak = null) 2857 { 2858 $start = '=?' . $this->CharSet . '?B?'; 2859 $end = '?='; 2860 $encoded = ''; 2861 if ($linebreak === null) { 2862 $linebreak = $this->LE; 2290 2863 } 2291 2864 … … 2306 2879 $lookBack++; 2307 2880 } while (strlen($chunk) > $length); 2308 $encoded .= $chunk . $l f;2881 $encoded .= $chunk . $linebreak; 2309 2882 } 2310 2883 2311 2884 // Chomp the last linefeed 2312 $encoded = substr($encoded, 0, -strlen($l f));2885 $encoded = substr($encoded, 0, -strlen($linebreak)); 2313 2886 return $encoded; 2314 2887 } … … 2321 2894 * @param integer $line_max Number of chars allowed on a line before wrapping 2322 2895 * @return string 2323 * @link PHP version adapted from http://www.php.net/manual/en/function.quoted-printable-decode.php#894172896 * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment 2324 2897 */ 2325 2898 public function encodeQP($string, $line_max = 76) 2326 2899 { 2327 if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3) 2900 // Use native function if it's available (>= PHP5.3) 2901 if (function_exists('quoted_printable_encode')) { 2328 2902 return quoted_printable_encode($string); 2329 2903 } 2330 // Fall back to a pure PHP implementation2904 // Fall back to a pure PHP implementation 2331 2905 $string = str_replace( 2332 2906 array('%20', '%0D%0A.', '%0D%0A', '%'), … … 2334 2908 rawurlencode($string) 2335 2909 ); 2336 $string = preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); 2337 return $string; 2910 return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); 2338 2911 } 2339 2912 … … 2344 2917 * @param string $string 2345 2918 * @param integer $line_max 2346 * @param bool $space_conv2919 * @param boolean $space_conv 2347 2920 * @return string 2348 2921 * @deprecated Use encodeQP instead. … … 2366 2939 public function encodeQ($str, $position = 'text') 2367 2940 { 2368 // There should not be any EOL in the string2941 // There should not be any EOL in the string 2369 2942 $pattern = ''; 2370 2943 $encoded = str_replace(array("\r", "\n"), '', $str); 2371 2944 switch (strtolower($position)) { 2372 2945 case 'phrase': 2373 // RFC 2047 section 5.32946 // RFC 2047 section 5.3 2374 2947 $pattern = '^A-Za-z0-9!*+\/ -'; 2375 2948 break; 2376 2949 /** @noinspection PhpMissingBreakStatementInspection */ 2377 2950 case 'comment': 2378 // RFC 2047 section 5.22951 // RFC 2047 section 5.2 2379 2952 $pattern = '\(\)"'; 2380 // intentional fall-through2381 // for this reason we build the $pattern without including delimiters and []2953 // intentional fall-through 2954 // for this reason we build the $pattern without including delimiters and [] 2382 2955 case 'text': 2383 2956 default: 2384 // RFC 2047 section 5.12385 // Replace every high ascii, control, =, ? and _ characters2957 // RFC 2047 section 5.1 2958 // Replace every high ascii, control, =, ? and _ characters 2386 2959 $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; 2387 2960 break; … … 2389 2962 $matches = array(); 2390 2963 if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { 2391 // If the string contains an '=', make sure it's the first thing we replace2392 // so as to avoid double-encoding2393 $ s= array_search('=', $matches[0]);2394 if ( $s !== false) {2395 unset($matches[0][$ s]);2964 // If the string contains an '=', make sure it's the first thing we replace 2965 // so as to avoid double-encoding 2966 $eqkey = array_search('=', $matches[0]); 2967 if (false !== $eqkey) { 2968 unset($matches[0][$eqkey]); 2396 2969 array_unshift($matches[0], '='); 2397 2970 } … … 2400 2973 } 2401 2974 } 2402 // Replace every spaces to _ (more readable than =20)2975 // Replace every spaces to _ (more readable than =20) 2403 2976 return str_replace(' ', '_', $encoded); 2404 2977 } 2405 2406 2978 2407 2979 /** … … 2423 2995 $disposition = 'attachment' 2424 2996 ) { 2425 // If a MIME type is not specified, try to work it out from the file name2997 // If a MIME type is not specified, try to work it out from the file name 2426 2998 if ($type == '') { 2427 2999 $type = self::filenameToType($filename); … … 2443 3015 * Add an embedded (inline) attachment from a file. 2444 3016 * This can include images, sounds, and just about any other document type. 2445 * These differ from 'regular' attachm ants in that they are intended to be3017 * These differ from 'regular' attachments in that they are intended to be 2446 3018 * displayed inline with the message, not just attached for download. 2447 3019 * This is used in HTML messages that embed the images … … 2454 3026 * @param string $type File MIME type. 2455 3027 * @param string $disposition Disposition to use 2456 * @return bool True on successfully adding an attachment3028 * @return boolean True on successfully adding an attachment 2457 3029 */ 2458 3030 public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') … … 2463 3035 } 2464 3036 2465 // If a MIME type is not specified, try to work it out from the file name3037 // If a MIME type is not specified, try to work it out from the file name 2466 3038 if ($type == '') { 2467 3039 $type = self::filenameToType($path); … … 2499 3071 * @param string $type MIME type. 2500 3072 * @param string $disposition Disposition to use 2501 * @return bool True on successfully adding an attachment3073 * @return boolean True on successfully adding an attachment 2502 3074 */ 2503 3075 public function addStringEmbeddedImage( … … 2509 3081 $disposition = 'inline' 2510 3082 ) { 2511 // If a MIME type is not specified, try to work it out from the name2512 if ($type == '' ) {3083 // If a MIME type is not specified, try to work it out from the name 3084 if ($type == '' and !empty($name)) { 2513 3085 $type = self::filenameToType($name); 2514 3086 } … … 2531 3103 * Check if an inline attachment is present. 2532 3104 * @access public 2533 * @return bool 3105 * @return boolean 2534 3106 */ 2535 3107 public function inlineImageExists() … … 2545 3117 /** 2546 3118 * Check if an attachment (non-inline) is present. 2547 * @return bool 3119 * @return boolean 2548 3120 */ 2549 3121 public function attachmentExists() … … 2559 3131 /** 2560 3132 * Check if this message has an alternative body set. 2561 * @return bool 3133 * @return boolean 2562 3134 */ 2563 3135 public function alternativeExists() 2564 3136 { 2565 3137 return !empty($this->AltBody); 3138 } 3139 3140 /** 3141 * Clear queued addresses of given kind. 3142 * @access protected 3143 * @param string $kind 'to', 'cc', or 'bcc' 3144 * @return void 3145 */ 3146 public function clearQueuedAddresses($kind) 3147 { 3148 $RecipientsQueue = $this->RecipientsQueue; 3149 foreach ($RecipientsQueue as $address => $params) { 3150 if ($params[0] == $kind) { 3151 unset($this->RecipientsQueue[$address]); 3152 } 3153 } 2566 3154 } 2567 3155 … … 2576 3164 } 2577 3165 $this->to = array(); 3166 $this->clearQueuedAddresses('to'); 2578 3167 } 2579 3168 … … 2588 3177 } 2589 3178 $this->cc = array(); 3179 $this->clearQueuedAddresses('cc'); 2590 3180 } 2591 3181 … … 2600 3190 } 2601 3191 $this->bcc = array(); 3192 $this->clearQueuedAddresses('bcc'); 2602 3193 } 2603 3194 … … 2609 3200 { 2610 3201 $this->ReplyTo = array(); 3202 $this->ReplyToQueue = array(); 2611 3203 } 2612 3204 … … 2621 3213 $this->bcc = array(); 2622 3214 $this->all_recipients = array(); 3215 $this->RecipientsQueue = array(); 2623 3216 } 2624 3217 … … 2652 3245 if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { 2653 3246 $lasterror = $this->smtp->getError(); 2654 if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) { 2655 $msg .= '<p>' . $this->lang('smtp_error') . $lasterror['smtp_msg'] . "</p>\n"; 3247 if (!empty($lasterror['error'])) { 3248 $msg .= $this->lang('smtp_error') . $lasterror['error']; 3249 if (!empty($lasterror['detail'])) { 3250 $msg .= ' Detail: '. $lasterror['detail']; 3251 } 3252 if (!empty($lasterror['smtp_code'])) { 3253 $msg .= ' SMTP code: ' . $lasterror['smtp_code']; 3254 } 3255 if (!empty($lasterror['smtp_code_ex'])) { 3256 $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; 3257 } 2656 3258 } 2657 3259 } … … 2667 3269 public static function rfcDate() 2668 3270 { 2669 // Set the time zone to whatever the default is to avoid 500 errors2670 // Will default to UTC if it's not set properly in php.ini3271 // Set the time zone to whatever the default is to avoid 500 errors 3272 // Will default to UTC if it's not set properly in php.ini 2671 3273 date_default_timezone_set(@date_default_timezone_get()); 2672 3274 return date('D, j M Y H:i:s O'); … … 2681 3283 protected function serverHostname() 2682 3284 { 3285 $result = 'localhost.localdomain'; 2683 3286 if (!empty($this->Hostname)) { 2684 3287 $result = $this->Hostname; 2685 } elseif (isset($_SERVER ['SERVER_NAME'])) {3288 } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) { 2686 3289 $result = $_SERVER['SERVER_NAME']; 2687 } else { 2688 $result = 'localhost.localdomain'; 2689 } 2690 3290 } elseif (function_exists('gethostname') && gethostname() !== false) { 3291 $result = gethostname(); 3292 } elseif (php_uname('n') !== false) { 3293 $result = php_uname('n'); 3294 } 2691 3295 return $result; 2692 3296 } … … 2704 3308 } 2705 3309 2706 if (isset($this->language[$key])) { 3310 if (array_key_exists($key, $this->language)) { 3311 if ($key == 'smtp_connect_failed') { 3312 //Include a link to troubleshooting docs on SMTP connection failure 3313 //this is by far the biggest cause of support questions 3314 //but it's usually not PHPMailer's fault. 3315 return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; 3316 } 2707 3317 return $this->language[$key]; 2708 3318 } else { 2709 return 'Language string failed to load: ' . $key; 3319 //Return the key as a fallback 3320 return $key; 2710 3321 } 2711 3322 } … … 2714 3325 * Check if an error occurred. 2715 3326 * @access public 2716 * @return bool True if an error did occur.3327 * @return boolean True if an error did occur. 2717 3328 */ 2718 3329 public function isError() … … 2759 3370 2760 3371 /** 2761 * Create a message from an HTML string. 2762 * Automatically makes modifications for inline images and backgrounds 2763 * and creates a plain-text version by converting the HTML. 2764 * Overwrites any existing values in $this->Body and $this->AltBody 3372 * Returns all custom headers. 3373 * @return array 3374 */ 3375 public function getCustomHeaders() 3376 { 3377 return $this->CustomHeader; 3378 } 3379 3380 /** 3381 * Create a message body from an HTML string. 3382 * Automatically inlines images and creates a plain-text version by converting the HTML, 3383 * overwriting any existing values in Body and AltBody. 3384 * $basedir is used when handling relative image paths, e.g. <img src="images/a.png"> 3385 * will look for an image file in $basedir/images/a.png and convert it to inline. 3386 * If you don't want to apply these transformations to your HTML, just set Body and AltBody yourself. 2765 3387 * @access public 2766 3388 * @param string $message HTML message string 2767 * @param string $basedir baseline directory for path 2768 * @param bool $advanced Whether to use the advanced HTML to text converter 2769 * @return string $message 3389 * @param string $basedir base directory for relative paths to images 3390 * @param boolean|callable $advanced Whether to use the internal HTML to text converter 3391 * or your own custom converter @see PHPMailer::html2text() 3392 * @return string $message The transformed message Body 2770 3393 */ 2771 3394 public function msgHTML($message, $basedir = '', $advanced = false) 2772 3395 { 2773 preg_match_all("/(src|background)=[\"'](.*)[\"']/Ui", $message, $images); 2774 if (isset($images[2])) { 2775 foreach ($images[2] as $i => $url) { 2776 // do not change urls for absolute images (thanks to corvuscorax) 2777 if (!preg_match('#^[A-z]+://#', $url)) { 3396 preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); 3397 if (array_key_exists(2, $images)) { 3398 foreach ($images[2] as $imgindex => $url) { 3399 // Convert data URIs into embedded images 3400 if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { 3401 $data = substr($url, strpos($url, ',')); 3402 if ($match[2]) { 3403 $data = base64_decode($data); 3404 } else { 3405 $data = rawurldecode($data); 3406 } 3407 $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 3408 if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) { 3409 $message = str_replace( 3410 $images[0][$imgindex], 3411 $images[1][$imgindex] . '="cid:' . $cid . '"', 3412 $message 3413 ); 3414 } 3415 } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) { 3416 // Do not change urls for absolute images (thanks to corvuscorax) 3417 // Do not change urls that are already inline images 2778 3418 $filename = basename($url); 2779 3419 $directory = dirname($url); … … 2781 3421 $directory = ''; 2782 3422 } 2783 $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 23423 $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 2784 3424 if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { 2785 3425 $basedir .= '/'; … … 2793 3433 $filename, 2794 3434 'base64', 2795 self::_mime_types( self::mb_pathinfo($filename, PATHINFO_EXTENSION))3435 self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION)) 2796 3436 ) 2797 3437 ) { 2798 3438 $message = preg_replace( 2799 "/" . $images[1][$i] . "=[\"']" . preg_quote($url, '/') . "[\"']/Ui",2800 $images[1][$i ] . "=\"cid:" . $cid . "\"",3439 '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', 3440 $images[1][$imgindex] . '="cid:' . $cid . '"', 2801 3441 $message 2802 3442 ); … … 2806 3446 } 2807 3447 $this->isHTML(true); 2808 if (empty($this->AltBody)) { 2809 $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n"; 2810 } 2811 //Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better 3448 // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better 2812 3449 $this->Body = $this->normalizeBreaks($message); 2813 3450 $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced)); 3451 if (!$this->alternativeExists()) { 3452 $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . 3453 self::CRLF . self::CRLF; 3454 } 2814 3455 return $this->Body; 2815 3456 } … … 2817 3458 /** 2818 3459 * Convert an HTML string into plain text. 3460 * This is used by msgHTML(). 3461 * Note - older versions of this function used a bundled advanced converter 3462 * which was been removed for license reasons in #232. 3463 * Example usage: 3464 * <code> 3465 * // Use default conversion 3466 * $plain = $mail->html2text($html); 3467 * // Use your own custom converter 3468 * $plain = $mail->html2text($html, function($html) { 3469 * $converter = new MyHtml2text($html); 3470 * return $converter->get_text(); 3471 * }); 3472 * </code> 2819 3473 * @param string $html The HTML text to convert 2820 * @param bool $advanced Should this use the more complex html2text converter or just a simple one? 3474 * @param boolean|callable $advanced Any boolean value to use the internal converter, 3475 * or provide your own callable for custom conversion. 2821 3476 * @return string 2822 3477 */ 2823 3478 public function html2text($html, $advanced = false) 2824 3479 { 2825 if ($advanced) { 2826 require_once 'extras/class.html2text.php'; 2827 $h = new html2text($html); 2828 return $h->get_text(); 3480 if (is_callable($advanced)) { 3481 return call_user_func($advanced, $html); 2829 3482 } 2830 3483 return html_entity_decode( … … 2845 3498 { 2846 3499 $mimes = array( 2847 'xl' => 'application/excel', 2848 'hqx' => 'application/mac-binhex40', 2849 'cpt' => 'application/mac-compactpro', 2850 'bin' => 'application/macbinary', 2851 'doc' => 'application/msword', 2852 'word' => 'application/msword', 3500 'xl' => 'application/excel', 3501 'js' => 'application/javascript', 3502 'hqx' => 'application/mac-binhex40', 3503 'cpt' => 'application/mac-compactpro', 3504 'bin' => 'application/macbinary', 3505 'doc' => 'application/msword', 3506 'word' => 'application/msword', 3507 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 3508 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 3509 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 3510 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 3511 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 3512 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', 3513 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 3514 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 3515 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', 3516 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 2853 3517 'class' => 'application/octet-stream', 2854 'dll' => 'application/octet-stream',2855 'dms' => 'application/octet-stream',2856 'exe' => 'application/octet-stream',2857 'lha' => 'application/octet-stream',2858 'lzh' => 'application/octet-stream',2859 'psd' => 'application/octet-stream',2860 'sea' => 'application/octet-stream',2861 'so' => 'application/octet-stream',2862 'oda' => 'application/oda',2863 'pdf' => 'application/pdf',2864 'ai' => 'application/postscript',2865 'eps' => 'application/postscript',2866 'ps' => 'application/postscript',2867 'smi' => 'application/smil',2868 'smil' => 'application/smil',2869 'mif' => 'application/vnd.mif',2870 'xls' => 'application/vnd.ms-excel',2871 'ppt' => 'application/vnd.ms-powerpoint',3518 'dll' => 'application/octet-stream', 3519 'dms' => 'application/octet-stream', 3520 'exe' => 'application/octet-stream', 3521 'lha' => 'application/octet-stream', 3522 'lzh' => 'application/octet-stream', 3523 'psd' => 'application/octet-stream', 3524 'sea' => 'application/octet-stream', 3525 'so' => 'application/octet-stream', 3526 'oda' => 'application/oda', 3527 'pdf' => 'application/pdf', 3528 'ai' => 'application/postscript', 3529 'eps' => 'application/postscript', 3530 'ps' => 'application/postscript', 3531 'smi' => 'application/smil', 3532 'smil' => 'application/smil', 3533 'mif' => 'application/vnd.mif', 3534 'xls' => 'application/vnd.ms-excel', 3535 'ppt' => 'application/vnd.ms-powerpoint', 2872 3536 'wbxml' => 'application/vnd.wap.wbxml', 2873 'wmlc' => 'application/vnd.wap.wmlc',2874 'dcr' => 'application/x-director',2875 'dir' => 'application/x-director',2876 'dxr' => 'application/x-director',2877 'dvi' => 'application/x-dvi',2878 'gtar' => 'application/x-gtar',2879 'php3' => 'application/x-httpd-php',2880 'php4' => 'application/x-httpd-php',2881 'php' => 'application/x-httpd-php',3537 'wmlc' => 'application/vnd.wap.wmlc', 3538 'dcr' => 'application/x-director', 3539 'dir' => 'application/x-director', 3540 'dxr' => 'application/x-director', 3541 'dvi' => 'application/x-dvi', 3542 'gtar' => 'application/x-gtar', 3543 'php3' => 'application/x-httpd-php', 3544 'php4' => 'application/x-httpd-php', 3545 'php' => 'application/x-httpd-php', 2882 3546 'phtml' => 'application/x-httpd-php', 2883 'phps' => 'application/x-httpd-php-source', 2884 'js' => 'application/x-javascript', 2885 'swf' => 'application/x-shockwave-flash', 2886 'sit' => 'application/x-stuffit', 2887 'tar' => 'application/x-tar', 2888 'tgz' => 'application/x-tar', 2889 'xht' => 'application/xhtml+xml', 3547 'phps' => 'application/x-httpd-php-source', 3548 'swf' => 'application/x-shockwave-flash', 3549 'sit' => 'application/x-stuffit', 3550 'tar' => 'application/x-tar', 3551 'tgz' => 'application/x-tar', 3552 'xht' => 'application/xhtml+xml', 2890 3553 'xhtml' => 'application/xhtml+xml', 2891 'zip' => 'application/zip',2892 'mid' => 'audio/midi',2893 'midi' => 'audio/midi',2894 'mp2' => 'audio/mpeg',2895 'mp3' => 'audio/mpeg',2896 'mpga' => 'audio/mpeg',2897 'aif' => 'audio/x-aiff',2898 'aifc' => 'audio/x-aiff',2899 'aiff' => 'audio/x-aiff',2900 'ram' => 'audio/x-pn-realaudio',2901 'rm' => 'audio/x-pn-realaudio',2902 'rpm' => 'audio/x-pn-realaudio-plugin',2903 'ra' => 'audio/x-realaudio',2904 'wav' => 'audio/x-wav',2905 'bmp' => 'image/bmp',2906 'gif' => 'image/gif',2907 'jpeg' => 'image/jpeg',2908 'jpe' => 'image/jpeg',2909 'jpg' => 'image/jpeg',2910 'png' => 'image/png',2911 'tiff' => 'image/tiff',2912 'tif' => 'image/tiff',2913 'eml' => 'message/rfc822',2914 'css' => 'text/css',2915 'html' => 'text/html',2916 'htm' => 'text/html',3554 'zip' => 'application/zip', 3555 'mid' => 'audio/midi', 3556 'midi' => 'audio/midi', 3557 'mp2' => 'audio/mpeg', 3558 'mp3' => 'audio/mpeg', 3559 'mpga' => 'audio/mpeg', 3560 'aif' => 'audio/x-aiff', 3561 'aifc' => 'audio/x-aiff', 3562 'aiff' => 'audio/x-aiff', 3563 'ram' => 'audio/x-pn-realaudio', 3564 'rm' => 'audio/x-pn-realaudio', 3565 'rpm' => 'audio/x-pn-realaudio-plugin', 3566 'ra' => 'audio/x-realaudio', 3567 'wav' => 'audio/x-wav', 3568 'bmp' => 'image/bmp', 3569 'gif' => 'image/gif', 3570 'jpeg' => 'image/jpeg', 3571 'jpe' => 'image/jpeg', 3572 'jpg' => 'image/jpeg', 3573 'png' => 'image/png', 3574 'tiff' => 'image/tiff', 3575 'tif' => 'image/tiff', 3576 'eml' => 'message/rfc822', 3577 'css' => 'text/css', 3578 'html' => 'text/html', 3579 'htm' => 'text/html', 2917 3580 'shtml' => 'text/html', 2918 'log' => 'text/plain', 2919 'text' => 'text/plain', 2920 'txt' => 'text/plain', 2921 'rtx' => 'text/richtext', 2922 'rtf' => 'text/rtf', 2923 'xml' => 'text/xml', 2924 'xsl' => 'text/xml', 2925 'mpeg' => 'video/mpeg', 2926 'mpe' => 'video/mpeg', 2927 'mpg' => 'video/mpeg', 2928 'mov' => 'video/quicktime', 2929 'qt' => 'video/quicktime', 2930 'rv' => 'video/vnd.rn-realvideo', 2931 'avi' => 'video/x-msvideo', 3581 'log' => 'text/plain', 3582 'text' => 'text/plain', 3583 'txt' => 'text/plain', 3584 'rtx' => 'text/richtext', 3585 'rtf' => 'text/rtf', 3586 'vcf' => 'text/vcard', 3587 'vcard' => 'text/vcard', 3588 'xml' => 'text/xml', 3589 'xsl' => 'text/xml', 3590 'mpeg' => 'video/mpeg', 3591 'mpe' => 'video/mpeg', 3592 'mpg' => 'video/mpeg', 3593 'mov' => 'video/quicktime', 3594 'qt' => 'video/quicktime', 3595 'rv' => 'video/vnd.rn-realvideo', 3596 'avi' => 'video/x-msvideo', 2932 3597 'movie' => 'video/x-sgi-movie' 2933 3598 ); 2934 return (array_key_exists(strtolower($ext), $mimes) ? $mimes[strtolower($ext)]: 'application/octet-stream'); 3599 if (array_key_exists(strtolower($ext), $mimes)) { 3600 return $mimes[strtolower($ext)]; 3601 } 3602 return 'application/octet-stream'; 2935 3603 } 2936 3604 … … 2944 3612 public static function filenameToType($filename) 2945 3613 { 2946 // In case the path is a URL, strip any query string before getting extension3614 // In case the path is a URL, strip any query string before getting extension 2947 3615 $qpos = strpos($filename, '?'); 2948 if ( $qpos !== false) {3616 if (false !== $qpos) { 2949 3617 $filename = substr($filename, 0, $qpos); 2950 3618 } … … 2967 3635 { 2968 3636 $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); 2969 $m = array(); 2970 preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $m); 2971 if (array_key_exists(1, $m)) { 2972 $ret['dirname'] = $m[1]; 2973 } 2974 if (array_key_exists(2, $m)) { 2975 $ret['basename'] = $m[2]; 2976 } 2977 if (array_key_exists(5, $m)) { 2978 $ret['extension'] = $m[5]; 2979 } 2980 if (array_key_exists(3, $m)) { 2981 $ret['filename'] = $m[3]; 3637 $pathinfo = array(); 3638 if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) { 3639 if (array_key_exists(1, $pathinfo)) { 3640 $ret['dirname'] = $pathinfo[1]; 3641 } 3642 if (array_key_exists(2, $pathinfo)) { 3643 $ret['basename'] = $pathinfo[2]; 3644 } 3645 if (array_key_exists(5, $pathinfo)) { 3646 $ret['extension'] = $pathinfo[5]; 3647 } 3648 if (array_key_exists(3, $pathinfo)) { 3649 $ret['filename'] = $pathinfo[3]; 3650 } 2982 3651 } 2983 3652 switch ($options) { … … 2985 3654 case 'dirname': 2986 3655 return $ret['dirname']; 2987 break;2988 3656 case PATHINFO_BASENAME: 2989 3657 case 'basename': 2990 3658 return $ret['basename']; 2991 break;2992 3659 case PATHINFO_EXTENSION: 2993 3660 case 'extension': 2994 3661 return $ret['extension']; 2995 break;2996 3662 case PATHINFO_FILENAME: 2997 3663 case 'filename': 2998 3664 return $ret['filename']; 2999 break;3000 3665 default: 3001 3666 return $ret; … … 3005 3670 /** 3006 3671 * Set or reset instance properties. 3007 * 3672 * You should avoid this function - it's more verbose, less efficient, more error-prone and 3673 * harder to debug than setting properties directly. 3008 3674 * Usage Example: 3009 * $page->set('X-Priority', '3'); 3010 * 3011 * @access public 3012 * @param string $name 3013 * @param mixed $value 3014 * NOTE: will not work with arrays, there are no arrays to set/reset 3015 * @throws phpmailerException 3016 * @return bool 3017 * @todo Should this not be using __set() magic function? 3675 * `$mail->set('SMTPSecure', 'tls');` 3676 * is the same as: 3677 * `$mail->SMTPSecure = 'tls';` 3678 * @access public 3679 * @param string $name The property name to set 3680 * @param mixed $value The value to set the property to 3681 * @return boolean 3682 * @TODO Should this not be using the __set() magic function? 3018 3683 */ 3019 3684 public function set($name, $value = '') 3020 3685 { 3021 try { 3022 if (isset($this->$name)) { 3023 $this->$name = $value; 3024 } else { 3025 throw new phpmailerException($this->lang('variable_set') . $name, self::STOP_CRITICAL); 3026 } 3027 } catch (Exception $e) { 3028 $this->setError($e->getMessage()); 3029 if ($e->getCode() == self::STOP_CRITICAL) { 3030 return false; 3031 } 3032 } 3033 return true; 3686 if (property_exists($this, $name)) { 3687 $this->$name = $value; 3688 return true; 3689 } else { 3690 $this->setError($this->lang('variable_set') . $name); 3691 return false; 3692 } 3034 3693 } 3035 3694 … … 3060 3719 } 3061 3720 3062 3063 /** 3064 * Set the private key file and password for S/MIME signing. 3721 /** 3722 * Set the public and private key files and password for S/MIME signing. 3065 3723 * @access public 3066 3724 * @param string $cert_filename 3067 3725 * @param string $key_filename 3068 3726 * @param string $key_pass Password for private key 3069 */ 3070 public function sign($cert_filename, $key_filename, $key_pass) 3727 * @param string $extracerts_filename Optional path to chain certificate 3728 */ 3729 public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '') 3071 3730 { 3072 3731 $this->sign_cert_file = $cert_filename; 3073 3732 $this->sign_key_file = $key_filename; 3074 3733 $this->sign_key_pass = $key_pass; 3734 $this->sign_extracerts_file = $extracerts_filename; 3075 3735 } 3076 3736 … … 3089 3749 $line .= $txt[$i]; 3090 3750 } else { 3091 $line .= "=" . sprintf("%02X", $ord);3751 $line .= '=' . sprintf('%02X', $ord); 3092 3752 } 3093 3753 } … … 3098 3758 * Generate a DKIM signature. 3099 3759 * @access public 3100 * @param string $s 3760 * @param string $signHeader 3101 3761 * @throws phpmailerException 3102 * @return string 3103 */ 3104 public function DKIM_Sign($s )3762 * @return string The DKIM signature value 3763 */ 3764 public function DKIM_Sign($signHeader) 3105 3765 { 3106 3766 if (!defined('PKCS7_TEXT')) { 3107 3767 if ($this->exceptions) { 3108 throw new phpmailerException($this->lang( "signing") . ' OpenSSL extension missing.');3768 throw new phpmailerException($this->lang('extension_missing') . 'openssl'); 3109 3769 } 3110 3770 return ''; 3111 3771 } 3112 $privKeyStr = file_get_contents($this->DKIM_private);3113 if ( $this->DKIM_passphrase != '') {3772 $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private); 3773 if ('' != $this->DKIM_passphrase) { 3114 3774 $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); 3115 3775 } else { 3116 $privKey = $privKeyStr; 3117 } 3118 if (openssl_sign($s, $signature, $privKey)) { 3119 return base64_encode($signature); 3120 } 3776 $privKey = openssl_pkey_get_private($privKeyStr); 3777 } 3778 //Workaround for missing digest algorithms in old PHP & OpenSSL versions 3779 //@link http://stackoverflow.com/a/11117338/333340 3780 if (version_compare(PHP_VERSION, '5.3.0') >= 0 and 3781 in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) { 3782 if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { 3783 openssl_pkey_free($privKey); 3784 return base64_encode($signature); 3785 } 3786 } else { 3787 $pinfo = openssl_pkey_get_details($privKey); 3788 $hash = hash('sha256', $signHeader); 3789 //'Magic' constant for SHA256 from RFC3447 3790 //@link https://tools.ietf.org/html/rfc3447#page-43 3791 $t = '3031300d060960864801650304020105000420' . $hash; 3792 $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3); 3793 $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t); 3794 3795 if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) { 3796 openssl_pkey_free($privKey); 3797 return base64_encode($signature); 3798 } 3799 } 3800 openssl_pkey_free($privKey); 3121 3801 return ''; 3122 3802 } … … 3125 3805 * Generate a DKIM canonicalization header. 3126 3806 * @access public 3127 * @param string $s Header3807 * @param string $signHeader Header 3128 3808 * @return string 3129 3809 */ 3130 public function DKIM_HeaderC($s )3131 { 3132 $s = preg_replace("/\r\n\s+/", " ", $s);3133 $lines = explode("\r\n", $s );3810 public function DKIM_HeaderC($signHeader) 3811 { 3812 $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader); 3813 $lines = explode("\r\n", $signHeader); 3134 3814 foreach ($lines as $key => $line) { 3135 list($heading, $value) = explode( ":", $line, 2);3815 list($heading, $value) = explode(':', $line, 2); 3136 3816 $heading = strtolower($heading); 3137 $value = preg_replace( "/\s+/", " ", $value); // Compress useless spaces3138 $lines[$key] = $heading . ":". trim($value); // Don't forget to remove WSP around the value3139 } 3140 $s = implode("\r\n", $lines);3141 return $s ;3817 $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces 3818 $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value 3819 } 3820 $signHeader = implode("\r\n", $lines); 3821 return $signHeader; 3142 3822 } 3143 3823 … … 3173 3853 public function DKIM_Add($headers_line, $subject, $body) 3174 3854 { 3175 $DKIMsignatureType = 'rsa-sha 1'; // Signature & hash algorithms3855 $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms 3176 3856 $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body 3177 3857 $DKIMquery = 'dns/txt'; // Query method … … 3181 3861 $from_header = ''; 3182 3862 $to_header = ''; 3863 $date_header = ''; 3183 3864 $current = ''; 3184 3865 foreach ($headers as $header) { … … 3189 3870 $to_header = $header; 3190 3871 $current = 'to_header'; 3872 } elseif (strpos($header, 'Date:') === 0) { 3873 $date_header = $header; 3874 $current = 'date_header'; 3191 3875 } else { 3192 if ( $current&& strpos($header, ' =?') === 0) {3193 $ current .= $header;3876 if (!empty($$current) && strpos($header, ' =?') === 0) { 3877 $$current .= $header; 3194 3878 } else { 3195 3879 $current = ''; … … 3199 3883 $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); 3200 3884 $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); 3885 $date = str_replace('|', '=7C', $this->DKIM_QP($date_header)); 3201 3886 $subject = str_replace( 3202 3887 '|', … … 3206 3891 $body = $this->DKIM_BodyC($body); 3207 3892 $DKIMlen = strlen($body); // Length of body 3208 $DKIMb64 = base64_encode(pack("H*", sha1($body))); // Base64 of packed binary SHA-1 hash of body 3209 $ident = ($this->DKIM_identity == '') ? '' : " i=" . $this->DKIM_identity . ";"; 3210 $dkimhdrs = "DKIM-Signature: v=1; a=" . 3211 $DKIMsignatureType . "; q=" . 3212 $DKIMquery . "; l=" . 3213 $DKIMlen . "; s=" . 3893 $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body 3894 if ('' == $this->DKIM_identity) { 3895 $ident = ''; 3896 } else { 3897 $ident = ' i=' . $this->DKIM_identity . ';'; 3898 } 3899 $dkimhdrs = 'DKIM-Signature: v=1; a=' . 3900 $DKIMsignatureType . '; q=' . 3901 $DKIMquery . '; l=' . 3902 $DKIMlen . '; s=' . 3214 3903 $this->DKIM_selector . 3215 3904 ";\r\n" . 3216 "\tt=" . $DKIMtime . "; c=". $DKIMcanonicalization . ";\r\n" .3217 "\th=From:To: Subject;\r\n" .3218 "\td=" . $this->DKIM_domain . ";". $ident . "\r\n" .3905 "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" . 3906 "\th=From:To:Date:Subject;\r\n" . 3907 "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" . 3219 3908 "\tz=$from\r\n" . 3220 3909 "\t|$to\r\n" . 3910 "\t|$date\r\n" . 3221 3911 "\t|$subject;\r\n" . 3222 3912 "\tbh=" . $DKIMb64 . ";\r\n" . 3223 3913 "\tb="; 3224 3914 $toSign = $this->DKIM_HeaderC( 3225 $from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs 3915 $from_header . "\r\n" . 3916 $to_header . "\r\n" . 3917 $date_header . "\r\n" . 3918 $subject_header . "\r\n" . 3919 $dkimhdrs 3226 3920 ); 3227 3921 $signed = $this->DKIM_Sign($toSign); … … 3230 3924 3231 3925 /** 3926 * Detect if a string contains a line longer than the maximum line length allowed. 3927 * @param string $str 3928 * @return boolean 3929 * @static 3930 */ 3931 public static function hasLineLongerThanMax($str) 3932 { 3933 //+2 to include CRLF line break for a 1000 total 3934 return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str); 3935 } 3936 3937 /** 3938 * Allows for public read access to 'to' property. 3939 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3940 * @access public 3941 * @return array 3942 */ 3943 public function getToAddresses() 3944 { 3945 return $this->to; 3946 } 3947 3948 /** 3949 * Allows for public read access to 'cc' property. 3950 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3951 * @access public 3952 * @return array 3953 */ 3954 public function getCcAddresses() 3955 { 3956 return $this->cc; 3957 } 3958 3959 /** 3960 * Allows for public read access to 'bcc' property. 3961 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3962 * @access public 3963 * @return array 3964 */ 3965 public function getBccAddresses() 3966 { 3967 return $this->bcc; 3968 } 3969 3970 /** 3971 * Allows for public read access to 'ReplyTo' property. 3972 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3973 * @access public 3974 * @return array 3975 */ 3976 public function getReplyToAddresses() 3977 { 3978 return $this->ReplyTo; 3979 } 3980 3981 /** 3982 * Allows for public read access to 'all_recipients' property. 3983 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. 3984 * @access public 3985 * @return array 3986 */ 3987 public function getAllRecipientAddresses() 3988 { 3989 return $this->all_recipients; 3990 } 3991 3992 /** 3232 3993 * Perform a callback. 3233 * @param bool $isSent3234 * @param string$to3235 * @param string$cc3236 * @param string$bcc3994 * @param boolean $isSent 3995 * @param array $to 3996 * @param array $cc 3997 * @param array $bcc 3237 3998 * @param string $subject 3238 3999 * @param string $body 3239 4000 * @param string $from 3240 4001 */ 3241 protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from = null)4002 protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from) 3242 4003 { 3243 4004 if (!empty($this->action_function) && is_callable($this->action_function)) { -
branches/4.1/src/wp-includes/class-smtp.php
r29783 r39727 2 2 /** 3 3 * PHPMailer RFC821 SMTP email transport class. 4 * Version 5.2.7 5 * PHP version 5.0.0 6 * @category PHP 7 * @package PHPMailer 8 * @link https://github.com/PHPMailer/PHPMailer/ 9 * @author Marcus Bointon (coolbru) <phpmailer@synchromedia.co.uk> 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> 10 8 * @author Jim Jagielski (jimjag) <jimjag@gmail.com> 11 9 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net> 12 * @ copyright 2013 Marcus Bointon13 * @copyright 20 04 - 2008 Andy Prevost10 * @author Brent R. Matzelle (original founder) 11 * @copyright 2014 Marcus Bointon 14 12 * @copyright 2010 - 2012 Jim Jagielski 15 * @license http://www.gnu.org/copyleft/lesser.html Distributed under the Lesser General Public License (LGPL) 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. 16 18 */ 17 19 18 20 /** 19 21 * PHPMailer RFC821 SMTP email transport class. 20 * 21 * Implements RFC 821 SMTP commands 22 * and provides some utility methods for sending mail to an SMTP server. 23 * 24 * PHP Version 5.0.0 25 * 26 * @category PHP 27 * @package PHPMailer 28 * @link https://github.com/PHPMailer/PHPMailer/blob/master/class.smtp.php 29 * @author Chris Ryan <unknown@example.com> 30 * @author Marcus Bointon <phpmailer@synchromedia.co.uk> 31 * @license http://www.gnu.org/copyleft/lesser.html Distributed under the Lesser General Public License (LGPL) 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> 32 26 */ 33 34 27 class SMTP 35 28 { 36 29 /** 30 * The PHPMailer SMTP version number. 31 * @var string 32 */ 33 const VERSION = '5.2.21'; 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 /** 37 79 * The PHPMailer SMTP Version number. 38 */ 39 const VERSION = '5.2.7'; 40 41 /** 42 * SMTP line break constant. 43 */ 44 const CRLF = "\r\n"; 45 46 /** 47 * The SMTP port to use if one is not specified. 48 */ 49 const DEFAULT_SMTP_PORT = 25; 50 51 /** 52 * The PHPMailer SMTP Version number. 53 * @type string 54 * @deprecated This should be a constant 80 * @var string 81 * @deprecated Use the `VERSION` constant instead 55 82 * @see SMTP::VERSION 56 83 */ 57 public $Version = '5.2. 7';84 public $Version = '5.2.21'; 58 85 59 86 /** 60 87 * SMTP server port number. 61 * @ type int62 * @deprecated This is only ever u ed as default value, so should be a constant88 * @var integer 89 * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead 63 90 * @see SMTP::DEFAULT_SMTP_PORT 64 91 */ … … 66 93 67 94 /** 68 * SMTP reply line ending 69 * @ typestring70 * @deprecated Use the classconstant instead95 * SMTP reply line ending. 96 * @var string 97 * @deprecated Use the `CRLF` constant instead 71 98 * @see SMTP::CRLF 72 99 */ … … 75 102 /** 76 103 * Debug output level. 77 * Options: 0 for no output, 1 for commands, 2 for data and commands 78 * @type int 79 */ 80 public $do_debug = 0; 81 82 /** 83 * The function/method to use for debugging output. 84 * Options: 'echo', 'html' or 'error_log' 85 * @type string 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 86 126 */ 87 127 public $Debugoutput = 'echo'; … … 89 129 /** 90 130 * Whether to use VERP. 91 * @type bool 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 92 134 */ 93 135 public $do_verp = false; 94 136 95 137 /** 96 * The SMTP timeout value for reads, in seconds. 97 * @type int 98 */ 99 public $Timeout = 15; 100 101 /** 102 * The SMTP timelimit value for reads, in seconds. 103 * @type int 104 */ 105 public $Timelimit = 30; 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 * @var array patterns to extract smtp transaction id from smtp reply 155 * Only first capture group will be use, use non-capturing group to deal with it 156 * Extend this class to override this property to fulfil your needs. 157 */ 158 protected $smtp_transaction_id_patterns = array( 159 'exim' => '/[0-9]{3} OK id=(.*)/', 160 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/', 161 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/' 162 ); 106 163 107 164 /** 108 165 * The socket for the server connection. 109 * @ typeresource166 * @var resource 110 167 */ 111 168 protected $smtp_conn; 112 169 113 170 /** 114 * Error message, if any, for the last call. 115 * @type string 116 */ 117 protected $error = ''; 171 * Error information, if any, for the last SMTP command. 172 * @var array 173 */ 174 protected $error = array( 175 'error' => '', 176 'detail' => '', 177 'smtp_code' => '', 178 'smtp_code_ex' => '' 179 ); 118 180 119 181 /** 120 182 * The reply the server sent to us for HELO. 121 * @type string 122 */ 123 protected $helo_rply = ''; 183 * If null, no HELO string has yet been received. 184 * @var string|null 185 */ 186 protected $helo_rply = null; 187 188 /** 189 * The set of SMTP extensions sent in reply to EHLO command. 190 * Indexes of the array are extension names. 191 * Value at index 'HELO' or 'EHLO' (according to command that was sent) 192 * represents the server name. In case of HELO it is the only element of the array. 193 * Other values can be boolean TRUE or an array containing extension options. 194 * If null, no HELO/EHLO string has yet been received. 195 * @var array|null 196 */ 197 protected $server_caps = null; 124 198 125 199 /** 126 200 * The most recent reply received from the server. 127 * @ typestring201 * @var string 128 202 */ 129 203 protected $last_reply = ''; 130 204 131 205 /** 132 * Constructor.133 * @access public134 */135 public function __construct()136 {137 $this->smtp_conn = 0;138 $this->error = null;139 $this->helo_rply = null;140 141 $this->do_debug = 0;142 }143 144 /**145 206 * Output debugging info via a user-selected method. 207 * @see SMTP::$Debugoutput 208 * @see SMTP::$do_debug 146 209 * @param string $str Debug string to output 210 * @param integer $level The debug level of this message; see DEBUG_* constants 147 211 * @return void 148 212 */ 149 protected function edebug($str) 150 { 213 protected function edebug($str, $level = 0) 214 { 215 if ($level > $this->do_debug) { 216 return; 217 } 218 //Avoid clash with built-in function names 219 if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { 220 call_user_func($this->Debugoutput, $str, $level); 221 return; 222 } 151 223 switch ($this->Debugoutput) { 152 224 case 'error_log': … … 165 237 case 'echo': 166 238 default: 167 //Just echoes whatever was received 168 echo $str; 239 //Normalize line breaks 240 $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); 241 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 242 "\n", 243 "\n \t ", 244 trim($str) 245 )."\n"; 169 246 } 170 247 } … … 172 249 /** 173 250 * Connect to an SMTP server. 174 * @param string $host 175 * @param int $portThe port number to connect to176 * @param int $timeout How long to wait for the connection to open251 * @param string $host SMTP server IP or host name 252 * @param integer $port The port number to connect to 253 * @param integer $timeout How long to wait for the connection to open 177 254 * @param array $options An array of options for stream_context_create() 178 255 * @access public 179 * @return bool 256 * @return boolean 180 257 */ 181 258 public function connect($host, $port = null, $timeout = 30, $options = array()) 182 259 { 260 static $streamok; 261 //This is enabled by default since 5.0.0 but some providers disable it 262 //Check this once and cache the result 263 if (is_null($streamok)) { 264 $streamok = function_exists('stream_socket_client'); 265 } 183 266 // Clear errors to avoid confusion 184 $this->error = null; 185 267 $this->setError(''); 186 268 // Make sure we are __not__ connected 187 269 if ($this->connected()) { 188 270 // Already connected, generate error 189 $this-> error = array('error' =>'Already connected to a server');271 $this->setError('Already connected to a server'); 190 272 return false; 191 273 } 192 193 274 if (empty($port)) { 194 275 $port = self::DEFAULT_SMTP_PORT; 195 276 } 196 197 277 // Connect to the SMTP server 278 $this->edebug( 279 "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true), 280 self::DEBUG_CONNECTION 281 ); 198 282 $errno = 0; 199 283 $errstr = ''; 200 $socket_context = stream_context_create($options); 201 //Suppress errors; connection failures are handled at a higher level 202 $this->smtp_conn = @stream_socket_client( 203 $host . ":" . $port, 204 $errno, 205 $errstr, 206 $timeout, 207 STREAM_CLIENT_CONNECT, 208 $socket_context 209 ); 210 284 if ($streamok) { 285 $socket_context = stream_context_create($options); 286 set_error_handler(array($this, 'errorHandler')); 287 $this->smtp_conn = stream_socket_client( 288 $host . ":" . $port, 289 $errno, 290 $errstr, 291 $timeout, 292 STREAM_CLIENT_CONNECT, 293 $socket_context 294 ); 295 restore_error_handler(); 296 } else { 297 //Fall back to fsockopen which should work in more places, but is missing some features 298 $this->edebug( 299 "Connection: stream_socket_client not available, falling back to fsockopen", 300 self::DEBUG_CONNECTION 301 ); 302 set_error_handler(array($this, 'errorHandler')); 303 $this->smtp_conn = fsockopen( 304 $host, 305 $port, 306 $errno, 307 $errstr, 308 $timeout 309 ); 310 restore_error_handler(); 311 } 211 312 // Verify we connected properly 212 if ( empty($this->smtp_conn)) {213 $this-> error = array(214 ' error' => 'Failed to connect to server',215 'errno' =>$errno,216 'errstr' =>$errstr313 if (!is_resource($this->smtp_conn)) { 314 $this->setError( 315 'Failed to connect to server', 316 $errno, 317 $errstr 217 318 ); 218 if ($this->do_debug >= 1) { 219 $this->edebug( 220 'SMTP -> ERROR: ' . $this->error['error'] 221 . ": $errstr ($errno)" 222 ); 223 } 319 $this->edebug( 320 'SMTP ERROR: ' . $this->error['error'] 321 . ": $errstr ($errno)", 322 self::DEBUG_CLIENT 323 ); 224 324 return false; 225 325 } 226 326 $this->edebug('Connection: opened', self::DEBUG_CONNECTION); 227 327 // SMTP server can take longer to respond, give longer timeout for first read 228 328 // Windows does not have support for this timeout function 229 329 if (substr(PHP_OS, 0, 3) != 'WIN') { 230 330 $max = ini_get('max_execution_time'); 231 if ($max != 0 && $timeout > $max) { // Don't bother if unlimited 331 // Don't bother if unlimited 332 if ($max != 0 && $timeout > $max) { 232 333 @set_time_limit($timeout); 233 334 } 234 335 stream_set_timeout($this->smtp_conn, $timeout, 0); 235 336 } 236 237 337 // Get any announcement 238 338 $announce = $this->get_lines(); 239 240 if ($this->do_debug >= 2) { 241 $this->edebug('SMTP -> FROM SERVER:' . $announce); 242 } 243 339 $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); 244 340 return true; 245 341 } … … 248 344 * Initiate a TLS (encrypted) session. 249 345 * @access public 250 * @return bool 346 * @return boolean 251 347 */ 252 348 public function startTLS() 253 349 { 254 if (!$this->sendCommand( "STARTTLS", "STARTTLS", 220)) {350 if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { 255 351 return false; 256 352 } 353 354 //Allow the best TLS version(s) we can 355 $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; 356 357 //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT 358 //so add them back in manually if we can 359 if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { 360 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; 361 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; 362 } 363 257 364 // Begin encrypted connection 258 365 if (!stream_socket_enable_crypto( 259 366 $this->smtp_conn, 260 367 true, 261 STREAM_CRYPTO_METHOD_TLS_CLIENT 262 ) 263 ) { 368 $crypto_method 369 )) { 264 370 return false; 265 371 } … … 271 377 * Must be run after hello(). 272 378 * @see hello() 273 * @param string $username 274 * @param string $password 275 * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5)276 * @param string $realm 379 * @param string $username The user name 380 * @param string $password The password 381 * @param string $authtype The auth type (PLAIN, LOGIN, CRAM-MD5) 382 * @param string $realm The auth realm for NTLM 277 383 * @param string $workstation The auth workstation for NTLM 278 * @ access public279 * @return bool True if successfully authenticated. 384 * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) 385 * @return bool True if successfully authenticated.* @access public 280 386 */ 281 387 public function authenticate( 282 388 $username, 283 389 $password, 284 $authtype = 'LOGIN',390 $authtype = null, 285 391 $realm = '', 286 $workstation = '' 392 $workstation = '', 393 $OAuth = null 287 394 ) { 288 if (empty($authtype)) { 395 if (!$this->server_caps) { 396 $this->setError('Authentication is not allowed before HELO/EHLO'); 397 return false; 398 } 399 400 if (array_key_exists('EHLO', $this->server_caps)) { 401 // SMTP extensions are available. Let's try to find a proper authentication method 402 403 if (!array_key_exists('AUTH', $this->server_caps)) { 404 $this->setError('Authentication is not allowed at this stage'); 405 // 'at this stage' means that auth may be allowed after the stage changes 406 // e.g. after STARTTLS 407 return false; 408 } 409 410 self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); 411 self::edebug( 412 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), 413 self::DEBUG_LOWLEVEL 414 ); 415 416 if (empty($authtype)) { 417 foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN') as $method) { 418 if (in_array($method, $this->server_caps['AUTH'])) { 419 $authtype = $method; 420 break; 421 } 422 } 423 if (empty($authtype)) { 424 $this->setError('No supported authentication methods found'); 425 return false; 426 } 427 self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL); 428 } 429 430 if (!in_array($authtype, $this->server_caps['AUTH'])) { 431 $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); 432 return false; 433 } 434 } elseif (empty($authtype)) { 289 435 $authtype = 'LOGIN'; 290 436 } 291 292 437 switch ($authtype) { 293 438 case 'PLAIN': … … 318 463 } 319 464 break; 320 case 'NTLM':321 /*322 * ntlm_sasl_client.php323 * Bundled with Permission324 *325 * How to telnet in windows:326 * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx327 * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication328 */329 require_once 'extras/ntlm_sasl_client.php';330 $temp = new stdClass();331 $ntlm_client = new ntlm_sasl_client_class;332 //Check that functions are available333 if (!$ntlm_client->Initialize($temp)) {334 $this->error = array('error' => $temp->error);335 if ($this->do_debug >= 1) {336 $this->edebug(337 'You need to enable some modules in your php.ini file: '338 . $this->error['error']339 );340 }341 return false;342 }343 //msg1344 $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1345 346 if (!$this->sendCommand(347 'AUTH NTLM',348 'AUTH NTLM ' . base64_encode($msg1),349 334350 )351 ) {352 return false;353 }354 355 //Though 0 based, there is a white space after the 3 digit number356 //msg2357 $challenge = substr($this->last_reply, 3);358 $challenge = base64_decode($challenge);359 $ntlm_res = $ntlm_client->NTLMResponse(360 substr($challenge, 24, 8),361 $password362 );363 //msg3364 $msg3 = $ntlm_client->TypeMsg3(365 $ntlm_res,366 $username,367 $realm,368 $workstation369 );370 // send encoded username371 return $this->sendCommand('Username', base64_encode($msg3), 235);372 break;373 465 case 'CRAM-MD5': 374 466 // Start authentication … … 384 476 // send encoded credentials 385 477 return $this->sendCommand('Username', base64_encode($response), 235); 386 break; 478 default: 479 $this->setError("Authentication method \"$authtype\" is not supported"); 480 return false; 387 481 } 388 482 return true; … … 412 506 // by Lance Rushing 413 507 414 $b = 64; // byte length for md5415 if (strlen($key) > $b ) {508 $bytelen = 64; // byte length for md5 509 if (strlen($key) > $bytelen) { 416 510 $key = pack('H*', md5($key)); 417 511 } 418 $key = str_pad($key, $b , chr(0x00));419 $ipad = str_pad('', $b , chr(0x36));420 $opad = str_pad('', $b , chr(0x5c));512 $key = str_pad($key, $bytelen, chr(0x00)); 513 $ipad = str_pad('', $bytelen, chr(0x36)); 514 $opad = str_pad('', $bytelen, chr(0x5c)); 421 515 $k_ipad = $key ^ $ipad; 422 516 $k_opad = $key ^ $opad; … … 428 522 * Check connection state. 429 523 * @access public 430 * @return bool True if connected.524 * @return boolean True if connected. 431 525 */ 432 526 public function connected() 433 527 { 434 if ( !empty($this->smtp_conn)) {528 if (is_resource($this->smtp_conn)) { 435 529 $sock_status = stream_get_meta_data($this->smtp_conn); 436 530 if ($sock_status['eof']) { 437 // the socket is valid but we are not connected 438 if ($this->do_debug >= 1) { 439 $this->edebug( 440 'SMTP -> NOTICE: EOF caught while checking if connected' 441 ); 442 } 531 // The socket is valid but we are not connected 532 $this->edebug( 533 'SMTP NOTICE: EOF caught while checking if connected', 534 self::DEBUG_CLIENT 535 ); 443 536 $this->close(); 444 537 return false; … … 458 551 public function close() 459 552 { 460 $this->error = null; // so there is no confusion 553 $this->setError(''); 554 $this->server_caps = null; 461 555 $this->helo_rply = null; 462 if ( !empty($this->smtp_conn)) {556 if (is_resource($this->smtp_conn)) { 463 557 // close the connection and cleanup 464 558 fclose($this->smtp_conn); 465 $this->smtp_conn = 0; 559 $this->smtp_conn = null; //Makes for cleaner serialization 560 $this->edebug('Connection: closed', self::DEBUG_CONNECTION); 466 561 } 467 562 } … … 477 572 * @param string $msg_data Message data to send 478 573 * @access public 479 * @return bool 574 * @return boolean 480 575 */ 481 576 public function data($msg_data) 482 577 { 578 //This will use the standard timelimit 483 579 if (!$this->sendCommand('DATA', 'DATA', 354)) { 484 580 return false; … … 486 582 487 583 /* The server is ready to accept data! 488 * according to rfc821 we should not send more than 1000 489 * including the CRLF 490 * characters on a single line so we will break the data up 491 * into lines by \r and/or \n then if needed we will break 492 * each of those into smaller lines to fit within the limit. 493 * in addition we will be looking for lines that start with 494 * a period '.' and append and additional period '.' to that 495 * line. NOTE: this does not count towards limit. 584 * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) 585 * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into 586 * smaller lines to fit within the limit. 587 * We will also look for lines that start with a '.' and prepend an additional '.'. 588 * NOTE: this does not count towards line-length limit. 496 589 */ 497 590 498 // Normalize the line breaks before exploding 499 $msg_data = str_replace("\r\n", "\n", $msg_data); 500 $msg_data = str_replace("\r", "\n", $msg_data); 501 $lines = explode("\n", $msg_data); 502 503 /* We need to find a good way to determine if headers are 504 * in the msg_data or if it is a straight msg body 505 * currently I am assuming rfc822 definitions of msg headers 506 * and if the first field of the first line (':' separated) 507 * does not contain a space then it _should_ be a header 508 * and we can process all lines before a blank "" line as 509 * headers. 591 // Normalize line breaks before exploding 592 $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); 593 594 /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field 595 * of the first line (':' separated) does not contain a space then it _should_ be a header and we will 596 * process all lines before a blank line as headers. 510 597 */ 511 598 512 599 $field = substr($lines[0], 0, strpos($lines[0], ':')); 513 600 $in_headers = false; 514 if (!empty($field) && !strstr($field, ' ')) {601 if (!empty($field) && strpos($field, ' ') === false) { 515 602 $in_headers = true; 516 603 } 517 604 518 //RFC 2822 section 2.1.1 limit519 $max_line_length = 998;520 521 605 foreach ($lines as $line) { 522 $lines_out = null;523 if ($ line == '' && $in_headers) {606 $lines_out = array(); 607 if ($in_headers and $line == '') { 524 608 $in_headers = false; 525 609 } 526 // ok we need to break this line up into several smaller lines 527 while (strlen($line) > $max_line_length) { 528 $pos = strrpos(substr($line, 0, $max_line_length), ' '); 529 530 // Patch to fix DOS attack 610 //Break this line up into several smaller lines if it's too long 611 //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), 612 while (isset($line[self::MAX_LINE_LENGTH])) { 613 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on 614 //so as to avoid breaking in the middle of a word 615 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); 616 //Deliberately matches both false and 0 531 617 if (!$pos) { 532 $pos = $max_line_length - 1; 618 //No nice break found, add a hard break 619 $pos = self::MAX_LINE_LENGTH - 1; 533 620 $lines_out[] = substr($line, 0, $pos); 534 621 $line = substr($line, $pos); 535 622 } else { 623 //Break at the found point 536 624 $lines_out[] = substr($line, 0, $pos); 625 //Move along by the amount we dealt with 537 626 $line = substr($line, $pos + 1); 538 627 } 539 540 /* If processing headers add a LWSP-char to the front of new line 541 * rfc822 on long msg headers 542 */ 628 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 543 629 if ($in_headers) { 544 630 $line = "\t" . $line; … … 547 633 $lines_out[] = $line; 548 634 549 // send the lines to the server 550 while (list(, $line_out) = @each($lines_out)) { 551 if (strlen($line_out) > 0) { 552 if (substr($line_out, 0, 1) == '.') { 553 $line_out = '.' . $line_out; 554 } 635 //Send the lines to the server 636 foreach ($lines_out as $line_out) { 637 //RFC2821 section 4.5.2 638 if (!empty($line_out) and $line_out[0] == '.') { 639 $line_out = '.' . $line_out; 555 640 } 556 641 $this->client_send($line_out . self::CRLF); … … 558 643 } 559 644 560 // Message data has been sent, complete the command 561 return $this->sendCommand('DATA END', '.', 250); 645 //Message data has been sent, complete the command 646 //Increase timelimit for end of DATA command 647 $savetimelimit = $this->Timelimit; 648 $this->Timelimit = $this->Timelimit * 2; 649 $result = $this->sendCommand('DATA END', '.', 250); 650 //Restore timelimit 651 $this->Timelimit = $savetimelimit; 652 return $result; 562 653 } 563 654 … … 566 657 * Used to identify the sending server to the receiving server. 567 658 * This makes sure that client and server are in a known state. 568 * Implements fromRFC 821: HELO <SP> <domain> <CRLF>659 * Implements RFC 821: HELO <SP> <domain> <CRLF> 569 660 * and RFC 2821 EHLO. 570 661 * @param string $host The host name or IP to connect to 571 662 * @access public 572 * @return bool 663 * @return boolean 573 664 */ 574 665 public function hello($host = '') 575 666 { 576 // Try extended hello first (RFC 2821) 577 if (!$this->sendHello('EHLO', $host)) { 578 if (!$this->sendHello('HELO', $host)) { 579 return false; 580 } 581 } 582 583 return true; 667 //Try extended hello first (RFC 2821) 668 return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); 584 669 } 585 670 … … 589 674 * @see hello() 590 675 * @param string $hello The HELO string 591 * @param string $host 676 * @param string $host The hostname to say we are 592 677 * @access protected 593 * @return bool 678 * @return boolean 594 679 */ 595 680 protected function sendHello($hello, $host) … … 597 682 $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); 598 683 $this->helo_rply = $this->last_reply; 684 if ($noerror) { 685 $this->parseHelloFields($hello); 686 } else { 687 $this->server_caps = null; 688 } 599 689 return $noerror; 690 } 691 692 /** 693 * Parse a reply to HELO/EHLO command to discover server extensions. 694 * In case of HELO, the only parameter that can be discovered is a server name. 695 * @access protected 696 * @param string $type - 'HELO' or 'EHLO' 697 */ 698 protected function parseHelloFields($type) 699 { 700 $this->server_caps = array(); 701 $lines = explode("\n", $this->helo_rply); 702 703 foreach ($lines as $n => $s) { 704 //First 4 chars contain response code followed by - or space 705 $s = trim(substr($s, 4)); 706 if (empty($s)) { 707 continue; 708 } 709 $fields = explode(' ', $s); 710 if (!empty($fields)) { 711 if (!$n) { 712 $name = $type; 713 $fields = $fields[0]; 714 } else { 715 $name = array_shift($fields); 716 switch ($name) { 717 case 'SIZE': 718 $fields = ($fields ? $fields[0] : 0); 719 break; 720 case 'AUTH': 721 if (!is_array($fields)) { 722 $fields = array(); 723 } 724 break; 725 default: 726 $fields = true; 727 } 728 } 729 $this->server_caps[$name] = $fields; 730 } 731 } 600 732 } 601 733 … … 609 741 * @param string $from Source address of this message 610 742 * @access public 611 * @return bool 743 * @return boolean 612 744 */ 613 745 public function mail($from) … … 625 757 * Closes the socket if there is no error or the $close_on_error argument is true. 626 758 * Implements from rfc 821: QUIT <CRLF> 627 * @param bool $close_on_error Should the connection close if an error occurs?628 * @access public 629 * @return bool 759 * @param boolean $close_on_error Should the connection close if an error occurs? 760 * @access public 761 * @return boolean 630 762 */ 631 763 public function quit($close_on_error = true) 632 764 { 633 765 $noerror = $this->sendCommand('QUIT', 'QUIT', 221); 634 $e = $this->error; //Save any error766 $err = $this->error; //Save any error 635 767 if ($noerror or $close_on_error) { 636 768 $this->close(); 637 $this->error = $e ; //Restore any error from the quit command769 $this->error = $err; //Restore any error from the quit command 638 770 } 639 771 return $noerror; … … 642 774 /** 643 775 * Send an SMTP RCPT command. 644 * Sets the TO argument to $to .776 * Sets the TO argument to $toaddr. 645 777 * Returns true if the recipient was accepted false if it was rejected. 646 778 * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF> 647 * @param string $ toThe address the message is being sent to648 * @access public 649 * @return bool 650 */ 651 public function recipient($ to)779 * @param string $address The address the message is being sent to 780 * @access public 781 * @return boolean 782 */ 783 public function recipient($address) 652 784 { 653 785 return $this->sendCommand( 654 'RCPT TO 655 'RCPT TO:<' . $ to. '>',786 'RCPT TO', 787 'RCPT TO:<' . $address . '>', 656 788 array(250, 251) 657 789 ); … … 663 795 * Implements rfc 821: RSET <CRLF> 664 796 * @access public 665 * @return bool True on success.797 * @return boolean True on success. 666 798 */ 667 799 public function reset() … … 672 804 /** 673 805 * Send a command to an SMTP server and check its return code. 674 * @param string $command 806 * @param string $command The command name - not sent to the server 675 807 * @param string $commandstring The actual command to send 676 * @param int |array $expectOne or more expected integer success codes808 * @param integer|array $expect One or more expected integer success codes 677 809 * @access protected 678 * @return bool True on success.810 * @return boolean True on success. 679 811 */ 680 812 protected function sendCommand($command, $commandstring, $expect) 681 813 { 682 814 if (!$this->connected()) { 683 $this->error = array( 684 "error" => "Called $command without being connected" 815 $this->setError("Called $command without being connected"); 816 return false; 817 } 818 //Reject line breaks in all commands 819 if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { 820 $this->setError("Command '$command' contained line breaks"); 821 return false; 822 } 823 $this->client_send($commandstring . self::CRLF); 824 825 $this->last_reply = $this->get_lines(); 826 // Fetch SMTP code and possible error code explanation 827 $matches = array(); 828 if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { 829 $code = $matches[1]; 830 $code_ex = (count($matches) > 2 ? $matches[2] : null); 831 // Cut off error code from each response line 832 $detail = preg_replace( 833 "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m", 834 '', 835 $this->last_reply 836 ); 837 } else { 838 // Fall back to simple parsing if regex fails 839 $code = substr($this->last_reply, 0, 3); 840 $code_ex = null; 841 $detail = substr($this->last_reply, 4); 842 } 843 844 $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); 845 846 if (!in_array($code, (array)$expect)) { 847 $this->setError( 848 "$command command failed", 849 $detail, 850 $code, 851 $code_ex 852 ); 853 $this->edebug( 854 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, 855 self::DEBUG_CLIENT 685 856 ); 686 857 return false; 687 858 } 688 $this->client_send($commandstring . self::CRLF); 689 690 $reply = $this->get_lines(); 691 $code = substr($reply, 0, 3); 692 693 if ($this->do_debug >= 2) { 694 $this->edebug('SMTP -> FROM SERVER:' . $reply); 695 } 696 697 if (!in_array($code, (array)$expect)) { 698 $this->last_reply = null; 699 $this->error = array( 700 "error" => "$command command failed", 701 "smtp_code" => $code, 702 "detail" => substr($reply, 4) 703 ); 704 if ($this->do_debug >= 1) { 705 $this->edebug( 706 'SMTP -> ERROR: ' . $this->error['error'] . ': ' . $reply 707 ); 708 } 709 return false; 710 } 711 712 $this->last_reply = $reply; 713 $this->error = null; 859 860 $this->setError(''); 714 861 return true; 715 862 } … … 726 873 * @param string $from The address the message is from 727 874 * @access public 728 * @return bool 875 * @return boolean 729 876 */ 730 877 public function sendAndMail($from) 731 878 { 732 return $this->sendCommand( "SAML", "SAML FROM:$from", 250);879 return $this->sendCommand('SAML', "SAML FROM:$from", 250); 733 880 } 734 881 … … 737 884 * @param string $name The name to verify 738 885 * @access public 739 * @return bool 886 * @return boolean 740 887 */ 741 888 public function verify($name) 742 889 { 743 return $this->sendCommand( "VRFY", "VRFY $name", array(250, 251));890 return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); 744 891 } 745 892 … … 748 895 * Used to keep keep-alives alive, doesn't actually do anything 749 896 * @access public 750 * @return bool 897 * @return boolean 751 898 */ 752 899 public function noop() 753 900 { 754 return $this->sendCommand( "NOOP", "NOOP", 250);901 return $this->sendCommand('NOOP', 'NOOP', 250); 755 902 } 756 903 … … 758 905 * Send an SMTP TURN command. 759 906 * This is an optional command for SMTP that this class does not support. 760 * This method is here to make the RFC821 Definition 761 * complete for this class and __may__ be implemented in future907 * This method is here to make the RFC821 Definition complete for this class 908 * and _may_ be implemented in future 762 909 * Implements from rfc 821: TURN <CRLF> 763 910 * @access public 764 * @return bool 911 * @return boolean 765 912 */ 766 913 public function turn() 767 914 { 768 $this->error = array( 769 'error' => 'The SMTP TURN command is not implemented' 770 ); 771 if ($this->do_debug >= 1) { 772 $this->edebug('SMTP -> NOTICE: ' . $this->error['error']); 773 } 915 $this->setError('The SMTP TURN command is not implemented'); 916 $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); 774 917 return false; 775 918 } … … 779 922 * @param string $data The data to send 780 923 * @access public 781 * @return int |bool The number of bytes sent to the server or FALSEon error924 * @return integer|boolean The number of bytes sent to the server or false on error 782 925 */ 783 926 public function client_send($data) 784 927 { 785 if ($this->do_debug >= 1) { 786 $this->edebug("CLIENT -> SMTP: $data"); 787 } 928 $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); 788 929 return fwrite($this->smtp_conn, $data); 789 930 } … … 797 938 { 798 939 return $this->error; 940 } 941 942 /** 943 * Get SMTP extensions available on the server 944 * @access public 945 * @return array|null 946 */ 947 public function getServerExtList() 948 { 949 return $this->server_caps; 950 } 951 952 /** 953 * A multipurpose method 954 * The method works in three ways, dependent on argument value and current state 955 * 1. HELO/EHLO was not sent - returns null and set up $this->error 956 * 2. HELO was sent 957 * $name = 'HELO': returns server name 958 * $name = 'EHLO': returns boolean false 959 * $name = any string: returns null and set up $this->error 960 * 3. EHLO was sent 961 * $name = 'HELO'|'EHLO': returns server name 962 * $name = any string: if extension $name exists, returns boolean True 963 * or its options. Otherwise returns boolean False 964 * In other words, one can use this method to detect 3 conditions: 965 * - null returned: handshake was not or we don't know about ext (refer to $this->error) 966 * - false returned: the requested feature exactly not exists 967 * - positive value returned: the requested feature exists 968 * @param string $name Name of SMTP extension or 'HELO'|'EHLO' 969 * @return mixed 970 */ 971 public function getServerExt($name) 972 { 973 if (!$this->server_caps) { 974 $this->setError('No HELO/EHLO was sent'); 975 return null; 976 } 977 978 // the tight logic knot ;) 979 if (!array_key_exists($name, $this->server_caps)) { 980 if ($name == 'HELO') { 981 return $this->server_caps['EHLO']; 982 } 983 if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { 984 return false; 985 } 986 $this->setError('HELO handshake was used. Client knows nothing about server extensions'); 987 return null; 988 } 989 990 return $this->server_caps[$name]; 799 991 } 800 992 … … 820 1012 protected function get_lines() 821 1013 { 1014 // If the connection is bad, give up straight away 1015 if (!is_resource($this->smtp_conn)) { 1016 return ''; 1017 } 822 1018 $data = ''; 823 1019 $endtime = 0; 824 // If the connection is bad, give up now825 if (!is_resource($this->smtp_conn)) {826 return $data;827 }828 1020 stream_set_timeout($this->smtp_conn, $this->Timeout); 829 1021 if ($this->Timelimit > 0) { … … 832 1024 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { 833 1025 $str = @fgets($this->smtp_conn, 515); 834 if ($this->do_debug >= 4) { 835 $this->edebug("SMTP -> get_lines(): \$data was \"$data\""); 836 $this->edebug("SMTP -> get_lines(): \$str is \"$str\""); 837 } 1026 $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); 1027 $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); 838 1028 $data .= $str; 839 if ($this->do_debug >= 4) { 840 $this->edebug("SMTP -> get_lines(): \$data is \"$data\""); 841 } 842 // if 4th character is a space, we are done reading, break the loop 843 if (substr($str, 3, 1) == ' ') { 1029 // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen 1030 if ((isset($str[3]) and $str[3] == ' ')) { 844 1031 break; 845 1032 } … … 847 1034 $info = stream_get_meta_data($this->smtp_conn); 848 1035 if ($info['timed_out']) { 849 if ($this->do_debug >= 4) { 850 $this->edebug( 851 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)' 852 ); 853 } 1036 $this->edebug( 1037 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', 1038 self::DEBUG_LOWLEVEL 1039 ); 854 1040 break; 855 1041 } 856 1042 // Now check if reads took too long 857 if ($endtime) { 858 if (time() > $endtime) { 859 if ($this->do_debug >= 4) { 860 $this->edebug( 861 'SMTP -> get_lines(): timelimit reached (' 862 . $this->Timelimit . ' sec)' 863 ); 864 } 865 break; 866 } 1043 if ($endtime and time() > $endtime) { 1044 $this->edebug( 1045 'SMTP -> get_lines(): timelimit reached ('. 1046 $this->Timelimit . ' sec)', 1047 self::DEBUG_LOWLEVEL 1048 ); 1049 break; 867 1050 } 868 1051 } … … 872 1055 /** 873 1056 * Enable or disable VERP address generation. 874 * @param bool $enabled1057 * @param boolean $enabled 875 1058 */ 876 1059 public function setVerp($enabled = false) … … 881 1064 /** 882 1065 * Get VERP address generation mode. 883 * @return bool 1066 * @return boolean 884 1067 */ 885 1068 public function getVerp() … … 889 1072 890 1073 /** 1074 * Set error messages and codes. 1075 * @param string $message The error message 1076 * @param string $detail Further detail on the error 1077 * @param string $smtp_code An associated SMTP error code 1078 * @param string $smtp_code_ex Extended SMTP code 1079 */ 1080 protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') 1081 { 1082 $this->error = array( 1083 'error' => $message, 1084 'detail' => $detail, 1085 'smtp_code' => $smtp_code, 1086 'smtp_code_ex' => $smtp_code_ex 1087 ); 1088 } 1089 1090 /** 891 1091 * Set debug output method. 892 * @param string $method The function/method to use for debugging output.1092 * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. 893 1093 */ 894 1094 public function setDebugOutput($method = 'echo') … … 908 1108 /** 909 1109 * Set debug output level. 910 * @param int $level1110 * @param integer $level 911 1111 */ 912 1112 public function setDebugLevel($level = 0) … … 917 1117 /** 918 1118 * Get debug output level. 919 * @return int 1119 * @return integer 920 1120 */ 921 1121 public function getDebugLevel() … … 926 1126 /** 927 1127 * Set SMTP timeout. 928 * @param int $timeout1128 * @param integer $timeout 929 1129 */ 930 1130 public function setTimeout($timeout = 0) … … 935 1135 /** 936 1136 * Get SMTP timeout. 937 * @return int 1137 * @return integer 938 1138 */ 939 1139 public function getTimeout() … … 941 1141 return $this->Timeout; 942 1142 } 1143 1144 /** 1145 * Reports an error number and string. 1146 * @param integer $errno The error number returned by PHP. 1147 * @param string $errmsg The error message returned by PHP. 1148 */ 1149 protected function errorHandler($errno, $errmsg) 1150 { 1151 $notice = 'Connection: Failed to connect to server.'; 1152 $this->setError( 1153 $notice, 1154 $errno, 1155 $errmsg 1156 ); 1157 $this->edebug( 1158 $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg, 1159 self::DEBUG_CONNECTION 1160 ); 1161 } 1162 1163 /** 1164 * Will return the ID of the last smtp transaction based on a list of patterns provided 1165 * in SMTP::$smtp_transaction_id_patterns. 1166 * If no reply has been received yet, it will return null. 1167 * If no pattern has been matched, it will return false. 1168 * @return bool|null|string 1169 */ 1170 public function getLastTransactionID() 1171 { 1172 $reply = $this->getLastReply(); 1173 1174 if (empty($reply)) { 1175 return null; 1176 } 1177 1178 foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { 1179 if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) { 1180 return $matches[1]; 1181 } 1182 } 1183 1184 return false; 1185 } 943 1186 }
Note: See TracChangeset
for help on using the changeset viewer.