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