Guest User

Untitled

a guest
Feb 19th, 2020
515
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 211.69 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4. * Owl PHP Mailer by [owlmailer.io]
  5. * @version : 1.6
  6. **/
  7.  
  8. session_start();
  9. set_time_limit(0);
  10. ini_set("memory_limit",-1);
  11.  
  12. header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
  13. header("Cache-Control: post-check=0, pre-check=0", false);
  14. header("Pragma: no-cache");
  15. header("Access-Control-Allow-Origin: *");
  16.  
  17.  
  18. $password = "mypass"; // Password
  19. $owl['version']="1.6";
  20. $owl['website']="owlmailer.io";
  21.  
  22.  
  23. if(isset($_POST['action']) && $_POST['action'] == "send"){
  24. $GLOBALS["recipient"] = owlTrim($_POST['recipient']);
  25. $GLOBALS["smtpAcct"] = owlTrim($_POST['smtpAcct']);
  26. $GLOBALS["senderName"] = owlTrim($_POST['senderName']);
  27. $GLOBALS["sendingMethod"] = owlTrim($_POST['sendingMethod']);
  28. $GLOBALS["senderEmail"] = owlTrim($_POST['senderEmail']);
  29. $GLOBALS["replyTo"] = owlTrim($_POST['replyTo']);
  30.  
  31. $messageLetter = owlTrim($_POST['messageLetter']);
  32. $messageLetter = urlencode($messageLetter);
  33. $messageLetter = preg_replace("/%5C%22/", "%22", $messageLetter);
  34. $messageLetter = urldecode($messageLetter);
  35. $GLOBALS["messageLetter"] = stripslashes($messageLetter);
  36.  
  37. $altMessageLetter = owlTrim($_POST['altMessageLetter']);
  38. $altMessageLetter = urlencode($altMessageLetter);
  39. $altMessageLetter = preg_replace("/%5C%22/", "%22", $altMessageLetter);
  40. $altMessageLetter = urldecode($altMessageLetter);
  41. $GLOBALS["altMessageLetter"] = stripslashes($altMessageLetter);
  42.  
  43. $GLOBALS["messageType"] = owlTrim($_POST['messageType']);
  44. $encodingType = owlTrim($_POST['encodingType']);
  45. $GLOBALS["encodingType"] = stripslashes($encodingType);
  46. $emailPriority = owlTrim($_POST['emailPriority']);
  47. $GLOBALS["emailPriority"] = stripslashes($emailPriority);
  48. $messageSubject = owlTrim($_POST['messageSubject']);
  49. $GLOBALS["messageSubject"] = stripslashes($messageSubject);
  50.  
  51. processEmailSendingRequest();
  52. }
  53.  
  54.  
  55.  
  56. $sessioncode = md5(__FILE__);
  57. if(!isset($_SESSION[$sessioncode]))
  58. $_SESSION[$sessioncode] = '';
  59.  
  60. if(!empty($password) && $_SESSION[$sessioncode] != $password){
  61. # _REQUEST mean _POST or _GET
  62. if (isset($_REQUEST['pass']) and $_REQUEST['pass'] == $password) {
  63. $_SESSION[$sessioncode] = $password;
  64. }
  65. else {
  66. print "<pre align=center><form method=post>Password: <input type='password' name='pass'><input type='submit' value='>>'></form></pre>";
  67. exit;
  68. }
  69. }
  70.  
  71. function setSendingMethod()
  72. {
  73. $mail = new PHPMailer;
  74.  
  75. if($GLOBALS["sendingMethod"] == "smtp")
  76. {
  77. $mail->IsSMTP();
  78. $parts = explode(':', $GLOBALS["smtpAcct"]);
  79. $mail->Host = owlTrim($parts[0]);
  80. $mail->Port = owlTrim($parts[1]);
  81.  
  82. if(owlTrim($parts[2]) == 'ssl')
  83. $mail->SMTPSecure = 'ssl';
  84. else if(owlTrim($parts[2]) == 'tls')
  85. $mail->SMTPSecure = 'tls';
  86. else
  87. $mail->SMTPSecure = '';
  88.  
  89. if(isset($parts[3]) && isset($parts[4]))
  90. {
  91. $mail->SMTPAuth = true;
  92. $mail->Username = owlTrim($parts[3]);
  93. $mail->Password = owlTrim($parts[4]);
  94. }
  95.  
  96.  
  97. }
  98. return $mail;
  99. }
  100.  
  101.  
  102. function processEmailSendingRequest()
  103. {
  104. if(!is_email($GLOBALS["recipient"]))
  105. exit("Incorrect Email");
  106.  
  107. $mail = setSendingMethod();
  108. $fromEmail = owlClear($GLOBALS["senderEmail"], $GLOBALS["recipient"]);
  109. $fromName = owlClear($GLOBALS["senderName"], $GLOBALS["recipient"]);
  110. $replyTo = owlClear($GLOBALS["replyTo"], $GLOBALS["recipient"]);
  111. $recipient = $GLOBALS["recipient"];
  112. $subject = owlClear($GLOBALS["messageSubject"], $GLOBALS["recipient"]);
  113. $body = owlClear($GLOBALS["messageLetter"], $GLOBALS["recipient"]);
  114. $altBody = owlClear($GLOBALS["altMessageLetter"], $GLOBALS["recipient"]);
  115. $charSet = $GLOBALS["encodingType"];
  116. $messageType = $GLOBALS["messageType"];
  117. $emailPriority = $GLOBALS["emailPriority"];
  118.  
  119. $mail->setFrom($fromEmail, $fromName);
  120.  
  121. if(isset($replyTo) && $replyTo !== "")
  122. $mail->addReplyTo($replyTo);
  123.  
  124. $mail->addAddress($recipient);
  125.  
  126. if(isset($subject) && $subject !== "")
  127. $mail->Subject = $subject;
  128.  
  129. if(isset($body) && $body !== "")
  130. $mail->Body = $body;
  131.  
  132. if(isset($charSet) && $charSet !== "")
  133. $mail->CharSet = $charSet;
  134.  
  135. if(isset($emailPriority) && $emailPriority !== "")
  136. $mail->Priority = $emailPriority;
  137.  
  138. if(isset($altBody) && $altBody !== "")
  139. $mail->AltBody = $altBody;
  140.  
  141. if(isset($_FILES['attachment']))
  142. {
  143. for($i=0; $i<count($_FILES['attachment']['name']); $i++) {
  144. if ($_FILES['attachment']['tmp_name'][$i] != ""){
  145. $mail->AddAttachment($_FILES['attachment']['tmp_name'][$i],$_FILES['attachment']['name'][$i]);
  146. }
  147. }
  148. }
  149.  
  150.  
  151. if($messageType == "html"){$mail->IsHTML(true);}
  152. else {$mail->IsHTML(false);}
  153.  
  154. if (!$mail->send()) {
  155. exit($mail->ErrorInfo);
  156. }
  157. else {
  158. exit("OK");
  159. }
  160. }
  161.  
  162.  
  163. function owlTrim($string){
  164. return stripslashes(ltrim(rtrim($string)));
  165. }
  166.  
  167. function owlClear($text,$email){
  168. $emailuser = preg_replace('/([^@]*).*/', '$1', $email);
  169. $text = str_replace("[-time-]", date("m/d/Y h:i:s a", time()), $text);
  170. $text = str_replace("[-email-]", $email, $text);
  171. $text = str_replace("[-emailuser-]", $emailuser, $text);
  172. $text = str_replace("[-randomletters-]", randString('abcdefghijklmnopqrstuvwxyz', 8, 15), $text);
  173. $text = str_replace("[-randomstring-]", randString('abcdefghijklmnopqrstuvwxyz0123456789', 8, 15), $text);
  174. $text = str_replace("[-randomnumber-]", randString('0123456789', 7, 15), $text);
  175. $text = str_replace("[-randommd5-]", md5(rand()), $text);
  176. $text = str_replace("[-ra-]", rand(1, 20000), $text);
  177. return $text;
  178. }
  179.  
  180. function randString($consonants, $min_length, $max_length) {
  181. $length=rand($min_length, $max_length);
  182. $password = '';
  183. for ($i = 0; $i < $length; $i++) {
  184. $password .= $consonants[(rand() % strlen($consonants))];
  185. }
  186. return $password;
  187. }
  188.  
  189. function is_email($input) {
  190. $email_pattern = "/^([a-zA-Z0-9\-\_\.]{1,})+@+([a-zA-Z0-9\-\_\.]{1,})+\.+([a-z]{2,4})$/i";
  191. if(preg_match($email_pattern, $input)) return TRUE;
  192. }
  193.  
  194.  
  195. /**
  196. * PHPMailer - PHP email creation and transport class.
  197. *
  198. * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
  199. * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
  200. * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
  201. * @author Brent R. Matzelle (original founder)
  202. */
  203. class PHPMailer
  204. {
  205. const CHARSET_ISO88591 = 'iso-8859-1';
  206. const CHARSET_UTF8 = 'utf-8';
  207. const CONTENT_TYPE_PLAINTEXT = 'text/plain';
  208. const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar';
  209. const CONTENT_TYPE_TEXT_HTML = 'text/html';
  210. const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative';
  211. const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed';
  212. const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related';
  213. const ENCODING_7BIT = '7bit';
  214. const ENCODING_8BIT = '8bit';
  215. const ENCODING_BASE64 = 'base64';
  216. const ENCODING_BINARY = 'binary';
  217. const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
  218. /**
  219. * Email priority.
  220. * Options: null (default), 1 = High, 3 = Normal, 5 = low.
  221. * When null, the header is not set at all.
  222. *
  223. * @var int
  224. */
  225. public $Priority;
  226. /**
  227. * The character set of the message.
  228. *
  229. * @var string
  230. */
  231. public $CharSet = self::CHARSET_ISO88591;
  232. /**
  233. * The MIME Content-type of the message.
  234. *
  235. * @var string
  236. */
  237. public $ContentType = self::CONTENT_TYPE_PLAINTEXT;
  238. /**
  239. * The message encoding.
  240. * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
  241. *
  242. * @var string
  243. */
  244. public $Encoding = self::ENCODING_8BIT;
  245. /**
  246. * Holds the most recent mailer error message.
  247. *
  248. * @var string
  249. */
  250. public $ErrorInfo = '';
  251. /**
  252. * The From email address for the message.
  253. *
  254. * @var string
  255. */
  256. public $From = 'root@localhost';
  257. /**
  258. * The From name of the message.
  259. *
  260. * @var string
  261. */
  262. public $FromName = 'Root User';
  263. /**
  264. * The envelope sender of the message.
  265. * This will usually be turned into a Return-Path header by the receiver,
  266. * and is the address that bounces will be sent to.
  267. * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP.
  268. *
  269. * @var string
  270. */
  271. public $Sender = '';
  272. /**
  273. * The Subject of the message.
  274. *
  275. * @var string
  276. */
  277. public $Subject = '';
  278. /**
  279. * An HTML or plain text message body.
  280. * If HTML then call isHTML(true).
  281. *
  282. * @var string
  283. */
  284. public $Body = '';
  285. /**
  286. * The plain-text message body.
  287. * This body can be read by mail clients that do not have HTML email
  288. * capability such as mutt & Eudora.
  289. * Clients that can read HTML will view the normal Body.
  290. *
  291. * @var string
  292. */
  293. public $AltBody = '';
  294. /**
  295. * An iCal message part body.
  296. * Only supported in simple alt or alt_inline message types
  297. * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator.
  298. *
  299. * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
  300. * @see http://kigkonsult.se/iCalcreator/
  301. *
  302. * @var string
  303. */
  304. public $Ical = '';
  305. /**
  306. * The complete compiled MIME message body.
  307. *
  308. * @var string
  309. */
  310. protected $MIMEBody = '';
  311. /**
  312. * The complete compiled MIME message headers.
  313. *
  314. * @var string
  315. */
  316. protected $MIMEHeader = '';
  317. /**
  318. * Extra headers that createHeader() doesn't fold in.
  319. *
  320. * @var string
  321. */
  322. protected $mailHeader = '';
  323. /**
  324. * Word-wrap the message body to this number of chars.
  325. * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
  326. *
  327. * @see static::STD_LINE_LENGTH
  328. *
  329. * @var int
  330. */
  331. public $WordWrap = 0;
  332. /**
  333. * Which method to use to send mail.
  334. * Options: "mail", "sendmail", or "smtp".
  335. *
  336. * @var string
  337. */
  338. public $Mailer = 'mail';
  339. /**
  340. * The path to the sendmail program.
  341. *
  342. * @var string
  343. */
  344. public $Sendmail = '/usr/sbin/sendmail';
  345. /**
  346. * Whether mail() uses a fully sendmail-compatible MTA.
  347. * One which supports sendmail's "-oi -f" options.
  348. *
  349. * @var bool
  350. */
  351. public $UseSendmailOptions = true;
  352. /**
  353. * The email address that a reading confirmation should be sent to, also known as read receipt.
  354. *
  355. * @var string
  356. */
  357. public $ConfirmReadingTo = '';
  358. /**
  359. * The hostname to use in the Message-ID header and as default HELO string.
  360. * If empty, PHPMailer attempts to find one with, in order,
  361. * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
  362. * 'localhost.localdomain'.
  363. *
  364. * @var string
  365. */
  366. public $Hostname = '';
  367. /**
  368. * An ID to be used in the Message-ID header.
  369. * If empty, a unique id will be generated.
  370. * You can set your own, but it must be in the format "<id@domain>",
  371. * as defined in RFC5322 section 3.6.4 or it will be ignored.
  372. *
  373. * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
  374. *
  375. * @var string
  376. */
  377. public $MessageID = '';
  378. /**
  379. * The message Date to be used in the Date header.
  380. * If empty, the current date will be added.
  381. *
  382. * @var string
  383. */
  384. public $MessageDate = '';
  385. /**
  386. * SMTP hosts.
  387. * Either a single hostname or multiple semicolon-delimited hostnames.
  388. * You can also specify a different port
  389. * for each host by using this format: [hostname:port]
  390. * (e.g. "smtp1.example.com:25;smtp2.example.com").
  391. * You can also specify encryption type, for example:
  392. * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
  393. * Hosts will be tried in order.
  394. *
  395. * @var string
  396. */
  397. public $Host = 'localhost';
  398. /**
  399. * The default SMTP server port.
  400. *
  401. * @var int
  402. */
  403. public $Port = 25;
  404. /**
  405. * The SMTP HELO of the message.
  406. * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
  407. * one with the same method described above for $Hostname.
  408. *
  409. * @see PHPMailer::$Hostname
  410. *
  411. * @var string
  412. */
  413. public $Helo = '';
  414. /**
  415. * What kind of encryption to use on the SMTP connection.
  416. * Options: '', 'ssl' or 'tls'.
  417. *
  418. * @var string
  419. */
  420. public $SMTPSecure = '';
  421. /**
  422. * Whether to enable TLS encryption automatically if a server supports it,
  423. * even if `SMTPSecure` is not set to 'tls'.
  424. * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
  425. *
  426. * @var bool
  427. */
  428. public $SMTPAutoTLS = true;
  429. /**
  430. * Whether to use SMTP authentication.
  431. * Uses the Username and Password properties.
  432. *
  433. * @see PHPMailer::$Username
  434. * @see PHPMailer::$Password
  435. *
  436. * @var bool
  437. */
  438. public $SMTPAuth = false;
  439. /**
  440. * Options array passed to stream_context_create when connecting via SMTP.
  441. *
  442. * @var array
  443. */
  444. public $SMTPOptions = [];
  445. /**
  446. * SMTP username.
  447. *
  448. * @var string
  449. */
  450. public $Username = '';
  451. /**
  452. * SMTP password.
  453. *
  454. * @var string
  455. */
  456. public $Password = '';
  457. /**
  458. * SMTP auth type.
  459. * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified.
  460. *
  461. * @var string
  462. */
  463. public $AuthType = '';
  464. /**
  465. * An instance of the PHPMailer OAuth class.
  466. *
  467. * @var OAuth
  468. */
  469. protected $oauth;
  470. /**
  471. * The SMTP server timeout in seconds.
  472. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
  473. *
  474. * @var int
  475. */
  476. public $Timeout = 300;
  477. /**
  478. * Comma separated list of DSN notifications
  479. * 'NEVER' under no circumstances a DSN must be returned to the sender.
  480. * If you use NEVER all other notifications will be ignored.
  481. * 'SUCCESS' will notify you when your mail has arrived at its destination.
  482. * 'FAILURE' will arrive if an error occurred during delivery.
  483. * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual
  484. * delivery's outcome (success or failure) is not yet decided.
  485. *
  486. * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY
  487. */
  488. public $dsn = '';
  489. /**
  490. * SMTP class debug output mode.
  491. * Debug output level.
  492. * Options:
  493. * * `0` No output
  494. * * `1` Commands
  495. * * `2` Data and commands
  496. * * `3` As 2 plus connection status
  497. * * `4` Low-level data output.
  498. *
  499. * @see SMTP::$do_debug
  500. *
  501. * @var int
  502. */
  503. public $SMTPDebug = 0;
  504. /**
  505. * How to handle debug output.
  506. * Options:
  507. * * `echo` Output plain-text as-is, appropriate for CLI
  508. * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
  509. * * `error_log` Output to error log as configured in php.ini
  510. * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise.
  511. * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
  512. *
  513. * ```php
  514. * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
  515. * ```
  516. *
  517. * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
  518. * level output is used:
  519. *
  520. * ```php
  521. * $mail->Debugoutput = new myPsr3Logger;
  522. * ```
  523. *
  524. * @see SMTP::$Debugoutput
  525. *
  526. * @var string|callable|\Psr\Log\LoggerInterface
  527. */
  528. public $Debugoutput = 'echo';
  529. /**
  530. * Whether to keep SMTP connection open after each message.
  531. * If this is set to true then to close the connection
  532. * requires an explicit call to smtpClose().
  533. *
  534. * @var bool
  535. */
  536. public $SMTPKeepAlive = false;
  537. /**
  538. * Whether to split multiple to addresses into multiple messages
  539. * or send them all in one message.
  540. * Only supported in `mail` and `sendmail` transports, not in SMTP.
  541. *
  542. * @var bool
  543. */
  544. public $SingleTo = false;
  545. /**
  546. * Storage for addresses when SingleTo is enabled.
  547. *
  548. * @var array
  549. */
  550. protected $SingleToArray = [];
  551. /**
  552. * Whether to generate VERP addresses on send.
  553. * Only applicable when sending via SMTP.
  554. *
  555. * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
  556. * @see http://www.postfix.org/VERP_README.html Postfix VERP info
  557. *
  558. * @var bool
  559. */
  560. public $do_verp = false;
  561. /**
  562. * Whether to allow sending messages with an empty body.
  563. *
  564. * @var bool
  565. */
  566. public $AllowEmpty = false;
  567. /**
  568. * DKIM selector.
  569. *
  570. * @var string
  571. */
  572. public $DKIM_selector = '';
  573. /**
  574. * DKIM Identity.
  575. * Usually the email address used as the source of the email.
  576. *
  577. * @var string
  578. */
  579. public $DKIM_identity = '';
  580. /**
  581. * DKIM passphrase.
  582. * Used if your key is encrypted.
  583. *
  584. * @var string
  585. */
  586. public $DKIM_passphrase = '';
  587. /**
  588. * DKIM signing domain name.
  589. *
  590. * @example 'example.com'
  591. *
  592. * @var string
  593. */
  594. public $DKIM_domain = '';
  595. /**
  596. * DKIM Copy header field values for diagnostic use.
  597. *
  598. * @var bool
  599. */
  600. public $DKIM_copyHeaderFields = true;
  601. /**
  602. * DKIM Extra signing headers.
  603. *
  604. * @example ['List-Unsubscribe', 'List-Help']
  605. *
  606. * @var array
  607. */
  608. public $DKIM_extraHeaders = [];
  609. /**
  610. * DKIM private key file path.
  611. *
  612. * @var string
  613. */
  614. public $DKIM_private = '';
  615. /**
  616. * DKIM private key string.
  617. *
  618. * If set, takes precedence over `$DKIM_private`.
  619. *
  620. * @var string
  621. */
  622. public $DKIM_private_string = '';
  623. /**
  624. * Callback Action function name.
  625. *
  626. * The function that handles the result of the send email action.
  627. * It is called out by send() for each email sent.
  628. *
  629. * Value can be any php callable: http://www.php.net/is_callable
  630. *
  631. * Parameters:
  632. * bool $result result of the send action
  633. * array $to email addresses of the recipients
  634. * array $cc cc email addresses
  635. * array $bcc bcc email addresses
  636. * string $subject the subject
  637. * string $body the email body
  638. * string $from email address of sender
  639. * string $extra extra information of possible use
  640. * "smtp_transaction_id' => last smtp transaction id
  641. *
  642. * @var string
  643. */
  644. public $action_function = '';
  645. /**
  646. * What to put in the X-Mailer header.
  647. * Options: An empty string for PHPMailer default, whitespace for none, or a string to use.
  648. *
  649. * @var string
  650. */
  651. public $XMailer = '';
  652. /**
  653. * Which validator to use by default when validating email addresses.
  654. * May be a callable to inject your own validator, but there are several built-in validators.
  655. * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option.
  656. *
  657. * @see PHPMailer::validateAddress()
  658. *
  659. * @var string|callable
  660. */
  661. public static $validator = 'php';
  662. /**
  663. * An instance of the SMTP sender class.
  664. *
  665. * @var SMTP
  666. */
  667. protected $smtp;
  668. /**
  669. * The array of 'to' names and addresses.
  670. *
  671. * @var array
  672. */
  673. protected $to = [];
  674. /**
  675. * The array of 'cc' names and addresses.
  676. *
  677. * @var array
  678. */
  679. protected $cc = [];
  680. /**
  681. * The array of 'bcc' names and addresses.
  682. *
  683. * @var array
  684. */
  685. protected $bcc = [];
  686. /**
  687. * The array of reply-to names and addresses.
  688. *
  689. * @var array
  690. */
  691. protected $ReplyTo = [];
  692. /**
  693. * An array of all kinds of addresses.
  694. * Includes all of $to, $cc, $bcc.
  695. *
  696. * @see PHPMailer::$to
  697. * @see PHPMailer::$cc
  698. * @see PHPMailer::$bcc
  699. *
  700. * @var array
  701. */
  702. protected $all_recipients = [];
  703. /**
  704. * An array of names and addresses queued for validation.
  705. * In send(), valid and non duplicate entries are moved to $all_recipients
  706. * and one of $to, $cc, or $bcc.
  707. * This array is used only for addresses with IDN.
  708. *
  709. * @see PHPMailer::$to
  710. * @see PHPMailer::$cc
  711. * @see PHPMailer::$bcc
  712. * @see PHPMailer::$all_recipients
  713. *
  714. * @var array
  715. */
  716. protected $RecipientsQueue = [];
  717. /**
  718. * An array of reply-to names and addresses queued for validation.
  719. * In send(), valid and non duplicate entries are moved to $ReplyTo.
  720. * This array is used only for addresses with IDN.
  721. *
  722. * @see PHPMailer::$ReplyTo
  723. *
  724. * @var array
  725. */
  726. protected $ReplyToQueue = [];
  727. /**
  728. * The array of attachments.
  729. *
  730. * @var array
  731. */
  732. protected $attachment = [];
  733. /**
  734. * The array of custom headers.
  735. *
  736. * @var array
  737. */
  738. protected $CustomHeader = [];
  739. /**
  740. * The most recent Message-ID (including angular brackets).
  741. *
  742. * @var string
  743. */
  744. protected $lastMessageID = '';
  745. /**
  746. * The message's MIME type.
  747. *
  748. * @var string
  749. */
  750. protected $message_type = '';
  751. /**
  752. * The array of MIME boundary strings.
  753. *
  754. * @var array
  755. */
  756. protected $boundary = [];
  757. /**
  758. * The array of available languages.
  759. *
  760. * @var array
  761. */
  762. protected $language = [];
  763. /**
  764. * The number of errors encountered.
  765. *
  766. * @var int
  767. */
  768. protected $error_count = 0;
  769. /**
  770. * The S/MIME certificate file path.
  771. *
  772. * @var string
  773. */
  774. protected $sign_cert_file = '';
  775. /**
  776. * The S/MIME key file path.
  777. *
  778. * @var string
  779. */
  780. protected $sign_key_file = '';
  781. /**
  782. * The optional S/MIME extra certificates ("CA Chain") file path.
  783. *
  784. * @var string
  785. */
  786. protected $sign_extracerts_file = '';
  787. /**
  788. * The S/MIME password for the key.
  789. * Used only if the key is encrypted.
  790. *
  791. * @var string
  792. */
  793. protected $sign_key_pass = '';
  794. /**
  795. * Whether to throw exceptions for errors.
  796. *
  797. * @var bool
  798. */
  799. protected $exceptions = false;
  800. /**
  801. * Unique ID used for message ID and boundaries.
  802. *
  803. * @var string
  804. */
  805. protected $uniqueid = '';
  806. /**
  807. * The PHPMailer Version number.
  808. *
  809. * @var string
  810. */
  811. const VERSION = '6.0.7';
  812. /**
  813. * Error severity: message only, continue processing.
  814. *
  815. * @var int
  816. */
  817. const STOP_MESSAGE = 0;
  818. /**
  819. * Error severity: message, likely ok to continue processing.
  820. *
  821. * @var int
  822. */
  823. const STOP_CONTINUE = 1;
  824. /**
  825. * Error severity: message, plus full stop, critical error reached.
  826. *
  827. * @var int
  828. */
  829. const STOP_CRITICAL = 2;
  830. /**
  831. * SMTP RFC standard line ending.
  832. *
  833. * @var string
  834. */
  835. protected static $LE = "\r\n";
  836. /**
  837. * The maximum line length allowed by RFC 2822 section 2.1.1.
  838. *
  839. * @var int
  840. */
  841. const MAX_LINE_LENGTH = 998;
  842. /**
  843. * The lower maximum line length allowed by RFC 2822 section 2.1.1.
  844. * This length does NOT include the line break
  845. * 76 means that lines will be 77 or 78 chars depending on whether
  846. * the line break format is LF or CRLF; both are valid.
  847. *
  848. * @var int
  849. */
  850. const STD_LINE_LENGTH = 76;
  851. /**
  852. * Constructor.
  853. *
  854. * @param bool $exceptions Should we throw external exceptions?
  855. */
  856. public function __construct($exceptions = null)
  857. {
  858. if (null !== $exceptions) {
  859. $this->exceptions = (bool) $exceptions;
  860. }
  861. //Pick an appropriate debug output format automatically
  862. $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
  863. }
  864. /**
  865. * Destructor.
  866. */
  867. public function __destruct()
  868. {
  869. //Close any open SMTP connection nicely
  870. $this->smtpClose();
  871. }
  872. /**
  873. * Call mail() in a safe_mode-aware fashion.
  874. * Also, unless sendmail_path points to sendmail (or something that
  875. * claims to be sendmail), don't pass params (not a perfect fix,
  876. * but it will do).
  877. *
  878. * @param string $to To
  879. * @param string $subject Subject
  880. * @param string $body Message Body
  881. * @param string $header Additional Header(s)
  882. * @param string|null $params Params
  883. *
  884. * @return bool
  885. */
  886. private function mailPassthru($to, $subject, $body, $header, $params)
  887. {
  888. //Check overloading of mail function to avoid double-encoding
  889. if (ini_get('mbstring.func_overload') & 1) {
  890. $subject = $this->secureHeader($subject);
  891. } else {
  892. $subject = $this->encodeHeader($this->secureHeader($subject));
  893. }
  894. //Calling mail() with null params breaks
  895. if (!$this->UseSendmailOptions or null === $params) {
  896. $result = @mail($to, $subject, $body, $header);
  897. } else {
  898. $result = @mail($to, $subject, $body, $header, $params);
  899. }
  900. return $result;
  901. }
  902. /**
  903. * Output debugging info via user-defined method.
  904. * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
  905. *
  906. * @see PHPMailer::$Debugoutput
  907. * @see PHPMailer::$SMTPDebug
  908. *
  909. * @param string $str
  910. */
  911. protected function edebug($str)
  912. {
  913. if ($this->SMTPDebug <= 0) {
  914. return;
  915. }
  916. //Is this a PSR-3 logger?
  917. if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
  918. $this->Debugoutput->debug($str);
  919. return;
  920. }
  921. //Avoid clash with built-in function names
  922. if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo']) and is_callable($this->Debugoutput)) {
  923. call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
  924. return;
  925. }
  926. switch ($this->Debugoutput) {
  927. case 'error_log':
  928. //Don't output, just log
  929. error_log($str);
  930. break;
  931. case 'html':
  932. //Cleans up output a bit for a better looking, HTML-safe output
  933. echo htmlentities(
  934. preg_replace('/[\r\n]+/', '', $str),
  935. ENT_QUOTES,
  936. 'UTF-8'
  937. ), "<br>\n";
  938. break;
  939. case 'echo':
  940. default:
  941. //Normalize line breaks
  942. $str = preg_replace('/\r\n|\r/ms', "\n", $str);
  943. echo gmdate('Y-m-d H:i:s'),
  944. "\t",
  945. //Trim trailing space
  946. trim(
  947. //Indent for readability, except for trailing break
  948. str_replace(
  949. "\n",
  950. "\n \t ",
  951. trim($str)
  952. )
  953. ),
  954. "\n";
  955. }
  956. }
  957. /**
  958. * Sets message type to HTML or plain.
  959. *
  960. * @param bool $isHtml True for HTML mode
  961. */
  962. public function isHTML($isHtml = true)
  963. {
  964. if ($isHtml) {
  965. $this->ContentType = static::CONTENT_TYPE_TEXT_HTML;
  966. } else {
  967. $this->ContentType = static::CONTENT_TYPE_PLAINTEXT;
  968. }
  969. }
  970. /**
  971. * Send messages using SMTP.
  972. */
  973. public function isSMTP()
  974. {
  975. $this->Mailer = 'smtp';
  976. }
  977. /**
  978. * Send messages using PHP's mail() function.
  979. */
  980. public function isMail()
  981. {
  982. $this->Mailer = 'mail';
  983. }
  984. /**
  985. * Send messages using $Sendmail.
  986. */
  987. public function isSendmail()
  988. {
  989. $ini_sendmail_path = ini_get('sendmail_path');
  990. if (false === stripos($ini_sendmail_path, 'sendmail')) {
  991. $this->Sendmail = '/usr/sbin/sendmail';
  992. } else {
  993. $this->Sendmail = $ini_sendmail_path;
  994. }
  995. $this->Mailer = 'sendmail';
  996. }
  997. /**
  998. * Send messages using qmail.
  999. */
  1000. public function isQmail()
  1001. {
  1002. $ini_sendmail_path = ini_get('sendmail_path');
  1003. if (false === stripos($ini_sendmail_path, 'qmail')) {
  1004. $this->Sendmail = '/var/qmail/bin/qmail-inject';
  1005. } else {
  1006. $this->Sendmail = $ini_sendmail_path;
  1007. }
  1008. $this->Mailer = 'qmail';
  1009. }
  1010. /**
  1011. * Add a "To" address.
  1012. *
  1013. * @param string $address The email address to send to
  1014. * @param string $name
  1015. *
  1016. * @throws Exception
  1017. *
  1018. * @return bool true on success, false if address already used or invalid in some way
  1019. */
  1020. public function addAddress($address, $name = '')
  1021. {
  1022. return $this->addOrEnqueueAnAddress('to', $address, $name);
  1023. }
  1024. /**
  1025. * Add a "CC" address.
  1026. *
  1027. * @param string $address The email address to send to
  1028. * @param string $name
  1029. *
  1030. * @throws Exception
  1031. *
  1032. * @return bool true on success, false if address already used or invalid in some way
  1033. */
  1034. public function addCC($address, $name = '')
  1035. {
  1036. return $this->addOrEnqueueAnAddress('cc', $address, $name);
  1037. }
  1038. /**
  1039. * Add a "BCC" address.
  1040. *
  1041. * @param string $address The email address to send to
  1042. * @param string $name
  1043. *
  1044. * @throws Exception
  1045. *
  1046. * @return bool true on success, false if address already used or invalid in some way
  1047. */
  1048. public function addBCC($address, $name = '')
  1049. {
  1050. return $this->addOrEnqueueAnAddress('bcc', $address, $name);
  1051. }
  1052. /**
  1053. * Add a "Reply-To" address.
  1054. *
  1055. * @param string $address The email address to reply to
  1056. * @param string $name
  1057. *
  1058. * @throws Exception
  1059. *
  1060. * @return bool true on success, false if address already used or invalid in some way
  1061. */
  1062. public function addReplyTo($address, $name = '')
  1063. {
  1064. return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
  1065. }
  1066. /**
  1067. * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
  1068. * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
  1069. * be modified after calling this function), addition of such addresses is delayed until send().
  1070. * Addresses that have been added already return false, but do not throw exceptions.
  1071. *
  1072. * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
  1073. * @param string $address The email address to send, resp. to reply to
  1074. * @param string $name
  1075. *
  1076. * @throws Exception
  1077. *
  1078. * @return bool true on success, false if address already used or invalid in some way
  1079. */
  1080. protected function addOrEnqueueAnAddress($kind, $address, $name)
  1081. {
  1082. $address = trim($address);
  1083. $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
  1084. $pos = strrpos($address, '@');
  1085. if (false === $pos) {
  1086. // At-sign is missing.
  1087. $error_message = sprintf('%s (%s): %s',
  1088. $this->lang('invalid_address'),
  1089. $kind,
  1090. $address);
  1091. $this->setError($error_message);
  1092. $this->edebug($error_message);
  1093. if ($this->exceptions) {
  1094. throw new Exception($error_message);
  1095. }
  1096. return false;
  1097. }
  1098. $params = [$kind, $address, $name];
  1099. // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
  1100. if ($this->has8bitChars(substr($address, ++$pos)) and static::idnSupported()) {
  1101. if ('Reply-To' != $kind) {
  1102. if (!array_key_exists($address, $this->RecipientsQueue)) {
  1103. $this->RecipientsQueue[$address] = $params;
  1104. return true;
  1105. }
  1106. } else {
  1107. if (!array_key_exists($address, $this->ReplyToQueue)) {
  1108. $this->ReplyToQueue[$address] = $params;
  1109. return true;
  1110. }
  1111. }
  1112. return false;
  1113. }
  1114. // Immediately add standard addresses without IDN.
  1115. return call_user_func_array([$this, 'addAnAddress'], $params);
  1116. }
  1117. /**
  1118. * Add an address to one of the recipient arrays or to the ReplyTo array.
  1119. * Addresses that have been added already return false, but do not throw exceptions.
  1120. *
  1121. * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
  1122. * @param string $address The email address to send, resp. to reply to
  1123. * @param string $name
  1124. *
  1125. * @throws Exception
  1126. *
  1127. * @return bool true on success, false if address already used or invalid in some way
  1128. */
  1129. protected function addAnAddress($kind, $address, $name = '')
  1130. {
  1131. if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
  1132. $error_message = sprintf('%s: %s',
  1133. $this->lang('Invalid recipient kind'),
  1134. $kind);
  1135. $this->setError($error_message);
  1136. $this->edebug($error_message);
  1137. if ($this->exceptions) {
  1138. throw new Exception($error_message);
  1139. }
  1140. return false;
  1141. }
  1142. if (!static::validateAddress($address)) {
  1143. $error_message = sprintf('%s (%s): %s',
  1144. $this->lang('invalid_address'),
  1145. $kind,
  1146. $address);
  1147. $this->setError($error_message);
  1148. $this->edebug($error_message);
  1149. if ($this->exceptions) {
  1150. throw new Exception($error_message);
  1151. }
  1152. return false;
  1153. }
  1154. if ('Reply-To' != $kind) {
  1155. if (!array_key_exists(strtolower($address), $this->all_recipients)) {
  1156. $this->{$kind}[] = [$address, $name];
  1157. $this->all_recipients[strtolower($address)] = true;
  1158. return true;
  1159. }
  1160. } else {
  1161. if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
  1162. $this->ReplyTo[strtolower($address)] = [$address, $name];
  1163. return true;
  1164. }
  1165. }
  1166. return false;
  1167. }
  1168. /**
  1169. * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
  1170. * of the form "display name <address>" into an array of name/address pairs.
  1171. * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
  1172. * Note that quotes in the name part are removed.
  1173. *
  1174. * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
  1175. *
  1176. * @param string $addrstr The address list string
  1177. * @param bool $useimap Whether to use the IMAP extension to parse the list
  1178. *
  1179. * @return array
  1180. */
  1181. public static function parseAddresses($addrstr, $useimap = true)
  1182. {
  1183. $addresses = [];
  1184. if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
  1185. //Use this built-in parser if it's available
  1186. $list = imap_rfc822_parse_adrlist($addrstr, '');
  1187. foreach ($list as $address) {
  1188. if ('.SYNTAX-ERROR.' != $address->host) {
  1189. if (static::validateAddress($address->mailbox . '@' . $address->host)) {
  1190. $addresses[] = [
  1191. 'name' => (property_exists($address, 'personal') ? $address->personal : ''),
  1192. 'address' => $address->mailbox . '@' . $address->host,
  1193. ];
  1194. }
  1195. }
  1196. }
  1197. } else {
  1198. //Use this simpler parser
  1199. $list = explode(',', $addrstr);
  1200. foreach ($list as $address) {
  1201. $address = trim($address);
  1202. //Is there a separate name part?
  1203. if (strpos($address, '<') === false) {
  1204. //No separate name, just use the whole thing
  1205. if (static::validateAddress($address)) {
  1206. $addresses[] = [
  1207. 'name' => '',
  1208. 'address' => $address,
  1209. ];
  1210. }
  1211. } else {
  1212. list($name, $email) = explode('<', $address);
  1213. $email = trim(str_replace('>', '', $email));
  1214. if (static::validateAddress($email)) {
  1215. $addresses[] = [
  1216. 'name' => trim(str_replace(['"', "'"], '', $name)),
  1217. 'address' => $email,
  1218. ];
  1219. }
  1220. }
  1221. }
  1222. }
  1223. return $addresses;
  1224. }
  1225. /**
  1226. * Set the From and FromName properties.
  1227. *
  1228. * @param string $address
  1229. * @param string $name
  1230. * @param bool $auto Whether to also set the Sender address, defaults to true
  1231. *
  1232. * @throws Exception
  1233. *
  1234. * @return bool
  1235. */
  1236. public function setFrom($address, $name = '', $auto = true)
  1237. {
  1238. $address = trim($address);
  1239. $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
  1240. // Don't validate now addresses with IDN. Will be done in send().
  1241. $pos = strrpos($address, '@');
  1242. if (false === $pos or
  1243. (!$this->has8bitChars(substr($address, ++$pos)) or !static::idnSupported()) and
  1244. !static::validateAddress($address)) {
  1245. $error_message = sprintf('%s (From): %s',
  1246. $this->lang('invalid_address'),
  1247. $address);
  1248. $this->setError($error_message);
  1249. $this->edebug($error_message);
  1250. if ($this->exceptions) {
  1251. throw new Exception($error_message);
  1252. }
  1253. return false;
  1254. }
  1255. $this->From = $address;
  1256. $this->FromName = $name;
  1257. if ($auto) {
  1258. if (empty($this->Sender)) {
  1259. $this->Sender = $address;
  1260. }
  1261. }
  1262. return true;
  1263. }
  1264. /**
  1265. * Return the Message-ID header of the last email.
  1266. * Technically this is the value from the last time the headers were created,
  1267. * but it's also the message ID of the last sent message except in
  1268. * pathological cases.
  1269. *
  1270. * @return string
  1271. */
  1272. public function getLastMessageID()
  1273. {
  1274. return $this->lastMessageID;
  1275. }
  1276. /**
  1277. * Check that a string looks like an email address.
  1278. * Validation patterns supported:
  1279. * * `auto` Pick best pattern automatically;
  1280. * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0;
  1281. * * `pcre` Use old PCRE implementation;
  1282. * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
  1283. * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
  1284. * * `noregex` Don't use a regex: super fast, really dumb.
  1285. * Alternatively you may pass in a callable to inject your own validator, for example:
  1286. *
  1287. * ```php
  1288. * PHPMailer::validateAddress('user@example.com', function($address) {
  1289. * return (strpos($address, '@') !== false);
  1290. * });
  1291. * ```
  1292. *
  1293. * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
  1294. *
  1295. * @param string $address The email address to check
  1296. * @param string|callable $patternselect Which pattern to use
  1297. *
  1298. * @return bool
  1299. */
  1300. public static function validateAddress($address, $patternselect = null)
  1301. {
  1302. if (null === $patternselect) {
  1303. $patternselect = static::$validator;
  1304. }
  1305. if (is_callable($patternselect)) {
  1306. return call_user_func($patternselect, $address);
  1307. }
  1308. //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
  1309. if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
  1310. return false;
  1311. }
  1312. switch ($patternselect) {
  1313. case 'pcre': //Kept for BC
  1314. case 'pcre8':
  1315. /*
  1316. * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL
  1317. * is based.
  1318. * In addition to the addresses allowed by filter_var, also permits:
  1319. * * dotless domains: `a@b`
  1320. * * comments: `1234 @ local(blah) .machine .example`
  1321. * * quoted elements: `'"test blah"@example.org'`
  1322. * * numeric TLDs: `a@b.123`
  1323. * * unbracketed IPv4 literals: `a@192.168.0.1`
  1324. * * IPv6 literals: 'first.last@[IPv6:a1::]'
  1325. * Not all of these will necessarily work for sending!
  1326. *
  1327. * @see http://squiloople.com/2009/12/20/email-address-validation/
  1328. * @copyright 2009-2010 Michael Rushton
  1329. * Feel free to use and redistribute this code. But please keep this copyright notice.
  1330. */
  1331. return (bool) preg_match(
  1332. '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
  1333. '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
  1334. '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
  1335. '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
  1336. '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
  1337. '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
  1338. '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
  1339. '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
  1340. '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
  1341. $address
  1342. );
  1343. case 'html5':
  1344. /*
  1345. * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
  1346. *
  1347. * @see http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
  1348. */
  1349. return (bool) preg_match(
  1350. '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
  1351. '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
  1352. $address
  1353. );
  1354. case 'php':
  1355. default:
  1356. return (bool) filter_var($address, FILTER_VALIDATE_EMAIL);
  1357. }
  1358. }
  1359. /**
  1360. * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
  1361. * `intl` and `mbstring` PHP extensions.
  1362. *
  1363. * @return bool `true` if required functions for IDN support are present
  1364. */
  1365. public static function idnSupported()
  1366. {
  1367. return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
  1368. }
  1369. /**
  1370. * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
  1371. * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
  1372. * This function silently returns unmodified address if:
  1373. * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
  1374. * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
  1375. * or fails for any reason (e.g. domain contains characters not allowed in an IDN).
  1376. *
  1377. * @see PHPMailer::$CharSet
  1378. *
  1379. * @param string $address The email address to convert
  1380. *
  1381. * @return string The encoded address in ASCII form
  1382. */
  1383. public function punyencodeAddress($address)
  1384. {
  1385. // Verify we have required functions, CharSet, and at-sign.
  1386. $pos = strrpos($address, '@');
  1387. if (static::idnSupported() and
  1388. !empty($this->CharSet) and
  1389. false !== $pos
  1390. ) {
  1391. $domain = substr($address, ++$pos);
  1392. // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
  1393. if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
  1394. $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
  1395. //Ignore IDE complaints about this line - method signature changed in PHP 5.4
  1396. $errorcode = 0;
  1397. $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46);
  1398. if (false !== $punycode) {
  1399. return substr($address, 0, $pos) . $punycode;
  1400. }
  1401. }
  1402. }
  1403. return $address;
  1404. }
  1405. /**
  1406. * Create a message and send it.
  1407. * Uses the sending method specified by $Mailer.
  1408. *
  1409. * @throws Exception
  1410. *
  1411. * @return bool false on error - See the ErrorInfo property for details of the error
  1412. */
  1413. public function send()
  1414. {
  1415. try {
  1416. if (!$this->preSend()) {
  1417. return false;
  1418. }
  1419. return $this->postSend();
  1420. } catch (Exception $exc) {
  1421. $this->mailHeader = '';
  1422. $this->setError($exc->getMessage());
  1423. if ($this->exceptions) {
  1424. throw $exc;
  1425. }
  1426. return false;
  1427. }
  1428. }
  1429. /**
  1430. * Prepare a message for sending.
  1431. *
  1432. * @throws Exception
  1433. *
  1434. * @return bool
  1435. */
  1436. public function preSend()
  1437. {
  1438. if ('smtp' == $this->Mailer or
  1439. ('mail' == $this->Mailer and stripos(PHP_OS, 'WIN') === 0)
  1440. ) {
  1441. //SMTP mandates RFC-compliant line endings
  1442. //and it's also used with mail() on Windows
  1443. static::setLE("\r\n");
  1444. } else {
  1445. //Maintain backward compatibility with legacy Linux command line mailers
  1446. static::setLE(PHP_EOL);
  1447. }
  1448. //Check for buggy PHP versions that add a header with an incorrect line break
  1449. if (ini_get('mail.add_x_header') == 1
  1450. and 'mail' == $this->Mailer
  1451. and stripos(PHP_OS, 'WIN') === 0
  1452. and ((version_compare(PHP_VERSION, '7.0.0', '>=')
  1453. and version_compare(PHP_VERSION, '7.0.17', '<'))
  1454. or (version_compare(PHP_VERSION, '7.1.0', '>=')
  1455. and version_compare(PHP_VERSION, '7.1.3', '<')))
  1456. ) {
  1457. trigger_error(
  1458. 'Your version of PHP is affected by a bug that may result in corrupted messages.' .
  1459. ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' .
  1460. ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
  1461. E_USER_WARNING
  1462. );
  1463. }
  1464. try {
  1465. $this->error_count = 0; // Reset errors
  1466. $this->mailHeader = '';
  1467. // Dequeue recipient and Reply-To addresses with IDN
  1468. foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
  1469. $params[1] = $this->punyencodeAddress($params[1]);
  1470. call_user_func_array([$this, 'addAnAddress'], $params);
  1471. }
  1472. if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
  1473. throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
  1474. }
  1475. // Validate From, Sender, and ConfirmReadingTo addresses
  1476. foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
  1477. $this->$address_kind = trim($this->$address_kind);
  1478. if (empty($this->$address_kind)) {
  1479. continue;
  1480. }
  1481. $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
  1482. if (!static::validateAddress($this->$address_kind)) {
  1483. $error_message = sprintf('%s (%s): %s',
  1484. $this->lang('invalid_address'),
  1485. $address_kind,
  1486. $this->$address_kind);
  1487. $this->setError($error_message);
  1488. $this->edebug($error_message);
  1489. if ($this->exceptions) {
  1490. throw new Exception($error_message);
  1491. }
  1492. return false;
  1493. }
  1494. }
  1495. // Set whether the message is multipart/alternative
  1496. if ($this->alternativeExists()) {
  1497. $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;
  1498. }
  1499. $this->setMessageType();
  1500. // Refuse to send an empty message unless we are specifically allowing it
  1501. if (!$this->AllowEmpty and empty($this->Body)) {
  1502. throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
  1503. }
  1504. //Trim subject consistently
  1505. $this->Subject = trim($this->Subject);
  1506. // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
  1507. $this->MIMEHeader = '';
  1508. $this->MIMEBody = $this->createBody();
  1509. // createBody may have added some headers, so retain them
  1510. $tempheaders = $this->MIMEHeader;
  1511. $this->MIMEHeader = $this->createHeader();
  1512. $this->MIMEHeader .= $tempheaders;
  1513. // To capture the complete message when using mail(), create
  1514. // an extra header list which createHeader() doesn't fold in
  1515. if ('mail' == $this->Mailer) {
  1516. if (count($this->to) > 0) {
  1517. $this->mailHeader .= $this->addrAppend('To', $this->to);
  1518. } else {
  1519. $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
  1520. }
  1521. $this->mailHeader .= $this->headerLine(
  1522. 'Subject',
  1523. $this->encodeHeader($this->secureHeader($this->Subject))
  1524. );
  1525. }
  1526. // Sign with DKIM if enabled
  1527. if (!empty($this->DKIM_domain)
  1528. and !empty($this->DKIM_selector)
  1529. and (!empty($this->DKIM_private_string)
  1530. or (!empty($this->DKIM_private)
  1531. and static::isPermittedPath($this->DKIM_private)
  1532. and file_exists($this->DKIM_private)
  1533. )
  1534. )
  1535. ) {
  1536. $header_dkim = $this->DKIM_Add(
  1537. $this->MIMEHeader . $this->mailHeader,
  1538. $this->encodeHeader($this->secureHeader($this->Subject)),
  1539. $this->MIMEBody
  1540. );
  1541. $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . static::$LE .
  1542. static::normalizeBreaks($header_dkim) . static::$LE;
  1543. }
  1544. return true;
  1545. } catch (Exception $exc) {
  1546. $this->setError($exc->getMessage());
  1547. if ($this->exceptions) {
  1548. throw $exc;
  1549. }
  1550. return false;
  1551. }
  1552. }
  1553. /**
  1554. * Actually send a message via the selected mechanism.
  1555. *
  1556. * @throws Exception
  1557. *
  1558. * @return bool
  1559. */
  1560. public function postSend()
  1561. {
  1562. try {
  1563. // Choose the mailer and send through it
  1564. switch ($this->Mailer) {
  1565. case 'sendmail':
  1566. case 'qmail':
  1567. return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
  1568. case 'smtp':
  1569. return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
  1570. case 'mail':
  1571. return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
  1572. default:
  1573. $sendMethod = $this->Mailer . 'Send';
  1574. if (method_exists($this, $sendMethod)) {
  1575. return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
  1576. }
  1577. return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
  1578. }
  1579. } catch (Exception $exc) {
  1580. $this->setError($exc->getMessage());
  1581. $this->edebug($exc->getMessage());
  1582. if ($this->exceptions) {
  1583. throw $exc;
  1584. }
  1585. }
  1586. return false;
  1587. }
  1588. /**
  1589. * Send mail using the $Sendmail program.
  1590. *
  1591. * @see PHPMailer::$Sendmail
  1592. *
  1593. * @param string $header The message headers
  1594. * @param string $body The message body
  1595. *
  1596. * @throws Exception
  1597. *
  1598. * @return bool
  1599. */
  1600. protected function sendmailSend($header, $body)
  1601. {
  1602. // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
  1603. if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
  1604. if ('qmail' == $this->Mailer) {
  1605. $sendmailFmt = '%s -f%s';
  1606. } else {
  1607. $sendmailFmt = '%s -oi -f%s -t';
  1608. }
  1609. } else {
  1610. if ('qmail' == $this->Mailer) {
  1611. $sendmailFmt = '%s';
  1612. } else {
  1613. $sendmailFmt = '%s -oi -t';
  1614. }
  1615. }
  1616. $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
  1617. if ($this->SingleTo) {
  1618. foreach ($this->SingleToArray as $toAddr) {
  1619. $mail = @popen($sendmail, 'w');
  1620. if (!$mail) {
  1621. throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
  1622. }
  1623. fwrite($mail, 'To: ' . $toAddr . "\n");
  1624. fwrite($mail, $header);
  1625. fwrite($mail, $body);
  1626. $result = pclose($mail);
  1627. $this->doCallback(
  1628. ($result == 0),
  1629. [$toAddr],
  1630. $this->cc,
  1631. $this->bcc,
  1632. $this->Subject,
  1633. $body,
  1634. $this->From,
  1635. []
  1636. );
  1637. if (0 !== $result) {
  1638. throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
  1639. }
  1640. }
  1641. } else {
  1642. $mail = @popen($sendmail, 'w');
  1643. if (!$mail) {
  1644. throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
  1645. }
  1646. fwrite($mail, $header);
  1647. fwrite($mail, $body);
  1648. $result = pclose($mail);
  1649. $this->doCallback(
  1650. ($result == 0),
  1651. $this->to,
  1652. $this->cc,
  1653. $this->bcc,
  1654. $this->Subject,
  1655. $body,
  1656. $this->From,
  1657. []
  1658. );
  1659. if (0 !== $result) {
  1660. throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
  1661. }
  1662. }
  1663. return true;
  1664. }
  1665. /**
  1666. * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
  1667. * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
  1668. *
  1669. * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
  1670. *
  1671. * @param string $string The string to be validated
  1672. *
  1673. * @return bool
  1674. */
  1675. protected static function isShellSafe($string)
  1676. {
  1677. // Future-proof
  1678. if (escapeshellcmd($string) !== $string
  1679. or !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
  1680. ) {
  1681. return false;
  1682. }
  1683. $length = strlen($string);
  1684. for ($i = 0; $i < $length; ++$i) {
  1685. $c = $string[$i];
  1686. // All other characters have a special meaning in at least one common shell, including = and +.
  1687. // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
  1688. // Note that this does permit non-Latin alphanumeric characters based on the current locale.
  1689. if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
  1690. return false;
  1691. }
  1692. }
  1693. return true;
  1694. }
  1695. /**
  1696. * Check whether a file path is of a permitted type.
  1697. * Used to reject URLs and phar files from functions that access local file paths,
  1698. * such as addAttachment.
  1699. *
  1700. * @param string $path A relative or absolute path to a file
  1701. *
  1702. * @return bool
  1703. */
  1704. protected static function isPermittedPath($path)
  1705. {
  1706. return !preg_match('#^[a-z]+://#i', $path);
  1707. }
  1708. /**
  1709. * Send mail using the PHP mail() function.
  1710. *
  1711. * @see http://www.php.net/manual/en/book.mail.php
  1712. *
  1713. * @param string $header The message headers
  1714. * @param string $body The message body
  1715. *
  1716. * @throws Exception
  1717. *
  1718. * @return bool
  1719. */
  1720. protected function mailSend($header, $body)
  1721. {
  1722. $toArr = [];
  1723. foreach ($this->to as $toaddr) {
  1724. $toArr[] = $this->addrFormat($toaddr);
  1725. }
  1726. $to = implode(', ', $toArr);
  1727. $params = null;
  1728. //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
  1729. if (!empty($this->Sender) and static::validateAddress($this->Sender)) {
  1730. //A space after `-f` is optional, but there is a long history of its presence
  1731. //causing problems, so we don't use one
  1732. //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
  1733. //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
  1734. //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
  1735. //Example problem: https://www.drupal.org/node/1057954
  1736. // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
  1737. if (self::isShellSafe($this->Sender)) {
  1738. $params = sprintf('-f%s', $this->Sender);
  1739. }
  1740. }
  1741. if (!empty($this->Sender) and static::validateAddress($this->Sender)) {
  1742. $old_from = ini_get('sendmail_from');
  1743. ini_set('sendmail_from', $this->Sender);
  1744. }
  1745. $result = false;
  1746. if ($this->SingleTo and count($toArr) > 1) {
  1747. foreach ($toArr as $toAddr) {
  1748. $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
  1749. $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
  1750. }
  1751. } else {
  1752. $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
  1753. $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
  1754. }
  1755. if (isset($old_from)) {
  1756. ini_set('sendmail_from', $old_from);
  1757. }
  1758. if (!$result) {
  1759. throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
  1760. }
  1761. return true;
  1762. }
  1763. /**
  1764. * Get an instance to use for SMTP operations.
  1765. * Override this function to load your own SMTP implementation,
  1766. * or set one with setSMTPInstance.
  1767. *
  1768. * @return SMTP
  1769. */
  1770. public function getSMTPInstance()
  1771. {
  1772. if (!is_object($this->smtp)) {
  1773. $this->smtp = new SMTP();
  1774. }
  1775. return $this->smtp;
  1776. }
  1777. /**
  1778. * Provide an instance to use for SMTP operations.
  1779. *
  1780. * @param SMTP $smtp
  1781. *
  1782. * @return SMTP
  1783. */
  1784. public function setSMTPInstance(SMTP $smtp)
  1785. {
  1786. $this->smtp = $smtp;
  1787. return $this->smtp;
  1788. }
  1789. /**
  1790. * Send mail via SMTP.
  1791. * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
  1792. *
  1793. * @see PHPMailer::setSMTPInstance() to use a different class.
  1794. *
  1795. * @uses \PHPMailer\PHPMailer\SMTP
  1796. *
  1797. * @param string $header The message headers
  1798. * @param string $body The message body
  1799. *
  1800. * @throws Exception
  1801. *
  1802. * @return bool
  1803. */
  1804. protected function smtpSend($header, $body)
  1805. {
  1806. $bad_rcpt = [];
  1807. if (!$this->smtpConnect($this->SMTPOptions)) {
  1808. throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
  1809. }
  1810. //Sender already validated in preSend()
  1811. if ('' == $this->Sender) {
  1812. $smtp_from = $this->From;
  1813. } else {
  1814. $smtp_from = $this->Sender;
  1815. }
  1816. if (!$this->smtp->mail($smtp_from)) {
  1817. $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
  1818. throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
  1819. }
  1820. $callbacks = [];
  1821. // Attempt to send to all recipients
  1822. foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {
  1823. foreach ($togroup as $to) {
  1824. if (!$this->smtp->recipient($to[0], $this->dsn)) {
  1825. $error = $this->smtp->getError();
  1826. $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
  1827. $isSent = false;
  1828. } else {
  1829. $isSent = true;
  1830. }
  1831. $callbacks[] = ['issent'=>$isSent, 'to'=>$to[0]];
  1832. }
  1833. }
  1834. // Only send the DATA command if we have viable recipients
  1835. if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
  1836. throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
  1837. }
  1838. $smtp_transaction_id = $this->smtp->getLastTransactionID();
  1839. if ($this->SMTPKeepAlive) {
  1840. $this->smtp->reset();
  1841. } else {
  1842. $this->smtp->quit();
  1843. $this->smtp->close();
  1844. }
  1845. foreach ($callbacks as $cb) {
  1846. $this->doCallback(
  1847. $cb['issent'],
  1848. [$cb['to']],
  1849. [],
  1850. [],
  1851. $this->Subject,
  1852. $body,
  1853. $this->From,
  1854. ['smtp_transaction_id' => $smtp_transaction_id]
  1855. );
  1856. }
  1857. //Create error message for any bad addresses
  1858. if (count($bad_rcpt) > 0) {
  1859. $errstr = '';
  1860. foreach ($bad_rcpt as $bad) {
  1861. $errstr .= $bad['to'] . ': ' . $bad['error'];
  1862. }
  1863. throw new Exception(
  1864. $this->lang('recipients_failed') . $errstr,
  1865. self::STOP_CONTINUE
  1866. );
  1867. }
  1868. return true;
  1869. }
  1870. /**
  1871. * Initiate a connection to an SMTP server.
  1872. * Returns false if the operation failed.
  1873. *
  1874. * @param array $options An array of options compatible with stream_context_create()
  1875. *
  1876. * @throws Exception
  1877. *
  1878. * @uses \PHPMailer\PHPMailer\SMTP
  1879. *
  1880. * @return bool
  1881. */
  1882. public function smtpConnect($options = null)
  1883. {
  1884. if (null === $this->smtp) {
  1885. $this->smtp = $this->getSMTPInstance();
  1886. }
  1887. //If no options are provided, use whatever is set in the instance
  1888. if (null === $options) {
  1889. $options = $this->SMTPOptions;
  1890. }
  1891. // Already connected?
  1892. if ($this->smtp->connected()) {
  1893. return true;
  1894. }
  1895. $this->smtp->setTimeout($this->Timeout);
  1896. $this->smtp->setDebugLevel($this->SMTPDebug);
  1897. $this->smtp->setDebugOutput($this->Debugoutput);
  1898. $this->smtp->setVerp($this->do_verp);
  1899. $hosts = explode(';', $this->Host);
  1900. $lastexception = null;
  1901. foreach ($hosts as $hostentry) {
  1902. $hostinfo = [];
  1903. if (!preg_match(
  1904. '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/',
  1905. trim($hostentry),
  1906. $hostinfo
  1907. )) {
  1908. static::edebug($this->lang('connect_host') . ' ' . $hostentry);
  1909. // Not a valid host entry
  1910. continue;
  1911. }
  1912. // $hostinfo[2]: optional ssl or tls prefix
  1913. // $hostinfo[3]: the hostname
  1914. // $hostinfo[4]: optional port number
  1915. // The host string prefix can temporarily override the current setting for SMTPSecure
  1916. // If it's not specified, the default value is used
  1917. //Check the host name is a valid name or IP address before trying to use it
  1918. if (!static::isValidHost($hostinfo[3])) {
  1919. static::edebug($this->lang('connect_host') . ' ' . $hostentry);
  1920. continue;
  1921. }
  1922. $prefix = '';
  1923. $secure = $this->SMTPSecure;
  1924. $tls = ('tls' == $this->SMTPSecure);
  1925. if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
  1926. $prefix = 'ssl://';
  1927. $tls = false; // Can't have SSL and TLS at the same time
  1928. $secure = 'ssl';
  1929. } elseif ('tls' == $hostinfo[2]) {
  1930. $tls = true;
  1931. // tls doesn't use a prefix
  1932. $secure = 'tls';
  1933. }
  1934. //Do we need the OpenSSL extension?
  1935. $sslext = defined('OPENSSL_ALGO_SHA256');
  1936. if ('tls' === $secure or 'ssl' === $secure) {
  1937. //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
  1938. if (!$sslext) {
  1939. throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
  1940. }
  1941. }
  1942. $host = $hostinfo[3];
  1943. $port = $this->Port;
  1944. $tport = (int) $hostinfo[4];
  1945. if ($tport > 0 and $tport < 65536) {
  1946. $port = $tport;
  1947. }
  1948. if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
  1949. try {
  1950. if ($this->Helo) {
  1951. $hello = $this->Helo;
  1952. } else {
  1953. $hello = $this->serverHostname();
  1954. }
  1955. $this->smtp->hello($hello);
  1956. //Automatically enable TLS encryption if:
  1957. // * it's not disabled
  1958. // * we have openssl extension
  1959. // * we are not already using SSL
  1960. // * the server offers STARTTLS
  1961. if ($this->SMTPAutoTLS and $sslext and 'ssl' != $secure and $this->smtp->getServerExt('STARTTLS')) {
  1962. $tls = true;
  1963. }
  1964. if ($tls) {
  1965. if (!$this->smtp->startTLS()) {
  1966. throw new Exception($this->lang('connect_host'));
  1967. }
  1968. // We must resend EHLO after TLS negotiation
  1969. $this->smtp->hello($hello);
  1970. }
  1971. if ($this->SMTPAuth) {
  1972. if (!$this->smtp->authenticate(
  1973. $this->Username,
  1974. $this->Password,
  1975. $this->AuthType,
  1976. $this->oauth
  1977. )
  1978. ) {
  1979. throw new Exception($this->lang('authenticate'));
  1980. }
  1981. }
  1982. return true;
  1983. } catch (Exception $exc) {
  1984. $lastexception = $exc;
  1985. $this->edebug($exc->getMessage());
  1986. // We must have connected, but then failed TLS or Auth, so close connection nicely
  1987. $this->smtp->quit();
  1988. }
  1989. }
  1990. }
  1991. // If we get here, all connection attempts have failed, so close connection hard
  1992. $this->smtp->close();
  1993. // As we've caught all exceptions, just report whatever the last one was
  1994. if ($this->exceptions and null !== $lastexception) {
  1995. throw $lastexception;
  1996. }
  1997. return false;
  1998. }
  1999. /**
  2000. * Close the active SMTP session if one exists.
  2001. */
  2002. public function smtpClose()
  2003. {
  2004. if (null !== $this->smtp) {
  2005. if ($this->smtp->connected()) {
  2006. $this->smtp->quit();
  2007. $this->smtp->close();
  2008. }
  2009. }
  2010. }
  2011. /**
  2012. * Set the language for error messages.
  2013. * Returns false if it cannot load the language file.
  2014. * The default language is English.
  2015. *
  2016. * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
  2017. * @param string $lang_path Path to the language file directory, with trailing separator (slash)
  2018. *
  2019. * @return bool
  2020. */
  2021. public function setLanguage($langcode = 'en', $lang_path = '')
  2022. {
  2023. // Backwards compatibility for renamed language codes
  2024. $renamed_langcodes = [
  2025. 'br' => 'pt_br',
  2026. 'cz' => 'cs',
  2027. 'dk' => 'da',
  2028. 'no' => 'nb',
  2029. 'se' => 'sv',
  2030. 'rs' => 'sr',
  2031. 'tg' => 'tl',
  2032. ];
  2033. if (isset($renamed_langcodes[$langcode])) {
  2034. $langcode = $renamed_langcodes[$langcode];
  2035. }
  2036. // Define full set of translatable strings in English
  2037. $PHPMAILER_LANG = [
  2038. 'authenticate' => 'SMTP Error: Could not authenticate.',
  2039. 'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
  2040. 'data_not_accepted' => 'SMTP Error: data not accepted.',
  2041. 'empty_message' => 'Message body empty',
  2042. 'encoding' => 'Unknown encoding: ',
  2043. 'execute' => 'Could not execute: ',
  2044. 'file_access' => 'Could not access file: ',
  2045. 'file_open' => 'File Error: Could not open file: ',
  2046. 'from_failed' => 'The following From address failed: ',
  2047. 'instantiate' => 'Could not instantiate mail function.',
  2048. 'invalid_address' => 'Invalid address: ',
  2049. 'mailer_not_supported' => ' mailer is not supported.',
  2050. 'provide_address' => 'You must provide at least one recipient email address.',
  2051. 'recipients_failed' => 'SMTP Error: The following recipients failed: ',
  2052. 'signing' => 'Signing Error: ',
  2053. 'smtp_connect_failed' => 'SMTP connect() failed.',
  2054. 'smtp_error' => 'SMTP server error: ',
  2055. 'variable_set' => 'Cannot set or reset variable: ',
  2056. 'extension_missing' => 'Extension missing: ',
  2057. ];
  2058. if (empty($lang_path)) {
  2059. // Calculate an absolute path so it can work if CWD is not here
  2060. $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR;
  2061. }
  2062. //Validate $langcode
  2063. if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
  2064. $langcode = 'en';
  2065. }
  2066. $foundlang = true;
  2067. $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
  2068. // There is no English translation file
  2069. if ('en' != $langcode) {
  2070. // Make sure language file path is readable
  2071. if (!static::isPermittedPath($lang_file) || !file_exists($lang_file)) {
  2072. $foundlang = false;
  2073. } else {
  2074. // Overwrite language-specific strings.
  2075. // This way we'll never have missing translation keys.
  2076. $foundlang = include $lang_file;
  2077. }
  2078. }
  2079. $this->language = $PHPMAILER_LANG;
  2080. return (bool) $foundlang; // Returns false if language not found
  2081. }
  2082. /**
  2083. * Get the array of strings for the current language.
  2084. *
  2085. * @return array
  2086. */
  2087. public function getTranslations()
  2088. {
  2089. return $this->language;
  2090. }
  2091. /**
  2092. * Create recipient headers.
  2093. *
  2094. * @param string $type
  2095. * @param array $addr An array of recipients,
  2096. * where each recipient is a 2-element indexed array with element 0 containing an address
  2097. * and element 1 containing a name, like:
  2098. * [['joe@example.com', 'Joe User'], ['zoe@example.com', 'Zoe User']]
  2099. *
  2100. * @return string
  2101. */
  2102. public function addrAppend($type, $addr)
  2103. {
  2104. $addresses = [];
  2105. foreach ($addr as $address) {
  2106. $addresses[] = $this->addrFormat($address);
  2107. }
  2108. return $type . ': ' . implode(', ', $addresses) . static::$LE;
  2109. }
  2110. /**
  2111. * Format an address for use in a message header.
  2112. *
  2113. * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like
  2114. * ['joe@example.com', 'Joe User']
  2115. *
  2116. * @return string
  2117. */
  2118. public function addrFormat($addr)
  2119. {
  2120. if (empty($addr[1])) { // No name provided
  2121. return $this->secureHeader($addr[0]);
  2122. }
  2123. return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
  2124. $addr[0]
  2125. ) . '>';
  2126. }
  2127. /**
  2128. * Word-wrap message.
  2129. * For use with mailers that do not automatically perform wrapping
  2130. * and for quoted-printable encoded messages.
  2131. * Original written by philippe.
  2132. *
  2133. * @param string $message The message to wrap
  2134. * @param int $length The line length to wrap to
  2135. * @param bool $qp_mode Whether to run in Quoted-Printable mode
  2136. *
  2137. * @return string
  2138. */
  2139. public function wrapText($message, $length, $qp_mode = false)
  2140. {
  2141. if ($qp_mode) {
  2142. $soft_break = sprintf(' =%s', static::$LE);
  2143. } else {
  2144. $soft_break = static::$LE;
  2145. }
  2146. // If utf-8 encoding is used, we will need to make sure we don't
  2147. // split multibyte characters when we wrap
  2148. $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);
  2149. $lelen = strlen(static::$LE);
  2150. $crlflen = strlen(static::$LE);
  2151. $message = static::normalizeBreaks($message);
  2152. //Remove a trailing line break
  2153. if (substr($message, -$lelen) == static::$LE) {
  2154. $message = substr($message, 0, -$lelen);
  2155. }
  2156. //Split message into lines
  2157. $lines = explode(static::$LE, $message);
  2158. //Message will be rebuilt in here
  2159. $message = '';
  2160. foreach ($lines as $line) {
  2161. $words = explode(' ', $line);
  2162. $buf = '';
  2163. $firstword = true;
  2164. foreach ($words as $word) {
  2165. if ($qp_mode and (strlen($word) > $length)) {
  2166. $space_left = $length - strlen($buf) - $crlflen;
  2167. if (!$firstword) {
  2168. if ($space_left > 20) {
  2169. $len = $space_left;
  2170. if ($is_utf8) {
  2171. $len = $this->utf8CharBoundary($word, $len);
  2172. } elseif ('=' == substr($word, $len - 1, 1)) {
  2173. --$len;
  2174. } elseif ('=' == substr($word, $len - 2, 1)) {
  2175. $len -= 2;
  2176. }
  2177. $part = substr($word, 0, $len);
  2178. $word = substr($word, $len);
  2179. $buf .= ' ' . $part;
  2180. $message .= $buf . sprintf('=%s', static::$LE);
  2181. } else {
  2182. $message .= $buf . $soft_break;
  2183. }
  2184. $buf = '';
  2185. }
  2186. while (strlen($word) > 0) {
  2187. if ($length <= 0) {
  2188. break;
  2189. }
  2190. $len = $length;
  2191. if ($is_utf8) {
  2192. $len = $this->utf8CharBoundary($word, $len);
  2193. } elseif ('=' == substr($word, $len - 1, 1)) {
  2194. --$len;
  2195. } elseif ('=' == substr($word, $len - 2, 1)) {
  2196. $len -= 2;
  2197. }
  2198. $part = substr($word, 0, $len);
  2199. $word = substr($word, $len);
  2200. if (strlen($word) > 0) {
  2201. $message .= $part . sprintf('=%s', static::$LE);
  2202. } else {
  2203. $buf = $part;
  2204. }
  2205. }
  2206. } else {
  2207. $buf_o = $buf;
  2208. if (!$firstword) {
  2209. $buf .= ' ';
  2210. }
  2211. $buf .= $word;
  2212. if (strlen($buf) > $length and '' != $buf_o) {
  2213. $message .= $buf_o . $soft_break;
  2214. $buf = $word;
  2215. }
  2216. }
  2217. $firstword = false;
  2218. }
  2219. $message .= $buf . static::$LE;
  2220. }
  2221. return $message;
  2222. }
  2223. /**
  2224. * Find the last character boundary prior to $maxLength in a utf-8
  2225. * quoted-printable encoded string.
  2226. * Original written by Colin Brown.
  2227. *
  2228. * @param string $encodedText utf-8 QP text
  2229. * @param int $maxLength Find the last character boundary prior to this length
  2230. *
  2231. * @return int
  2232. */
  2233. public function utf8CharBoundary($encodedText, $maxLength)
  2234. {
  2235. $foundSplitPos = false;
  2236. $lookBack = 3;
  2237. while (!$foundSplitPos) {
  2238. $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
  2239. $encodedCharPos = strpos($lastChunk, '=');
  2240. if (false !== $encodedCharPos) {
  2241. // Found start of encoded character byte within $lookBack block.
  2242. // Check the encoded byte value (the 2 chars after the '=')
  2243. $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
  2244. $dec = hexdec($hex);
  2245. if ($dec < 128) {
  2246. // Single byte character.
  2247. // If the encoded char was found at pos 0, it will fit
  2248. // otherwise reduce maxLength to start of the encoded char
  2249. if ($encodedCharPos > 0) {
  2250. $maxLength -= $lookBack - $encodedCharPos;
  2251. }
  2252. $foundSplitPos = true;
  2253. } elseif ($dec >= 192) {
  2254. // First byte of a multi byte character
  2255. // Reduce maxLength to split at start of character
  2256. $maxLength -= $lookBack - $encodedCharPos;
  2257. $foundSplitPos = true;
  2258. } elseif ($dec < 192) {
  2259. // Middle byte of a multi byte character, look further back
  2260. $lookBack += 3;
  2261. }
  2262. } else {
  2263. // No encoded character found
  2264. $foundSplitPos = true;
  2265. }
  2266. }
  2267. return $maxLength;
  2268. }
  2269. /**
  2270. * Apply word wrapping to the message body.
  2271. * Wraps the message body to the number of chars set in the WordWrap property.
  2272. * You should only do this to plain-text bodies as wrapping HTML tags may break them.
  2273. * This is called automatically by createBody(), so you don't need to call it yourself.
  2274. */
  2275. public function setWordWrap()
  2276. {
  2277. if ($this->WordWrap < 1) {
  2278. return;
  2279. }
  2280. switch ($this->message_type) {
  2281. case 'alt':
  2282. case 'alt_inline':
  2283. case 'alt_attach':
  2284. case 'alt_inline_attach':
  2285. $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
  2286. break;
  2287. default:
  2288. $this->Body = $this->wrapText($this->Body, $this->WordWrap);
  2289. break;
  2290. }
  2291. }
  2292. /**
  2293. * Assemble message headers.
  2294. *
  2295. * @return string The assembled headers
  2296. */
  2297. public function createHeader()
  2298. {
  2299. $result = '';
  2300. $result .= $this->headerLine('Date', '' == $this->MessageDate ? self::rfcDate() : $this->MessageDate);
  2301. // To be created automatically by mail()
  2302. if ($this->SingleTo) {
  2303. if ('mail' != $this->Mailer) {
  2304. foreach ($this->to as $toaddr) {
  2305. $this->SingleToArray[] = $this->addrFormat($toaddr);
  2306. }
  2307. }
  2308. } else {
  2309. if (count($this->to) > 0) {
  2310. if ('mail' != $this->Mailer) {
  2311. $result .= $this->addrAppend('To', $this->to);
  2312. }
  2313. } elseif (count($this->cc) == 0) {
  2314. $result .= $this->headerLine('To', 'undisclosed-recipients:;');
  2315. }
  2316. }
  2317. $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
  2318. // sendmail and mail() extract Cc from the header before sending
  2319. if (count($this->cc) > 0) {
  2320. $result .= $this->addrAppend('Cc', $this->cc);
  2321. }
  2322. // sendmail and mail() extract Bcc from the header before sending
  2323. if ((
  2324. 'sendmail' == $this->Mailer or 'qmail' == $this->Mailer or 'mail' == $this->Mailer
  2325. )
  2326. and count($this->bcc) > 0
  2327. ) {
  2328. $result .= $this->addrAppend('Bcc', $this->bcc);
  2329. }
  2330. if (count($this->ReplyTo) > 0) {
  2331. $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
  2332. }
  2333. // mail() sets the subject itself
  2334. if ('mail' != $this->Mailer) {
  2335. $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
  2336. }
  2337. // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
  2338. // https://tools.ietf.org/html/rfc5322#section-3.6.4
  2339. if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
  2340. $this->lastMessageID = $this->MessageID;
  2341. } else {
  2342. $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
  2343. }
  2344. $result .= $this->headerLine('Message-ID', $this->lastMessageID);
  2345. if (null !== $this->Priority) {
  2346. $result .= $this->headerLine('X-Priority', $this->Priority);
  2347. }
  2348. if ('' == $this->XMailer) {
  2349. $result .= $this->headerLine(
  2350. 'X-Mailer',
  2351. 'PHPMailer ' . self::VERSION
  2352. );
  2353. } else {
  2354. $myXmailer = trim($this->XMailer);
  2355. if ($myXmailer) {
  2356. $result .= $this->headerLine('X-Mailer', $myXmailer);
  2357. }
  2358. }
  2359. if ('' != $this->ConfirmReadingTo) {
  2360. $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
  2361. }
  2362. // Add custom headers
  2363. foreach ($this->CustomHeader as $header) {
  2364. $result .= $this->headerLine(
  2365. trim($header[0]),
  2366. $this->encodeHeader(trim($header[1]))
  2367. );
  2368. }
  2369. if (!$this->sign_key_file) {
  2370. $result .= $this->headerLine('MIME-Version', '1.0');
  2371. $result .= $this->getMailMIME();
  2372. }
  2373. return $result;
  2374. }
  2375. /**
  2376. * Get the message MIME type headers.
  2377. *
  2378. * @return string
  2379. */
  2380. public function getMailMIME()
  2381. {
  2382. $result = '';
  2383. $ismultipart = true;
  2384. switch ($this->message_type) {
  2385. case 'inline':
  2386. $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
  2387. $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
  2388. break;
  2389. case 'attach':
  2390. case 'inline_attach':
  2391. case 'alt_attach':
  2392. case 'alt_inline_attach':
  2393. $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';');
  2394. $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
  2395. break;
  2396. case 'alt':
  2397. case 'alt_inline':
  2398. $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
  2399. $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
  2400. break;
  2401. default:
  2402. // Catches case 'plain': and case '':
  2403. $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
  2404. $ismultipart = false;
  2405. break;
  2406. }
  2407. // RFC1341 part 5 says 7bit is assumed if not specified
  2408. if (static::ENCODING_7BIT != $this->Encoding) {
  2409. // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
  2410. if ($ismultipart) {
  2411. if (static::ENCODING_8BIT == $this->Encoding) {
  2412. $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
  2413. }
  2414. // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
  2415. } else {
  2416. $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
  2417. }
  2418. }
  2419. if ('mail' != $this->Mailer) {
  2420. $result .= static::$LE;
  2421. }
  2422. return $result;
  2423. }
  2424. /**
  2425. * Returns the whole MIME message.
  2426. * Includes complete headers and body.
  2427. * Only valid post preSend().
  2428. *
  2429. * @see PHPMailer::preSend()
  2430. *
  2431. * @return string
  2432. */
  2433. public function getSentMIMEMessage()
  2434. {
  2435. return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . static::$LE . static::$LE . $this->MIMEBody;
  2436. }
  2437. /**
  2438. * Create a unique ID to use for boundaries.
  2439. *
  2440. * @return string
  2441. */
  2442. protected function generateId()
  2443. {
  2444. $len = 32; //32 bytes = 256 bits
  2445. if (function_exists('random_bytes')) {
  2446. $bytes = random_bytes($len);
  2447. } elseif (function_exists('openssl_random_pseudo_bytes')) {
  2448. $bytes = openssl_random_pseudo_bytes($len);
  2449. } else {
  2450. //Use a hash to force the length to the same as the other methods
  2451. $bytes = hash('sha256', uniqid((string) mt_rand(), true), true);
  2452. }
  2453. //We don't care about messing up base64 format here, just want a random string
  2454. return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));
  2455. }
  2456. /**
  2457. * Assemble the message body.
  2458. * Returns an empty string on failure.
  2459. *
  2460. * @throws Exception
  2461. *
  2462. * @return string The assembled message body
  2463. */
  2464. public function createBody()
  2465. {
  2466. $body = '';
  2467. //Create unique IDs and preset boundaries
  2468. $this->uniqueid = $this->generateId();
  2469. $this->boundary[1] = 'b1_' . $this->uniqueid;
  2470. $this->boundary[2] = 'b2_' . $this->uniqueid;
  2471. $this->boundary[3] = 'b3_' . $this->uniqueid;
  2472. if ($this->sign_key_file) {
  2473. $body .= $this->getMailMIME() . static::$LE;
  2474. }
  2475. $this->setWordWrap();
  2476. $bodyEncoding = $this->Encoding;
  2477. $bodyCharSet = $this->CharSet;
  2478. //Can we do a 7-bit downgrade?
  2479. if (static::ENCODING_8BIT == $bodyEncoding and !$this->has8bitChars($this->Body)) {
  2480. $bodyEncoding = static::ENCODING_7BIT;
  2481. //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
  2482. $bodyCharSet = 'us-ascii';
  2483. }
  2484. //If lines are too long, and we're not already using an encoding that will shorten them,
  2485. //change to quoted-printable transfer encoding for the body part only
  2486. if (static::ENCODING_BASE64 != $this->Encoding and static::hasLineLongerThanMax($this->Body)) {
  2487. $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
  2488. }
  2489. $altBodyEncoding = $this->Encoding;
  2490. $altBodyCharSet = $this->CharSet;
  2491. //Can we do a 7-bit downgrade?
  2492. if (static::ENCODING_8BIT == $altBodyEncoding and !$this->has8bitChars($this->AltBody)) {
  2493. $altBodyEncoding = static::ENCODING_7BIT;
  2494. //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
  2495. $altBodyCharSet = 'us-ascii';
  2496. }
  2497. //If lines are too long, and we're not already using an encoding that will shorten them,
  2498. //change to quoted-printable transfer encoding for the alt body part only
  2499. if (static::ENCODING_BASE64 != $altBodyEncoding and static::hasLineLongerThanMax($this->AltBody)) {
  2500. $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
  2501. }
  2502. //Use this as a preamble in all multipart message types
  2503. $mimepre = 'This is a multi-part message in MIME format.' . static::$LE;
  2504. switch ($this->message_type) {
  2505. case 'inline':
  2506. $body .= $mimepre;
  2507. $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
  2508. $body .= $this->encodeString($this->Body, $bodyEncoding);
  2509. $body .= static::$LE;
  2510. $body .= $this->attachAll('inline', $this->boundary[1]);
  2511. break;
  2512. case 'attach':
  2513. $body .= $mimepre;
  2514. $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
  2515. $body .= $this->encodeString($this->Body, $bodyEncoding);
  2516. $body .= static::$LE;
  2517. $body .= $this->attachAll('attachment', $this->boundary[1]);
  2518. break;
  2519. case 'inline_attach':
  2520. $body .= $mimepre;
  2521. $body .= $this->textLine('--' . $this->boundary[1]);
  2522. $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
  2523. $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
  2524. $body .= static::$LE;
  2525. $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
  2526. $body .= $this->encodeString($this->Body, $bodyEncoding);
  2527. $body .= static::$LE;
  2528. $body .= $this->attachAll('inline', $this->boundary[2]);
  2529. $body .= static::$LE;
  2530. $body .= $this->attachAll('attachment', $this->boundary[1]);
  2531. break;
  2532. case 'alt':
  2533. $body .= $mimepre;
  2534. $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding);
  2535. $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
  2536. $body .= static::$LE;
  2537. $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding);
  2538. $body .= $this->encodeString($this->Body, $bodyEncoding);
  2539. $body .= static::$LE;
  2540. if (!empty($this->Ical)) {
  2541. $body .= $this->getBoundary($this->boundary[1], '', static::CONTENT_TYPE_TEXT_CALENDAR . '; method=REQUEST', '');
  2542. $body .= $this->encodeString($this->Ical, $this->Encoding);
  2543. $body .= static::$LE;
  2544. }
  2545. $body .= $this->endBoundary($this->boundary[1]);
  2546. break;
  2547. case 'alt_inline':
  2548. $body .= $mimepre;
  2549. $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding);
  2550. $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
  2551. $body .= static::$LE;
  2552. $body .= $this->textLine('--' . $this->boundary[1]);
  2553. $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
  2554. $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
  2555. $body .= static::$LE;
  2556. $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding);
  2557. $body .= $this->encodeString($this->Body, $bodyEncoding);
  2558. $body .= static::$LE;
  2559. $body .= $this->attachAll('inline', $this->boundary[2]);
  2560. $body .= static::$LE;
  2561. $body .= $this->endBoundary($this->boundary[1]);
  2562. break;
  2563. case 'alt_attach':
  2564. $body .= $mimepre;
  2565. $body .= $this->textLine('--' . $this->boundary[1]);
  2566. $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
  2567. $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
  2568. $body .= static::$LE;
  2569. $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding);
  2570. $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
  2571. $body .= static::$LE;
  2572. $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding);
  2573. $body .= $this->encodeString($this->Body, $bodyEncoding);
  2574. $body .= static::$LE;
  2575. if (!empty($this->Ical)) {
  2576. $body .= $this->getBoundary($this->boundary[2], '', static::CONTENT_TYPE_TEXT_CALENDAR . '; method=REQUEST', '');
  2577. $body .= $this->encodeString($this->Ical, $this->Encoding);
  2578. }
  2579. $body .= $this->endBoundary($this->boundary[2]);
  2580. $body .= static::$LE;
  2581. $body .= $this->attachAll('attachment', $this->boundary[1]);
  2582. break;
  2583. case 'alt_inline_attach':
  2584. $body .= $mimepre;
  2585. $body .= $this->textLine('--' . $this->boundary[1]);
  2586. $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
  2587. $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
  2588. $body .= static::$LE;
  2589. $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding);
  2590. $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
  2591. $body .= static::$LE;
  2592. $body .= $this->textLine('--' . $this->boundary[2]);
  2593. $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
  2594. $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
  2595. $body .= static::$LE;
  2596. $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding);
  2597. $body .= $this->encodeString($this->Body, $bodyEncoding);
  2598. $body .= static::$LE;
  2599. $body .= $this->attachAll('inline', $this->boundary[3]);
  2600. $body .= static::$LE;
  2601. $body .= $this->endBoundary($this->boundary[2]);
  2602. $body .= static::$LE;
  2603. $body .= $this->attachAll('attachment', $this->boundary[1]);
  2604. break;
  2605. default:
  2606. // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
  2607. //Reset the `Encoding` property in case we changed it for line length reasons
  2608. $this->Encoding = $bodyEncoding;
  2609. $body .= $this->encodeString($this->Body, $this->Encoding);
  2610. break;
  2611. }
  2612. if ($this->isError()) {
  2613. $body = '';
  2614. if ($this->exceptions) {
  2615. throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
  2616. }
  2617. } elseif ($this->sign_key_file) {
  2618. try {
  2619. if (!defined('PKCS7_TEXT')) {
  2620. throw new Exception($this->lang('extension_missing') . 'openssl');
  2621. }
  2622. $file = fopen('php://temp', 'rb+');
  2623. $signed = fopen('php://temp', 'rb+');
  2624. fwrite($file, $body);
  2625. //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
  2626. if (empty($this->sign_extracerts_file)) {
  2627. $sign = @openssl_pkcs7_sign(
  2628. $file,
  2629. $signed,
  2630. 'file://' . realpath($this->sign_cert_file),
  2631. ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
  2632. []
  2633. );
  2634. } else {
  2635. $sign = @openssl_pkcs7_sign(
  2636. $file,
  2637. $signed,
  2638. 'file://' . realpath($this->sign_cert_file),
  2639. ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
  2640. [],
  2641. PKCS7_DETACHED,
  2642. $this->sign_extracerts_file
  2643. );
  2644. }
  2645. fclose($file);
  2646. if ($sign) {
  2647. $body = file_get_contents($signed);
  2648. fclose($signed);
  2649. //The message returned by openssl contains both headers and body, so need to split them up
  2650. $parts = explode("\n\n", $body, 2);
  2651. $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE;
  2652. $body = $parts[1];
  2653. } else {
  2654. fclose($signed);
  2655. throw new Exception($this->lang('signing') . openssl_error_string());
  2656. }
  2657. } catch (Exception $exc) {
  2658. $body = '';
  2659. if ($this->exceptions) {
  2660. throw $exc;
  2661. }
  2662. }
  2663. }
  2664. return $body;
  2665. }
  2666. /**
  2667. * Return the start of a message boundary.
  2668. *
  2669. * @param string $boundary
  2670. * @param string $charSet
  2671. * @param string $contentType
  2672. * @param string $encoding
  2673. *
  2674. * @return string
  2675. */
  2676. protected function getBoundary($boundary, $charSet, $contentType, $encoding)
  2677. {
  2678. $result = '';
  2679. if ('' == $charSet) {
  2680. $charSet = $this->CharSet;
  2681. }
  2682. if ('' == $contentType) {
  2683. $contentType = $this->ContentType;
  2684. }
  2685. if ('' == $encoding) {
  2686. $encoding = $this->Encoding;
  2687. }
  2688. $result .= $this->textLine('--' . $boundary);
  2689. $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
  2690. $result .= static::$LE;
  2691. // RFC1341 part 5 says 7bit is assumed if not specified
  2692. if (static::ENCODING_7BIT != $encoding) {
  2693. $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
  2694. }
  2695. $result .= static::$LE;
  2696. return $result;
  2697. }
  2698. /**
  2699. * Return the end of a message boundary.
  2700. *
  2701. * @param string $boundary
  2702. *
  2703. * @return string
  2704. */
  2705. protected function endBoundary($boundary)
  2706. {
  2707. return static::$LE . '--' . $boundary . '--' . static::$LE;
  2708. }
  2709. /**
  2710. * Set the message type.
  2711. * PHPMailer only supports some preset message types, not arbitrary MIME structures.
  2712. */
  2713. protected function setMessageType()
  2714. {
  2715. $type = [];
  2716. if ($this->alternativeExists()) {
  2717. $type[] = 'alt';
  2718. }
  2719. if ($this->inlineImageExists()) {
  2720. $type[] = 'inline';
  2721. }
  2722. if ($this->attachmentExists()) {
  2723. $type[] = 'attach';
  2724. }
  2725. $this->message_type = implode('_', $type);
  2726. if ('' == $this->message_type) {
  2727. //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
  2728. $this->message_type = 'plain';
  2729. }
  2730. }
  2731. /**
  2732. * Format a header line.
  2733. *
  2734. * @param string $name
  2735. * @param string|int $value
  2736. *
  2737. * @return string
  2738. */
  2739. public function headerLine($name, $value)
  2740. {
  2741. return $name . ': ' . $value . static::$LE;
  2742. }
  2743. /**
  2744. * Return a formatted mail line.
  2745. *
  2746. * @param string $value
  2747. *
  2748. * @return string
  2749. */
  2750. public function textLine($value)
  2751. {
  2752. return $value . static::$LE;
  2753. }
  2754. /**
  2755. * Add an attachment from a path on the filesystem.
  2756. * Never use a user-supplied path to a file!
  2757. * Returns false if the file could not be found or read.
  2758. * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
  2759. * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
  2760. *
  2761. * @param string $path Path to the attachment
  2762. * @param string $name Overrides the attachment name
  2763. * @param string $encoding File encoding (see $Encoding)
  2764. * @param string $type File extension (MIME) type
  2765. * @param string $disposition Disposition to use
  2766. *
  2767. * @throws Exception
  2768. *
  2769. * @return bool
  2770. */
  2771. public function addAttachment($path, $name = '', $encoding = self::ENCODING_BASE64, $type = '', $disposition = 'attachment')
  2772. {
  2773. try {
  2774. if (!static::isPermittedPath($path) || !@is_file($path)) {
  2775. throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
  2776. }
  2777. // If a MIME type is not specified, try to work it out from the file name
  2778. if ('' == $type) {
  2779. $type = static::filenameToType($path);
  2780. }
  2781. $filename = basename($path);
  2782. if ('' == $name) {
  2783. $name = $filename;
  2784. }
  2785. $this->attachment[] = [
  2786. 0 => $path,
  2787. 1 => $filename,
  2788. 2 => $name,
  2789. 3 => $encoding,
  2790. 4 => $type,
  2791. 5 => false, // isStringAttachment
  2792. 6 => $disposition,
  2793. 7 => $name,
  2794. ];
  2795. } catch (Exception $exc) {
  2796. $this->setError($exc->getMessage());
  2797. $this->edebug($exc->getMessage());
  2798. if ($this->exceptions) {
  2799. throw $exc;
  2800. }
  2801. return false;
  2802. }
  2803. return true;
  2804. }
  2805. /**
  2806. * Return the array of attachments.
  2807. *
  2808. * @return array
  2809. */
  2810. public function getAttachments()
  2811. {
  2812. return $this->attachment;
  2813. }
  2814. /**
  2815. * Attach all file, string, and binary attachments to the message.
  2816. * Returns an empty string on failure.
  2817. *
  2818. * @param string $disposition_type
  2819. * @param string $boundary
  2820. *
  2821. * @return string
  2822. */
  2823. protected function attachAll($disposition_type, $boundary)
  2824. {
  2825. // Return text of body
  2826. $mime = [];
  2827. $cidUniq = [];
  2828. $incl = [];
  2829. // Add all attachments
  2830. foreach ($this->attachment as $attachment) {
  2831. // Check if it is a valid disposition_filter
  2832. if ($attachment[6] == $disposition_type) {
  2833. // Check for string attachment
  2834. $string = '';
  2835. $path = '';
  2836. $bString = $attachment[5];
  2837. if ($bString) {
  2838. $string = $attachment[0];
  2839. } else {
  2840. $path = $attachment[0];
  2841. }
  2842. $inclhash = hash('sha256', serialize($attachment));
  2843. if (in_array($inclhash, $incl)) {
  2844. continue;
  2845. }
  2846. $incl[] = $inclhash;
  2847. $name = $attachment[2];
  2848. $encoding = $attachment[3];
  2849. $type = $attachment[4];
  2850. $disposition = $attachment[6];
  2851. $cid = $attachment[7];
  2852. if ('inline' == $disposition and array_key_exists($cid, $cidUniq)) {
  2853. continue;
  2854. }
  2855. $cidUniq[$cid] = true;
  2856. $mime[] = sprintf('--%s%s', $boundary, static::$LE);
  2857. //Only include a filename property if we have one
  2858. if (!empty($name)) {
  2859. $mime[] = sprintf(
  2860. 'Content-Type: %s; name="%s"%s',
  2861. $type,
  2862. $this->encodeHeader($this->secureHeader($name)),
  2863. static::$LE
  2864. );
  2865. } else {
  2866. $mime[] = sprintf(
  2867. 'Content-Type: %s%s',
  2868. $type,
  2869. static::$LE
  2870. );
  2871. }
  2872. // RFC1341 part 5 says 7bit is assumed if not specified
  2873. if (static::ENCODING_7BIT != $encoding) {
  2874. $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE);
  2875. }
  2876. if (!empty($cid)) {
  2877. $mime[] = sprintf('Content-ID: <%s>%s', $cid, static::$LE);
  2878. }
  2879. // If a filename contains any of these chars, it should be quoted,
  2880. // but not otherwise: RFC2183 & RFC2045 5.1
  2881. // Fixes a warning in IETF's msglint MIME checker
  2882. // Allow for bypassing the Content-Disposition header totally
  2883. if (!(empty($disposition))) {
  2884. $encoded_name = $this->encodeHeader($this->secureHeader($name));
  2885. if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
  2886. $mime[] = sprintf(
  2887. 'Content-Disposition: %s; filename="%s"%s',
  2888. $disposition,
  2889. $encoded_name,
  2890. static::$LE . static::$LE
  2891. );
  2892. } else {
  2893. if (!empty($encoded_name)) {
  2894. $mime[] = sprintf(
  2895. 'Content-Disposition: %s; filename=%s%s',
  2896. $disposition,
  2897. $encoded_name,
  2898. static::$LE . static::$LE
  2899. );
  2900. } else {
  2901. $mime[] = sprintf(
  2902. 'Content-Disposition: %s%s',
  2903. $disposition,
  2904. static::$LE . static::$LE
  2905. );
  2906. }
  2907. }
  2908. } else {
  2909. $mime[] = static::$LE;
  2910. }
  2911. // Encode as string attachment
  2912. if ($bString) {
  2913. $mime[] = $this->encodeString($string, $encoding);
  2914. } else {
  2915. $mime[] = $this->encodeFile($path, $encoding);
  2916. }
  2917. if ($this->isError()) {
  2918. return '';
  2919. }
  2920. $mime[] = static::$LE;
  2921. }
  2922. }
  2923. $mime[] = sprintf('--%s--%s', $boundary, static::$LE);
  2924. return implode('', $mime);
  2925. }
  2926. /**
  2927. * Encode a file attachment in requested format.
  2928. * Returns an empty string on failure.
  2929. *
  2930. * @param string $path The full path to the file
  2931. * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
  2932. *
  2933. * @throws Exception
  2934. *
  2935. * @return string
  2936. */
  2937. protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
  2938. {
  2939. try {
  2940. if (!static::isPermittedPath($path) || !file_exists($path)) {
  2941. throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
  2942. }
  2943. $file_buffer = file_get_contents($path);
  2944. if (false === $file_buffer) {
  2945. throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
  2946. }
  2947. $file_buffer = $this->encodeString($file_buffer, $encoding);
  2948. return $file_buffer;
  2949. } catch (Exception $exc) {
  2950. $this->setError($exc->getMessage());
  2951. return '';
  2952. }
  2953. }
  2954. /**
  2955. * Encode a string in requested format.
  2956. * Returns an empty string on failure.
  2957. *
  2958. * @param string $str The text to encode
  2959. * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
  2960. *
  2961. * @return string
  2962. */
  2963. public function encodeString($str, $encoding = self::ENCODING_BASE64)
  2964. {
  2965. $encoded = '';
  2966. switch (strtolower($encoding)) {
  2967. case static::ENCODING_BASE64:
  2968. $encoded = chunk_split(
  2969. base64_encode($str),
  2970. static::STD_LINE_LENGTH,
  2971. static::$LE
  2972. );
  2973. break;
  2974. case static::ENCODING_7BIT:
  2975. case static::ENCODING_8BIT:
  2976. $encoded = static::normalizeBreaks($str);
  2977. // Make sure it ends with a line break
  2978. if (substr($encoded, -(strlen(static::$LE))) != static::$LE) {
  2979. $encoded .= static::$LE;
  2980. }
  2981. break;
  2982. case static::ENCODING_BINARY:
  2983. $encoded = $str;
  2984. break;
  2985. case static::ENCODING_QUOTED_PRINTABLE:
  2986. $encoded = $this->encodeQP($str);
  2987. break;
  2988. default:
  2989. $this->setError($this->lang('encoding') . $encoding);
  2990. break;
  2991. }
  2992. return $encoded;
  2993. }
  2994. /**
  2995. * Encode a header value (not including its label) optimally.
  2996. * Picks shortest of Q, B, or none. Result includes folding if needed.
  2997. * See RFC822 definitions for phrase, comment and text positions.
  2998. *
  2999. * @param string $str The header value to encode
  3000. * @param string $position What context the string will be used in
  3001. *
  3002. * @return string
  3003. */
  3004. public function encodeHeader($str, $position = 'text')
  3005. {
  3006. $matchcount = 0;
  3007. switch (strtolower($position)) {
  3008. case 'phrase':
  3009. if (!preg_match('/[\200-\377]/', $str)) {
  3010. // Can't use addslashes as we don't know the value of magic_quotes_sybase
  3011. $encoded = addcslashes($str, "\0..\37\177\\\"");
  3012. if (($str == $encoded) and !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
  3013. return $encoded;
  3014. }
  3015. return "\"$encoded\"";
  3016. }
  3017. $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
  3018. break;
  3019. /* @noinspection PhpMissingBreakStatementInspection */
  3020. case 'comment':
  3021. $matchcount = preg_match_all('/[()"]/', $str, $matches);
  3022. //fallthrough
  3023. case 'text':
  3024. default:
  3025. $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
  3026. break;
  3027. }
  3028. //RFCs specify a maximum line length of 78 chars, however mail() will sometimes
  3029. //corrupt messages with headers longer than 65 chars. See #818
  3030. $lengthsub = 'mail' == $this->Mailer ? 13 : 0;
  3031. $maxlen = static::STD_LINE_LENGTH - $lengthsub;
  3032. // Try to select the encoding which should produce the shortest output
  3033. if ($matchcount > strlen($str) / 3) {
  3034. // More than a third of the content will need encoding, so B encoding will be most efficient
  3035. $encoding = 'B';
  3036. //This calculation is:
  3037. // max line length
  3038. // - shorten to avoid mail() corruption
  3039. // - Q/B encoding char overhead ("` =?<charset>?[QB]?<content>?=`")
  3040. // - charset name length
  3041. $maxlen = static::STD_LINE_LENGTH - $lengthsub - 8 - strlen($this->CharSet);
  3042. if ($this->hasMultiBytes($str)) {
  3043. // Use a custom function which correctly encodes and wraps long
  3044. // multibyte strings without breaking lines within a character
  3045. $encoded = $this->base64EncodeWrapMB($str, "\n");
  3046. } else {
  3047. $encoded = base64_encode($str);
  3048. $maxlen -= $maxlen % 4;
  3049. $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
  3050. }
  3051. $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
  3052. } elseif ($matchcount > 0) {
  3053. //1 or more chars need encoding, use Q-encode
  3054. $encoding = 'Q';
  3055. //Recalc max line length for Q encoding - see comments on B encode
  3056. $maxlen = static::STD_LINE_LENGTH - $lengthsub - 8 - strlen($this->CharSet);
  3057. $encoded = $this->encodeQ($str, $position);
  3058. $encoded = $this->wrapText($encoded, $maxlen, true);
  3059. $encoded = str_replace('=' . static::$LE, "\n", trim($encoded));
  3060. $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
  3061. } elseif (strlen($str) > $maxlen) {
  3062. //No chars need encoding, but line is too long, so fold it
  3063. $encoded = trim($this->wrapText($str, $maxlen, false));
  3064. if ($str == $encoded) {
  3065. //Wrapping nicely didn't work, wrap hard instead
  3066. $encoded = trim(chunk_split($str, static::STD_LINE_LENGTH, static::$LE));
  3067. }
  3068. $encoded = str_replace(static::$LE, "\n", trim($encoded));
  3069. $encoded = preg_replace('/^(.*)$/m', ' \\1', $encoded);
  3070. } else {
  3071. //No reformatting needed
  3072. return $str;
  3073. }
  3074. return trim(static::normalizeBreaks($encoded));
  3075. }
  3076. /**
  3077. * Check if a string contains multi-byte characters.
  3078. *
  3079. * @param string $str multi-byte text to wrap encode
  3080. *
  3081. * @return bool
  3082. */
  3083. public function hasMultiBytes($str)
  3084. {
  3085. if (function_exists('mb_strlen')) {
  3086. return strlen($str) > mb_strlen($str, $this->CharSet);
  3087. }
  3088. // Assume no multibytes (we can't handle without mbstring functions anyway)
  3089. return false;
  3090. }
  3091. /**
  3092. * Does a string contain any 8-bit chars (in any charset)?
  3093. *
  3094. * @param string $text
  3095. *
  3096. * @return bool
  3097. */
  3098. public function has8bitChars($text)
  3099. {
  3100. return (bool) preg_match('/[\x80-\xFF]/', $text);
  3101. }
  3102. /**
  3103. * Encode and wrap long multibyte strings for mail headers
  3104. * without breaking lines within a character.
  3105. * Adapted from a function by paravoid.
  3106. *
  3107. * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
  3108. *
  3109. * @param string $str multi-byte text to wrap encode
  3110. * @param string $linebreak string to use as linefeed/end-of-line
  3111. *
  3112. * @return string
  3113. */
  3114. public function base64EncodeWrapMB($str, $linebreak = null)
  3115. {
  3116. $start = '=?' . $this->CharSet . '?B?';
  3117. $end = '?=';
  3118. $encoded = '';
  3119. if (null === $linebreak) {
  3120. $linebreak = static::$LE;
  3121. }
  3122. $mb_length = mb_strlen($str, $this->CharSet);
  3123. // Each line must have length <= 75, including $start and $end
  3124. $length = 75 - strlen($start) - strlen($end);
  3125. // Average multi-byte ratio
  3126. $ratio = $mb_length / strlen($str);
  3127. // Base64 has a 4:3 ratio
  3128. $avgLength = floor($length * $ratio * .75);
  3129. for ($i = 0; $i < $mb_length; $i += $offset) {
  3130. $lookBack = 0;
  3131. do {
  3132. $offset = $avgLength - $lookBack;
  3133. $chunk = mb_substr($str, $i, $offset, $this->CharSet);
  3134. $chunk = base64_encode($chunk);
  3135. ++$lookBack;
  3136. } while (strlen($chunk) > $length);
  3137. $encoded .= $chunk . $linebreak;
  3138. }
  3139. // Chomp the last linefeed
  3140. return substr($encoded, 0, -strlen($linebreak));
  3141. }
  3142. /**
  3143. * Encode a string in quoted-printable format.
  3144. * According to RFC2045 section 6.7.
  3145. *
  3146. * @param string $string The text to encode
  3147. *
  3148. * @return string
  3149. */
  3150. public function encodeQP($string)
  3151. {
  3152. return static::normalizeBreaks(quoted_printable_encode($string));
  3153. }
  3154. /**
  3155. * Encode a string using Q encoding.
  3156. *
  3157. * @see http://tools.ietf.org/html/rfc2047#section-4.2
  3158. *
  3159. * @param string $str the text to encode
  3160. * @param string $position Where the text is going to be used, see the RFC for what that means
  3161. *
  3162. * @return string
  3163. */
  3164. public function encodeQ($str, $position = 'text')
  3165. {
  3166. // There should not be any EOL in the string
  3167. $pattern = '';
  3168. $encoded = str_replace(["\r", "\n"], '', $str);
  3169. switch (strtolower($position)) {
  3170. case 'phrase':
  3171. // RFC 2047 section 5.3
  3172. $pattern = '^A-Za-z0-9!*+\/ -';
  3173. break;
  3174. /*
  3175. * RFC 2047 section 5.2.
  3176. * Build $pattern without including delimiters and []
  3177. */
  3178. /* @noinspection PhpMissingBreakStatementInspection */
  3179. case 'comment':
  3180. $pattern = '\(\)"';
  3181. /* Intentional fall through */
  3182. case 'text':
  3183. default:
  3184. // RFC 2047 section 5.1
  3185. // Replace every high ascii, control, =, ? and _ characters
  3186. /** @noinspection SuspiciousAssignmentsInspection */
  3187. $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
  3188. break;
  3189. }
  3190. $matches = [];
  3191. if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
  3192. // If the string contains an '=', make sure it's the first thing we replace
  3193. // so as to avoid double-encoding
  3194. $eqkey = array_search('=', $matches[0]);
  3195. if (false !== $eqkey) {
  3196. unset($matches[0][$eqkey]);
  3197. array_unshift($matches[0], '=');
  3198. }
  3199. foreach (array_unique($matches[0]) as $char) {
  3200. $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
  3201. }
  3202. }
  3203. // Replace spaces with _ (more readable than =20)
  3204. // RFC 2047 section 4.2(2)
  3205. return str_replace(' ', '_', $encoded);
  3206. }
  3207. /**
  3208. * Add a string or binary attachment (non-filesystem).
  3209. * This method can be used to attach ascii or binary data,
  3210. * such as a BLOB record from a database.
  3211. *
  3212. * @param string $string String attachment data
  3213. * @param string $filename Name of the attachment
  3214. * @param string $encoding File encoding (see $Encoding)
  3215. * @param string $type File extension (MIME) type
  3216. * @param string $disposition Disposition to use
  3217. */
  3218. public function addStringAttachment(
  3219. $string,
  3220. $filename,
  3221. $encoding = self::ENCODING_BASE64,
  3222. $type = '',
  3223. $disposition = 'attachment'
  3224. ) {
  3225. // If a MIME type is not specified, try to work it out from the file name
  3226. if ('' == $type) {
  3227. $type = static::filenameToType($filename);
  3228. }
  3229. // Append to $attachment array
  3230. $this->attachment[] = [
  3231. 0 => $string,
  3232. 1 => $filename,
  3233. 2 => basename($filename),
  3234. 3 => $encoding,
  3235. 4 => $type,
  3236. 5 => true, // isStringAttachment
  3237. 6 => $disposition,
  3238. 7 => 0,
  3239. ];
  3240. }
  3241. /**
  3242. * Add an embedded (inline) attachment from a file.
  3243. * This can include images, sounds, and just about any other document type.
  3244. * These differ from 'regular' attachments in that they are intended to be
  3245. * displayed inline with the message, not just attached for download.
  3246. * This is used in HTML messages that embed the images
  3247. * the HTML refers to using the $cid value.
  3248. * Never use a user-supplied path to a file!
  3249. *
  3250. * @param string $path Path to the attachment
  3251. * @param string $cid Content ID of the attachment; Use this to reference
  3252. * the content when using an embedded image in HTML
  3253. * @param string $name Overrides the attachment name
  3254. * @param string $encoding File encoding (see $Encoding)
  3255. * @param string $type File MIME type
  3256. * @param string $disposition Disposition to use
  3257. *
  3258. * @return bool True on successfully adding an attachment
  3259. */
  3260. public function addEmbeddedImage($path, $cid, $name = '', $encoding = self::ENCODING_BASE64, $type = '', $disposition = 'inline')
  3261. {
  3262. if (!static::isPermittedPath($path) || !@is_file($path)) {
  3263. $this->setError($this->lang('file_access') . $path);
  3264. return false;
  3265. }
  3266. // If a MIME type is not specified, try to work it out from the file name
  3267. if ('' == $type) {
  3268. $type = static::filenameToType($path);
  3269. }
  3270. $filename = basename($path);
  3271. if ('' == $name) {
  3272. $name = $filename;
  3273. }
  3274. // Append to $attachment array
  3275. $this->attachment[] = [
  3276. 0 => $path,
  3277. 1 => $filename,
  3278. 2 => $name,
  3279. 3 => $encoding,
  3280. 4 => $type,
  3281. 5 => false, // isStringAttachment
  3282. 6 => $disposition,
  3283. 7 => $cid,
  3284. ];
  3285. return true;
  3286. }
  3287. /**
  3288. * Add an embedded stringified attachment.
  3289. * This can include images, sounds, and just about any other document type.
  3290. * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type.
  3291. *
  3292. * @param string $string The attachment binary data
  3293. * @param string $cid Content ID of the attachment; Use this to reference
  3294. * the content when using an embedded image in HTML
  3295. * @param string $name A filename for the attachment. If this contains an extension,
  3296. * PHPMailer will attempt to set a MIME type for the attachment.
  3297. * For example 'file.jpg' would get an 'image/jpeg' MIME type.
  3298. * @param string $encoding File encoding (see $Encoding), defaults to 'base64'
  3299. * @param string $type MIME type - will be used in preference to any automatically derived type
  3300. * @param string $disposition Disposition to use
  3301. *
  3302. * @return bool True on successfully adding an attachment
  3303. */
  3304. public function addStringEmbeddedImage(
  3305. $string,
  3306. $cid,
  3307. $name = '',
  3308. $encoding = self::ENCODING_BASE64,
  3309. $type = '',
  3310. $disposition = 'inline'
  3311. ) {
  3312. // If a MIME type is not specified, try to work it out from the name
  3313. if ('' == $type and !empty($name)) {
  3314. $type = static::filenameToType($name);
  3315. }
  3316. // Append to $attachment array
  3317. $this->attachment[] = [
  3318. 0 => $string,
  3319. 1 => $name,
  3320. 2 => $name,
  3321. 3 => $encoding,
  3322. 4 => $type,
  3323. 5 => true, // isStringAttachment
  3324. 6 => $disposition,
  3325. 7 => $cid,
  3326. ];
  3327. return true;
  3328. }
  3329. /**
  3330. * Check if an embedded attachment is present with this cid.
  3331. *
  3332. * @param string $cid
  3333. *
  3334. * @return bool
  3335. */
  3336. protected function cidExists($cid)
  3337. {
  3338. foreach ($this->attachment as $attachment) {
  3339. if ('inline' == $attachment[6] and $cid == $attachment[7]) {
  3340. return true;
  3341. }
  3342. }
  3343. return false;
  3344. }
  3345. /**
  3346. * Check if an inline attachment is present.
  3347. *
  3348. * @return bool
  3349. */
  3350. public function inlineImageExists()
  3351. {
  3352. foreach ($this->attachment as $attachment) {
  3353. if ('inline' == $attachment[6]) {
  3354. return true;
  3355. }
  3356. }
  3357. return false;
  3358. }
  3359. /**
  3360. * Check if an attachment (non-inline) is present.
  3361. *
  3362. * @return bool
  3363. */
  3364. public function attachmentExists()
  3365. {
  3366. foreach ($this->attachment as $attachment) {
  3367. if ('attachment' == $attachment[6]) {
  3368. return true;
  3369. }
  3370. }
  3371. return false;
  3372. }
  3373. /**
  3374. * Check if this message has an alternative body set.
  3375. *
  3376. * @return bool
  3377. */
  3378. public function alternativeExists()
  3379. {
  3380. return !empty($this->AltBody);
  3381. }
  3382. /**
  3383. * Clear queued addresses of given kind.
  3384. *
  3385. * @param string $kind 'to', 'cc', or 'bcc'
  3386. */
  3387. public function clearQueuedAddresses($kind)
  3388. {
  3389. $this->RecipientsQueue = array_filter(
  3390. $this->RecipientsQueue,
  3391. function ($params) use ($kind) {
  3392. return $params[0] != $kind;
  3393. }
  3394. );
  3395. }
  3396. /**
  3397. * Clear all To recipients.
  3398. */
  3399. public function clearAddresses()
  3400. {
  3401. foreach ($this->to as $to) {
  3402. unset($this->all_recipients[strtolower($to[0])]);
  3403. }
  3404. $this->to = [];
  3405. $this->clearQueuedAddresses('to');
  3406. }
  3407. /**
  3408. * Clear all CC recipients.
  3409. */
  3410. public function clearCCs()
  3411. {
  3412. foreach ($this->cc as $cc) {
  3413. unset($this->all_recipients[strtolower($cc[0])]);
  3414. }
  3415. $this->cc = [];
  3416. $this->clearQueuedAddresses('cc');
  3417. }
  3418. /**
  3419. * Clear all BCC recipients.
  3420. */
  3421. public function clearBCCs()
  3422. {
  3423. foreach ($this->bcc as $bcc) {
  3424. unset($this->all_recipients[strtolower($bcc[0])]);
  3425. }
  3426. $this->bcc = [];
  3427. $this->clearQueuedAddresses('bcc');
  3428. }
  3429. /**
  3430. * Clear all ReplyTo recipients.
  3431. */
  3432. public function clearReplyTos()
  3433. {
  3434. $this->ReplyTo = [];
  3435. $this->ReplyToQueue = [];
  3436. }
  3437. /**
  3438. * Clear all recipient types.
  3439. */
  3440. public function clearAllRecipients()
  3441. {
  3442. $this->to = [];
  3443. $this->cc = [];
  3444. $this->bcc = [];
  3445. $this->all_recipients = [];
  3446. $this->RecipientsQueue = [];
  3447. }
  3448. /**
  3449. * Clear all filesystem, string, and binary attachments.
  3450. */
  3451. public function clearAttachments()
  3452. {
  3453. $this->attachment = [];
  3454. }
  3455. /**
  3456. * Clear all custom headers.
  3457. */
  3458. public function clearCustomHeaders()
  3459. {
  3460. $this->CustomHeader = [];
  3461. }
  3462. /**
  3463. * Add an error message to the error container.
  3464. *
  3465. * @param string $msg
  3466. */
  3467. protected function setError($msg)
  3468. {
  3469. ++$this->error_count;
  3470. if ('smtp' == $this->Mailer and null !== $this->smtp) {
  3471. $lasterror = $this->smtp->getError();
  3472. if (!empty($lasterror['error'])) {
  3473. $msg .= $this->lang('smtp_error') . $lasterror['error'];
  3474. if (!empty($lasterror['detail'])) {
  3475. $msg .= ' Detail: ' . $lasterror['detail'];
  3476. }
  3477. if (!empty($lasterror['smtp_code'])) {
  3478. $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
  3479. }
  3480. if (!empty($lasterror['smtp_code_ex'])) {
  3481. $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
  3482. }
  3483. }
  3484. }
  3485. $this->ErrorInfo = $msg;
  3486. }
  3487. /**
  3488. * Return an RFC 822 formatted date.
  3489. *
  3490. * @return string
  3491. */
  3492. public static function rfcDate()
  3493. {
  3494. // Set the time zone to whatever the default is to avoid 500 errors
  3495. // Will default to UTC if it's not set properly in php.ini
  3496. date_default_timezone_set(@date_default_timezone_get());
  3497. return date('D, j M Y H:i:s O');
  3498. }
  3499. /**
  3500. * Get the server hostname.
  3501. * Returns 'localhost.localdomain' if unknown.
  3502. *
  3503. * @return string
  3504. */
  3505. protected function serverHostname()
  3506. {
  3507. $result = '';
  3508. if (!empty($this->Hostname)) {
  3509. $result = $this->Hostname;
  3510. } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER)) {
  3511. $result = $_SERVER['SERVER_NAME'];
  3512. } elseif (function_exists('gethostname') and gethostname() !== false) {
  3513. $result = gethostname();
  3514. } elseif (php_uname('n') !== false) {
  3515. $result = php_uname('n');
  3516. }
  3517. if (!static::isValidHost($result)) {
  3518. return 'localhost.localdomain';
  3519. }
  3520. return $result;
  3521. }
  3522. /**
  3523. * Validate whether a string contains a valid value to use as a hostname or IP address.
  3524. * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`.
  3525. *
  3526. * @param string $host The host name or IP address to check
  3527. *
  3528. * @return bool
  3529. */
  3530. public static function isValidHost($host)
  3531. {
  3532. //Simple syntax limits
  3533. if (empty($host)
  3534. or !is_string($host)
  3535. or strlen($host) > 256
  3536. ) {
  3537. return false;
  3538. }
  3539. //Looks like a bracketed IPv6 address
  3540. if (trim($host, '[]') != $host) {
  3541. return (bool) filter_var(trim($host, '[]'), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
  3542. }
  3543. //If removing all the dots results in a numeric string, it must be an IPv4 address.
  3544. //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names
  3545. if (is_numeric(str_replace('.', '', $host))) {
  3546. //Is it a valid IPv4 address?
  3547. return (bool) filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
  3548. }
  3549. if (filter_var('http://' . $host, FILTER_VALIDATE_URL)) {
  3550. //Is it a syntactically valid hostname?
  3551. return true;
  3552. }
  3553. return false;
  3554. }
  3555. /**
  3556. * Get an error message in the current language.
  3557. *
  3558. * @param string $key
  3559. *
  3560. * @return string
  3561. */
  3562. protected function lang($key)
  3563. {
  3564. if (count($this->language) < 1) {
  3565. $this->setLanguage('en'); // set the default language
  3566. }
  3567. if (array_key_exists($key, $this->language)) {
  3568. if ('smtp_connect_failed' == $key) {
  3569. //Include a link to troubleshooting docs on SMTP connection failure
  3570. //this is by far the biggest cause of support questions
  3571. //but it's usually not PHPMailer's fault.
  3572. return $this->language[$key];
  3573. }
  3574. return $this->language[$key];
  3575. }
  3576. //Return the key as a fallback
  3577. return $key;
  3578. }
  3579. /**
  3580. * Check if an error occurred.
  3581. *
  3582. * @return bool True if an error did occur
  3583. */
  3584. public function isError()
  3585. {
  3586. return $this->error_count > 0;
  3587. }
  3588. /**
  3589. * Add a custom header.
  3590. * $name value can be overloaded to contain
  3591. * both header name and value (name:value).
  3592. *
  3593. * @param string $name Custom header name
  3594. * @param string|null $value Header value
  3595. */
  3596. public function addCustomHeader($name, $value = null)
  3597. {
  3598. if (null === $value) {
  3599. // Value passed in as name:value
  3600. $this->CustomHeader[] = explode(':', $name, 2);
  3601. } else {
  3602. $this->CustomHeader[] = [$name, $value];
  3603. }
  3604. }
  3605. /**
  3606. * Returns all custom headers.
  3607. *
  3608. * @return array
  3609. */
  3610. public function getCustomHeaders()
  3611. {
  3612. return $this->CustomHeader;
  3613. }
  3614. /**
  3615. * Create a message body from an HTML string.
  3616. * Automatically inlines images and creates a plain-text version by converting the HTML,
  3617. * overwriting any existing values in Body and AltBody.
  3618. * Do not source $message content from user input!
  3619. * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
  3620. * will look for an image file in $basedir/images/a.png and convert it to inline.
  3621. * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
  3622. * Converts data-uri images into embedded attachments.
  3623. * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
  3624. *
  3625. * @param string $message HTML message string
  3626. * @param string $basedir Absolute path to a base directory to prepend to relative paths to images
  3627. * @param bool|callable $advanced Whether to use the internal HTML to text converter
  3628. * or your own custom converter @see PHPMailer::html2text()
  3629. *
  3630. * @return string $message The transformed message Body
  3631. */
  3632. public function msgHTML($message, $basedir = '', $advanced = false)
  3633. {
  3634. preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
  3635. if (array_key_exists(2, $images)) {
  3636. if (strlen($basedir) > 1 && '/' != substr($basedir, -1)) {
  3637. // Ensure $basedir has a trailing /
  3638. $basedir .= '/';
  3639. }
  3640. foreach ($images[2] as $imgindex => $url) {
  3641. // Convert data URIs into embedded images
  3642. //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
  3643. if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
  3644. if (count($match) == 4 and static::ENCODING_BASE64 == $match[2]) {
  3645. $data = base64_decode($match[3]);
  3646. } elseif ('' == $match[2]) {
  3647. $data = rawurldecode($match[3]);
  3648. } else {
  3649. //Not recognised so leave it alone
  3650. continue;
  3651. }
  3652. //Hash the decoded data, not the URL so that the same data-URI image used in multiple places
  3653. //will only be embedded once, even if it used a different encoding
  3654. $cid = hash('sha256', $data) . '@phpmailer.0'; // RFC2392 S 2
  3655. if (!$this->cidExists($cid)) {
  3656. $this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, static::ENCODING_BASE64, $match[1]);
  3657. }
  3658. $message = str_replace(
  3659. $images[0][$imgindex],
  3660. $images[1][$imgindex] . '="cid:' . $cid . '"',
  3661. $message
  3662. );
  3663. continue;
  3664. }
  3665. if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
  3666. !empty($basedir)
  3667. // Ignore URLs containing parent dir traversal (..)
  3668. and (strpos($url, '..') === false)
  3669. // Do not change urls that are already inline images
  3670. and 0 !== strpos($url, 'cid:')
  3671. // Do not change absolute URLs, including anonymous protocol
  3672. and !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
  3673. ) {
  3674. $filename = basename($url);
  3675. $directory = dirname($url);
  3676. if ('.' == $directory) {
  3677. $directory = '';
  3678. }
  3679. $cid = hash('sha256', $url) . '@phpmailer.0'; // RFC2392 S 2
  3680. if (strlen($basedir) > 1 and '/' != substr($basedir, -1)) {
  3681. $basedir .= '/';
  3682. }
  3683. if (strlen($directory) > 1 and '/' != substr($directory, -1)) {
  3684. $directory .= '/';
  3685. }
  3686. if ($this->addEmbeddedImage(
  3687. $basedir . $directory . $filename,
  3688. $cid,
  3689. $filename,
  3690. static::ENCODING_BASE64,
  3691. static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION))
  3692. )
  3693. ) {
  3694. $message = preg_replace(
  3695. '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
  3696. $images[1][$imgindex] . '="cid:' . $cid . '"',
  3697. $message
  3698. );
  3699. }
  3700. }
  3701. }
  3702. }
  3703. $this->isHTML(true);
  3704. // Convert all message body line breaks to LE, makes quoted-printable encoding work much better
  3705. $this->Body = static::normalizeBreaks($message);
  3706. $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));
  3707. if (!$this->alternativeExists()) {
  3708. $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'
  3709. . static::$LE;
  3710. }
  3711. return $this->Body;
  3712. }
  3713. /**
  3714. * Convert an HTML string into plain text.
  3715. * This is used by msgHTML().
  3716. * Note - older versions of this function used a bundled advanced converter
  3717. * which was removed for license reasons in #232.
  3718. * Example usage:
  3719. *
  3720. * ```php
  3721. * // Use default conversion
  3722. * $plain = $mail->html2text($html);
  3723. * // Use your own custom converter
  3724. * $plain = $mail->html2text($html, function($html) {
  3725. * $converter = new MyHtml2text($html);
  3726. * return $converter->get_text();
  3727. * });
  3728. * ```
  3729. *
  3730. * @param string $html The HTML text to convert
  3731. * @param bool|callable $advanced Any boolean value to use the internal converter,
  3732. * or provide your own callable for custom conversion
  3733. *
  3734. * @return string
  3735. */
  3736. public function html2text($html, $advanced = false)
  3737. {
  3738. if (is_callable($advanced)) {
  3739. return call_user_func($advanced, $html);
  3740. }
  3741. return html_entity_decode(
  3742. trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
  3743. ENT_QUOTES,
  3744. $this->CharSet
  3745. );
  3746. }
  3747. /**
  3748. * Get the MIME type for a file extension.
  3749. *
  3750. * @param string $ext File extension
  3751. *
  3752. * @return string MIME type of file
  3753. */
  3754. public static function _mime_types($ext = '')
  3755. {
  3756. $mimes = [
  3757. 'xl' => 'application/excel',
  3758. 'js' => 'application/javascript',
  3759. 'hqx' => 'application/mac-binhex40',
  3760. 'cpt' => 'application/mac-compactpro',
  3761. 'bin' => 'application/macbinary',
  3762. 'doc' => 'application/msword',
  3763. 'word' => 'application/msword',
  3764. 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  3765. 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
  3766. 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
  3767. 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
  3768. 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  3769. 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
  3770. 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  3771. 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
  3772. 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
  3773. 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
  3774. 'class' => 'application/octet-stream',
  3775. 'dll' => 'application/octet-stream',
  3776. 'dms' => 'application/octet-stream',
  3777. 'exe' => 'application/octet-stream',
  3778. 'lha' => 'application/octet-stream',
  3779. 'lzh' => 'application/octet-stream',
  3780. 'psd' => 'application/octet-stream',
  3781. 'sea' => 'application/octet-stream',
  3782. 'so' => 'application/octet-stream',
  3783. 'oda' => 'application/oda',
  3784. 'pdf' => 'application/pdf',
  3785. 'ai' => 'application/postscript',
  3786. 'eps' => 'application/postscript',
  3787. 'ps' => 'application/postscript',
  3788. 'smi' => 'application/smil',
  3789. 'smil' => 'application/smil',
  3790. 'mif' => 'application/vnd.mif',
  3791. 'xls' => 'application/vnd.ms-excel',
  3792. 'ppt' => 'application/vnd.ms-powerpoint',
  3793. 'wbxml' => 'application/vnd.wap.wbxml',
  3794. 'wmlc' => 'application/vnd.wap.wmlc',
  3795. 'dcr' => 'application/x-director',
  3796. 'dir' => 'application/x-director',
  3797. 'dxr' => 'application/x-director',
  3798. 'dvi' => 'application/x-dvi',
  3799. 'gtar' => 'application/x-gtar',
  3800. 'php3' => 'application/x-httpd-php',
  3801. 'php4' => 'application/x-httpd-php',
  3802. 'php' => 'application/x-httpd-php',
  3803. 'phtml' => 'application/x-httpd-php',
  3804. 'phps' => 'application/x-httpd-php-source',
  3805. 'swf' => 'application/x-shockwave-flash',
  3806. 'sit' => 'application/x-stuffit',
  3807. 'tar' => 'application/x-tar',
  3808. 'tgz' => 'application/x-tar',
  3809. 'xht' => 'application/xhtml+xml',
  3810. 'xhtml' => 'application/xhtml+xml',
  3811. 'zip' => 'application/zip',
  3812. 'mid' => 'audio/midi',
  3813. 'midi' => 'audio/midi',
  3814. 'mp2' => 'audio/mpeg',
  3815. 'mp3' => 'audio/mpeg',
  3816. 'm4a' => 'audio/mp4',
  3817. 'mpga' => 'audio/mpeg',
  3818. 'aif' => 'audio/x-aiff',
  3819. 'aifc' => 'audio/x-aiff',
  3820. 'aiff' => 'audio/x-aiff',
  3821. 'ram' => 'audio/x-pn-realaudio',
  3822. 'rm' => 'audio/x-pn-realaudio',
  3823. 'rpm' => 'audio/x-pn-realaudio-plugin',
  3824. 'ra' => 'audio/x-realaudio',
  3825. 'wav' => 'audio/x-wav',
  3826. 'mka' => 'audio/x-matroska',
  3827. 'bmp' => 'image/bmp',
  3828. 'gif' => 'image/gif',
  3829. 'jpeg' => 'image/jpeg',
  3830. 'jpe' => 'image/jpeg',
  3831. 'jpg' => 'image/jpeg',
  3832. 'png' => 'image/png',
  3833. 'tiff' => 'image/tiff',
  3834. 'tif' => 'image/tiff',
  3835. 'webp' => 'image/webp',
  3836. 'heif' => 'image/heif',
  3837. 'heifs' => 'image/heif-sequence',
  3838. 'heic' => 'image/heic',
  3839. 'heics' => 'image/heic-sequence',
  3840. 'eml' => 'message/rfc822',
  3841. 'css' => 'text/css',
  3842. 'html' => 'text/html',
  3843. 'htm' => 'text/html',
  3844. 'shtml' => 'text/html',
  3845. 'log' => 'text/plain',
  3846. 'text' => 'text/plain',
  3847. 'txt' => 'text/plain',
  3848. 'rtx' => 'text/richtext',
  3849. 'rtf' => 'text/rtf',
  3850. 'vcf' => 'text/vcard',
  3851. 'vcard' => 'text/vcard',
  3852. 'ics' => 'text/calendar',
  3853. 'xml' => 'text/xml',
  3854. 'xsl' => 'text/xml',
  3855. 'wmv' => 'video/x-ms-wmv',
  3856. 'mpeg' => 'video/mpeg',
  3857. 'mpe' => 'video/mpeg',
  3858. 'mpg' => 'video/mpeg',
  3859. 'mp4' => 'video/mp4',
  3860. 'm4v' => 'video/mp4',
  3861. 'mov' => 'video/quicktime',
  3862. 'qt' => 'video/quicktime',
  3863. 'rv' => 'video/vnd.rn-realvideo',
  3864. 'avi' => 'video/x-msvideo',
  3865. 'movie' => 'video/x-sgi-movie',
  3866. 'webm' => 'video/webm',
  3867. 'mkv' => 'video/x-matroska',
  3868. ];
  3869. $ext = strtolower($ext);
  3870. if (array_key_exists($ext, $mimes)) {
  3871. return $mimes[$ext];
  3872. }
  3873. return 'application/octet-stream';
  3874. }
  3875. /**
  3876. * Map a file name to a MIME type.
  3877. * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
  3878. *
  3879. * @param string $filename A file name or full path, does not need to exist as a file
  3880. *
  3881. * @return string
  3882. */
  3883. public static function filenameToType($filename)
  3884. {
  3885. // In case the path is a URL, strip any query string before getting extension
  3886. $qpos = strpos($filename, '?');
  3887. if (false !== $qpos) {
  3888. $filename = substr($filename, 0, $qpos);
  3889. }
  3890. $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);
  3891. return static::_mime_types($ext);
  3892. }
  3893. /**
  3894. * Multi-byte-safe pathinfo replacement.
  3895. * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
  3896. *
  3897. * @see http://www.php.net/manual/en/function.pathinfo.php#107461
  3898. *
  3899. * @param string $path A filename or path, does not need to exist as a file
  3900. * @param int|string $options Either a PATHINFO_* constant,
  3901. * or a string name to return only the specified piece
  3902. *
  3903. * @return string|array
  3904. */
  3905. public static function mb_pathinfo($path, $options = null)
  3906. {
  3907. $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
  3908. $pathinfo = [];
  3909. if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$#im', $path, $pathinfo)) {
  3910. if (array_key_exists(1, $pathinfo)) {
  3911. $ret['dirname'] = $pathinfo[1];
  3912. }
  3913. if (array_key_exists(2, $pathinfo)) {
  3914. $ret['basename'] = $pathinfo[2];
  3915. }
  3916. if (array_key_exists(5, $pathinfo)) {
  3917. $ret['extension'] = $pathinfo[5];
  3918. }
  3919. if (array_key_exists(3, $pathinfo)) {
  3920. $ret['filename'] = $pathinfo[3];
  3921. }
  3922. }
  3923. switch ($options) {
  3924. case PATHINFO_DIRNAME:
  3925. case 'dirname':
  3926. return $ret['dirname'];
  3927. case PATHINFO_BASENAME:
  3928. case 'basename':
  3929. return $ret['basename'];
  3930. case PATHINFO_EXTENSION:
  3931. case 'extension':
  3932. return $ret['extension'];
  3933. case PATHINFO_FILENAME:
  3934. case 'filename':
  3935. return $ret['filename'];
  3936. default:
  3937. return $ret;
  3938. }
  3939. }
  3940. /**
  3941. * Set or reset instance properties.
  3942. * You should avoid this function - it's more verbose, less efficient, more error-prone and
  3943. * harder to debug than setting properties directly.
  3944. * Usage Example:
  3945. * `$mail->set('SMTPSecure', 'tls');`
  3946. * is the same as:
  3947. * `$mail->SMTPSecure = 'tls';`.
  3948. *
  3949. * @param string $name The property name to set
  3950. * @param mixed $value The value to set the property to
  3951. *
  3952. * @return bool
  3953. */
  3954. public function set($name, $value = '')
  3955. {
  3956. if (property_exists($this, $name)) {
  3957. $this->$name = $value;
  3958. return true;
  3959. }
  3960. $this->setError($this->lang('variable_set') . $name);
  3961. return false;
  3962. }
  3963. /**
  3964. * Strip newlines to prevent header injection.
  3965. *
  3966. * @param string $str
  3967. *
  3968. * @return string
  3969. */
  3970. public function secureHeader($str)
  3971. {
  3972. return trim(str_replace(["\r", "\n"], '', $str));
  3973. }
  3974. /**
  3975. * Normalize line breaks in a string.
  3976. * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
  3977. * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
  3978. *
  3979. * @param string $text
  3980. * @param string $breaktype What kind of line break to use; defaults to static::$LE
  3981. *
  3982. * @return string
  3983. */
  3984. public static function normalizeBreaks($text, $breaktype = null)
  3985. {
  3986. if (null === $breaktype) {
  3987. $breaktype = static::$LE;
  3988. }
  3989. // Normalise to \n
  3990. $text = str_replace(["\r\n", "\r"], "\n", $text);
  3991. // Now convert LE as needed
  3992. if ("\n" !== $breaktype) {
  3993. $text = str_replace("\n", $breaktype, $text);
  3994. }
  3995. return $text;
  3996. }
  3997. /**
  3998. * Return the current line break format string.
  3999. *
  4000. * @return string
  4001. */
  4002. public static function getLE()
  4003. {
  4004. return static::$LE;
  4005. }
  4006. /**
  4007. * Set the line break format string, e.g. "\r\n".
  4008. *
  4009. * @param string $le
  4010. */
  4011. protected static function setLE($le)
  4012. {
  4013. static::$LE = $le;
  4014. }
  4015. /**
  4016. * Set the public and private key files and password for S/MIME signing.
  4017. *
  4018. * @param string $cert_filename
  4019. * @param string $key_filename
  4020. * @param string $key_pass Password for private key
  4021. * @param string $extracerts_filename Optional path to chain certificate
  4022. */
  4023. public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
  4024. {
  4025. $this->sign_cert_file = $cert_filename;
  4026. $this->sign_key_file = $key_filename;
  4027. $this->sign_key_pass = $key_pass;
  4028. $this->sign_extracerts_file = $extracerts_filename;
  4029. }
  4030. /**
  4031. * Quoted-Printable-encode a DKIM header.
  4032. *
  4033. * @param string $txt
  4034. *
  4035. * @return string
  4036. */
  4037. public function DKIM_QP($txt)
  4038. {
  4039. $line = '';
  4040. $len = strlen($txt);
  4041. for ($i = 0; $i < $len; ++$i) {
  4042. $ord = ord($txt[$i]);
  4043. if (((0x21 <= $ord) and ($ord <= 0x3A)) or $ord == 0x3C or ((0x3E <= $ord) and ($ord <= 0x7E))) {
  4044. $line .= $txt[$i];
  4045. } else {
  4046. $line .= '=' . sprintf('%02X', $ord);
  4047. }
  4048. }
  4049. return $line;
  4050. }
  4051. /**
  4052. * Generate a DKIM signature.
  4053. *
  4054. * @param string $signHeader
  4055. *
  4056. * @throws Exception
  4057. *
  4058. * @return string The DKIM signature value
  4059. */
  4060. public function DKIM_Sign($signHeader)
  4061. {
  4062. if (!defined('PKCS7_TEXT')) {
  4063. if ($this->exceptions) {
  4064. throw new Exception($this->lang('extension_missing') . 'openssl');
  4065. }
  4066. return '';
  4067. }
  4068. $privKeyStr = !empty($this->DKIM_private_string) ?
  4069. $this->DKIM_private_string :
  4070. file_get_contents($this->DKIM_private);
  4071. if ('' != $this->DKIM_passphrase) {
  4072. $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
  4073. } else {
  4074. $privKey = openssl_pkey_get_private($privKeyStr);
  4075. }
  4076. if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
  4077. openssl_pkey_free($privKey);
  4078. return base64_encode($signature);
  4079. }
  4080. openssl_pkey_free($privKey);
  4081. return '';
  4082. }
  4083. /**
  4084. * Generate a DKIM canonicalization header.
  4085. * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
  4086. * Canonicalized headers should *always* use CRLF, regardless of mailer setting.
  4087. *
  4088. * @see https://tools.ietf.org/html/rfc6376#section-3.4.2
  4089. *
  4090. * @param string $signHeader Header
  4091. *
  4092. * @return string
  4093. */
  4094. public function DKIM_HeaderC($signHeader)
  4095. {
  4096. //Unfold all header continuation lines
  4097. //Also collapses folded whitespace.
  4098. //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
  4099. //@see https://tools.ietf.org/html/rfc5322#section-2.2
  4100. //That means this may break if you do something daft like put vertical tabs in your headers.
  4101. $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
  4102. $lines = explode("\r\n", $signHeader);
  4103. foreach ($lines as $key => $line) {
  4104. //If the header is missing a :, skip it as it's invalid
  4105. //This is likely to happen because the explode() above will also split
  4106. //on the trailing LE, leaving an empty line
  4107. if (strpos($line, ':') === false) {
  4108. continue;
  4109. }
  4110. list($heading, $value) = explode(':', $line, 2);
  4111. //Lower-case header name
  4112. $heading = strtolower($heading);
  4113. //Collapse white space within the value
  4114. $value = preg_replace('/[ \t]{2,}/', ' ', $value);
  4115. //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value
  4116. //But then says to delete space before and after the colon.
  4117. //Net result is the same as trimming both ends of the value.
  4118. //by elimination, the same applies to the field name
  4119. $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");
  4120. }
  4121. return implode("\r\n", $lines);
  4122. }
  4123. /**
  4124. * Generate a DKIM canonicalization body.
  4125. * Uses the 'simple' algorithm from RFC6376 section 3.4.3.
  4126. * Canonicalized bodies should *always* use CRLF, regardless of mailer setting.
  4127. *
  4128. * @see https://tools.ietf.org/html/rfc6376#section-3.4.3
  4129. *
  4130. * @param string $body Message Body
  4131. *
  4132. * @return string
  4133. */
  4134. public function DKIM_BodyC($body)
  4135. {
  4136. if (empty($body)) {
  4137. return "\r\n";
  4138. }
  4139. // Normalize line endings to CRLF
  4140. $body = static::normalizeBreaks($body, "\r\n");
  4141. //Reduce multiple trailing line breaks to a single one
  4142. return rtrim($body, "\r\n") . "\r\n";
  4143. }
  4144. /**
  4145. * Create the DKIM header and body in a new message header.
  4146. *
  4147. * @param string $headers_line Header lines
  4148. * @param string $subject Subject
  4149. * @param string $body Body
  4150. *
  4151. * @return string
  4152. */
  4153. public function DKIM_Add($headers_line, $subject, $body)
  4154. {
  4155. $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
  4156. $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
  4157. $DKIMquery = 'dns/txt'; // Query method
  4158. $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
  4159. $subject_header = "Subject: $subject";
  4160. $headers = explode(static::$LE, $headers_line);
  4161. $from_header = '';
  4162. $to_header = '';
  4163. $date_header = '';
  4164. $current = '';
  4165. $copiedHeaderFields = '';
  4166. $foundExtraHeaders = [];
  4167. $extraHeaderKeys = '';
  4168. $extraHeaderValues = '';
  4169. $extraCopyHeaderFields = '';
  4170. foreach ($headers as $header) {
  4171. if (strpos($header, 'From:') === 0) {
  4172. $from_header = $header;
  4173. $current = 'from_header';
  4174. } elseif (strpos($header, 'To:') === 0) {
  4175. $to_header = $header;
  4176. $current = 'to_header';
  4177. } elseif (strpos($header, 'Date:') === 0) {
  4178. $date_header = $header;
  4179. $current = 'date_header';
  4180. } elseif (!empty($this->DKIM_extraHeaders)) {
  4181. foreach ($this->DKIM_extraHeaders as $extraHeader) {
  4182. if (strpos($header, $extraHeader . ':') === 0) {
  4183. $headerValue = $header;
  4184. foreach ($this->CustomHeader as $customHeader) {
  4185. if ($customHeader[0] === $extraHeader) {
  4186. $headerValue = trim($customHeader[0]) .
  4187. ': ' .
  4188. $this->encodeHeader(trim($customHeader[1]));
  4189. break;
  4190. }
  4191. }
  4192. $foundExtraHeaders[$extraHeader] = $headerValue;
  4193. $current = '';
  4194. break;
  4195. }
  4196. }
  4197. } else {
  4198. if (!empty($$current) and strpos($header, ' =?') === 0) {
  4199. $$current .= $header;
  4200. } else {
  4201. $current = '';
  4202. }
  4203. }
  4204. }
  4205. foreach ($foundExtraHeaders as $key => $value) {
  4206. $extraHeaderKeys .= ':' . $key;
  4207. $extraHeaderValues .= $value . "\r\n";
  4208. if ($this->DKIM_copyHeaderFields) {
  4209. $extraCopyHeaderFields .= "\t|" . str_replace('|', '=7C', $this->DKIM_QP($value)) . ";\r\n";
  4210. }
  4211. }
  4212. if ($this->DKIM_copyHeaderFields) {
  4213. $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
  4214. $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
  4215. $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
  4216. $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header));
  4217. $copiedHeaderFields = "\tz=$from\r\n" .
  4218. "\t|$to\r\n" .
  4219. "\t|$date\r\n" .
  4220. "\t|$subject;\r\n" .
  4221. $extraCopyHeaderFields;
  4222. }
  4223. $body = $this->DKIM_BodyC($body);
  4224. $DKIMlen = strlen($body); // Length of body
  4225. $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
  4226. if ('' == $this->DKIM_identity) {
  4227. $ident = '';
  4228. } else {
  4229. $ident = ' i=' . $this->DKIM_identity . ';';
  4230. }
  4231. $dkimhdrs = 'DKIM-Signature: v=1; a=' .
  4232. $DKIMsignatureType . '; q=' .
  4233. $DKIMquery . '; l=' .
  4234. $DKIMlen . '; s=' .
  4235. $this->DKIM_selector .
  4236. ";\r\n" .
  4237. "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
  4238. "\th=From:To:Date:Subject" . $extraHeaderKeys . ";\r\n" .
  4239. "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
  4240. $copiedHeaderFields .
  4241. "\tbh=" . $DKIMb64 . ";\r\n" .
  4242. "\tb=";
  4243. $toSign = $this->DKIM_HeaderC(
  4244. $from_header . "\r\n" .
  4245. $to_header . "\r\n" .
  4246. $date_header . "\r\n" .
  4247. $subject_header . "\r\n" .
  4248. $extraHeaderValues .
  4249. $dkimhdrs
  4250. );
  4251. $signed = $this->DKIM_Sign($toSign);
  4252. return static::normalizeBreaks($dkimhdrs . $signed) . static::$LE;
  4253. }
  4254. /**
  4255. * Detect if a string contains a line longer than the maximum line length
  4256. * allowed by RFC 2822 section 2.1.1.
  4257. *
  4258. * @param string $str
  4259. *
  4260. * @return bool
  4261. */
  4262. public static function hasLineLongerThanMax($str)
  4263. {
  4264. return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
  4265. }
  4266. /**
  4267. * Allows for public read access to 'to' property.
  4268. * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4269. *
  4270. * @return array
  4271. */
  4272. public function getToAddresses()
  4273. {
  4274. return $this->to;
  4275. }
  4276. /**
  4277. * Allows for public read access to 'cc' property.
  4278. * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4279. *
  4280. * @return array
  4281. */
  4282. public function getCcAddresses()
  4283. {
  4284. return $this->cc;
  4285. }
  4286. /**
  4287. * Allows for public read access to 'bcc' property.
  4288. * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4289. *
  4290. * @return array
  4291. */
  4292. public function getBccAddresses()
  4293. {
  4294. return $this->bcc;
  4295. }
  4296. /**
  4297. * Allows for public read access to 'ReplyTo' property.
  4298. * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4299. *
  4300. * @return array
  4301. */
  4302. public function getReplyToAddresses()
  4303. {
  4304. return $this->ReplyTo;
  4305. }
  4306. /**
  4307. * Allows for public read access to 'all_recipients' property.
  4308. * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
  4309. *
  4310. * @return array
  4311. */
  4312. public function getAllRecipientAddresses()
  4313. {
  4314. return $this->all_recipients;
  4315. }
  4316. /**
  4317. * Perform a callback.
  4318. *
  4319. * @param bool $isSent
  4320. * @param array $to
  4321. * @param array $cc
  4322. * @param array $bcc
  4323. * @param string $subject
  4324. * @param string $body
  4325. * @param string $from
  4326. * @param array $extra
  4327. */
  4328. protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
  4329. {
  4330. if (!empty($this->action_function) and is_callable($this->action_function)) {
  4331. call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);
  4332. }
  4333. }
  4334. /**
  4335. * Get the OAuth instance.
  4336. *
  4337. * @return OAuth
  4338. */
  4339. public function getOAuth()
  4340. {
  4341. return $this->oauth;
  4342. }
  4343. /**
  4344. * Set an OAuth instance.
  4345. *
  4346. * @param OAuth $oauth
  4347. */
  4348. public function setOAuth(OAuth $oauth)
  4349. {
  4350. $this->oauth = $oauth;
  4351. }
  4352. }
  4353.  
  4354. /**
  4355. * PHPMailer RFC821 SMTP email transport class.
  4356. * PHP Version 5.5.
  4357. *
  4358. * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
  4359. *
  4360. * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
  4361. * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
  4362. * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
  4363. * @author Brent R. Matzelle (original founder)
  4364. * @copyright 2012 - 2017 Marcus Bointon
  4365. * @copyright 2010 - 2012 Jim Jagielski
  4366. * @copyright 2004 - 2009 Andy Prevost
  4367. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  4368. * @note This program is distributed in the hope that it will be useful - WITHOUT
  4369. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  4370. * FITNESS FOR A PARTICULAR PURPOSE.
  4371. */
  4372.  
  4373. /**
  4374. * PHPMailer RFC821 SMTP email transport class.
  4375. * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
  4376. *
  4377. * @author Chris Ryan
  4378. * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
  4379. */
  4380. class SMTP
  4381. {
  4382. /**
  4383. * The PHPMailer SMTP version number.
  4384. *
  4385. * @var string
  4386. */
  4387. const VERSION = '6.0.7';
  4388. /**
  4389. * SMTP line break constant.
  4390. *
  4391. * @var string
  4392. */
  4393. const LE = "\r\n";
  4394. /**
  4395. * The SMTP port to use if one is not specified.
  4396. *
  4397. * @var int
  4398. */
  4399. const DEFAULT_PORT = 25;
  4400. /**
  4401. * The maximum line length allowed by RFC 2822 section 2.1.1.
  4402. *
  4403. * @var int
  4404. */
  4405. const MAX_LINE_LENGTH = 998;
  4406. /**
  4407. * Debug level for no output.
  4408. */
  4409. const DEBUG_OFF = 0;
  4410. /**
  4411. * Debug level to show client -> server messages.
  4412. */
  4413. const DEBUG_CLIENT = 1;
  4414. /**
  4415. * Debug level to show client -> server and server -> client messages.
  4416. */
  4417. const DEBUG_SERVER = 2;
  4418. /**
  4419. * Debug level to show connection status, client -> server and server -> client messages.
  4420. */
  4421. const DEBUG_CONNECTION = 3;
  4422. /**
  4423. * Debug level to show all messages.
  4424. */
  4425. const DEBUG_LOWLEVEL = 4;
  4426. /**
  4427. * Debug output level.
  4428. * Options:
  4429. * * self::DEBUG_OFF (`0`) No debug output, default
  4430. * * self::DEBUG_CLIENT (`1`) Client commands
  4431. * * self::DEBUG_SERVER (`2`) Client commands and server responses
  4432. * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
  4433. * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages.
  4434. *
  4435. * @var int
  4436. */
  4437. public $do_debug = self::DEBUG_OFF;
  4438. /**
  4439. * How to handle debug output.
  4440. * Options:
  4441. * * `echo` Output plain-text as-is, appropriate for CLI
  4442. * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
  4443. * * `error_log` Output to error log as configured in php.ini
  4444. * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
  4445. *
  4446. * ```php
  4447. * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
  4448. * ```
  4449. *
  4450. * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
  4451. * level output is used:
  4452. *
  4453. * ```php
  4454. * $mail->Debugoutput = new myPsr3Logger;
  4455. * ```
  4456. *
  4457. * @var string|callable|\Psr\Log\LoggerInterface
  4458. */
  4459. public $Debugoutput = 'echo';
  4460. /**
  4461. * Whether to use VERP.
  4462. *
  4463. * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path
  4464. * @see http://www.postfix.org/VERP_README.html Info on VERP
  4465. *
  4466. * @var bool
  4467. */
  4468. public $do_verp = false;
  4469. /**
  4470. * The timeout value for connection, in seconds.
  4471. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
  4472. * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
  4473. *
  4474. * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2
  4475. *
  4476. * @var int
  4477. */
  4478. public $Timeout = 300;
  4479. /**
  4480. * How long to wait for commands to complete, in seconds.
  4481. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
  4482. *
  4483. * @var int
  4484. */
  4485. public $Timelimit = 300;
  4486. /**
  4487. * Patterns to extract an SMTP transaction id from reply to a DATA command.
  4488. * The first capture group in each regex will be used as the ID.
  4489. * MS ESMTP returns the message ID, which may not be correct for internal tracking.
  4490. *
  4491. * @var string[]
  4492. */
  4493. protected $smtp_transaction_id_patterns = [
  4494. 'exim' => '/[\d]{3} OK id=(.*)/',
  4495. 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/',
  4496. 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/',
  4497. 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/',
  4498. 'Amazon_SES' => '/[\d]{3} Ok (.*)/',
  4499. 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
  4500. 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
  4501. ];
  4502. /**
  4503. * The last transaction ID issued in response to a DATA command,
  4504. * if one was detected.
  4505. *
  4506. * @var string|bool|null
  4507. */
  4508. protected $last_smtp_transaction_id;
  4509. /**
  4510. * The socket for the server connection.
  4511. *
  4512. * @var ?resource
  4513. */
  4514. protected $smtp_conn;
  4515. /**
  4516. * Error information, if any, for the last SMTP command.
  4517. *
  4518. * @var array
  4519. */
  4520. protected $error = [
  4521. 'error' => '',
  4522. 'detail' => '',
  4523. 'smtp_code' => '',
  4524. 'smtp_code_ex' => '',
  4525. ];
  4526. /**
  4527. * The reply the server sent to us for HELO.
  4528. * If null, no HELO string has yet been received.
  4529. *
  4530. * @var string|null
  4531. */
  4532. protected $helo_rply = null;
  4533. /**
  4534. * The set of SMTP extensions sent in reply to EHLO command.
  4535. * Indexes of the array are extension names.
  4536. * Value at index 'HELO' or 'EHLO' (according to command that was sent)
  4537. * represents the server name. In case of HELO it is the only element of the array.
  4538. * Other values can be boolean TRUE or an array containing extension options.
  4539. * If null, no HELO/EHLO string has yet been received.
  4540. *
  4541. * @var array|null
  4542. */
  4543. protected $server_caps = null;
  4544. /**
  4545. * The most recent reply received from the server.
  4546. *
  4547. * @var string
  4548. */
  4549. protected $last_reply = '';
  4550. /**
  4551. * Output debugging info via a user-selected method.
  4552. *
  4553. * @param string $str Debug string to output
  4554. * @param int $level The debug level of this message; see DEBUG_* constants
  4555. *
  4556. * @see SMTP::$Debugoutput
  4557. * @see SMTP::$do_debug
  4558. */
  4559. protected function edebug($str, $level = 0)
  4560. {
  4561. if ($level > $this->do_debug) {
  4562. return;
  4563. }
  4564. //Is this a PSR-3 logger?
  4565. if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
  4566. $this->Debugoutput->debug($str);
  4567. return;
  4568. }
  4569. //Avoid clash with built-in function names
  4570. if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo']) and is_callable($this->Debugoutput)) {
  4571. call_user_func($this->Debugoutput, $str, $level);
  4572. return;
  4573. }
  4574. switch ($this->Debugoutput) {
  4575. case 'error_log':
  4576. //Don't output, just log
  4577. error_log($str);
  4578. break;
  4579. case 'html':
  4580. //Cleans up output a bit for a better looking, HTML-safe output
  4581. echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
  4582. preg_replace('/[\r\n]+/', '', $str),
  4583. ENT_QUOTES,
  4584. 'UTF-8'
  4585. ), "<br>\n";
  4586. break;
  4587. case 'echo':
  4588. default:
  4589. //Normalize line breaks
  4590. $str = preg_replace('/\r\n|\r/ms', "\n", $str);
  4591. echo gmdate('Y-m-d H:i:s'),
  4592. "\t",
  4593. //Trim trailing space
  4594. trim(
  4595. //Indent for readability, except for trailing break
  4596. str_replace(
  4597. "\n",
  4598. "\n \t ",
  4599. trim($str)
  4600. )
  4601. ),
  4602. "\n";
  4603. }
  4604. }
  4605. /**
  4606. * Connect to an SMTP server.
  4607. *
  4608. * @param string $host SMTP server IP or host name
  4609. * @param int $port The port number to connect to
  4610. * @param int $timeout How long to wait for the connection to open
  4611. * @param array $options An array of options for stream_context_create()
  4612. *
  4613. * @return bool
  4614. */
  4615. public function connect($host, $port = null, $timeout = 30, $options = [])
  4616. {
  4617. static $streamok;
  4618. //This is enabled by default since 5.0.0 but some providers disable it
  4619. //Check this once and cache the result
  4620. if (null === $streamok) {
  4621. $streamok = function_exists('stream_socket_client');
  4622. }
  4623. // Clear errors to avoid confusion
  4624. $this->setError('');
  4625. // Make sure we are __not__ connected
  4626. if ($this->connected()) {
  4627. // Already connected, generate error
  4628. $this->setError('Already connected to a server');
  4629. return false;
  4630. }
  4631. if (empty($port)) {
  4632. $port = self::DEFAULT_PORT;
  4633. }
  4634. // Connect to the SMTP server
  4635. $this->edebug(
  4636. "Connection: opening to $host:$port, timeout=$timeout, options=" .
  4637. (count($options) > 0 ? var_export($options, true) : 'array()'),
  4638. self::DEBUG_CONNECTION
  4639. );
  4640. $errno = 0;
  4641. $errstr = '';
  4642. if ($streamok) {
  4643. $socket_context = stream_context_create($options);
  4644. set_error_handler([$this, 'errorHandler']);
  4645. $this->smtp_conn = stream_socket_client(
  4646. $host . ':' . $port,
  4647. $errno,
  4648. $errstr,
  4649. $timeout,
  4650. STREAM_CLIENT_CONNECT,
  4651. $socket_context
  4652. );
  4653. restore_error_handler();
  4654. } else {
  4655. //Fall back to fsockopen which should work in more places, but is missing some features
  4656. $this->edebug(
  4657. 'Connection: stream_socket_client not available, falling back to fsockopen',
  4658. self::DEBUG_CONNECTION
  4659. );
  4660. set_error_handler([$this, 'errorHandler']);
  4661. $this->smtp_conn = fsockopen(
  4662. $host,
  4663. $port,
  4664. $errno,
  4665. $errstr,
  4666. $timeout
  4667. );
  4668. restore_error_handler();
  4669. }
  4670. // Verify we connected properly
  4671. if (!is_resource($this->smtp_conn)) {
  4672. $this->setError(
  4673. 'Failed to connect to server',
  4674. '',
  4675. (string) $errno,
  4676. (string) $errstr
  4677. );
  4678. $this->edebug(
  4679. 'SMTP ERROR: ' . $this->error['error']
  4680. . ": $errstr ($errno)",
  4681. self::DEBUG_CLIENT
  4682. );
  4683. return false;
  4684. }
  4685. $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
  4686. // SMTP server can take longer to respond, give longer timeout for first read
  4687. // Windows does not have support for this timeout function
  4688. if (substr(PHP_OS, 0, 3) != 'WIN') {
  4689. $max = ini_get('max_execution_time');
  4690. // Don't bother if unlimited
  4691. if (0 != $max and $timeout > $max) {
  4692. @set_time_limit($timeout);
  4693. }
  4694. stream_set_timeout($this->smtp_conn, $timeout, 0);
  4695. }
  4696. // Get any announcement
  4697. $announce = $this->get_lines();
  4698. $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
  4699. return true;
  4700. }
  4701. /**
  4702. * Initiate a TLS (encrypted) session.
  4703. *
  4704. * @return bool
  4705. */
  4706. public function startTLS()
  4707. {
  4708. if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
  4709. return false;
  4710. }
  4711. //Allow the best TLS version(s) we can
  4712. $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
  4713. //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
  4714. //so add them back in manually if we can
  4715. if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
  4716. $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
  4717. $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
  4718. }
  4719. // Begin encrypted connection
  4720. set_error_handler([$this, 'errorHandler']);
  4721. $crypto_ok = stream_socket_enable_crypto(
  4722. $this->smtp_conn,
  4723. true,
  4724. $crypto_method
  4725. );
  4726. restore_error_handler();
  4727. return (bool) $crypto_ok;
  4728. }
  4729. /**
  4730. * Perform SMTP authentication.
  4731. * Must be run after hello().
  4732. *
  4733. * @see hello()
  4734. *
  4735. * @param string $username The user name
  4736. * @param string $password The password
  4737. * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
  4738. * @param OAuth $OAuth An optional OAuth instance for XOAUTH2 authentication
  4739. *
  4740. * @return bool True if successfully authenticated
  4741. */
  4742. public function authenticate(
  4743. $username,
  4744. $password,
  4745. $authtype = null,
  4746. $OAuth = null
  4747. ) {
  4748. if (!$this->server_caps) {
  4749. $this->setError('Authentication is not allowed before HELO/EHLO');
  4750. return false;
  4751. }
  4752. if (array_key_exists('EHLO', $this->server_caps)) {
  4753. // SMTP extensions are available; try to find a proper authentication method
  4754. if (!array_key_exists('AUTH', $this->server_caps)) {
  4755. $this->setError('Authentication is not allowed at this stage');
  4756. // 'at this stage' means that auth may be allowed after the stage changes
  4757. // e.g. after STARTTLS
  4758. return false;
  4759. }
  4760. $this->edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
  4761. $this->edebug(
  4762. 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
  4763. self::DEBUG_LOWLEVEL
  4764. );
  4765. //If we have requested a specific auth type, check the server supports it before trying others
  4766. if (null !== $authtype and !in_array($authtype, $this->server_caps['AUTH'])) {
  4767. $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
  4768. $authtype = null;
  4769. }
  4770. if (empty($authtype)) {
  4771. //If no auth mechanism is specified, attempt to use these, in this order
  4772. //Try CRAM-MD5 first as it's more secure than the others
  4773. foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
  4774. if (in_array($method, $this->server_caps['AUTH'])) {
  4775. $authtype = $method;
  4776. break;
  4777. }
  4778. }
  4779. if (empty($authtype)) {
  4780. $this->setError('No supported authentication methods found');
  4781. return false;
  4782. }
  4783. self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
  4784. }
  4785. if (!in_array($authtype, $this->server_caps['AUTH'])) {
  4786. $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
  4787. return false;
  4788. }
  4789. } elseif (empty($authtype)) {
  4790. $authtype = 'LOGIN';
  4791. }
  4792. switch ($authtype) {
  4793. case 'PLAIN':
  4794. // Start authentication
  4795. if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
  4796. return false;
  4797. }
  4798. // Send encoded username and password
  4799. if (!$this->sendCommand(
  4800. 'User & Password',
  4801. base64_encode("\0" . $username . "\0" . $password),
  4802. 235
  4803. )
  4804. ) {
  4805. return false;
  4806. }
  4807. break;
  4808. case 'LOGIN':
  4809. // Start authentication
  4810. if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
  4811. return false;
  4812. }
  4813. if (!$this->sendCommand('Username', base64_encode($username), 334)) {
  4814. return false;
  4815. }
  4816. if (!$this->sendCommand('Password', base64_encode($password), 235)) {
  4817. return false;
  4818. }
  4819. break;
  4820. case 'CRAM-MD5':
  4821. // Start authentication
  4822. if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
  4823. return false;
  4824. }
  4825. // Get the challenge
  4826. $challenge = base64_decode(substr($this->last_reply, 4));
  4827. // Build the response
  4828. $response = $username . ' ' . $this->hmac($challenge, $password);
  4829. // send encoded credentials
  4830. return $this->sendCommand('Username', base64_encode($response), 235);
  4831. case 'XOAUTH2':
  4832. //The OAuth instance must be set up prior to requesting auth.
  4833. if (null === $OAuth) {
  4834. return false;
  4835. }
  4836. $oauth = $OAuth->getOauth64();
  4837. // Start authentication
  4838. if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
  4839. return false;
  4840. }
  4841. break;
  4842. default:
  4843. $this->setError("Authentication method \"$authtype\" is not supported");
  4844. return false;
  4845. }
  4846. return true;
  4847. }
  4848. /**
  4849. * Calculate an MD5 HMAC hash.
  4850. * Works like hash_hmac('md5', $data, $key)
  4851. * in case that function is not available.
  4852. *
  4853. * @param string $data The data to hash
  4854. * @param string $key The key to hash with
  4855. *
  4856. * @return string
  4857. */
  4858. protected function hmac($data, $key)
  4859. {
  4860. if (function_exists('hash_hmac')) {
  4861. return hash_hmac('md5', $data, $key);
  4862. }
  4863. // The following borrowed from
  4864. // http://php.net/manual/en/function.mhash.php#27225
  4865. // RFC 2104 HMAC implementation for php.
  4866. // Creates an md5 HMAC.
  4867. // Eliminates the need to install mhash to compute a HMAC
  4868. // by Lance Rushing
  4869. $bytelen = 64; // byte length for md5
  4870. if (strlen($key) > $bytelen) {
  4871. $key = pack('H*', md5($key));
  4872. }
  4873. $key = str_pad($key, $bytelen, chr(0x00));
  4874. $ipad = str_pad('', $bytelen, chr(0x36));
  4875. $opad = str_pad('', $bytelen, chr(0x5c));
  4876. $k_ipad = $key ^ $ipad;
  4877. $k_opad = $key ^ $opad;
  4878. return md5($k_opad . pack('H*', md5($k_ipad . $data)));
  4879. }
  4880. /**
  4881. * Check connection state.
  4882. *
  4883. * @return bool True if connected
  4884. */
  4885. public function connected()
  4886. {
  4887. if (is_resource($this->smtp_conn)) {
  4888. $sock_status = stream_get_meta_data($this->smtp_conn);
  4889. if ($sock_status['eof']) {
  4890. // The socket is valid but we are not connected
  4891. $this->edebug(
  4892. 'SMTP NOTICE: EOF caught while checking if connected',
  4893. self::DEBUG_CLIENT
  4894. );
  4895. $this->close();
  4896. return false;
  4897. }
  4898. return true; // everything looks good
  4899. }
  4900. return false;
  4901. }
  4902. /**
  4903. * Close the socket and clean up the state of the class.
  4904. * Don't use this function without first trying to use QUIT.
  4905. *
  4906. * @see quit()
  4907. */
  4908. public function close()
  4909. {
  4910. $this->setError('');
  4911. $this->server_caps = null;
  4912. $this->helo_rply = null;
  4913. if (is_resource($this->smtp_conn)) {
  4914. // close the connection and cleanup
  4915. fclose($this->smtp_conn);
  4916. $this->smtp_conn = null; //Makes for cleaner serialization
  4917. $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
  4918. }
  4919. }
  4920. /**
  4921. * Send an SMTP DATA command.
  4922. * Issues a data command and sends the msg_data to the server,
  4923. * finializing the mail transaction. $msg_data is the message
  4924. * that is to be send with the headers. Each header needs to be
  4925. * on a single line followed by a <CRLF> with the message headers
  4926. * and the message body being separated by an additional <CRLF>.
  4927. * Implements RFC 821: DATA <CRLF>.
  4928. *
  4929. * @param string $msg_data Message data to send
  4930. *
  4931. * @return bool
  4932. */
  4933. public function data($msg_data)
  4934. {
  4935. //This will use the standard timelimit
  4936. if (!$this->sendCommand('DATA', 'DATA', 354)) {
  4937. return false;
  4938. }
  4939. /* The server is ready to accept data!
  4940. * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
  4941. * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
  4942. * smaller lines to fit within the limit.
  4943. * We will also look for lines that start with a '.' and prepend an additional '.'.
  4944. * NOTE: this does not count towards line-length limit.
  4945. */
  4946. // Normalize line breaks before exploding
  4947. $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
  4948. /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
  4949. * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
  4950. * process all lines before a blank line as headers.
  4951. */
  4952. $field = substr($lines[0], 0, strpos($lines[0], ':'));
  4953. $in_headers = false;
  4954. if (!empty($field) and strpos($field, ' ') === false) {
  4955. $in_headers = true;
  4956. }
  4957. foreach ($lines as $line) {
  4958. $lines_out = [];
  4959. if ($in_headers and $line == '') {
  4960. $in_headers = false;
  4961. }
  4962. //Break this line up into several smaller lines if it's too long
  4963. //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
  4964. while (isset($line[self::MAX_LINE_LENGTH])) {
  4965. //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
  4966. //so as to avoid breaking in the middle of a word
  4967. $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
  4968. //Deliberately matches both false and 0
  4969. if (!$pos) {
  4970. //No nice break found, add a hard break
  4971. $pos = self::MAX_LINE_LENGTH - 1;
  4972. $lines_out[] = substr($line, 0, $pos);
  4973. $line = substr($line, $pos);
  4974. } else {
  4975. //Break at the found point
  4976. $lines_out[] = substr($line, 0, $pos);
  4977. //Move along by the amount we dealt with
  4978. $line = substr($line, $pos + 1);
  4979. }
  4980. //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
  4981. if ($in_headers) {
  4982. $line = "\t" . $line;
  4983. }
  4984. }
  4985. $lines_out[] = $line;
  4986. //Send the lines to the server
  4987. foreach ($lines_out as $line_out) {
  4988. //RFC2821 section 4.5.2
  4989. if (!empty($line_out) and $line_out[0] == '.') {
  4990. $line_out = '.' . $line_out;
  4991. }
  4992. $this->client_send($line_out . static::LE, 'DATA');
  4993. }
  4994. }
  4995. //Message data has been sent, complete the command
  4996. //Increase timelimit for end of DATA command
  4997. $savetimelimit = $this->Timelimit;
  4998. $this->Timelimit = $this->Timelimit * 2;
  4999. $result = $this->sendCommand('DATA END', '.', 250);
  5000. $this->recordLastTransactionID();
  5001. //Restore timelimit
  5002. $this->Timelimit = $savetimelimit;
  5003. return $result;
  5004. }
  5005. /**
  5006. * Send an SMTP HELO or EHLO command.
  5007. * Used to identify the sending server to the receiving server.
  5008. * This makes sure that client and server are in a known state.
  5009. * Implements RFC 821: HELO <SP> <domain> <CRLF>
  5010. * and RFC 2821 EHLO.
  5011. *
  5012. * @param string $host The host name or IP to connect to
  5013. *
  5014. * @return bool
  5015. */
  5016. public function hello($host = '')
  5017. {
  5018. //Try extended hello first (RFC 2821)
  5019. return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host);
  5020. }
  5021. /**
  5022. * Send an SMTP HELO or EHLO command.
  5023. * Low-level implementation used by hello().
  5024. *
  5025. * @param string $hello The HELO string
  5026. * @param string $host The hostname to say we are
  5027. *
  5028. * @return bool
  5029. *
  5030. * @see hello()
  5031. */
  5032. protected function sendHello($hello, $host)
  5033. {
  5034. $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
  5035. $this->helo_rply = $this->last_reply;
  5036. if ($noerror) {
  5037. $this->parseHelloFields($hello);
  5038. } else {
  5039. $this->server_caps = null;
  5040. }
  5041. return $noerror;
  5042. }
  5043. /**
  5044. * Parse a reply to HELO/EHLO command to discover server extensions.
  5045. * In case of HELO, the only parameter that can be discovered is a server name.
  5046. *
  5047. * @param string $type `HELO` or `EHLO`
  5048. */
  5049. protected function parseHelloFields($type)
  5050. {
  5051. $this->server_caps = [];
  5052. $lines = explode("\n", $this->helo_rply);
  5053. foreach ($lines as $n => $s) {
  5054. //First 4 chars contain response code followed by - or space
  5055. $s = trim(substr($s, 4));
  5056. if (empty($s)) {
  5057. continue;
  5058. }
  5059. $fields = explode(' ', $s);
  5060. if (!empty($fields)) {
  5061. if (!$n) {
  5062. $name = $type;
  5063. $fields = $fields[0];
  5064. } else {
  5065. $name = array_shift($fields);
  5066. switch ($name) {
  5067. case 'SIZE':
  5068. $fields = ($fields ? $fields[0] : 0);
  5069. break;
  5070. case 'AUTH':
  5071. if (!is_array($fields)) {
  5072. $fields = [];
  5073. }
  5074. break;
  5075. default:
  5076. $fields = true;
  5077. }
  5078. }
  5079. $this->server_caps[$name] = $fields;
  5080. }
  5081. }
  5082. }
  5083. /**
  5084. * Send an SMTP MAIL command.
  5085. * Starts a mail transaction from the email address specified in
  5086. * $from. Returns true if successful or false otherwise. If True
  5087. * the mail transaction is started and then one or more recipient
  5088. * commands may be called followed by a data command.
  5089. * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
  5090. *
  5091. * @param string $from Source address of this message
  5092. *
  5093. * @return bool
  5094. */
  5095. public function mail($from)
  5096. {
  5097. $useVerp = ($this->do_verp ? ' XVERP' : '');
  5098. return $this->sendCommand(
  5099. 'MAIL FROM',
  5100. 'MAIL FROM:<' . $from . '>' . $useVerp,
  5101. 250
  5102. );
  5103. }
  5104. /**
  5105. * Send an SMTP QUIT command.
  5106. * Closes the socket if there is no error or the $close_on_error argument is true.
  5107. * Implements from RFC 821: QUIT <CRLF>.
  5108. *
  5109. * @param bool $close_on_error Should the connection close if an error occurs?
  5110. *
  5111. * @return bool
  5112. */
  5113. public function quit($close_on_error = true)
  5114. {
  5115. $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
  5116. $err = $this->error; //Save any error
  5117. if ($noerror or $close_on_error) {
  5118. $this->close();
  5119. $this->error = $err; //Restore any error from the quit command
  5120. }
  5121. return $noerror;
  5122. }
  5123. /**
  5124. * Send an SMTP RCPT command.
  5125. * Sets the TO argument to $toaddr.
  5126. * Returns true if the recipient was accepted false if it was rejected.
  5127. * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
  5128. *
  5129. * @param string $address The address the message is being sent to
  5130. * @param string $dsn Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE
  5131. * or DELAY. If you specify NEVER all other notifications are ignored.
  5132. *
  5133. * @return bool
  5134. */
  5135. public function recipient($address, $dsn = '')
  5136. {
  5137. if (empty($dsn)) {
  5138. $rcpt = 'RCPT TO:<' . $address . '>';
  5139. } else {
  5140. $dsn = strtoupper($dsn);
  5141. $notify = [];
  5142. if (strpos($dsn, 'NEVER') !== false) {
  5143. $notify[] = 'NEVER';
  5144. } else {
  5145. foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) {
  5146. if (strpos($dsn, $value) !== false) {
  5147. $notify[] = $value;
  5148. }
  5149. }
  5150. }
  5151. $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify);
  5152. }
  5153. return $this->sendCommand(
  5154. 'RCPT TO',
  5155. $rcpt,
  5156. [250, 251]
  5157. );
  5158. }
  5159. /**
  5160. * Send an SMTP RSET command.
  5161. * Abort any transaction that is currently in progress.
  5162. * Implements RFC 821: RSET <CRLF>.
  5163. *
  5164. * @return bool True on success
  5165. */
  5166. public function reset()
  5167. {
  5168. return $this->sendCommand('RSET', 'RSET', 250);
  5169. }
  5170. /**
  5171. * Send a command to an SMTP server and check its return code.
  5172. *
  5173. * @param string $command The command name - not sent to the server
  5174. * @param string $commandstring The actual command to send
  5175. * @param int|array $expect One or more expected integer success codes
  5176. *
  5177. * @return bool True on success
  5178. */
  5179. protected function sendCommand($command, $commandstring, $expect)
  5180. {
  5181. if (!$this->connected()) {
  5182. $this->setError("Called $command without being connected");
  5183. return false;
  5184. }
  5185. //Reject line breaks in all commands
  5186. if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
  5187. $this->setError("Command '$command' contained line breaks");
  5188. return false;
  5189. }
  5190. $this->client_send($commandstring . static::LE, $command);
  5191. $this->last_reply = $this->get_lines();
  5192. // Fetch SMTP code and possible error code explanation
  5193. $matches = [];
  5194. if (preg_match('/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]{1,2}) )?/', $this->last_reply, $matches)) {
  5195. $code = $matches[1];
  5196. $code_ex = (count($matches) > 2 ? $matches[2] : null);
  5197. // Cut off error code from each response line
  5198. $detail = preg_replace(
  5199. "/{$code}[ -]" .
  5200. ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
  5201. '',
  5202. $this->last_reply
  5203. );
  5204. } else {
  5205. // Fall back to simple parsing if regex fails
  5206. $code = substr($this->last_reply, 0, 3);
  5207. $code_ex = null;
  5208. $detail = substr($this->last_reply, 4);
  5209. }
  5210. $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
  5211. if (!in_array($code, (array) $expect)) {
  5212. $this->setError(
  5213. "$command command failed",
  5214. $detail,
  5215. $code,
  5216. $code_ex
  5217. );
  5218. $this->edebug(
  5219. 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
  5220. self::DEBUG_CLIENT
  5221. );
  5222. return false;
  5223. }
  5224. $this->setError('');
  5225. return true;
  5226. }
  5227. /**
  5228. * Send an SMTP SAML command.
  5229. * Starts a mail transaction from the email address specified in $from.
  5230. * Returns true if successful or false otherwise. If True
  5231. * the mail transaction is started and then one or more recipient
  5232. * commands may be called followed by a data command. This command
  5233. * will send the message to the users terminal if they are logged
  5234. * in and send them an email.
  5235. * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
  5236. *
  5237. * @param string $from The address the message is from
  5238. *
  5239. * @return bool
  5240. */
  5241. public function sendAndMail($from)
  5242. {
  5243. return $this->sendCommand('SAML', "SAML FROM:$from", 250);
  5244. }
  5245. /**
  5246. * Send an SMTP VRFY command.
  5247. *
  5248. * @param string $name The name to verify
  5249. *
  5250. * @return bool
  5251. */
  5252. public function verify($name)
  5253. {
  5254. return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
  5255. }
  5256. /**
  5257. * Send an SMTP NOOP command.
  5258. * Used to keep keep-alives alive, doesn't actually do anything.
  5259. *
  5260. * @return bool
  5261. */
  5262. public function noop()
  5263. {
  5264. return $this->sendCommand('NOOP', 'NOOP', 250);
  5265. }
  5266. /**
  5267. * Send an SMTP TURN command.
  5268. * This is an optional command for SMTP that this class does not support.
  5269. * This method is here to make the RFC821 Definition complete for this class
  5270. * and _may_ be implemented in future.
  5271. * Implements from RFC 821: TURN <CRLF>.
  5272. *
  5273. * @return bool
  5274. */
  5275. public function turn()
  5276. {
  5277. $this->setError('The SMTP TURN command is not implemented');
  5278. $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
  5279. return false;
  5280. }
  5281. /**
  5282. * Send raw data to the server.
  5283. *
  5284. * @param string $data The data to send
  5285. * @param string $command Optionally, the command this is part of, used only for controlling debug output
  5286. *
  5287. * @return int|bool The number of bytes sent to the server or false on error
  5288. */
  5289. public function client_send($data, $command = '')
  5290. {
  5291. //If SMTP transcripts are left enabled, or debug output is posted online
  5292. //it can leak credentials, so hide credentials in all but lowest level
  5293. if (self::DEBUG_LOWLEVEL > $this->do_debug and
  5294. in_array($command, ['User & Password', 'Username', 'Password'], true)) {
  5295. $this->edebug('CLIENT -> SERVER: <credentials hidden>', self::DEBUG_CLIENT);
  5296. } else {
  5297. $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
  5298. }
  5299. set_error_handler([$this, 'errorHandler']);
  5300. $result = fwrite($this->smtp_conn, $data);
  5301. restore_error_handler();
  5302. return $result;
  5303. }
  5304. /**
  5305. * Get the latest error.
  5306. *
  5307. * @return array
  5308. */
  5309. public function getError()
  5310. {
  5311. return $this->error;
  5312. }
  5313. /**
  5314. * Get SMTP extensions available on the server.
  5315. *
  5316. * @return array|null
  5317. */
  5318. public function getServerExtList()
  5319. {
  5320. return $this->server_caps;
  5321. }
  5322. /**
  5323. * Get metadata about the SMTP server from its HELO/EHLO response.
  5324. * The method works in three ways, dependent on argument value and current state:
  5325. * 1. HELO/EHLO has not been sent - returns null and populates $this->error.
  5326. * 2. HELO has been sent -
  5327. * $name == 'HELO': returns server name
  5328. * $name == 'EHLO': returns boolean false
  5329. * $name == any other string: returns null and populates $this->error
  5330. * 3. EHLO has been sent -
  5331. * $name == 'HELO'|'EHLO': returns the server name
  5332. * $name == any other string: if extension $name exists, returns True
  5333. * or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
  5334. *
  5335. * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
  5336. *
  5337. * @return mixed
  5338. */
  5339. public function getServerExt($name)
  5340. {
  5341. if (!$this->server_caps) {
  5342. $this->setError('No HELO/EHLO was sent');
  5343. return;
  5344. }
  5345. if (!array_key_exists($name, $this->server_caps)) {
  5346. if ('HELO' == $name) {
  5347. return $this->server_caps['EHLO'];
  5348. }
  5349. if ('EHLO' == $name || array_key_exists('EHLO', $this->server_caps)) {
  5350. return false;
  5351. }
  5352. $this->setError('HELO handshake was used; No information about server extensions available');
  5353. return;
  5354. }
  5355. return $this->server_caps[$name];
  5356. }
  5357. /**
  5358. * Get the last reply from the server.
  5359. *
  5360. * @return string
  5361. */
  5362. public function getLastReply()
  5363. {
  5364. return $this->last_reply;
  5365. }
  5366. /**
  5367. * Read the SMTP server's response.
  5368. * Either before eof or socket timeout occurs on the operation.
  5369. * With SMTP we can tell if we have more lines to read if the
  5370. * 4th character is '-' symbol. If it is a space then we don't
  5371. * need to read anything else.
  5372. *
  5373. * @return string
  5374. */
  5375. protected function get_lines()
  5376. {
  5377. // If the connection is bad, give up straight away
  5378. if (!is_resource($this->smtp_conn)) {
  5379. return '';
  5380. }
  5381. $data = '';
  5382. $endtime = 0;
  5383. stream_set_timeout($this->smtp_conn, $this->Timeout);
  5384. if ($this->Timelimit > 0) {
  5385. $endtime = time() + $this->Timelimit;
  5386. }
  5387. $selR = [$this->smtp_conn];
  5388. $selW = null;
  5389. while (is_resource($this->smtp_conn) and !feof($this->smtp_conn)) {
  5390. //Must pass vars in here as params are by reference
  5391. if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
  5392. $this->edebug(
  5393. 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
  5394. self::DEBUG_LOWLEVEL
  5395. );
  5396. break;
  5397. }
  5398. //Deliberate noise suppression - errors are handled afterwards
  5399. $str = @fgets($this->smtp_conn, 515);
  5400. $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
  5401. $data .= $str;
  5402. // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
  5403. // or 4th character is a space, we are done reading, break the loop,
  5404. // string array access is a micro-optimisation over strlen
  5405. if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
  5406. break;
  5407. }
  5408. // Timed-out? Log and break
  5409. $info = stream_get_meta_data($this->smtp_conn);
  5410. if ($info['timed_out']) {
  5411. $this->edebug(
  5412. 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
  5413. self::DEBUG_LOWLEVEL
  5414. );
  5415. break;
  5416. }
  5417. // Now check if reads took too long
  5418. if ($endtime and time() > $endtime) {
  5419. $this->edebug(
  5420. 'SMTP -> get_lines(): timelimit reached (' .
  5421. $this->Timelimit . ' sec)',
  5422. self::DEBUG_LOWLEVEL
  5423. );
  5424. break;
  5425. }
  5426. }
  5427. return $data;
  5428. }
  5429. /**
  5430. * Enable or disable VERP address generation.
  5431. *
  5432. * @param bool $enabled
  5433. */
  5434. public function setVerp($enabled = false)
  5435. {
  5436. $this->do_verp = $enabled;
  5437. }
  5438. /**
  5439. * Get VERP address generation mode.
  5440. *
  5441. * @return bool
  5442. */
  5443. public function getVerp()
  5444. {
  5445. return $this->do_verp;
  5446. }
  5447. /**
  5448. * Set error messages and codes.
  5449. *
  5450. * @param string $message The error message
  5451. * @param string $detail Further detail on the error
  5452. * @param string $smtp_code An associated SMTP error code
  5453. * @param string $smtp_code_ex Extended SMTP code
  5454. */
  5455. protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
  5456. {
  5457. $this->error = [
  5458. 'error' => $message,
  5459. 'detail' => $detail,
  5460. 'smtp_code' => $smtp_code,
  5461. 'smtp_code_ex' => $smtp_code_ex,
  5462. ];
  5463. }
  5464. /**
  5465. * Set debug output method.
  5466. *
  5467. * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
  5468. */
  5469. public function setDebugOutput($method = 'echo')
  5470. {
  5471. $this->Debugoutput = $method;
  5472. }
  5473. /**
  5474. * Get debug output method.
  5475. *
  5476. * @return string
  5477. */
  5478. public function getDebugOutput()
  5479. {
  5480. return $this->Debugoutput;
  5481. }
  5482. /**
  5483. * Set debug output level.
  5484. *
  5485. * @param int $level
  5486. */
  5487. public function setDebugLevel($level = 0)
  5488. {
  5489. $this->do_debug = $level;
  5490. }
  5491. /**
  5492. * Get debug output level.
  5493. *
  5494. * @return int
  5495. */
  5496. public function getDebugLevel()
  5497. {
  5498. return $this->do_debug;
  5499. }
  5500. /**
  5501. * Set SMTP timeout.
  5502. *
  5503. * @param int $timeout The timeout duration in seconds
  5504. */
  5505. public function setTimeout($timeout = 0)
  5506. {
  5507. $this->Timeout = $timeout;
  5508. }
  5509. /**
  5510. * Get SMTP timeout.
  5511. *
  5512. * @return int
  5513. */
  5514. public function getTimeout()
  5515. {
  5516. return $this->Timeout;
  5517. }
  5518. /**
  5519. * Reports an error number and string.
  5520. *
  5521. * @param int $errno The error number returned by PHP
  5522. * @param string $errmsg The error message returned by PHP
  5523. * @param string $errfile The file the error occurred in
  5524. * @param int $errline The line number the error occurred on
  5525. */
  5526. protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
  5527. {
  5528. $notice = 'Connection failed.';
  5529. $this->setError(
  5530. $notice,
  5531. $errmsg,
  5532. (string) $errno
  5533. );
  5534. $this->edebug(
  5535. "$notice Error #$errno: $errmsg [$errfile line $errline]",
  5536. self::DEBUG_CONNECTION
  5537. );
  5538. }
  5539. /**
  5540. * Extract and return the ID of the last SMTP transaction based on
  5541. * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
  5542. * Relies on the host providing the ID in response to a DATA command.
  5543. * If no reply has been received yet, it will return null.
  5544. * If no pattern was matched, it will return false.
  5545. *
  5546. * @return bool|null|string
  5547. */
  5548. protected function recordLastTransactionID()
  5549. {
  5550. $reply = $this->getLastReply();
  5551. if (empty($reply)) {
  5552. $this->last_smtp_transaction_id = null;
  5553. } else {
  5554. $this->last_smtp_transaction_id = false;
  5555. foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
  5556. if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
  5557. $this->last_smtp_transaction_id = trim($matches[1]);
  5558. break;
  5559. }
  5560. }
  5561. }
  5562. return $this->last_smtp_transaction_id;
  5563. }
  5564. /**
  5565. * Get the queue/transaction ID of the last SMTP transaction
  5566. * If no reply has been received yet, it will return null.
  5567. * If no pattern was matched, it will return false.
  5568. *
  5569. * @return bool|null|string
  5570. *
  5571. * @see recordLastTransactionID()
  5572. */
  5573. public function getLastTransactionID()
  5574. {
  5575. return $this->last_smtp_transaction_id;
  5576. }
  5577. }
  5578.  
  5579.  
  5580.  
  5581.  
  5582.  
  5583. /**
  5584. * PHPMailer exception handler
  5585. * @package PHPMailer
  5586. */
  5587. class phpmailerException extends Exception
  5588. {
  5589. /**
  5590. * Prettify error message output
  5591. * @return string
  5592. */
  5593. public function errorMessage()
  5594. {
  5595. $errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n";
  5596. return $errorMsg;
  5597. }
  5598. }
  5599.  
  5600.  
  5601.  
  5602. ?>
  5603. <!DOCTYPE html>
  5604. <html lang="en">
  5605. <head>
  5606. <meta charset="utf-8">
  5607. <meta http-equiv="X-UA-Compatible" content="IE = edge">
  5608. <meta name="viewport" content="width = device-width, initial-scale = 1">
  5609. <title>Owl PHPMailer <?php echo $owl['version']?></title>
  5610. <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/cosmo/bootstrap.min.css" rel="stylesheet">
  5611. <link href="https://owlmailer.io/css/bootstrap-3.3.1.min.css" rel="stylesheet">
  5612. <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  5613. <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  5614. <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
  5615. <link rel="icon" href="https://owlmailer.io/img/favicon.ico" type="image/x-icon" />
  5616.  
  5617.  
  5618. </head>
  5619.  
  5620. <body>
  5621. <script>
  5622. var running = false;
  5623. var request;
  5624. </script>
  5625.  
  5626. <script>
  5627. Array.prototype.randomElement = function () {
  5628. return this[Math.floor(Math.random() * this.length)]
  5629. }
  5630.  
  5631. function stopSending()
  5632. {
  5633. running = false;
  5634.  
  5635. if (request) {
  5636. request.abort();
  5637. }
  5638.  
  5639. $("#btnStart").attr("disabled", false);
  5640. $("#btnStop").attr("disabled", true);
  5641. }
  5642.  
  5643. function handleSendingResponse(recipient, response, processedCount, totalEmailCount) {
  5644. $("#progress").append('<div class="col-lg-3">' + processedCount.toString() + '/' + totalEmailCount.toString() + '</div><div class="col-lg-6">' + recipient + '</div>');
  5645.  
  5646. if (response == "OK"){
  5647. $("#progress").append('<div class="col-lg-1"><span class="label label-success">Ok</span></div>');
  5648. }
  5649. else if(response == "Incorrect Email"){
  5650. $("#progress").append('<div class="col-lg-1"><span class="label label-default">Incorrect Email</span></div>');
  5651. } else {
  5652. $("#progress").append('<div class="col-lg-1"><span class="label label-default">' + response + '</span></div>');
  5653. }
  5654. $("#progress").append('<br>');
  5655. }
  5656.  
  5657. function startSending() {
  5658.  
  5659.  
  5660.  
  5661. var eMailTextArea = document.getElementById("emailList");
  5662. var eMailTextAreaLines = eMailTextArea.value.split("\n");
  5663. var smtpAccountsTextArea = document.getElementById("smtpAccounts");
  5664. var smtpAccountsTextAreaLines = smtpAccountsTextArea.value.split("\n");
  5665. var encodingTypeE = document.getElementById('encoding');
  5666. var encodingType = encodingTypeE.options[encodingTypeE.selectedIndex].value;
  5667. var emailPriorityE = document.getElementById('priority');
  5668. var emailPriority = emailPriorityE.options[emailPriorityE.selectedIndex].value;
  5669.  
  5670. var form_data = new FormData();
  5671. form_data.append("action", "send");
  5672. form_data.append("sendingMethod", document.querySelector('input[name="sendingMethod"]:checked').value);
  5673. form_data.append("senderEmail", document.getElementById('senderEmail').value);
  5674. form_data.append("senderName", document.getElementById('senderName').value);
  5675. form_data.append("replyTo", document.getElementById('replyTo').value);
  5676. form_data.append("messageSubject", document.getElementById('subject').value);
  5677. form_data.append("messageLetter", document.getElementById('messageLetter').value);
  5678. form_data.append("altMessageLetter", document.getElementById('altMessageLetter').value);
  5679. form_data.append("messageType", document.querySelector('input[name="messageType"]:checked').value);
  5680. form_data.append("encodingType", encodingType);
  5681. form_data.append("emailPriority", emailPriority);
  5682.  
  5683. for (var x = 0; x < document.getElementById('attachment').files.length; x++) {
  5684. form_data.append("attachment[]", document.getElementById('attachment').files[x]);
  5685. }
  5686.  
  5687. $("#progress").empty();
  5688. var processedCount = 0;
  5689. $(function () {
  5690. var i = 0;
  5691. running = true;
  5692.  
  5693. $("#btnStart").attr("disabled", true);
  5694. $("#btnStop").attr("disabled", false);
  5695.  
  5696.  
  5697. function nextCall() {
  5698. if (i == eMailTextAreaLines.length){
  5699.  
  5700. $("#btnStart").attr("disabled", false);
  5701. $("#btnStop").attr("disabled", true);
  5702. return; //last call was last item in the array
  5703. }
  5704.  
  5705. // Abort any pending request
  5706. if (request) {
  5707. request.abort();
  5708. }
  5709. if(!running)
  5710. {
  5711. return;
  5712. }
  5713.  
  5714. var recipient = eMailTextAreaLines[i++]
  5715. form_data.append("recipient", recipient);
  5716. form_data.append("smtpAcct", smtpAccountsTextAreaLines.randomElement());
  5717.  
  5718. request = $.ajax({
  5719. type: "post",
  5720. data: form_data,
  5721. contentType: false,
  5722. processData: false,
  5723. });
  5724. // Callback handler that will be called on success
  5725. request.done(function (response, textStatus, jqXHR) {
  5726. processedCount += 1;
  5727. handleSendingResponse(recipient, response, processedCount, eMailTextAreaLines.length);
  5728. nextCall();
  5729. });
  5730. }
  5731. nextCall();
  5732. });
  5733. }
  5734. </script>
  5735. <div class="container col-lg-6">
  5736. <h3>&#129417; Owl PHPMailer<small> <?php echo $owl['version']?></small></h3>
  5737. <div class="row">
  5738. <div class="form-group col-lg-6 ">
  5739. <label for="senderEmail">Sender Email</label>
  5740. <input type="text" class="form-control input-sm" id="senderEmail" name="senderEmail">
  5741. </div>
  5742. <div class="form-group col-lg-6 ">
  5743. <label for="senderEmail">Sender Name</label>
  5744. <input type="text" class="form-control input-sm" id="senderName" name="senderName">
  5745. </div>
  5746. </div>
  5747. <div class="row"> <span class="form-group col-lg-6"><label for="attachment">Attachment <small>(Multiple Available)</small></label><input type="file" name="attachment[]" id="attachment" multiple/></span>
  5748. <div class="form-group col-lg-6">
  5749. <label for="replyTo">Reply-to</label>
  5750. <input type="text" class="form-control input-sm " id="replyTo" name="replyTo" />
  5751. </div>
  5752. </div>
  5753. <div class="row">
  5754. <div class="form-group col-lg-12 ">
  5755. <label for="subject">Subject</label>
  5756. <input type="text" class="form-control input-sm " id="subject" name="subject" />
  5757. </div>
  5758. </div>
  5759. <div class="row">
  5760. <div class="form-group col-lg-6">
  5761. <label for="messageLetter">Message Letter</label>
  5762. <textarea placeholder="Message Letter" name="messageLetter" id="messageLetter" class="form-control" rows="10" id="textArea"></textarea>
  5763. </div>
  5764. <div class="form-group col-lg-6">
  5765. <label for="altMessageLetter">Alternative Message Letter</label>
  5766. <textarea placeholder="Alternative Message Letter.&#10;This body can be read by mail clients that do not have HTML email.&#10;capability such as mutt &#38; Eudora.&#10;Clients that can read HTML will view the normal Body." name="altMessageLetter" id="altMessageLetter" class="form-control" rows="10" id="textArea"></textarea>
  5767. </div>
  5768. </div>
  5769. <div class="row">
  5770. <div class="form-group col-lg-6 ">
  5771. <label for="emailList">Email List</label>
  5772. <textarea name="emailList" id="emailList" class="form-control" rows="10" id="textArea"></textarea>
  5773. </div>
  5774. <div class="form-group col-lg-6 ">
  5775. <label for="smtpAccounts">SMTP Accounts</label>
  5776. <textarea placeholder="Random account will be used when sending a message.&#10;1) Format: HOST:PORT:SSL&#10;2) Format: HOST:PORT:SSL:Username:Password&#10;Example: smtp.gmail.com:587:tls:xx@gmail.com:123&#10;Example: smtp.gmail.com:587:nossl:xx@gmail.com:123&#10;&#10;SSL Options: tls , ssl, nossl" name="smtpAccounts" id="smtpAccounts" class="form-control" rows="10" id="textArea"></textarea>
  5777. </div>
  5778. </div>
  5779.  
  5780. <div class="row">
  5781. <div class="form-group col-lg-6 ">
  5782. <label for="messageType">Message Type</label>
  5783. <input type="radio" name="messageType" id="messageType" value="html" checked> HTML
  5784. <input type="radio" name="messageType" id="messageType" value="plain"> Plain
  5785. </div>
  5786. <div class="form-group col-lg-6 ">
  5787. <label for="sendingMethod">Sending Method</label>
  5788. <input type="radio" name="sendingMethod" id="sendingMethod" value="builtin" checked> Builtin
  5789. <input type="radio" name="sendingMethod" id="sendingMethod" value="smtp"> SMTP
  5790. </div>
  5791. <div class="form-group col-lg-6">
  5792. <label for="encoding">Encoding Type</label>
  5793. <select class="form-control input-sm" id="encoding" name="encoding">
  5794. <option value="UTF-8" selected>UTF-8 International Unicode</option>
  5795. <option value="ISO-8859-1">ISO-8859-1 Latin 1 (West European)</option>
  5796. <option value="ISO-8859-2">ISO-8859-2 Latin 2 (East European)</option>
  5797. <option value="ISO-8859-3">ISO-8859-3 Latin 3 (South European)</option>
  5798. <option value="ISO-8859-4">ISO-8859-4 Latin 4 (North European)</option>
  5799. <option value="ISO-8859-5">ISO-8859-5 Cyrillic</option>
  5800. <option value="ISO-8859-6">ISO-8859-6 Arabic</option>
  5801. <option value="ISO-8859-7">ISO-8859-7 Greek</option>
  5802. <option value="ISO-8859-8">ISO-8859-8 Hebrew</option>
  5803. <option value="ISO-8859-9">ISO-8859-9 Latin 5 (Turkish)</option>
  5804. <option value="ISO-8859-15">ISO-8859-15 Latin 9</option>
  5805. <option value="BIG5">BIG5 Traditional Chinese</option>
  5806. <option value="GB-2312">GB-2312 Simplified Chinese</option>
  5807. <option value="ISO-2022-JP">ISO-2022-JP Japanese</option>
  5808. <option value="EUC-JP">EUC-JP Japanese</option>
  5809. <option value="Shift-JIS">Shift-JIS Japanese</option>
  5810. <option value="KSC-5601">KSC-5601 Korean</option>
  5811. <option value="EIC-KR">EIC-KR Korean</option>
  5812. </select>
  5813. </div>
  5814. <div class="form-group col-lg-3">
  5815. <label for="priority">Email Priority</label>
  5816. <select class="form-control input-sm" id="priority" name="priority">
  5817. <option value="" selected>Default</option>
  5818. <option value="5">Low</option>
  5819. <option value="3">Normal</option>
  5820. <option value="1">High</option>
  5821. </select>
  5822. </div>
  5823. </div>
  5824.  
  5825. <button type="button" id="btnStart" class="btn btn-default btn-sm" onclick="startSending();">Start</button>
  5826. <button type="button"id="btnStop" class="btn btn-default btn-sm" onclick="stopSending();">Stop</button>
  5827.  
  5828. </div>
  5829.  
  5830. <div class="col-lg-6"><br>
  5831. <label for="well">Instruction</label>
  5832. <div id="well" class="well well">
  5833. <h4>Server Information</h4>
  5834. <ul>
  5835. <li>ServerIP : <b> <?php echo $_SERVER['SERVER_ADDR'] ?></b></li>
  5836.  
  5837. </ul>
  5838. <h4>HELP</h4>
  5839. <ul>
  5840. <li>[-email-] : <b>Reciver Email</b></li>
  5841. <li>[-time-] : <b>Date and Time</b> (<?php echo date("m/d/Y h:i:s a", time()) ?>)</li>
  5842. <li>[-emailuser-] : <b>Email User</b> (emailuser@emaildomain)</li>
  5843. <li>[-randomstring-] : <b>Random string (0-9,a-z)</b></li>
  5844. <li>[-randomnumber-] : <b>Random number (0-9) </b></li>
  5845. <li>[-randomletters-] : <b>Random Letters(a-z) </b></li>
  5846. <li>[-randommd5-] : <b>Random MD5 </b></li>
  5847. </ul>
  5848. <h4>example</h4>
  5849. Reciver Email = <b>user@domain.com</b><br>
  5850. <ul>
  5851. <li>hello <b>[-emailuser-]</b> -> hello <b>user</b></li>
  5852. <li>your code is <b>[-randommd5-]</b> -> your code is <b>e10adc3949ba59abbe56e057f20f883e</b></li>
  5853. </ul>
  5854. <h6>by <b><a href="http://<?php echo $owl['website']?>"><?php echo $owl['website']?></a></b></h6>
  5855. </div>
  5856.  
  5857. <div id="progress" class="col-lg-16">
  5858.  
  5859. </div>
  5860. </div>
  5861.  
  5862. <script>
  5863. $("#btnStart").attr("disabled", false);
  5864. $("#btnStop").attr("disabled", true);
  5865. </script>
  5866. </body>
  5867. <footer></footer>
  5868.  
  5869. </html>
Add Comment
Please, Sign In to add comment