<?php1/**2* PHPMailer - PHP email creation and transport class.3* PHP Version 5.5.4*5* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project6*7* @author Marcus Bointon (Synchro/coolbru) <[email protected]>8* @author Jim Jagielski (jimjag) <[email protected]>9* @author Andy Prevost (codeworxtech) <[email protected]>10* @author Brent R. Matzelle (original founder)11* @copyright 2012 - 2020 Marcus Bointon12* @copyright 2010 - 2012 Jim Jagielski13* @copyright 2004 - 2009 Andy Prevost14* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License15* @note This program is distributed in the hope that it will be useful - WITHOUT16* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or17* FITNESS FOR A PARTICULAR PURPOSE.18*/1920namespace PHPMailer\PHPMailer;2122/**23* PHPMailer - PHP email creation and transport class.24*25* @author Marcus Bointon (Synchro/coolbru) <[email protected]>26* @author Jim Jagielski (jimjag) <[email protected]>27* @author Andy Prevost (codeworxtech) <[email protected]>28* @author Brent R. Matzelle (original founder)29*/30class PHPMailer31{32const CHARSET_ASCII = 'us-ascii';33const CHARSET_ISO88591 = 'iso-8859-1';34const CHARSET_UTF8 = 'utf-8';3536const CONTENT_TYPE_PLAINTEXT = 'text/plain';37const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar';38const CONTENT_TYPE_TEXT_HTML = 'text/html';39const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative';40const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed';41const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related';4243const ENCODING_7BIT = '7bit';44const ENCODING_8BIT = '8bit';45const ENCODING_BASE64 = 'base64';46const ENCODING_BINARY = 'binary';47const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';4849const ENCRYPTION_STARTTLS = 'tls';50const ENCRYPTION_SMTPS = 'ssl';5152const ICAL_METHOD_REQUEST = 'REQUEST';53const ICAL_METHOD_PUBLISH = 'PUBLISH';54const ICAL_METHOD_REPLY = 'REPLY';55const ICAL_METHOD_ADD = 'ADD';56const ICAL_METHOD_CANCEL = 'CANCEL';57const ICAL_METHOD_REFRESH = 'REFRESH';58const ICAL_METHOD_COUNTER = 'COUNTER';59const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER';6061/**62* Email priority.63* Options: null (default), 1 = High, 3 = Normal, 5 = low.64* When null, the header is not set at all.65*66* @var int|null67*/68public $Priority;6970/**71* The character set of the message.72*73* @var string74*/75public $CharSet = self::CHARSET_ISO88591;7677/**78* The MIME Content-type of the message.79*80* @var string81*/82public $ContentType = self::CONTENT_TYPE_PLAINTEXT;8384/**85* The message encoding.86* Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".87*88* @var string89*/90public $Encoding = self::ENCODING_8BIT;9192/**93* Holds the most recent mailer error message.94*95* @var string96*/97public $ErrorInfo = '';9899/**100* The From email address for the message.101*102* @var string103*/104public $From = 'root@localhost';105106/**107* The From name of the message.108*109* @var string110*/111public $FromName = 'Root User';112113/**114* The envelope sender of the message.115* This will usually be turned into a Return-Path header by the receiver,116* and is the address that bounces will be sent to.117* If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP.118*119* @var string120*/121public $Sender = '';122123/**124* The Subject of the message.125*126* @var string127*/128public $Subject = '';129130/**131* An HTML or plain text message body.132* If HTML then call isHTML(true).133*134* @var string135*/136public $Body = '';137138/**139* The plain-text message body.140* This body can be read by mail clients that do not have HTML email141* capability such as mutt & Eudora.142* Clients that can read HTML will view the normal Body.143*144* @var string145*/146public $AltBody = '';147148/**149* An iCal message part body.150* Only supported in simple alt or alt_inline message types151* To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator.152*153* @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/154* @see http://kigkonsult.se/iCalcreator/155*156* @var string157*/158public $Ical = '';159160/**161* Value-array of "method" in Contenttype header "text/calendar"162*163* @var string[]164*/165protected static $IcalMethods = [166self::ICAL_METHOD_REQUEST,167self::ICAL_METHOD_PUBLISH,168self::ICAL_METHOD_REPLY,169self::ICAL_METHOD_ADD,170self::ICAL_METHOD_CANCEL,171self::ICAL_METHOD_REFRESH,172self::ICAL_METHOD_COUNTER,173self::ICAL_METHOD_DECLINECOUNTER,174];175176/**177* The complete compiled MIME message body.178*179* @var string180*/181protected $MIMEBody = '';182183/**184* The complete compiled MIME message headers.185*186* @var string187*/188protected $MIMEHeader = '';189190/**191* Extra headers that createHeader() doesn't fold in.192*193* @var string194*/195protected $mailHeader = '';196197/**198* Word-wrap the message body to this number of chars.199* Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.200*201* @see static::STD_LINE_LENGTH202*203* @var int204*/205public $WordWrap = 0;206207/**208* Which method to use to send mail.209* Options: "mail", "sendmail", or "smtp".210*211* @var string212*/213public $Mailer = 'mail';214215/**216* The path to the sendmail program.217*218* @var string219*/220public $Sendmail = '/usr/sbin/sendmail';221222/**223* Whether mail() uses a fully sendmail-compatible MTA.224* One which supports sendmail's "-oi -f" options.225*226* @var bool227*/228public $UseSendmailOptions = true;229230/**231* The email address that a reading confirmation should be sent to, also known as read receipt.232*233* @var string234*/235public $ConfirmReadingTo = '';236237/**238* The hostname to use in the Message-ID header and as default HELO string.239* If empty, PHPMailer attempts to find one with, in order,240* $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value241* 'localhost.localdomain'.242*243* @see PHPMailer::$Helo244*245* @var string246*/247public $Hostname = '';248249/**250* An ID to be used in the Message-ID header.251* If empty, a unique id will be generated.252* You can set your own, but it must be in the format "<id@domain>",253* as defined in RFC5322 section 3.6.4 or it will be ignored.254*255* @see https://tools.ietf.org/html/rfc5322#section-3.6.4256*257* @var string258*/259public $MessageID = '';260261/**262* The message Date to be used in the Date header.263* If empty, the current date will be added.264*265* @var string266*/267public $MessageDate = '';268269/**270* SMTP hosts.271* Either a single hostname or multiple semicolon-delimited hostnames.272* You can also specify a different port273* for each host by using this format: [hostname:port]274* (e.g. "smtp1.example.com:25;smtp2.example.com").275* You can also specify encryption type, for example:276* (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").277* Hosts will be tried in order.278*279* @var string280*/281public $Host = 'localhost';282283/**284* The default SMTP server port.285*286* @var int287*/288public $Port = 25;289290/**291* The SMTP HELO/EHLO name used for the SMTP connection.292* Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find293* one with the same method described above for $Hostname.294*295* @see PHPMailer::$Hostname296*297* @var string298*/299public $Helo = '';300301/**302* What kind of encryption to use on the SMTP connection.303* Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS.304*305* @var string306*/307public $SMTPSecure = '';308309/**310* Whether to enable TLS encryption automatically if a server supports it,311* even if `SMTPSecure` is not set to 'tls'.312* Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.313*314* @var bool315*/316public $SMTPAutoTLS = true;317318/**319* Whether to use SMTP authentication.320* Uses the Username and Password properties.321*322* @see PHPMailer::$Username323* @see PHPMailer::$Password324*325* @var bool326*/327public $SMTPAuth = false;328329/**330* Options array passed to stream_context_create when connecting via SMTP.331*332* @var array333*/334public $SMTPOptions = [];335336/**337* SMTP username.338*339* @var string340*/341public $Username = '';342343/**344* SMTP password.345*346* @var string347*/348public $Password = '';349350/**351* SMTP auth type.352* Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified.353*354* @var string355*/356public $AuthType = '';357358/**359* An instance of the PHPMailer OAuth class.360*361* @var OAuth362*/363protected $oauth;364365/**366* The SMTP server timeout in seconds.367* Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.368*369* @var int370*/371public $Timeout = 300;372373/**374* Comma separated list of DSN notifications375* 'NEVER' under no circumstances a DSN must be returned to the sender.376* If you use NEVER all other notifications will be ignored.377* 'SUCCESS' will notify you when your mail has arrived at its destination.378* 'FAILURE' will arrive if an error occurred during delivery.379* 'DELAY' will notify you if there is an unusual delay in delivery, but the actual380* delivery's outcome (success or failure) is not yet decided.381*382* @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY383*/384public $dsn = '';385386/**387* SMTP class debug output mode.388* Debug output level.389* Options:390* * SMTP::DEBUG_OFF: No output391* * SMTP::DEBUG_CLIENT: Client messages392* * SMTP::DEBUG_SERVER: Client and server messages393* * SMTP::DEBUG_CONNECTION: As SERVER plus connection status394* * SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed395*396* @see SMTP::$do_debug397*398* @var int399*/400public $SMTPDebug = 0;401402/**403* How to handle debug output.404* Options:405* * `echo` Output plain-text as-is, appropriate for CLI406* * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output407* * `error_log` Output to error log as configured in php.ini408* By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise.409* Alternatively, you can provide a callable expecting two params: a message string and the debug level:410*411* ```php412* $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};413* ```414*415* Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`416* level output is used:417*418* ```php419* $mail->Debugoutput = new myPsr3Logger;420* ```421*422* @see SMTP::$Debugoutput423*424* @var string|callable|\Psr\Log\LoggerInterface425*/426public $Debugoutput = 'echo';427428/**429* Whether to keep SMTP connection open after each message.430* If this is set to true then to close the connection431* requires an explicit call to smtpClose().432*433* @var bool434*/435public $SMTPKeepAlive = false;436437/**438* Whether to split multiple to addresses into multiple messages439* or send them all in one message.440* Only supported in `mail` and `sendmail` transports, not in SMTP.441*442* @var bool443*444* @deprecated 6.0.0 PHPMailer isn't a mailing list manager!445*/446public $SingleTo = false;447448/**449* Storage for addresses when SingleTo is enabled.450*451* @var array452*/453protected $SingleToArray = [];454455/**456* Whether to generate VERP addresses on send.457* Only applicable when sending via SMTP.458*459* @see https://en.wikipedia.org/wiki/Variable_envelope_return_path460* @see http://www.postfix.org/VERP_README.html Postfix VERP info461*462* @var bool463*/464public $do_verp = false;465466/**467* Whether to allow sending messages with an empty body.468*469* @var bool470*/471public $AllowEmpty = false;472473/**474* DKIM selector.475*476* @var string477*/478public $DKIM_selector = '';479480/**481* DKIM Identity.482* Usually the email address used as the source of the email.483*484* @var string485*/486public $DKIM_identity = '';487488/**489* DKIM passphrase.490* Used if your key is encrypted.491*492* @var string493*/494public $DKIM_passphrase = '';495496/**497* DKIM signing domain name.498*499* @example 'example.com'500*501* @var string502*/503public $DKIM_domain = '';504505/**506* DKIM Copy header field values for diagnostic use.507*508* @var bool509*/510public $DKIM_copyHeaderFields = true;511512/**513* DKIM Extra signing headers.514*515* @example ['List-Unsubscribe', 'List-Help']516*517* @var array518*/519public $DKIM_extraHeaders = [];520521/**522* DKIM private key file path.523*524* @var string525*/526public $DKIM_private = '';527528/**529* DKIM private key string.530*531* If set, takes precedence over `$DKIM_private`.532*533* @var string534*/535public $DKIM_private_string = '';536537/**538* Callback Action function name.539*540* The function that handles the result of the send email action.541* It is called out by send() for each email sent.542*543* Value can be any php callable: http://www.php.net/is_callable544*545* Parameters:546* bool $result result of the send action547* array $to email addresses of the recipients548* array $cc cc email addresses549* array $bcc bcc email addresses550* string $subject the subject551* string $body the email body552* string $from email address of sender553* string $extra extra information of possible use554* "smtp_transaction_id' => last smtp transaction id555*556* @var string557*/558public $action_function = '';559560/**561* What to put in the X-Mailer header.562* Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use.563*564* @var string|null565*/566public $XMailer = '';567568/**569* Which validator to use by default when validating email addresses.570* May be a callable to inject your own validator, but there are several built-in validators.571* The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option.572*573* @see PHPMailer::validateAddress()574*575* @var string|callable576*/577public static $validator = 'php';578579/**580* An instance of the SMTP sender class.581*582* @var SMTP583*/584protected $smtp;585586/**587* The array of 'to' names and addresses.588*589* @var array590*/591protected $to = [];592593/**594* The array of 'cc' names and addresses.595*596* @var array597*/598protected $cc = [];599600/**601* The array of 'bcc' names and addresses.602*603* @var array604*/605protected $bcc = [];606607/**608* The array of reply-to names and addresses.609*610* @var array611*/612protected $ReplyTo = [];613614/**615* An array of all kinds of addresses.616* Includes all of $to, $cc, $bcc.617*618* @see PHPMailer::$to619* @see PHPMailer::$cc620* @see PHPMailer::$bcc621*622* @var array623*/624protected $all_recipients = [];625626/**627* An array of names and addresses queued for validation.628* In send(), valid and non duplicate entries are moved to $all_recipients629* and one of $to, $cc, or $bcc.630* This array is used only for addresses with IDN.631*632* @see PHPMailer::$to633* @see PHPMailer::$cc634* @see PHPMailer::$bcc635* @see PHPMailer::$all_recipients636*637* @var array638*/639protected $RecipientsQueue = [];640641/**642* An array of reply-to names and addresses queued for validation.643* In send(), valid and non duplicate entries are moved to $ReplyTo.644* This array is used only for addresses with IDN.645*646* @see PHPMailer::$ReplyTo647*648* @var array649*/650protected $ReplyToQueue = [];651652/**653* The array of attachments.654*655* @var array656*/657protected $attachment = [];658659/**660* The array of custom headers.661*662* @var array663*/664protected $CustomHeader = [];665666/**667* The most recent Message-ID (including angular brackets).668*669* @var string670*/671protected $lastMessageID = '';672673/**674* The message's MIME type.675*676* @var string677*/678protected $message_type = '';679680/**681* The array of MIME boundary strings.682*683* @var array684*/685protected $boundary = [];686687/**688* The array of available languages.689*690* @var array691*/692protected $language = [];693694/**695* The number of errors encountered.696*697* @var int698*/699protected $error_count = 0;700701/**702* The S/MIME certificate file path.703*704* @var string705*/706protected $sign_cert_file = '';707708/**709* The S/MIME key file path.710*711* @var string712*/713protected $sign_key_file = '';714715/**716* The optional S/MIME extra certificates ("CA Chain") file path.717*718* @var string719*/720protected $sign_extracerts_file = '';721722/**723* The S/MIME password for the key.724* Used only if the key is encrypted.725*726* @var string727*/728protected $sign_key_pass = '';729730/**731* Whether to throw exceptions for errors.732*733* @var bool734*/735protected $exceptions = false;736737/**738* Unique ID used for message ID and boundaries.739*740* @var string741*/742protected $uniqueid = '';743744/**745* The PHPMailer Version number.746*747* @var string748*/749const VERSION = '6.1.7';750751/**752* Error severity: message only, continue processing.753*754* @var int755*/756const STOP_MESSAGE = 0;757758/**759* Error severity: message, likely ok to continue processing.760*761* @var int762*/763const STOP_CONTINUE = 1;764765/**766* Error severity: message, plus full stop, critical error reached.767*768* @var int769*/770const STOP_CRITICAL = 2;771772/**773* The SMTP standard CRLF line break.774* If you want to change line break format, change static::$LE, not this.775*/776const CRLF = "\r\n";777778/**779* "Folding White Space" a white space string used for line folding.780*/781const FWS = ' ';782783/**784* SMTP RFC standard line ending; Carriage Return, Line Feed.785*786* @var string787*/788protected static $LE = self::CRLF;789790/**791* The maximum line length supported by mail().792*793* Background: mail() will sometimes corrupt messages794* with headers headers longer than 65 chars, see #818.795*796* @var int797*/798const MAIL_MAX_LINE_LENGTH = 63;799800/**801* The maximum line length allowed by RFC 2822 section 2.1.1.802*803* @var int804*/805const MAX_LINE_LENGTH = 998;806807/**808* The lower maximum line length allowed by RFC 2822 section 2.1.1.809* This length does NOT include the line break810* 76 means that lines will be 77 or 78 chars depending on whether811* the line break format is LF or CRLF; both are valid.812*813* @var int814*/815const STD_LINE_LENGTH = 76;816817/**818* Constructor.819*820* @param bool $exceptions Should we throw external exceptions?821*/822public function __construct($exceptions = null)823{824if (null !== $exceptions) {825$this->exceptions = (bool) $exceptions;826}827//Pick an appropriate debug output format automatically828$this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');829}830831/**832* Destructor.833*/834public function __destruct()835{836//Close any open SMTP connection nicely837$this->smtpClose();838}839840/**841* Call mail() in a safe_mode-aware fashion.842* Also, unless sendmail_path points to sendmail (or something that843* claims to be sendmail), don't pass params (not a perfect fix,844* but it will do).845*846* @param string $to To847* @param string $subject Subject848* @param string $body Message Body849* @param string $header Additional Header(s)850* @param string|null $params Params851*852* @return bool853*/854private function mailPassthru($to, $subject, $body, $header, $params)855{856//Check overloading of mail function to avoid double-encoding857if (ini_get('mbstring.func_overload') & 1) {858$subject = $this->secureHeader($subject);859} else {860$subject = $this->encodeHeader($this->secureHeader($subject));861}862//Calling mail() with null params breaks863if (!$this->UseSendmailOptions || null === $params) {864$result = @mail($to, $subject, $body, $header);865} else {866$result = @mail($to, $subject, $body, $header, $params);867}868869return $result;870}871872/**873* Output debugging info via user-defined method.874* Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).875*876* @see PHPMailer::$Debugoutput877* @see PHPMailer::$SMTPDebug878*879* @param string $str880*/881protected function edebug($str)882{883if ($this->SMTPDebug <= 0) {884return;885}886//Is this a PSR-3 logger?887if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {888$this->Debugoutput->debug($str);889890return;891}892//Avoid clash with built-in function names893if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {894call_user_func($this->Debugoutput, $str, $this->SMTPDebug);895896return;897}898switch ($this->Debugoutput) {899case 'error_log':900//Don't output, just log901/** @noinspection ForgottenDebugOutputInspection */902error_log($str);903break;904case 'html':905//Cleans up output a bit for a better looking, HTML-safe output906echo htmlentities(907preg_replace('/[\r\n]+/', '', $str),908ENT_QUOTES,909'UTF-8'910), "<br>\n";911break;912case 'echo':913default:914//Normalize line breaks915$str = preg_replace('/\r\n|\r/m', "\n", $str);916echo gmdate('Y-m-d H:i:s'),917"\t",918//Trim trailing space919trim(920//Indent for readability, except for trailing break921str_replace(922"\n",923"\n \t ",924trim($str)925)926),927"\n";928}929}930931/**932* Sets message type to HTML or plain.933*934* @param bool $isHtml True for HTML mode935*/936public function isHTML($isHtml = true)937{938if ($isHtml) {939$this->ContentType = static::CONTENT_TYPE_TEXT_HTML;940} else {941$this->ContentType = static::CONTENT_TYPE_PLAINTEXT;942}943}944945/**946* Send messages using SMTP.947*/948public function isSMTP()949{950$this->Mailer = 'smtp';951}952953/**954* Send messages using PHP's mail() function.955*/956public function isMail()957{958$this->Mailer = 'mail';959}960961/**962* Send messages using $Sendmail.963*/964public function isSendmail()965{966$ini_sendmail_path = ini_get('sendmail_path');967968if (false === stripos($ini_sendmail_path, 'sendmail')) {969$this->Sendmail = '/usr/sbin/sendmail';970} else {971$this->Sendmail = $ini_sendmail_path;972}973$this->Mailer = 'sendmail';974}975976/**977* Send messages using qmail.978*/979public function isQmail()980{981$ini_sendmail_path = ini_get('sendmail_path');982983if (false === stripos($ini_sendmail_path, 'qmail')) {984$this->Sendmail = '/var/qmail/bin/qmail-inject';985} else {986$this->Sendmail = $ini_sendmail_path;987}988$this->Mailer = 'qmail';989}990991/**992* Add a "To" address.993*994* @param string $address The email address to send to995* @param string $name996*997* @throws Exception998*999* @return bool true on success, false if address already used or invalid in some way1000*/1001public function addAddress($address, $name = '')1002{1003return $this->addOrEnqueueAnAddress('to', $address, $name);1004}10051006/**1007* Add a "CC" address.1008*1009* @param string $address The email address to send to1010* @param string $name1011*1012* @throws Exception1013*1014* @return bool true on success, false if address already used or invalid in some way1015*/1016public function addCC($address, $name = '')1017{1018return $this->addOrEnqueueAnAddress('cc', $address, $name);1019}10201021/**1022* Add a "BCC" address.1023*1024* @param string $address The email address to send to1025* @param string $name1026*1027* @throws Exception1028*1029* @return bool true on success, false if address already used or invalid in some way1030*/1031public function addBCC($address, $name = '')1032{1033return $this->addOrEnqueueAnAddress('bcc', $address, $name);1034}10351036/**1037* Add a "Reply-To" address.1038*1039* @param string $address The email address to reply to1040* @param string $name1041*1042* @throws Exception1043*1044* @return bool true on success, false if address already used or invalid in some way1045*/1046public function addReplyTo($address, $name = '')1047{1048return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);1049}10501051/**1052* Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer1053* can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still1054* be modified after calling this function), addition of such addresses is delayed until send().1055* Addresses that have been added already return false, but do not throw exceptions.1056*1057* @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'1058* @param string $address The email address to send, resp. to reply to1059* @param string $name1060*1061* @throws Exception1062*1063* @return bool true on success, false if address already used or invalid in some way1064*/1065protected function addOrEnqueueAnAddress($kind, $address, $name)1066{1067$address = trim($address);1068$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim1069$pos = strrpos($address, '@');1070if (false === $pos) {1071// At-sign is missing.1072$error_message = sprintf(1073'%s (%s): %s',1074$this->lang('invalid_address'),1075$kind,1076$address1077);1078$this->setError($error_message);1079$this->edebug($error_message);1080if ($this->exceptions) {1081throw new Exception($error_message);1082}10831084return false;1085}1086$params = [$kind, $address, $name];1087// Enqueue addresses with IDN until we know the PHPMailer::$CharSet.1088if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) {1089if ('Reply-To' !== $kind) {1090if (!array_key_exists($address, $this->RecipientsQueue)) {1091$this->RecipientsQueue[$address] = $params;10921093return true;1094}1095} elseif (!array_key_exists($address, $this->ReplyToQueue)) {1096$this->ReplyToQueue[$address] = $params;10971098return true;1099}11001101return false;1102}11031104// Immediately add standard addresses without IDN.1105return call_user_func_array([$this, 'addAnAddress'], $params);1106}11071108/**1109* Add an address to one of the recipient arrays or to the ReplyTo array.1110* Addresses that have been added already return false, but do not throw exceptions.1111*1112* @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'1113* @param string $address The email address to send, resp. to reply to1114* @param string $name1115*1116* @throws Exception1117*1118* @return bool true on success, false if address already used or invalid in some way1119*/1120protected function addAnAddress($kind, $address, $name = '')1121{1122if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {1123$error_message = sprintf(1124'%s: %s',1125$this->lang('Invalid recipient kind'),1126$kind1127);1128$this->setError($error_message);1129$this->edebug($error_message);1130if ($this->exceptions) {1131throw new Exception($error_message);1132}11331134return false;1135}1136if (!static::validateAddress($address)) {1137$error_message = sprintf(1138'%s (%s): %s',1139$this->lang('invalid_address'),1140$kind,1141$address1142);1143$this->setError($error_message);1144$this->edebug($error_message);1145if ($this->exceptions) {1146throw new Exception($error_message);1147}11481149return false;1150}1151if ('Reply-To' !== $kind) {1152if (!array_key_exists(strtolower($address), $this->all_recipients)) {1153$this->{$kind}[] = [$address, $name];1154$this->all_recipients[strtolower($address)] = true;11551156return true;1157}1158} elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {1159$this->ReplyTo[strtolower($address)] = [$address, $name];11601161return true;1162}11631164return false;1165}11661167/**1168* Parse and validate a string containing one or more RFC822-style comma-separated email addresses1169* of the form "display name <address>" into an array of name/address pairs.1170* Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.1171* Note that quotes in the name part are removed.1172*1173* @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation1174*1175* @param string $addrstr The address list string1176* @param bool $useimap Whether to use the IMAP extension to parse the list1177*1178* @return array1179*/1180public static function parseAddresses($addrstr, $useimap = true)1181{1182$addresses = [];1183if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {1184//Use this built-in parser if it's available1185$list = imap_rfc822_parse_adrlist($addrstr, '');1186foreach ($list as $address) {1187if (('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress(1188$address->mailbox . '@' . $address->host1189)) {1190$addresses[] = [1191'name' => (property_exists($address, 'personal') ? $address->personal : ''),1192'address' => $address->mailbox . '@' . $address->host,1193];1194}1195}1196} else {1197//Use this simpler parser1198$list = explode(',', $addrstr);1199foreach ($list as $address) {1200$address = trim($address);1201//Is there a separate name part?1202if (strpos($address, '<') === false) {1203//No separate name, just use the whole thing1204if (static::validateAddress($address)) {1205$addresses[] = [1206'name' => '',1207'address' => $address,1208];1209}1210} else {1211list($name, $email) = explode('<', $address);1212$email = trim(str_replace('>', '', $email));1213if (static::validateAddress($email)) {1214$addresses[] = [1215'name' => trim(str_replace(['"', "'"], '', $name)),1216'address' => $email,1217];1218}1219}1220}1221}12221223return $addresses;1224}12251226/**1227* Set the From and FromName properties.1228*1229* @param string $address1230* @param string $name1231* @param bool $auto Whether to also set the Sender address, defaults to true1232*1233* @throws Exception1234*1235* @return bool1236*/1237public function setFrom($address, $name = '', $auto = true)1238{1239$address = trim($address);1240$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim1241// Don't validate now addresses with IDN. Will be done in send().1242$pos = strrpos($address, '@');1243if ((false === $pos)1244|| ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported())1245&& !static::validateAddress($address))1246) {1247$error_message = sprintf(1248'%s (From): %s',1249$this->lang('invalid_address'),1250$address1251);1252$this->setError($error_message);1253$this->edebug($error_message);1254if ($this->exceptions) {1255throw new Exception($error_message);1256}12571258return false;1259}1260$this->From = $address;1261$this->FromName = $name;1262if ($auto && empty($this->Sender)) {1263$this->Sender = $address;1264}12651266return true;1267}12681269/**1270* Return the Message-ID header of the last email.1271* Technically this is the value from the last time the headers were created,1272* but it's also the message ID of the last sent message except in1273* pathological cases.1274*1275* @return string1276*/1277public function getLastMessageID()1278{1279return $this->lastMessageID;1280}12811282/**1283* Check that a string looks like an email address.1284* Validation patterns supported:1285* * `auto` Pick best pattern automatically;1286* * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0;1287* * `pcre` Use old PCRE implementation;1288* * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;1289* * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.1290* * `noregex` Don't use a regex: super fast, really dumb.1291* Alternatively you may pass in a callable to inject your own validator, for example:1292*1293* ```php1294* PHPMailer::validateAddress('[email protected]', function($address) {1295* return (strpos($address, '@') !== false);1296* });1297* ```1298*1299* You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.1300*1301* @param string $address The email address to check1302* @param string|callable $patternselect Which pattern to use1303*1304* @return bool1305*/1306public static function validateAddress($address, $patternselect = null)1307{1308if (null === $patternselect) {1309$patternselect = static::$validator;1310}1311if (is_callable($patternselect)) {1312return call_user_func($patternselect, $address);1313}1314//Reject line breaks in addresses; it's valid RFC5322, but not RFC53211315if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) {1316return false;1317}1318switch ($patternselect) {1319case 'pcre': //Kept for BC1320case 'pcre8':1321/*1322* A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL1323* is based.1324* In addition to the addresses allowed by filter_var, also permits:1325* * dotless domains: `a@b`1326* * comments: `1234 @ local(blah) .machine .example`1327* * quoted elements: `'"test blah"@example.org'`1328* * numeric TLDs: `[email protected]`1329* * unbracketed IPv4 literals: `[email protected]`1330* * IPv6 literals: 'first.last@[IPv6:a1::]'1331* Not all of these will necessarily work for sending!1332*1333* @see http://squiloople.com/2009/12/20/email-address-validation/1334* @copyright 2009-2010 Michael Rushton1335* Feel free to use and redistribute this code. But please keep this copyright notice.1336*/1337return (bool) preg_match(1338'/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .1339'((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .1340'(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .1341'([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .1342'(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .1343'(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .1344'|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .1345'|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .1346'|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',1347$address1348);1349case 'html5':1350/*1351* This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.1352*1353* @see https://html.spec.whatwg.org/#e-mail-state-(type=email)1354*/1355return (bool) preg_match(1356'/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .1357'[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',1358$address1359);1360case 'php':1361default:1362return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;1363}1364}13651366/**1367* Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the1368* `intl` and `mbstring` PHP extensions.1369*1370* @return bool `true` if required functions for IDN support are present1371*/1372public static function idnSupported()1373{1374return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding');1375}13761377/**1378* Converts IDN in given email address to its ASCII form, also known as punycode, if possible.1379* Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.1380* This function silently returns unmodified address if:1381* - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)1382* - Conversion to punycode is impossible (e.g. required PHP functions are not available)1383* or fails for any reason (e.g. domain contains characters not allowed in an IDN).1384*1385* @see PHPMailer::$CharSet1386*1387* @param string $address The email address to convert1388*1389* @return string The encoded address in ASCII form1390*/1391public function punyencodeAddress($address)1392{1393// Verify we have required functions, CharSet, and at-sign.1394$pos = strrpos($address, '@');1395if (!empty($this->CharSet) &&1396false !== $pos &&1397static::idnSupported()1398) {1399$domain = substr($address, ++$pos);1400// Verify CharSet string is a valid one, and domain properly encoded in this CharSet.1401if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) {1402$domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);1403//Ignore IDE complaints about this line - method signature changed in PHP 5.41404$errorcode = 0;1405if (defined('INTL_IDNA_VARIANT_UTS46')) {1406$punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46);1407} elseif (defined('INTL_IDNA_VARIANT_2003')) {1408$punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003);1409} else {1410$punycode = idn_to_ascii($domain, $errorcode);1411}1412if (false !== $punycode) {1413return substr($address, 0, $pos) . $punycode;1414}1415}1416}14171418return $address;1419}14201421/**1422* Create a message and send it.1423* Uses the sending method specified by $Mailer.1424*1425* @throws Exception1426*1427* @return bool false on error - See the ErrorInfo property for details of the error1428*/1429public function send()1430{1431try {1432if (!$this->preSend()) {1433return false;1434}14351436return $this->postSend();1437} catch (Exception $exc) {1438$this->mailHeader = '';1439$this->setError($exc->getMessage());1440if ($this->exceptions) {1441throw $exc;1442}14431444return false;1445}1446}14471448/**1449* Prepare a message for sending.1450*1451* @throws Exception1452*1453* @return bool1454*/1455public function preSend()1456{1457if ('smtp' === $this->Mailer1458|| ('mail' === $this->Mailer && stripos(PHP_OS, 'WIN') === 0)1459) {1460//SMTP mandates RFC-compliant line endings1461//and it's also used with mail() on Windows1462static::setLE(self::CRLF);1463} else {1464//Maintain backward compatibility with legacy Linux command line mailers1465static::setLE(PHP_EOL);1466}1467//Check for buggy PHP versions that add a header with an incorrect line break1468if ('mail' === $this->Mailer1469&& ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017)1470|| (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103))1471&& ini_get('mail.add_x_header') === '1'1472&& stripos(PHP_OS, 'WIN') === 01473) {1474trigger_error(1475'Your version of PHP is affected by a bug that may result in corrupted messages.' .1476' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' .1477' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',1478E_USER_WARNING1479);1480}14811482try {1483$this->error_count = 0; // Reset errors1484$this->mailHeader = '';14851486// Dequeue recipient and Reply-To addresses with IDN1487foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {1488$params[1] = $this->punyencodeAddress($params[1]);1489call_user_func_array([$this, 'addAnAddress'], $params);1490}1491if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {1492throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);1493}14941495// Validate From, Sender, and ConfirmReadingTo addresses1496foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {1497$this->$address_kind = trim($this->$address_kind);1498if (empty($this->$address_kind)) {1499continue;1500}1501$this->$address_kind = $this->punyencodeAddress($this->$address_kind);1502if (!static::validateAddress($this->$address_kind)) {1503$error_message = sprintf(1504'%s (%s): %s',1505$this->lang('invalid_address'),1506$address_kind,1507$this->$address_kind1508);1509$this->setError($error_message);1510$this->edebug($error_message);1511if ($this->exceptions) {1512throw new Exception($error_message);1513}15141515return false;1516}1517}15181519// Set whether the message is multipart/alternative1520if ($this->alternativeExists()) {1521$this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;1522}15231524$this->setMessageType();1525// Refuse to send an empty message unless we are specifically allowing it1526if (!$this->AllowEmpty && empty($this->Body)) {1527throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);1528}15291530//Trim subject consistently1531$this->Subject = trim($this->Subject);1532// Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)1533$this->MIMEHeader = '';1534$this->MIMEBody = $this->createBody();1535// createBody may have added some headers, so retain them1536$tempheaders = $this->MIMEHeader;1537$this->MIMEHeader = $this->createHeader();1538$this->MIMEHeader .= $tempheaders;15391540// To capture the complete message when using mail(), create1541// an extra header list which createHeader() doesn't fold in1542if ('mail' === $this->Mailer) {1543if (count($this->to) > 0) {1544$this->mailHeader .= $this->addrAppend('To', $this->to);1545} else {1546$this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');1547}1548$this->mailHeader .= $this->headerLine(1549'Subject',1550$this->encodeHeader($this->secureHeader($this->Subject))1551);1552}15531554// Sign with DKIM if enabled1555if (!empty($this->DKIM_domain)1556&& !empty($this->DKIM_selector)1557&& (!empty($this->DKIM_private_string)1558|| (!empty($this->DKIM_private)1559&& static::isPermittedPath($this->DKIM_private)1560&& file_exists($this->DKIM_private)1561)1562)1563) {1564$header_dkim = $this->DKIM_Add(1565$this->MIMEHeader . $this->mailHeader,1566$this->encodeHeader($this->secureHeader($this->Subject)),1567$this->MIMEBody1568);1569$this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE .1570static::normalizeBreaks($header_dkim) . static::$LE;1571}15721573return true;1574} catch (Exception $exc) {1575$this->setError($exc->getMessage());1576if ($this->exceptions) {1577throw $exc;1578}15791580return false;1581}1582}15831584/**1585* Actually send a message via the selected mechanism.1586*1587* @throws Exception1588*1589* @return bool1590*/1591public function postSend()1592{1593try {1594// Choose the mailer and send through it1595switch ($this->Mailer) {1596case 'sendmail':1597case 'qmail':1598return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);1599case 'smtp':1600return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);1601case 'mail':1602return $this->mailSend($this->MIMEHeader, $this->MIMEBody);1603default:1604$sendMethod = $this->Mailer . 'Send';1605if (method_exists($this, $sendMethod)) {1606return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);1607}16081609return $this->mailSend($this->MIMEHeader, $this->MIMEBody);1610}1611} catch (Exception $exc) {1612if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true) {1613$this->smtp->reset();1614}1615$this->setError($exc->getMessage());1616$this->edebug($exc->getMessage());1617if ($this->exceptions) {1618throw $exc;1619}1620}16211622return false;1623}16241625/**1626* Send mail using the $Sendmail program.1627*1628* @see PHPMailer::$Sendmail1629*1630* @param string $header The message headers1631* @param string $body The message body1632*1633* @throws Exception1634*1635* @return bool1636*/1637protected function sendmailSend($header, $body)1638{1639$header = static::stripTrailingWSP($header) . static::$LE . static::$LE;16401641// CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.1642if (!empty($this->Sender) && self::isShellSafe($this->Sender)) {1643if ('qmail' === $this->Mailer) {1644$sendmailFmt = '%s -f%s';1645} else {1646$sendmailFmt = '%s -oi -f%s -t';1647}1648} elseif ('qmail' === $this->Mailer) {1649$sendmailFmt = '%s';1650} else {1651$sendmailFmt = '%s -oi -t';1652}16531654$sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);16551656if ($this->SingleTo) {1657foreach ($this->SingleToArray as $toAddr) {1658$mail = @popen($sendmail, 'w');1659if (!$mail) {1660throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);1661}1662fwrite($mail, 'To: ' . $toAddr . "\n");1663fwrite($mail, $header);1664fwrite($mail, $body);1665$result = pclose($mail);1666$this->doCallback(1667($result === 0),1668[$toAddr],1669$this->cc,1670$this->bcc,1671$this->Subject,1672$body,1673$this->From,1674[]1675);1676if (0 !== $result) {1677throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);1678}1679}1680} else {1681$mail = @popen($sendmail, 'w');1682if (!$mail) {1683throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);1684}1685fwrite($mail, $header);1686fwrite($mail, $body);1687$result = pclose($mail);1688$this->doCallback(1689($result === 0),1690$this->to,1691$this->cc,1692$this->bcc,1693$this->Subject,1694$body,1695$this->From,1696[]1697);1698if (0 !== $result) {1699throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);1700}1701}17021703return true;1704}17051706/**1707* Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.1708* Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.1709*1710* @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report1711*1712* @param string $string The string to be validated1713*1714* @return bool1715*/1716protected static function isShellSafe($string)1717{1718// Future-proof1719if (escapeshellcmd($string) !== $string1720|| !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])1721) {1722return false;1723}17241725$length = strlen($string);17261727for ($i = 0; $i < $length; ++$i) {1728$c = $string[$i];17291730// All other characters have a special meaning in at least one common shell, including = and +.1731// Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.1732// Note that this does permit non-Latin alphanumeric characters based on the current locale.1733if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {1734return false;1735}1736}17371738return true;1739}17401741/**1742* Check whether a file path is of a permitted type.1743* Used to reject URLs and phar files from functions that access local file paths,1744* such as addAttachment.1745*1746* @param string $path A relative or absolute path to a file1747*1748* @return bool1749*/1750protected static function isPermittedPath($path)1751{1752return !preg_match('#^[a-z]+://#i', $path);1753}17541755/**1756* Check whether a file path is safe, accessible, and readable.1757*1758* @param string $path A relative or absolute path to a file1759*1760* @return bool1761*/1762protected static function fileIsAccessible($path)1763{1764$readable = file_exists($path);1765//If not a UNC path (expected to start with \\), check read permission, see #20691766if (strpos($path, '\\\\') !== 0) {1767$readable = $readable && is_readable($path);1768}1769return static::isPermittedPath($path) && $readable;1770}17711772/**1773* Send mail using the PHP mail() function.1774*1775* @see http://www.php.net/manual/en/book.mail.php1776*1777* @param string $header The message headers1778* @param string $body The message body1779*1780* @throws Exception1781*1782* @return bool1783*/1784protected function mailSend($header, $body)1785{1786$header = static::stripTrailingWSP($header) . static::$LE . static::$LE;17871788$toArr = [];1789foreach ($this->to as $toaddr) {1790$toArr[] = $this->addrFormat($toaddr);1791}1792$to = implode(', ', $toArr);17931794$params = null;1795//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver1796//A space after `-f` is optional, but there is a long history of its presence1797//causing problems, so we don't use one1798//Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html1799//Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html1800//Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html1801//Example problem: https://www.drupal.org/node/10579541802// CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.1803if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {1804$params = sprintf('-f%s', $this->Sender);1805}1806if (!empty($this->Sender) && static::validateAddress($this->Sender)) {1807$old_from = ini_get('sendmail_from');1808ini_set('sendmail_from', $this->Sender);1809}1810$result = false;1811if ($this->SingleTo && count($toArr) > 1) {1812foreach ($toArr as $toAddr) {1813$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);1814$this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);1815}1816} else {1817$result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);1818$this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);1819}1820if (isset($old_from)) {1821ini_set('sendmail_from', $old_from);1822}1823if (!$result) {1824throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);1825}18261827return true;1828}18291830/**1831* Get an instance to use for SMTP operations.1832* Override this function to load your own SMTP implementation,1833* or set one with setSMTPInstance.1834*1835* @return SMTP1836*/1837public function getSMTPInstance()1838{1839if (!is_object($this->smtp)) {1840$this->smtp = new SMTP();1841}18421843return $this->smtp;1844}18451846/**1847* Provide an instance to use for SMTP operations.1848*1849* @return SMTP1850*/1851public function setSMTPInstance(SMTP $smtp)1852{1853$this->smtp = $smtp;18541855return $this->smtp;1856}18571858/**1859* Send mail via SMTP.1860* Returns false if there is a bad MAIL FROM, RCPT, or DATA input.1861*1862* @see PHPMailer::setSMTPInstance() to use a different class.1863*1864* @uses \PHPMailer\PHPMailer\SMTP1865*1866* @param string $header The message headers1867* @param string $body The message body1868*1869* @throws Exception1870*1871* @return bool1872*/1873protected function smtpSend($header, $body)1874{1875$header = static::stripTrailingWSP($header) . static::$LE . static::$LE;1876$bad_rcpt = [];1877if (!$this->smtpConnect($this->SMTPOptions)) {1878throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);1879}1880//Sender already validated in preSend()1881if ('' === $this->Sender) {1882$smtp_from = $this->From;1883} else {1884$smtp_from = $this->Sender;1885}1886if (!$this->smtp->mail($smtp_from)) {1887$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));1888throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);1889}18901891$callbacks = [];1892// Attempt to send to all recipients1893foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {1894foreach ($togroup as $to) {1895if (!$this->smtp->recipient($to[0], $this->dsn)) {1896$error = $this->smtp->getError();1897$bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];1898$isSent = false;1899} else {1900$isSent = true;1901}19021903$callbacks[] = ['issent'=>$isSent, 'to'=>$to[0]];1904}1905}19061907// Only send the DATA command if we have viable recipients1908if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {1909throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);1910}19111912$smtp_transaction_id = $this->smtp->getLastTransactionID();19131914if ($this->SMTPKeepAlive) {1915$this->smtp->reset();1916} else {1917$this->smtp->quit();1918$this->smtp->close();1919}19201921foreach ($callbacks as $cb) {1922$this->doCallback(1923$cb['issent'],1924[$cb['to']],1925[],1926[],1927$this->Subject,1928$body,1929$this->From,1930['smtp_transaction_id' => $smtp_transaction_id]1931);1932}19331934//Create error message for any bad addresses1935if (count($bad_rcpt) > 0) {1936$errstr = '';1937foreach ($bad_rcpt as $bad) {1938$errstr .= $bad['to'] . ': ' . $bad['error'];1939}1940throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);1941}19421943return true;1944}19451946/**1947* Initiate a connection to an SMTP server.1948* Returns false if the operation failed.1949*1950* @param array $options An array of options compatible with stream_context_create()1951*1952* @throws Exception1953*1954* @uses \PHPMailer\PHPMailer\SMTP1955*1956* @return bool1957*/1958public function smtpConnect($options = null)1959{1960if (null === $this->smtp) {1961$this->smtp = $this->getSMTPInstance();1962}19631964//If no options are provided, use whatever is set in the instance1965if (null === $options) {1966$options = $this->SMTPOptions;1967}19681969// Already connected?1970if ($this->smtp->connected()) {1971return true;1972}19731974$this->smtp->setTimeout($this->Timeout);1975$this->smtp->setDebugLevel($this->SMTPDebug);1976$this->smtp->setDebugOutput($this->Debugoutput);1977$this->smtp->setVerp($this->do_verp);1978$hosts = explode(';', $this->Host);1979$lastexception = null;19801981foreach ($hosts as $hostentry) {1982$hostinfo = [];1983if (!preg_match(1984'/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/',1985trim($hostentry),1986$hostinfo1987)) {1988$this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));1989// Not a valid host entry1990continue;1991}1992// $hostinfo[1]: optional ssl or tls prefix1993// $hostinfo[2]: the hostname1994// $hostinfo[3]: optional port number1995// The host string prefix can temporarily override the current setting for SMTPSecure1996// If it's not specified, the default value is used19971998//Check the host name is a valid name or IP address before trying to use it1999if (!static::isValidHost($hostinfo[2])) {2000$this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);2001continue;2002}2003$prefix = '';2004$secure = $this->SMTPSecure;2005$tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure);2006if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) {2007$prefix = 'ssl://';2008$tls = false; // Can't have SSL and TLS at the same time2009$secure = static::ENCRYPTION_SMTPS;2010} elseif ('tls' === $hostinfo[1]) {2011$tls = true;2012// tls doesn't use a prefix2013$secure = static::ENCRYPTION_STARTTLS;2014}2015//Do we need the OpenSSL extension?2016$sslext = defined('OPENSSL_ALGO_SHA256');2017if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {2018//Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled2019if (!$sslext) {2020throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);2021}2022}2023$host = $hostinfo[2];2024$port = $this->Port;2025if (2026array_key_exists(3, $hostinfo) &&2027is_numeric($hostinfo[3]) &&2028$hostinfo[3] > 0 &&2029$hostinfo[3] < 655362030) {2031$port = (int) $hostinfo[3];2032}2033if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {2034try {2035if ($this->Helo) {2036$hello = $this->Helo;2037} else {2038$hello = $this->serverHostname();2039}2040$this->smtp->hello($hello);2041//Automatically enable TLS encryption if:2042// * it's not disabled2043// * we have openssl extension2044// * we are not already using SSL2045// * the server offers STARTTLS2046if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) {2047$tls = true;2048}2049if ($tls) {2050if (!$this->smtp->startTLS()) {2051throw new Exception($this->lang('connect_host'));2052}2053// We must resend EHLO after TLS negotiation2054$this->smtp->hello($hello);2055}2056if ($this->SMTPAuth && !$this->smtp->authenticate(2057$this->Username,2058$this->Password,2059$this->AuthType,2060$this->oauth2061)) {2062throw new Exception($this->lang('authenticate'));2063}20642065return true;2066} catch (Exception $exc) {2067$lastexception = $exc;2068$this->edebug($exc->getMessage());2069// We must have connected, but then failed TLS or Auth, so close connection nicely2070$this->smtp->quit();2071}2072}2073}2074// If we get here, all connection attempts have failed, so close connection hard2075$this->smtp->close();2076// As we've caught all exceptions, just report whatever the last one was2077if ($this->exceptions && null !== $lastexception) {2078throw $lastexception;2079}20802081return false;2082}20832084/**2085* Close the active SMTP session if one exists.2086*/2087public function smtpClose()2088{2089if ((null !== $this->smtp) && $this->smtp->connected()) {2090$this->smtp->quit();2091$this->smtp->close();2092}2093}20942095/**2096* Set the language for error messages.2097* Returns false if it cannot load the language file.2098* The default language is English.2099*2100* @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")2101* @param string $lang_path Path to the language file directory, with trailing separator (slash)2102*2103* @return bool2104*/2105public function setLanguage($langcode = 'en', $lang_path = '')2106{2107// Backwards compatibility for renamed language codes2108$renamed_langcodes = [2109'br' => 'pt_br',2110'cz' => 'cs',2111'dk' => 'da',2112'no' => 'nb',2113'se' => 'sv',2114'rs' => 'sr',2115'tg' => 'tl',2116'am' => 'hy',2117];21182119if (isset($renamed_langcodes[$langcode])) {2120$langcode = $renamed_langcodes[$langcode];2121}21222123// Define full set of translatable strings in English2124$PHPMAILER_LANG = [2125'authenticate' => 'SMTP Error: Could not authenticate.',2126'connect_host' => 'SMTP Error: Could not connect to SMTP host.',2127'data_not_accepted' => 'SMTP Error: data not accepted.',2128'empty_message' => 'Message body empty',2129'encoding' => 'Unknown encoding: ',2130'execute' => 'Could not execute: ',2131'file_access' => 'Could not access file: ',2132'file_open' => 'File Error: Could not open file: ',2133'from_failed' => 'The following From address failed: ',2134'instantiate' => 'Could not instantiate mail function.',2135'invalid_address' => 'Invalid address: ',2136'invalid_hostentry' => 'Invalid hostentry: ',2137'invalid_host' => 'Invalid host: ',2138'mailer_not_supported' => ' mailer is not supported.',2139'provide_address' => 'You must provide at least one recipient email address.',2140'recipients_failed' => 'SMTP Error: The following recipients failed: ',2141'signing' => 'Signing Error: ',2142'smtp_connect_failed' => 'SMTP connect() failed.',2143'smtp_error' => 'SMTP server error: ',2144'variable_set' => 'Cannot set or reset variable: ',2145'extension_missing' => 'Extension missing: ',2146];2147if (empty($lang_path)) {2148// Calculate an absolute path so it can work if CWD is not here2149$lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR;2150}2151//Validate $langcode2152if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {2153$langcode = 'en';2154}2155$foundlang = true;2156$lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';2157// There is no English translation file2158if ('en' !== $langcode) {2159// Make sure language file path is readable2160if (!static::fileIsAccessible($lang_file)) {2161$foundlang = false;2162} else {2163// Overwrite language-specific strings.2164// This way we'll never have missing translation keys.2165$foundlang = include $lang_file;2166}2167}2168$this->language = $PHPMAILER_LANG;21692170return (bool) $foundlang; // Returns false if language not found2171}21722173/**2174* Get the array of strings for the current language.2175*2176* @return array2177*/2178public function getTranslations()2179{2180return $this->language;2181}21822183/**2184* Create recipient headers.2185*2186* @param string $type2187* @param array $addr An array of recipients,2188* where each recipient is a 2-element indexed array with element 0 containing an address2189* and element 1 containing a name, like:2190* [['[email protected]', 'Joe User'], ['[email protected]', 'Zoe User']]2191*2192* @return string2193*/2194public function addrAppend($type, $addr)2195{2196$addresses = [];2197foreach ($addr as $address) {2198$addresses[] = $this->addrFormat($address);2199}22002201return $type . ': ' . implode(', ', $addresses) . static::$LE;2202}22032204/**2205* Format an address for use in a message header.2206*2207* @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like2208* ['[email protected]', 'Joe User']2209*2210* @return string2211*/2212public function addrFormat($addr)2213{2214if (empty($addr[1])) { // No name provided2215return $this->secureHeader($addr[0]);2216}22172218return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') .2219' <' . $this->secureHeader($addr[0]) . '>';2220}22212222/**2223* Word-wrap message.2224* For use with mailers that do not automatically perform wrapping2225* and for quoted-printable encoded messages.2226* Original written by philippe.2227*2228* @param string $message The message to wrap2229* @param int $length The line length to wrap to2230* @param bool $qp_mode Whether to run in Quoted-Printable mode2231*2232* @return string2233*/2234public function wrapText($message, $length, $qp_mode = false)2235{2236if ($qp_mode) {2237$soft_break = sprintf(' =%s', static::$LE);2238} else {2239$soft_break = static::$LE;2240}2241// If utf-8 encoding is used, we will need to make sure we don't2242// split multibyte characters when we wrap2243$is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);2244$lelen = strlen(static::$LE);2245$crlflen = strlen(static::$LE);22462247$message = static::normalizeBreaks($message);2248//Remove a trailing line break2249if (substr($message, -$lelen) === static::$LE) {2250$message = substr($message, 0, -$lelen);2251}22522253//Split message into lines2254$lines = explode(static::$LE, $message);2255//Message will be rebuilt in here2256$message = '';2257foreach ($lines as $line) {2258$words = explode(' ', $line);2259$buf = '';2260$firstword = true;2261foreach ($words as $word) {2262if ($qp_mode && (strlen($word) > $length)) {2263$space_left = $length - strlen($buf) - $crlflen;2264if (!$firstword) {2265if ($space_left > 20) {2266$len = $space_left;2267if ($is_utf8) {2268$len = $this->utf8CharBoundary($word, $len);2269} elseif ('=' === substr($word, $len - 1, 1)) {2270--$len;2271} elseif ('=' === substr($word, $len - 2, 1)) {2272$len -= 2;2273}2274$part = substr($word, 0, $len);2275$word = substr($word, $len);2276$buf .= ' ' . $part;2277$message .= $buf . sprintf('=%s', static::$LE);2278} else {2279$message .= $buf . $soft_break;2280}2281$buf = '';2282}2283while ($word !== '') {2284if ($length <= 0) {2285break;2286}2287$len = $length;2288if ($is_utf8) {2289$len = $this->utf8CharBoundary($word, $len);2290} elseif ('=' === substr($word, $len - 1, 1)) {2291--$len;2292} elseif ('=' === substr($word, $len - 2, 1)) {2293$len -= 2;2294}2295$part = substr($word, 0, $len);2296$word = (string) substr($word, $len);22972298if ($word !== '') {2299$message .= $part . sprintf('=%s', static::$LE);2300} else {2301$buf = $part;2302}2303}2304} else {2305$buf_o = $buf;2306if (!$firstword) {2307$buf .= ' ';2308}2309$buf .= $word;23102311if ('' !== $buf_o && strlen($buf) > $length) {2312$message .= $buf_o . $soft_break;2313$buf = $word;2314}2315}2316$firstword = false;2317}2318$message .= $buf . static::$LE;2319}23202321return $message;2322}23232324/**2325* Find the last character boundary prior to $maxLength in a utf-82326* quoted-printable encoded string.2327* Original written by Colin Brown.2328*2329* @param string $encodedText utf-8 QP text2330* @param int $maxLength Find the last character boundary prior to this length2331*2332* @return int2333*/2334public function utf8CharBoundary($encodedText, $maxLength)2335{2336$foundSplitPos = false;2337$lookBack = 3;2338while (!$foundSplitPos) {2339$lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);2340$encodedCharPos = strpos($lastChunk, '=');2341if (false !== $encodedCharPos) {2342// Found start of encoded character byte within $lookBack block.2343// Check the encoded byte value (the 2 chars after the '=')2344$hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);2345$dec = hexdec($hex);2346if ($dec < 128) {2347// Single byte character.2348// If the encoded char was found at pos 0, it will fit2349// otherwise reduce maxLength to start of the encoded char2350if ($encodedCharPos > 0) {2351$maxLength -= $lookBack - $encodedCharPos;2352}2353$foundSplitPos = true;2354} elseif ($dec >= 192) {2355// First byte of a multi byte character2356// Reduce maxLength to split at start of character2357$maxLength -= $lookBack - $encodedCharPos;2358$foundSplitPos = true;2359} elseif ($dec < 192) {2360// Middle byte of a multi byte character, look further back2361$lookBack += 3;2362}2363} else {2364// No encoded character found2365$foundSplitPos = true;2366}2367}23682369return $maxLength;2370}23712372/**2373* Apply word wrapping to the message body.2374* Wraps the message body to the number of chars set in the WordWrap property.2375* You should only do this to plain-text bodies as wrapping HTML tags may break them.2376* This is called automatically by createBody(), so you don't need to call it yourself.2377*/2378public function setWordWrap()2379{2380if ($this->WordWrap < 1) {2381return;2382}23832384switch ($this->message_type) {2385case 'alt':2386case 'alt_inline':2387case 'alt_attach':2388case 'alt_inline_attach':2389$this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);2390break;2391default:2392$this->Body = $this->wrapText($this->Body, $this->WordWrap);2393break;2394}2395}23962397/**2398* Assemble message headers.2399*2400* @return string The assembled headers2401*/2402public function createHeader()2403{2404$result = '';24052406$result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);24072408// The To header is created automatically by mail(), so needs to be omitted here2409if ('mail' !== $this->Mailer) {2410if ($this->SingleTo) {2411foreach ($this->to as $toaddr) {2412$this->SingleToArray[] = $this->addrFormat($toaddr);2413}2414} elseif (count($this->to) > 0) {2415$result .= $this->addrAppend('To', $this->to);2416} elseif (count($this->cc) === 0) {2417$result .= $this->headerLine('To', 'undisclosed-recipients:;');2418}2419}2420$result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);24212422// sendmail and mail() extract Cc from the header before sending2423if (count($this->cc) > 0) {2424$result .= $this->addrAppend('Cc', $this->cc);2425}24262427// sendmail and mail() extract Bcc from the header before sending2428if ((2429'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer2430)2431&& count($this->bcc) > 02432) {2433$result .= $this->addrAppend('Bcc', $this->bcc);2434}24352436if (count($this->ReplyTo) > 0) {2437$result .= $this->addrAppend('Reply-To', $this->ReplyTo);2438}24392440// mail() sets the subject itself2441if ('mail' !== $this->Mailer) {2442$result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));2443}24442445// Only allow a custom message ID if it conforms to RFC 5322 section 3.6.42446// https://tools.ietf.org/html/rfc5322#section-3.6.42447if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) {2448$this->lastMessageID = $this->MessageID;2449} else {2450$this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());2451}2452$result .= $this->headerLine('Message-ID', $this->lastMessageID);2453if (null !== $this->Priority) {2454$result .= $this->headerLine('X-Priority', $this->Priority);2455}2456if ('' === $this->XMailer) {2457$result .= $this->headerLine(2458'X-Mailer',2459'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)'2460);2461} else {2462$myXmailer = trim($this->XMailer);2463if ($myXmailer) {2464$result .= $this->headerLine('X-Mailer', $myXmailer);2465}2466}24672468if ('' !== $this->ConfirmReadingTo) {2469$result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');2470}24712472// Add custom headers2473foreach ($this->CustomHeader as $header) {2474$result .= $this->headerLine(2475trim($header[0]),2476$this->encodeHeader(trim($header[1]))2477);2478}2479if (!$this->sign_key_file) {2480$result .= $this->headerLine('MIME-Version', '1.0');2481$result .= $this->getMailMIME();2482}24832484return $result;2485}24862487/**2488* Get the message MIME type headers.2489*2490* @return string2491*/2492public function getMailMIME()2493{2494$result = '';2495$ismultipart = true;2496switch ($this->message_type) {2497case 'inline':2498$result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');2499$result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');2500break;2501case 'attach':2502case 'inline_attach':2503case 'alt_attach':2504case 'alt_inline_attach':2505$result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';');2506$result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');2507break;2508case 'alt':2509case 'alt_inline':2510$result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');2511$result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');2512break;2513default:2514// Catches case 'plain': and case '':2515$result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);2516$ismultipart = false;2517break;2518}2519// RFC1341 part 5 says 7bit is assumed if not specified2520if (static::ENCODING_7BIT !== $this->Encoding) {2521// RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE2522if ($ismultipart) {2523if (static::ENCODING_8BIT === $this->Encoding) {2524$result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);2525}2526// The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible2527} else {2528$result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);2529}2530}25312532if ('mail' !== $this->Mailer) {2533// $result .= static::$LE;2534}25352536return $result;2537}25382539/**2540* Returns the whole MIME message.2541* Includes complete headers and body.2542* Only valid post preSend().2543*2544* @see PHPMailer::preSend()2545*2546* @return string2547*/2548public function getSentMIMEMessage()2549{2550return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) .2551static::$LE . static::$LE . $this->MIMEBody;2552}25532554/**2555* Create a unique ID to use for boundaries.2556*2557* @return string2558*/2559protected function generateId()2560{2561$len = 32; //32 bytes = 256 bits2562$bytes = '';2563if (function_exists('random_bytes')) {2564try {2565$bytes = random_bytes($len);2566} catch (\Exception $e) {2567//Do nothing2568}2569} elseif (function_exists('openssl_random_pseudo_bytes')) {2570/** @noinspection CryptographicallySecureRandomnessInspection */2571$bytes = openssl_random_pseudo_bytes($len);2572}2573if ($bytes === '') {2574//We failed to produce a proper random string, so make do.2575//Use a hash to force the length to the same as the other methods2576$bytes = hash('sha256', uniqid((string) mt_rand(), true), true);2577}25782579//We don't care about messing up base64 format here, just want a random string2580return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));2581}25822583/**2584* Assemble the message body.2585* Returns an empty string on failure.2586*2587* @throws Exception2588*2589* @return string The assembled message body2590*/2591public function createBody()2592{2593$body = '';2594//Create unique IDs and preset boundaries2595$this->uniqueid = $this->generateId();2596$this->boundary[1] = 'b1_' . $this->uniqueid;2597$this->boundary[2] = 'b2_' . $this->uniqueid;2598$this->boundary[3] = 'b3_' . $this->uniqueid;25992600if ($this->sign_key_file) {2601$body .= $this->getMailMIME() . static::$LE;2602}26032604$this->setWordWrap();26052606$bodyEncoding = $this->Encoding;2607$bodyCharSet = $this->CharSet;2608//Can we do a 7-bit downgrade?2609if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {2610$bodyEncoding = static::ENCODING_7BIT;2611//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit2612$bodyCharSet = static::CHARSET_ASCII;2613}2614//If lines are too long, and we're not already using an encoding that will shorten them,2615//change to quoted-printable transfer encoding for the body part only2616if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) {2617$bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;2618}26192620$altBodyEncoding = $this->Encoding;2621$altBodyCharSet = $this->CharSet;2622//Can we do a 7-bit downgrade?2623if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) {2624$altBodyEncoding = static::ENCODING_7BIT;2625//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit2626$altBodyCharSet = static::CHARSET_ASCII;2627}2628//If lines are too long, and we're not already using an encoding that will shorten them,2629//change to quoted-printable transfer encoding for the alt body part only2630if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {2631$altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;2632}2633//Use this as a preamble in all multipart message types2634$mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE;2635switch ($this->message_type) {2636case 'inline':2637$body .= $mimepre;2638$body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);2639$body .= $this->encodeString($this->Body, $bodyEncoding);2640$body .= static::$LE;2641$body .= $this->attachAll('inline', $this->boundary[1]);2642break;2643case 'attach':2644$body .= $mimepre;2645$body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);2646$body .= $this->encodeString($this->Body, $bodyEncoding);2647$body .= static::$LE;2648$body .= $this->attachAll('attachment', $this->boundary[1]);2649break;2650case 'inline_attach':2651$body .= $mimepre;2652$body .= $this->textLine('--' . $this->boundary[1]);2653$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');2654$body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');2655$body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');2656$body .= static::$LE;2657$body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);2658$body .= $this->encodeString($this->Body, $bodyEncoding);2659$body .= static::$LE;2660$body .= $this->attachAll('inline', $this->boundary[2]);2661$body .= static::$LE;2662$body .= $this->attachAll('attachment', $this->boundary[1]);2663break;2664case 'alt':2665$body .= $mimepre;2666$body .= $this->getBoundary(2667$this->boundary[1],2668$altBodyCharSet,2669static::CONTENT_TYPE_PLAINTEXT,2670$altBodyEncoding2671);2672$body .= $this->encodeString($this->AltBody, $altBodyEncoding);2673$body .= static::$LE;2674$body .= $this->getBoundary(2675$this->boundary[1],2676$bodyCharSet,2677static::CONTENT_TYPE_TEXT_HTML,2678$bodyEncoding2679);2680$body .= $this->encodeString($this->Body, $bodyEncoding);2681$body .= static::$LE;2682if (!empty($this->Ical)) {2683$method = static::ICAL_METHOD_REQUEST;2684foreach (static::$IcalMethods as $imethod) {2685if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {2686$method = $imethod;2687break;2688}2689}2690$body .= $this->getBoundary(2691$this->boundary[1],2692'',2693static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,2694''2695);2696$body .= $this->encodeString($this->Ical, $this->Encoding);2697$body .= static::$LE;2698}2699$body .= $this->endBoundary($this->boundary[1]);2700break;2701case 'alt_inline':2702$body .= $mimepre;2703$body .= $this->getBoundary(2704$this->boundary[1],2705$altBodyCharSet,2706static::CONTENT_TYPE_PLAINTEXT,2707$altBodyEncoding2708);2709$body .= $this->encodeString($this->AltBody, $altBodyEncoding);2710$body .= static::$LE;2711$body .= $this->textLine('--' . $this->boundary[1]);2712$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');2713$body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');2714$body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');2715$body .= static::$LE;2716$body .= $this->getBoundary(2717$this->boundary[2],2718$bodyCharSet,2719static::CONTENT_TYPE_TEXT_HTML,2720$bodyEncoding2721);2722$body .= $this->encodeString($this->Body, $bodyEncoding);2723$body .= static::$LE;2724$body .= $this->attachAll('inline', $this->boundary[2]);2725$body .= static::$LE;2726$body .= $this->endBoundary($this->boundary[1]);2727break;2728case 'alt_attach':2729$body .= $mimepre;2730$body .= $this->textLine('--' . $this->boundary[1]);2731$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');2732$body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');2733$body .= static::$LE;2734$body .= $this->getBoundary(2735$this->boundary[2],2736$altBodyCharSet,2737static::CONTENT_TYPE_PLAINTEXT,2738$altBodyEncoding2739);2740$body .= $this->encodeString($this->AltBody, $altBodyEncoding);2741$body .= static::$LE;2742$body .= $this->getBoundary(2743$this->boundary[2],2744$bodyCharSet,2745static::CONTENT_TYPE_TEXT_HTML,2746$bodyEncoding2747);2748$body .= $this->encodeString($this->Body, $bodyEncoding);2749$body .= static::$LE;2750if (!empty($this->Ical)) {2751$method = static::ICAL_METHOD_REQUEST;2752foreach (static::$IcalMethods as $imethod) {2753if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {2754$method = $imethod;2755break;2756}2757}2758$body .= $this->getBoundary(2759$this->boundary[2],2760'',2761static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,2762''2763);2764$body .= $this->encodeString($this->Ical, $this->Encoding);2765}2766$body .= $this->endBoundary($this->boundary[2]);2767$body .= static::$LE;2768$body .= $this->attachAll('attachment', $this->boundary[1]);2769break;2770case 'alt_inline_attach':2771$body .= $mimepre;2772$body .= $this->textLine('--' . $this->boundary[1]);2773$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');2774$body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');2775$body .= static::$LE;2776$body .= $this->getBoundary(2777$this->boundary[2],2778$altBodyCharSet,2779static::CONTENT_TYPE_PLAINTEXT,2780$altBodyEncoding2781);2782$body .= $this->encodeString($this->AltBody, $altBodyEncoding);2783$body .= static::$LE;2784$body .= $this->textLine('--' . $this->boundary[2]);2785$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');2786$body .= $this->textLine(' boundary="' . $this->boundary[3] . '";');2787$body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');2788$body .= static::$LE;2789$body .= $this->getBoundary(2790$this->boundary[3],2791$bodyCharSet,2792static::CONTENT_TYPE_TEXT_HTML,2793$bodyEncoding2794);2795$body .= $this->encodeString($this->Body, $bodyEncoding);2796$body .= static::$LE;2797$body .= $this->attachAll('inline', $this->boundary[3]);2798$body .= static::$LE;2799$body .= $this->endBoundary($this->boundary[2]);2800$body .= static::$LE;2801$body .= $this->attachAll('attachment', $this->boundary[1]);2802break;2803default:2804// Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types2805//Reset the `Encoding` property in case we changed it for line length reasons2806$this->Encoding = $bodyEncoding;2807$body .= $this->encodeString($this->Body, $this->Encoding);2808break;2809}28102811if ($this->isError()) {2812$body = '';2813if ($this->exceptions) {2814throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);2815}2816} elseif ($this->sign_key_file) {2817try {2818if (!defined('PKCS7_TEXT')) {2819throw new Exception($this->lang('extension_missing') . 'openssl');2820}28212822$file = tempnam(sys_get_temp_dir(), 'srcsign');2823$signed = tempnam(sys_get_temp_dir(), 'mailsign');2824file_put_contents($file, $body);28252826//Workaround for PHP bug https://bugs.php.net/bug.php?id=691972827if (empty($this->sign_extracerts_file)) {2828$sign = @openssl_pkcs7_sign(2829$file,2830$signed,2831'file://' . realpath($this->sign_cert_file),2832['file://' . realpath($this->sign_key_file), $this->sign_key_pass],2833[]2834);2835} else {2836$sign = @openssl_pkcs7_sign(2837$file,2838$signed,2839'file://' . realpath($this->sign_cert_file),2840['file://' . realpath($this->sign_key_file), $this->sign_key_pass],2841[],2842PKCS7_DETACHED,2843$this->sign_extracerts_file2844);2845}28462847@unlink($file);2848if ($sign) {2849$body = file_get_contents($signed);2850@unlink($signed);2851//The message returned by openssl contains both headers and body, so need to split them up2852$parts = explode("\n\n", $body, 2);2853$this->MIMEHeader .= $parts[0] . static::$LE . static::$LE;2854$body = $parts[1];2855} else {2856@unlink($signed);2857throw new Exception($this->lang('signing') . openssl_error_string());2858}2859} catch (Exception $exc) {2860$body = '';2861if ($this->exceptions) {2862throw $exc;2863}2864}2865}28662867return $body;2868}28692870/**2871* Return the start of a message boundary.2872*2873* @param string $boundary2874* @param string $charSet2875* @param string $contentType2876* @param string $encoding2877*2878* @return string2879*/2880protected function getBoundary($boundary, $charSet, $contentType, $encoding)2881{2882$result = '';2883if ('' === $charSet) {2884$charSet = $this->CharSet;2885}2886if ('' === $contentType) {2887$contentType = $this->ContentType;2888}2889if ('' === $encoding) {2890$encoding = $this->Encoding;2891}2892$result .= $this->textLine('--' . $boundary);2893$result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);2894$result .= static::$LE;2895// RFC1341 part 5 says 7bit is assumed if not specified2896if (static::ENCODING_7BIT !== $encoding) {2897$result .= $this->headerLine('Content-Transfer-Encoding', $encoding);2898}2899$result .= static::$LE;29002901return $result;2902}29032904/**2905* Return the end of a message boundary.2906*2907* @param string $boundary2908*2909* @return string2910*/2911protected function endBoundary($boundary)2912{2913return static::$LE . '--' . $boundary . '--' . static::$LE;2914}29152916/**2917* Set the message type.2918* PHPMailer only supports some preset message types, not arbitrary MIME structures.2919*/2920protected function setMessageType()2921{2922$type = [];2923if ($this->alternativeExists()) {2924$type[] = 'alt';2925}2926if ($this->inlineImageExists()) {2927$type[] = 'inline';2928}2929if ($this->attachmentExists()) {2930$type[] = 'attach';2931}2932$this->message_type = implode('_', $type);2933if ('' === $this->message_type) {2934//The 'plain' message_type refers to the message having a single body element, not that it is plain-text2935$this->message_type = 'plain';2936}2937}29382939/**2940* Format a header line.2941*2942* @param string $name2943* @param string|int $value2944*2945* @return string2946*/2947public function headerLine($name, $value)2948{2949return $name . ': ' . $value . static::$LE;2950}29512952/**2953* Return a formatted mail line.2954*2955* @param string $value2956*2957* @return string2958*/2959public function textLine($value)2960{2961return $value . static::$LE;2962}29632964/**2965* Add an attachment from a path on the filesystem.2966* Never use a user-supplied path to a file!2967* Returns false if the file could not be found or read.2968* Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.2969* If you need to do that, fetch the resource yourself and pass it in via a local file or string.2970*2971* @param string $path Path to the attachment2972* @param string $name Overrides the attachment name2973* @param string $encoding File encoding (see $Encoding)2974* @param string $type MIME type, e.g. `image/jpeg`; determined automatically from $path if not specified2975* @param string $disposition Disposition to use2976*2977* @throws Exception2978*2979* @return bool2980*/2981public function addAttachment(2982$path,2983$name = '',2984$encoding = self::ENCODING_BASE64,2985$type = '',2986$disposition = 'attachment'2987) {2988try {2989if (!static::fileIsAccessible($path)) {2990throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);2991}29922993// If a MIME type is not specified, try to work it out from the file name2994if ('' === $type) {2995$type = static::filenameToType($path);2996}29972998$filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);2999if ('' === $name) {3000$name = $filename;3001}3002if (!$this->validateEncoding($encoding)) {3003throw new Exception($this->lang('encoding') . $encoding);3004}30053006$this->attachment[] = [30070 => $path,30081 => $filename,30092 => $name,30103 => $encoding,30114 => $type,30125 => false, // isStringAttachment30136 => $disposition,30147 => $name,3015];3016} catch (Exception $exc) {3017$this->setError($exc->getMessage());3018$this->edebug($exc->getMessage());3019if ($this->exceptions) {3020throw $exc;3021}30223023return false;3024}30253026return true;3027}30283029/**3030* Return the array of attachments.3031*3032* @return array3033*/3034public function getAttachments()3035{3036return $this->attachment;3037}30383039/**3040* Attach all file, string, and binary attachments to the message.3041* Returns an empty string on failure.3042*3043* @param string $disposition_type3044* @param string $boundary3045*3046* @throws Exception3047*3048* @return string3049*/3050protected function attachAll($disposition_type, $boundary)3051{3052// Return text of body3053$mime = [];3054$cidUniq = [];3055$incl = [];30563057// Add all attachments3058foreach ($this->attachment as $attachment) {3059// Check if it is a valid disposition_filter3060if ($attachment[6] === $disposition_type) {3061// Check for string attachment3062$string = '';3063$path = '';3064$bString = $attachment[5];3065if ($bString) {3066$string = $attachment[0];3067} else {3068$path = $attachment[0];3069}30703071$inclhash = hash('sha256', serialize($attachment));3072if (in_array($inclhash, $incl, true)) {3073continue;3074}3075$incl[] = $inclhash;3076$name = $attachment[2];3077$encoding = $attachment[3];3078$type = $attachment[4];3079$disposition = $attachment[6];3080$cid = $attachment[7];3081if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) {3082continue;3083}3084$cidUniq[$cid] = true;30853086$mime[] = sprintf('--%s%s', $boundary, static::$LE);3087//Only include a filename property if we have one3088if (!empty($name)) {3089$mime[] = sprintf(3090'Content-Type: %s; name=%s%s',3091$type,3092static::quotedString($this->encodeHeader($this->secureHeader($name))),3093static::$LE3094);3095} else {3096$mime[] = sprintf(3097'Content-Type: %s%s',3098$type,3099static::$LE3100);3101}3102// RFC1341 part 5 says 7bit is assumed if not specified3103if (static::ENCODING_7BIT !== $encoding) {3104$mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE);3105}31063107//Only set Content-IDs on inline attachments3108if ((string) $cid !== '' && $disposition === 'inline') {3109$mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE;3110}31113112// Allow for bypassing the Content-Disposition header3113if (!empty($disposition)) {3114$encoded_name = $this->encodeHeader($this->secureHeader($name));3115if (!empty($encoded_name)) {3116$mime[] = sprintf(3117'Content-Disposition: %s; filename=%s%s',3118$disposition,3119static::quotedString($encoded_name),3120static::$LE . static::$LE3121);3122} else {3123$mime[] = sprintf(3124'Content-Disposition: %s%s',3125$disposition,3126static::$LE . static::$LE3127);3128}3129} else {3130$mime[] = static::$LE;3131}31323133// Encode as string attachment3134if ($bString) {3135$mime[] = $this->encodeString($string, $encoding);3136} else {3137$mime[] = $this->encodeFile($path, $encoding);3138}3139if ($this->isError()) {3140return '';3141}3142$mime[] = static::$LE;3143}3144}31453146$mime[] = sprintf('--%s--%s', $boundary, static::$LE);31473148return implode('', $mime);3149}31503151/**3152* Encode a file attachment in requested format.3153* Returns an empty string on failure.3154*3155* @param string $path The full path to the file3156* @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'3157*3158* @return string3159*/3160protected function encodeFile($path, $encoding = self::ENCODING_BASE64)3161{3162try {3163if (!static::fileIsAccessible($path)) {3164throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);3165}3166$file_buffer = file_get_contents($path);3167if (false === $file_buffer) {3168throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);3169}3170$file_buffer = $this->encodeString($file_buffer, $encoding);31713172return $file_buffer;3173} catch (Exception $exc) {3174$this->setError($exc->getMessage());3175$this->edebug($exc->getMessage());3176if ($this->exceptions) {3177throw $exc;3178}31793180return '';3181}3182}31833184/**3185* Encode a string in requested format.3186* Returns an empty string on failure.3187*3188* @param string $str The text to encode3189* @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'3190*3191* @throws Exception3192*3193* @return string3194*/3195public function encodeString($str, $encoding = self::ENCODING_BASE64)3196{3197$encoded = '';3198switch (strtolower($encoding)) {3199case static::ENCODING_BASE64:3200$encoded = chunk_split(3201base64_encode($str),3202static::STD_LINE_LENGTH,3203static::$LE3204);3205break;3206case static::ENCODING_7BIT:3207case static::ENCODING_8BIT:3208$encoded = static::normalizeBreaks($str);3209// Make sure it ends with a line break3210if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) {3211$encoded .= static::$LE;3212}3213break;3214case static::ENCODING_BINARY:3215$encoded = $str;3216break;3217case static::ENCODING_QUOTED_PRINTABLE:3218$encoded = $this->encodeQP($str);3219break;3220default:3221$this->setError($this->lang('encoding') . $encoding);3222if ($this->exceptions) {3223throw new Exception($this->lang('encoding') . $encoding);3224}3225break;3226}32273228return $encoded;3229}32303231/**3232* Encode a header value (not including its label) optimally.3233* Picks shortest of Q, B, or none. Result includes folding if needed.3234* See RFC822 definitions for phrase, comment and text positions.3235*3236* @param string $str The header value to encode3237* @param string $position What context the string will be used in3238*3239* @return string3240*/3241public function encodeHeader($str, $position = 'text')3242{3243$matchcount = 0;3244switch (strtolower($position)) {3245case 'phrase':3246if (!preg_match('/[\200-\377]/', $str)) {3247// Can't use addslashes as we don't know the value of magic_quotes_sybase3248$encoded = addcslashes($str, "\0..\37\177\\\"");3249if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {3250return $encoded;3251}32523253return "\"$encoded\"";3254}3255$matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);3256break;3257/* @noinspection PhpMissingBreakStatementInspection */3258case 'comment':3259$matchcount = preg_match_all('/[()"]/', $str, $matches);3260//fallthrough3261case 'text':3262default:3263$matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);3264break;3265}32663267if ($this->has8bitChars($str)) {3268$charset = $this->CharSet;3269} else {3270$charset = static::CHARSET_ASCII;3271}32723273// Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<content>?=`").3274$overhead = 8 + strlen($charset);32753276if ('mail' === $this->Mailer) {3277$maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead;3278} else {3279$maxlen = static::MAX_LINE_LENGTH - $overhead;3280}32813282// Select the encoding that produces the shortest output and/or prevents corruption.3283if ($matchcount > strlen($str) / 3) {3284// More than 1/3 of the content needs encoding, use B-encode.3285$encoding = 'B';3286} elseif ($matchcount > 0) {3287// Less than 1/3 of the content needs encoding, use Q-encode.3288$encoding = 'Q';3289} elseif (strlen($str) > $maxlen) {3290// No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption.3291$encoding = 'Q';3292} else {3293// No reformatting needed3294$encoding = false;3295}32963297switch ($encoding) {3298case 'B':3299if ($this->hasMultiBytes($str)) {3300// Use a custom function which correctly encodes and wraps long3301// multibyte strings without breaking lines within a character3302$encoded = $this->base64EncodeWrapMB($str, "\n");3303} else {3304$encoded = base64_encode($str);3305$maxlen -= $maxlen % 4;3306$encoded = trim(chunk_split($encoded, $maxlen, "\n"));3307}3308$encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);3309break;3310case 'Q':3311$encoded = $this->encodeQ($str, $position);3312$encoded = $this->wrapText($encoded, $maxlen, true);3313$encoded = str_replace('=' . static::$LE, "\n", trim($encoded));3314$encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);3315break;3316default:3317return $str;3318}33193320return trim(static::normalizeBreaks($encoded));3321}33223323/**3324* Check if a string contains multi-byte characters.3325*3326* @param string $str multi-byte text to wrap encode3327*3328* @return bool3329*/3330public function hasMultiBytes($str)3331{3332if (function_exists('mb_strlen')) {3333return strlen($str) > mb_strlen($str, $this->CharSet);3334}33353336// Assume no multibytes (we can't handle without mbstring functions anyway)3337return false;3338}33393340/**3341* Does a string contain any 8-bit chars (in any charset)?3342*3343* @param string $text3344*3345* @return bool3346*/3347public function has8bitChars($text)3348{3349return (bool) preg_match('/[\x80-\xFF]/', $text);3350}33513352/**3353* Encode and wrap long multibyte strings for mail headers3354* without breaking lines within a character.3355* Adapted from a function by paravoid.3356*3357* @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#602833358*3359* @param string $str multi-byte text to wrap encode3360* @param string $linebreak string to use as linefeed/end-of-line3361*3362* @return string3363*/3364public function base64EncodeWrapMB($str, $linebreak = null)3365{3366$start = '=?' . $this->CharSet . '?B?';3367$end = '?=';3368$encoded = '';3369if (null === $linebreak) {3370$linebreak = static::$LE;3371}33723373$mb_length = mb_strlen($str, $this->CharSet);3374// Each line must have length <= 75, including $start and $end3375$length = 75 - strlen($start) - strlen($end);3376// Average multi-byte ratio3377$ratio = $mb_length / strlen($str);3378// Base64 has a 4:3 ratio3379$avgLength = floor($length * $ratio * .75);33803381$offset = 0;3382for ($i = 0; $i < $mb_length; $i += $offset) {3383$lookBack = 0;3384do {3385$offset = $avgLength - $lookBack;3386$chunk = mb_substr($str, $i, $offset, $this->CharSet);3387$chunk = base64_encode($chunk);3388++$lookBack;3389} while (strlen($chunk) > $length);3390$encoded .= $chunk . $linebreak;3391}33923393// Chomp the last linefeed3394return substr($encoded, 0, -strlen($linebreak));3395}33963397/**3398* Encode a string in quoted-printable format.3399* According to RFC2045 section 6.7.3400*3401* @param string $string The text to encode3402*3403* @return string3404*/3405public function encodeQP($string)3406{3407return static::normalizeBreaks(quoted_printable_encode($string));3408}34093410/**3411* Encode a string using Q encoding.3412*3413* @see http://tools.ietf.org/html/rfc2047#section-4.23414*3415* @param string $str the text to encode3416* @param string $position Where the text is going to be used, see the RFC for what that means3417*3418* @return string3419*/3420public function encodeQ($str, $position = 'text')3421{3422// There should not be any EOL in the string3423$pattern = '';3424$encoded = str_replace(["\r", "\n"], '', $str);3425switch (strtolower($position)) {3426case 'phrase':3427// RFC 2047 section 5.33428$pattern = '^A-Za-z0-9!*+\/ -';3429break;3430/*3431* RFC 2047 section 5.2.3432* Build $pattern without including delimiters and []3433*/3434/* @noinspection PhpMissingBreakStatementInspection */3435case 'comment':3436$pattern = '\(\)"';3437/* Intentional fall through */3438case 'text':3439default:3440// RFC 2047 section 5.13441// Replace every high ascii, control, =, ? and _ characters3442$pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;3443break;3444}3445$matches = [];3446if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {3447// If the string contains an '=', make sure it's the first thing we replace3448// so as to avoid double-encoding3449$eqkey = array_search('=', $matches[0], true);3450if (false !== $eqkey) {3451unset($matches[0][$eqkey]);3452array_unshift($matches[0], '=');3453}3454foreach (array_unique($matches[0]) as $char) {3455$encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);3456}3457}3458// Replace spaces with _ (more readable than =20)3459// RFC 2047 section 4.2(2)3460return str_replace(' ', '_', $encoded);3461}34623463/**3464* Add a string or binary attachment (non-filesystem).3465* This method can be used to attach ascii or binary data,3466* such as a BLOB record from a database.3467*3468* @param string $string String attachment data3469* @param string $filename Name of the attachment3470* @param string $encoding File encoding (see $Encoding)3471* @param string $type File extension (MIME) type3472* @param string $disposition Disposition to use3473*3474* @throws Exception3475*3476* @return bool True on successfully adding an attachment3477*/3478public function addStringAttachment(3479$string,3480$filename,3481$encoding = self::ENCODING_BASE64,3482$type = '',3483$disposition = 'attachment'3484) {3485try {3486// If a MIME type is not specified, try to work it out from the file name3487if ('' === $type) {3488$type = static::filenameToType($filename);3489}34903491if (!$this->validateEncoding($encoding)) {3492throw new Exception($this->lang('encoding') . $encoding);3493}34943495// Append to $attachment array3496$this->attachment[] = [34970 => $string,34981 => $filename,34992 => static::mb_pathinfo($filename, PATHINFO_BASENAME),35003 => $encoding,35014 => $type,35025 => true, // isStringAttachment35036 => $disposition,35047 => 0,3505];3506} catch (Exception $exc) {3507$this->setError($exc->getMessage());3508$this->edebug($exc->getMessage());3509if ($this->exceptions) {3510throw $exc;3511}35123513return false;3514}35153516return true;3517}35183519/**3520* Add an embedded (inline) attachment from a file.3521* This can include images, sounds, and just about any other document type.3522* These differ from 'regular' attachments in that they are intended to be3523* displayed inline with the message, not just attached for download.3524* This is used in HTML messages that embed the images3525* the HTML refers to using the $cid value.3526* Never use a user-supplied path to a file!3527*3528* @param string $path Path to the attachment3529* @param string $cid Content ID of the attachment; Use this to reference3530* the content when using an embedded image in HTML3531* @param string $name Overrides the attachment name3532* @param string $encoding File encoding (see $Encoding)3533* @param string $type File MIME type3534* @param string $disposition Disposition to use3535*3536* @throws Exception3537*3538* @return bool True on successfully adding an attachment3539*/3540public function addEmbeddedImage(3541$path,3542$cid,3543$name = '',3544$encoding = self::ENCODING_BASE64,3545$type = '',3546$disposition = 'inline'3547) {3548try {3549if (!static::fileIsAccessible($path)) {3550throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);3551}35523553// If a MIME type is not specified, try to work it out from the file name3554if ('' === $type) {3555$type = static::filenameToType($path);3556}35573558if (!$this->validateEncoding($encoding)) {3559throw new Exception($this->lang('encoding') . $encoding);3560}35613562$filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);3563if ('' === $name) {3564$name = $filename;3565}35663567// Append to $attachment array3568$this->attachment[] = [35690 => $path,35701 => $filename,35712 => $name,35723 => $encoding,35734 => $type,35745 => false, // isStringAttachment35756 => $disposition,35767 => $cid,3577];3578} catch (Exception $exc) {3579$this->setError($exc->getMessage());3580$this->edebug($exc->getMessage());3581if ($this->exceptions) {3582throw $exc;3583}35843585return false;3586}35873588return true;3589}35903591/**3592* Add an embedded stringified attachment.3593* This can include images, sounds, and just about any other document type.3594* If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type.3595*3596* @param string $string The attachment binary data3597* @param string $cid Content ID of the attachment; Use this to reference3598* the content when using an embedded image in HTML3599* @param string $name A filename for the attachment. If this contains an extension,3600* PHPMailer will attempt to set a MIME type for the attachment.3601* For example 'file.jpg' would get an 'image/jpeg' MIME type.3602* @param string $encoding File encoding (see $Encoding), defaults to 'base64'3603* @param string $type MIME type - will be used in preference to any automatically derived type3604* @param string $disposition Disposition to use3605*3606* @throws Exception3607*3608* @return bool True on successfully adding an attachment3609*/3610public function addStringEmbeddedImage(3611$string,3612$cid,3613$name = '',3614$encoding = self::ENCODING_BASE64,3615$type = '',3616$disposition = 'inline'3617) {3618try {3619// If a MIME type is not specified, try to work it out from the name3620if ('' === $type && !empty($name)) {3621$type = static::filenameToType($name);3622}36233624if (!$this->validateEncoding($encoding)) {3625throw new Exception($this->lang('encoding') . $encoding);3626}36273628// Append to $attachment array3629$this->attachment[] = [36300 => $string,36311 => $name,36322 => $name,36333 => $encoding,36344 => $type,36355 => true, // isStringAttachment36366 => $disposition,36377 => $cid,3638];3639} catch (Exception $exc) {3640$this->setError($exc->getMessage());3641$this->edebug($exc->getMessage());3642if ($this->exceptions) {3643throw $exc;3644}36453646return false;3647}36483649return true;3650}36513652/**3653* Validate encodings.3654*3655* @param string $encoding3656*3657* @return bool3658*/3659protected function validateEncoding($encoding)3660{3661return in_array(3662$encoding,3663[3664self::ENCODING_7BIT,3665self::ENCODING_QUOTED_PRINTABLE,3666self::ENCODING_BASE64,3667self::ENCODING_8BIT,3668self::ENCODING_BINARY,3669],3670true3671);3672}36733674/**3675* Check if an embedded attachment is present with this cid.3676*3677* @param string $cid3678*3679* @return bool3680*/3681protected function cidExists($cid)3682{3683foreach ($this->attachment as $attachment) {3684if ('inline' === $attachment[6] && $cid === $attachment[7]) {3685return true;3686}3687}36883689return false;3690}36913692/**3693* Check if an inline attachment is present.3694*3695* @return bool3696*/3697public function inlineImageExists()3698{3699foreach ($this->attachment as $attachment) {3700if ('inline' === $attachment[6]) {3701return true;3702}3703}37043705return false;3706}37073708/**3709* Check if an attachment (non-inline) is present.3710*3711* @return bool3712*/3713public function attachmentExists()3714{3715foreach ($this->attachment as $attachment) {3716if ('attachment' === $attachment[6]) {3717return true;3718}3719}37203721return false;3722}37233724/**3725* Check if this message has an alternative body set.3726*3727* @return bool3728*/3729public function alternativeExists()3730{3731return !empty($this->AltBody);3732}37333734/**3735* Clear queued addresses of given kind.3736*3737* @param string $kind 'to', 'cc', or 'bcc'3738*/3739public function clearQueuedAddresses($kind)3740{3741$this->RecipientsQueue = array_filter(3742$this->RecipientsQueue,3743static function ($params) use ($kind) {3744return $params[0] !== $kind;3745}3746);3747}37483749/**3750* Clear all To recipients.3751*/3752public function clearAddresses()3753{3754foreach ($this->to as $to) {3755unset($this->all_recipients[strtolower($to[0])]);3756}3757$this->to = [];3758$this->clearQueuedAddresses('to');3759}37603761/**3762* Clear all CC recipients.3763*/3764public function clearCCs()3765{3766foreach ($this->cc as $cc) {3767unset($this->all_recipients[strtolower($cc[0])]);3768}3769$this->cc = [];3770$this->clearQueuedAddresses('cc');3771}37723773/**3774* Clear all BCC recipients.3775*/3776public function clearBCCs()3777{3778foreach ($this->bcc as $bcc) {3779unset($this->all_recipients[strtolower($bcc[0])]);3780}3781$this->bcc = [];3782$this->clearQueuedAddresses('bcc');3783}37843785/**3786* Clear all ReplyTo recipients.3787*/3788public function clearReplyTos()3789{3790$this->ReplyTo = [];3791$this->ReplyToQueue = [];3792}37933794/**3795* Clear all recipient types.3796*/3797public function clearAllRecipients()3798{3799$this->to = [];3800$this->cc = [];3801$this->bcc = [];3802$this->all_recipients = [];3803$this->RecipientsQueue = [];3804}38053806/**3807* Clear all filesystem, string, and binary attachments.3808*/3809public function clearAttachments()3810{3811$this->attachment = [];3812}38133814/**3815* Clear all custom headers.3816*/3817public function clearCustomHeaders()3818{3819$this->CustomHeader = [];3820}38213822/**3823* Add an error message to the error container.3824*3825* @param string $msg3826*/3827protected function setError($msg)3828{3829++$this->error_count;3830if ('smtp' === $this->Mailer && null !== $this->smtp) {3831$lasterror = $this->smtp->getError();3832if (!empty($lasterror['error'])) {3833$msg .= $this->lang('smtp_error') . $lasterror['error'];3834if (!empty($lasterror['detail'])) {3835$msg .= ' Detail: ' . $lasterror['detail'];3836}3837if (!empty($lasterror['smtp_code'])) {3838$msg .= ' SMTP code: ' . $lasterror['smtp_code'];3839}3840if (!empty($lasterror['smtp_code_ex'])) {3841$msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];3842}3843}3844}3845$this->ErrorInfo = $msg;3846}38473848/**3849* Return an RFC 822 formatted date.3850*3851* @return string3852*/3853public static function rfcDate()3854{3855// Set the time zone to whatever the default is to avoid 500 errors3856// Will default to UTC if it's not set properly in php.ini3857date_default_timezone_set(@date_default_timezone_get());38583859return date('D, j M Y H:i:s O');3860}38613862/**3863* Get the server hostname.3864* Returns 'localhost.localdomain' if unknown.3865*3866* @return string3867*/3868protected function serverHostname()3869{3870$result = '';3871if (!empty($this->Hostname)) {3872$result = $this->Hostname;3873} elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) {3874$result = $_SERVER['SERVER_NAME'];3875} elseif (function_exists('gethostname') && gethostname() !== false) {3876$result = gethostname();3877} elseif (php_uname('n') !== false) {3878$result = php_uname('n');3879}3880if (!static::isValidHost($result)) {3881return 'localhost.localdomain';3882}38833884return $result;3885}38863887/**3888* Validate whether a string contains a valid value to use as a hostname or IP address.3889* IPv6 addresses must include [], e.g. `[::1]`, not just `::1`.3890*3891* @param string $host The host name or IP address to check3892*3893* @return bool3894*/3895public static function isValidHost($host)3896{3897//Simple syntax limits3898if (empty($host)3899|| !is_string($host)3900|| strlen($host) > 2563901|| !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+])$/', $host)3902) {3903return false;3904}3905//Looks like a bracketed IPv6 address3906if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') {3907return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;3908}3909//If removing all the dots results in a numeric string, it must be an IPv4 address.3910//Need to check this first because otherwise things like `999.0.0.0` are considered valid host names3911if (is_numeric(str_replace('.', '', $host))) {3912//Is it a valid IPv4 address?3913return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;3914}3915if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) {3916//Is it a syntactically valid hostname?3917return true;3918}39193920return false;3921}39223923/**3924* Get an error message in the current language.3925*3926* @param string $key3927*3928* @return string3929*/3930protected function lang($key)3931{3932if (count($this->language) < 1) {3933$this->setLanguage(); // set the default language3934}39353936if (array_key_exists($key, $this->language)) {3937if ('smtp_connect_failed' === $key) {3938//Include a link to troubleshooting docs on SMTP connection failure3939//this is by far the biggest cause of support questions3940//but it's usually not PHPMailer's fault.3941return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';3942}39433944return $this->language[$key];3945}39463947//Return the key as a fallback3948return $key;3949}39503951/**3952* Check if an error occurred.3953*3954* @return bool True if an error did occur3955*/3956public function isError()3957{3958return $this->error_count > 0;3959}39603961/**3962* Add a custom header.3963* $name value can be overloaded to contain3964* both header name and value (name:value).3965*3966* @param string $name Custom header name3967* @param string|null $value Header value3968*3969* @throws Exception3970*/3971public function addCustomHeader($name, $value = null)3972{3973if (null === $value && strpos($name, ':') !== false) {3974// Value passed in as name:value3975list($name, $value) = explode(':', $name, 2);3976}3977$name = trim($name);3978$value = trim($value);3979//Ensure name is not empty, and that neither name nor value contain line breaks3980if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {3981if ($this->exceptions) {3982throw new Exception('Invalid header name or value');3983}39843985return false;3986}3987$this->CustomHeader[] = [$name, $value];39883989return true;3990}39913992/**3993* Returns all custom headers.3994*3995* @return array3996*/3997public function getCustomHeaders()3998{3999return $this->CustomHeader;4000}40014002/**4003* Create a message body from an HTML string.4004* Automatically inlines images and creates a plain-text version by converting the HTML,4005* overwriting any existing values in Body and AltBody.4006* Do not source $message content from user input!4007* $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty4008* will look for an image file in $basedir/images/a.png and convert it to inline.4009* If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)4010* Converts data-uri images into embedded attachments.4011* If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.4012*4013* @param string $message HTML message string4014* @param string $basedir Absolute path to a base directory to prepend to relative paths to images4015* @param bool|callable $advanced Whether to use the internal HTML to text converter4016* or your own custom converter4017* @return string The transformed message body4018*4019* @throws Exception4020*4021* @see PHPMailer::html2text()4022*/4023public function msgHTML($message, $basedir = '', $advanced = false)4024{4025preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $images);4026if (array_key_exists(2, $images)) {4027if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {4028// Ensure $basedir has a trailing /4029$basedir .= '/';4030}4031foreach ($images[2] as $imgindex => $url) {4032// Convert data URIs into embedded images4033//e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="4034$match = [];4035if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {4036if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) {4037$data = base64_decode($match[3]);4038} elseif ('' === $match[2]) {4039$data = rawurldecode($match[3]);4040} else {4041//Not recognised so leave it alone4042continue;4043}4044//Hash the decoded data, not the URL, so that the same data-URI image used in multiple places4045//will only be embedded once, even if it used a different encoding4046$cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // RFC2392 S 240474048if (!$this->cidExists($cid)) {4049$this->addStringEmbeddedImage(4050$data,4051$cid,4052'embed' . $imgindex,4053static::ENCODING_BASE64,4054$match[1]4055);4056}4057$message = str_replace(4058$images[0][$imgindex],4059$images[1][$imgindex] . '="cid:' . $cid . '"',4060$message4061);4062continue;4063}4064if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths)4065!empty($basedir)4066// Ignore URLs containing parent dir traversal (..)4067&& (strpos($url, '..') === false)4068// Do not change urls that are already inline images4069&& 0 !== strpos($url, 'cid:')4070// Do not change absolute URLs, including anonymous protocol4071&& !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)4072) {4073$filename = static::mb_pathinfo($url, PATHINFO_BASENAME);4074$directory = dirname($url);4075if ('.' === $directory) {4076$directory = '';4077}4078// RFC2392 S 24079$cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0';4080if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {4081$basedir .= '/';4082}4083if (strlen($directory) > 1 && '/' !== substr($directory, -1)) {4084$directory .= '/';4085}4086if ($this->addEmbeddedImage(4087$basedir . $directory . $filename,4088$cid,4089$filename,4090static::ENCODING_BASE64,4091static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION))4092)4093) {4094$message = preg_replace(4095'/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',4096$images[1][$imgindex] . '="cid:' . $cid . '"',4097$message4098);4099}4100}4101}4102}4103$this->isHTML();4104// Convert all message body line breaks to LE, makes quoted-printable encoding work much better4105$this->Body = static::normalizeBreaks($message);4106$this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));4107if (!$this->alternativeExists()) {4108$this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'4109. static::$LE;4110}41114112return $this->Body;4113}41144115/**4116* Convert an HTML string into plain text.4117* This is used by msgHTML().4118* Note - older versions of this function used a bundled advanced converter4119* which was removed for license reasons in #232.4120* Example usage:4121*4122* ```php4123* // Use default conversion4124* $plain = $mail->html2text($html);4125* // Use your own custom converter4126* $plain = $mail->html2text($html, function($html) {4127* $converter = new MyHtml2text($html);4128* return $converter->get_text();4129* });4130* ```4131*4132* @param string $html The HTML text to convert4133* @param bool|callable $advanced Any boolean value to use the internal converter,4134* or provide your own callable for custom conversion4135*4136* @return string4137*/4138public function html2text($html, $advanced = false)4139{4140if (is_callable($advanced)) {4141return call_user_func($advanced, $html);4142}41434144return html_entity_decode(4145trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),4146ENT_QUOTES,4147$this->CharSet4148);4149}41504151/**4152* Get the MIME type for a file extension.4153*4154* @param string $ext File extension4155*4156* @return string MIME type of file4157*/4158public static function _mime_types($ext = '')4159{4160$mimes = [4161'xl' => 'application/excel',4162'js' => 'application/javascript',4163'hqx' => 'application/mac-binhex40',4164'cpt' => 'application/mac-compactpro',4165'bin' => 'application/macbinary',4166'doc' => 'application/msword',4167'word' => 'application/msword',4168'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',4169'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',4170'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',4171'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',4172'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',4173'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',4174'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',4175'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',4176'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',4177'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',4178'class' => 'application/octet-stream',4179'dll' => 'application/octet-stream',4180'dms' => 'application/octet-stream',4181'exe' => 'application/octet-stream',4182'lha' => 'application/octet-stream',4183'lzh' => 'application/octet-stream',4184'psd' => 'application/octet-stream',4185'sea' => 'application/octet-stream',4186'so' => 'application/octet-stream',4187'oda' => 'application/oda',4188'pdf' => 'application/pdf',4189'ai' => 'application/postscript',4190'eps' => 'application/postscript',4191'ps' => 'application/postscript',4192'smi' => 'application/smil',4193'smil' => 'application/smil',4194'mif' => 'application/vnd.mif',4195'xls' => 'application/vnd.ms-excel',4196'ppt' => 'application/vnd.ms-powerpoint',4197'wbxml' => 'application/vnd.wap.wbxml',4198'wmlc' => 'application/vnd.wap.wmlc',4199'dcr' => 'application/x-director',4200'dir' => 'application/x-director',4201'dxr' => 'application/x-director',4202'dvi' => 'application/x-dvi',4203'gtar' => 'application/x-gtar',4204'php3' => 'application/x-httpd-php',4205'php4' => 'application/x-httpd-php',4206'php' => 'application/x-httpd-php',4207'phtml' => 'application/x-httpd-php',4208'phps' => 'application/x-httpd-php-source',4209'swf' => 'application/x-shockwave-flash',4210'sit' => 'application/x-stuffit',4211'tar' => 'application/x-tar',4212'tgz' => 'application/x-tar',4213'xht' => 'application/xhtml+xml',4214'xhtml' => 'application/xhtml+xml',4215'zip' => 'application/zip',4216'mid' => 'audio/midi',4217'midi' => 'audio/midi',4218'mp2' => 'audio/mpeg',4219'mp3' => 'audio/mpeg',4220'm4a' => 'audio/mp4',4221'mpga' => 'audio/mpeg',4222'aif' => 'audio/x-aiff',4223'aifc' => 'audio/x-aiff',4224'aiff' => 'audio/x-aiff',4225'ram' => 'audio/x-pn-realaudio',4226'rm' => 'audio/x-pn-realaudio',4227'rpm' => 'audio/x-pn-realaudio-plugin',4228'ra' => 'audio/x-realaudio',4229'wav' => 'audio/x-wav',4230'mka' => 'audio/x-matroska',4231'bmp' => 'image/bmp',4232'gif' => 'image/gif',4233'jpeg' => 'image/jpeg',4234'jpe' => 'image/jpeg',4235'jpg' => 'image/jpeg',4236'png' => 'image/png',4237'tiff' => 'image/tiff',4238'tif' => 'image/tiff',4239'webp' => 'image/webp',4240'avif' => 'image/avif',4241'heif' => 'image/heif',4242'heifs' => 'image/heif-sequence',4243'heic' => 'image/heic',4244'heics' => 'image/heic-sequence',4245'eml' => 'message/rfc822',4246'css' => 'text/css',4247'html' => 'text/html',4248'htm' => 'text/html',4249'shtml' => 'text/html',4250'log' => 'text/plain',4251'text' => 'text/plain',4252'txt' => 'text/plain',4253'rtx' => 'text/richtext',4254'rtf' => 'text/rtf',4255'vcf' => 'text/vcard',4256'vcard' => 'text/vcard',4257'ics' => 'text/calendar',4258'xml' => 'text/xml',4259'xsl' => 'text/xml',4260'wmv' => 'video/x-ms-wmv',4261'mpeg' => 'video/mpeg',4262'mpe' => 'video/mpeg',4263'mpg' => 'video/mpeg',4264'mp4' => 'video/mp4',4265'm4v' => 'video/mp4',4266'mov' => 'video/quicktime',4267'qt' => 'video/quicktime',4268'rv' => 'video/vnd.rn-realvideo',4269'avi' => 'video/x-msvideo',4270'movie' => 'video/x-sgi-movie',4271'webm' => 'video/webm',4272'mkv' => 'video/x-matroska',4273];4274$ext = strtolower($ext);4275if (array_key_exists($ext, $mimes)) {4276return $mimes[$ext];4277}42784279return 'application/octet-stream';4280}42814282/**4283* Map a file name to a MIME type.4284* Defaults to 'application/octet-stream', i.e.. arbitrary binary data.4285*4286* @param string $filename A file name or full path, does not need to exist as a file4287*4288* @return string4289*/4290public static function filenameToType($filename)4291{4292// In case the path is a URL, strip any query string before getting extension4293$qpos = strpos($filename, '?');4294if (false !== $qpos) {4295$filename = substr($filename, 0, $qpos);4296}4297$ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);42984299return static::_mime_types($ext);4300}43014302/**4303* Multi-byte-safe pathinfo replacement.4304* Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.4305*4306* @see http://www.php.net/manual/en/function.pathinfo.php#1074614307*4308* @param string $path A filename or path, does not need to exist as a file4309* @param int|string $options Either a PATHINFO_* constant,4310* or a string name to return only the specified piece4311*4312* @return string|array4313*/4314public static function mb_pathinfo($path, $options = null)4315{4316$ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];4317$pathinfo = [];4318if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {4319if (array_key_exists(1, $pathinfo)) {4320$ret['dirname'] = $pathinfo[1];4321}4322if (array_key_exists(2, $pathinfo)) {4323$ret['basename'] = $pathinfo[2];4324}4325if (array_key_exists(5, $pathinfo)) {4326$ret['extension'] = $pathinfo[5];4327}4328if (array_key_exists(3, $pathinfo)) {4329$ret['filename'] = $pathinfo[3];4330}4331}4332switch ($options) {4333case PATHINFO_DIRNAME:4334case 'dirname':4335return $ret['dirname'];4336case PATHINFO_BASENAME:4337case 'basename':4338return $ret['basename'];4339case PATHINFO_EXTENSION:4340case 'extension':4341return $ret['extension'];4342case PATHINFO_FILENAME:4343case 'filename':4344return $ret['filename'];4345default:4346return $ret;4347}4348}43494350/**4351* Set or reset instance properties.4352* You should avoid this function - it's more verbose, less efficient, more error-prone and4353* harder to debug than setting properties directly.4354* Usage Example:4355* `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);`4356* is the same as:4357* `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`.4358*4359* @param string $name The property name to set4360* @param mixed $value The value to set the property to4361*4362* @return bool4363*/4364public function set($name, $value = '')4365{4366if (property_exists($this, $name)) {4367$this->$name = $value;43684369return true;4370}4371$this->setError($this->lang('variable_set') . $name);43724373return false;4374}43754376/**4377* Strip newlines to prevent header injection.4378*4379* @param string $str4380*4381* @return string4382*/4383public function secureHeader($str)4384{4385return trim(str_replace(["\r", "\n"], '', $str));4386}43874388/**4389* Normalize line breaks in a string.4390* Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.4391* Defaults to CRLF (for message bodies) and preserves consecutive breaks.4392*4393* @param string $text4394* @param string $breaktype What kind of line break to use; defaults to static::$LE4395*4396* @return string4397*/4398public static function normalizeBreaks($text, $breaktype = null)4399{4400if (null === $breaktype) {4401$breaktype = static::$LE;4402}4403// Normalise to \n4404$text = str_replace([self::CRLF, "\r"], "\n", $text);4405// Now convert LE as needed4406if ("\n" !== $breaktype) {4407$text = str_replace("\n", $breaktype, $text);4408}44094410return $text;4411}44124413/**4414* Remove trailing breaks from a string.4415*4416* @param string $text4417*4418* @return string The text to remove breaks from4419*/4420public static function stripTrailingWSP($text)4421{4422return rtrim($text, " \r\n\t");4423}44244425/**4426* Return the current line break format string.4427*4428* @return string4429*/4430public static function getLE()4431{4432return static::$LE;4433}44344435/**4436* Set the line break format string, e.g. "\r\n".4437*4438* @param string $le4439*/4440protected static function setLE($le)4441{4442static::$LE = $le;4443}44444445/**4446* Set the public and private key files and password for S/MIME signing.4447*4448* @param string $cert_filename4449* @param string $key_filename4450* @param string $key_pass Password for private key4451* @param string $extracerts_filename Optional path to chain certificate4452*/4453public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')4454{4455$this->sign_cert_file = $cert_filename;4456$this->sign_key_file = $key_filename;4457$this->sign_key_pass = $key_pass;4458$this->sign_extracerts_file = $extracerts_filename;4459}44604461/**4462* Quoted-Printable-encode a DKIM header.4463*4464* @param string $txt4465*4466* @return string4467*/4468public function DKIM_QP($txt)4469{4470$line = '';4471$len = strlen($txt);4472for ($i = 0; $i < $len; ++$i) {4473$ord = ord($txt[$i]);4474if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {4475$line .= $txt[$i];4476} else {4477$line .= '=' . sprintf('%02X', $ord);4478}4479}44804481return $line;4482}44834484/**4485* Generate a DKIM signature.4486*4487* @param string $signHeader4488*4489* @throws Exception4490*4491* @return string The DKIM signature value4492*/4493public function DKIM_Sign($signHeader)4494{4495if (!defined('PKCS7_TEXT')) {4496if ($this->exceptions) {4497throw new Exception($this->lang('extension_missing') . 'openssl');4498}44994500return '';4501}4502$privKeyStr = !empty($this->DKIM_private_string) ?4503$this->DKIM_private_string :4504file_get_contents($this->DKIM_private);4505if ('' !== $this->DKIM_passphrase) {4506$privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);4507} else {4508$privKey = openssl_pkey_get_private($privKeyStr);4509}4510if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {4511openssl_pkey_free($privKey);45124513return base64_encode($signature);4514}4515openssl_pkey_free($privKey);45164517return '';4518}45194520/**4521* Generate a DKIM canonicalization header.4522* Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.4523* Canonicalized headers should *always* use CRLF, regardless of mailer setting.4524*4525* @see https://tools.ietf.org/html/rfc6376#section-3.4.24526*4527* @param string $signHeader Header4528*4529* @return string4530*/4531public function DKIM_HeaderC($signHeader)4532{4533//Normalize breaks to CRLF (regardless of the mailer)4534$signHeader = static::normalizeBreaks($signHeader, self::CRLF);4535//Unfold header lines4536//Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`4537//@see https://tools.ietf.org/html/rfc5322#section-2.24538//That means this may break if you do something daft like put vertical tabs in your headers.4539$signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);4540//Break headers out into an array4541$lines = explode(self::CRLF, $signHeader);4542foreach ($lines as $key => $line) {4543//If the header is missing a :, skip it as it's invalid4544//This is likely to happen because the explode() above will also split4545//on the trailing LE, leaving an empty line4546if (strpos($line, ':') === false) {4547continue;4548}4549list($heading, $value) = explode(':', $line, 2);4550//Lower-case header name4551$heading = strtolower($heading);4552//Collapse white space within the value, also convert WSP to space4553$value = preg_replace('/[ \t]+/', ' ', $value);4554//RFC6376 is slightly unclear here - it says to delete space at the *end* of each value4555//But then says to delete space before and after the colon.4556//Net result is the same as trimming both ends of the value.4557//By elimination, the same applies to the field name4558$lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");4559}45604561return implode(self::CRLF, $lines);4562}45634564/**4565* Generate a DKIM canonicalization body.4566* Uses the 'simple' algorithm from RFC6376 section 3.4.3.4567* Canonicalized bodies should *always* use CRLF, regardless of mailer setting.4568*4569* @see https://tools.ietf.org/html/rfc6376#section-3.4.34570*4571* @param string $body Message Body4572*4573* @return string4574*/4575public function DKIM_BodyC($body)4576{4577if (empty($body)) {4578return self::CRLF;4579}4580// Normalize line endings to CRLF4581$body = static::normalizeBreaks($body, self::CRLF);45824583//Reduce multiple trailing line breaks to a single one4584return static::stripTrailingWSP($body) . self::CRLF;4585}45864587/**4588* Create the DKIM header and body in a new message header.4589*4590* @param string $headers_line Header lines4591* @param string $subject Subject4592* @param string $body Body4593*4594* @throws Exception4595*4596* @return string4597*/4598public function DKIM_Add($headers_line, $subject, $body)4599{4600$DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms4601$DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of header & body4602$DKIMquery = 'dns/txt'; // Query method4603$DKIMtime = time();4604//Always sign these headers without being asked4605//Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.14606$autoSignHeaders = [4607'from',4608'to',4609'cc',4610'date',4611'subject',4612'reply-to',4613'message-id',4614'content-type',4615'mime-version',4616'x-mailer',4617];4618if (stripos($headers_line, 'Subject') === false) {4619$headers_line .= 'Subject: ' . $subject . static::$LE;4620}4621$headerLines = explode(static::$LE, $headers_line);4622$currentHeaderLabel = '';4623$currentHeaderValue = '';4624$parsedHeaders = [];4625$headerLineIndex = 0;4626$headerLineCount = count($headerLines);4627foreach ($headerLines as $headerLine) {4628$matches = [];4629if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) {4630if ($currentHeaderLabel !== '') {4631//We were previously in another header; This is the start of a new header, so save the previous one4632$parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];4633}4634$currentHeaderLabel = $matches[1];4635$currentHeaderValue = $matches[2];4636} elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) {4637//This is a folded continuation of the current header, so unfold it4638$currentHeaderValue .= ' ' . $matches[1];4639}4640++$headerLineIndex;4641if ($headerLineIndex >= $headerLineCount) {4642//This was the last line, so finish off this header4643$parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];4644}4645}4646$copiedHeaders = [];4647$headersToSignKeys = [];4648$headersToSign = [];4649foreach ($parsedHeaders as $header) {4650//Is this header one that must be included in the DKIM signature?4651if (in_array(strtolower($header['label']), $autoSignHeaders, true)) {4652$headersToSignKeys[] = $header['label'];4653$headersToSign[] = $header['label'] . ': ' . $header['value'];4654if ($this->DKIM_copyHeaderFields) {4655$copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC4656str_replace('|', '=7C', $this->DKIM_QP($header['value']));4657}4658continue;4659}4660//Is this an extra custom header we've been asked to sign?4661if (in_array($header['label'], $this->DKIM_extraHeaders, true)) {4662//Find its value in custom headers4663foreach ($this->CustomHeader as $customHeader) {4664if ($customHeader[0] === $header['label']) {4665$headersToSignKeys[] = $header['label'];4666$headersToSign[] = $header['label'] . ': ' . $header['value'];4667if ($this->DKIM_copyHeaderFields) {4668$copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC4669str_replace('|', '=7C', $this->DKIM_QP($header['value']));4670}4671//Skip straight to the next header4672continue 2;4673}4674}4675}4676}4677$copiedHeaderFields = '';4678if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) {4679//Assemble a DKIM 'z' tag4680$copiedHeaderFields = ' z=';4681$first = true;4682foreach ($copiedHeaders as $copiedHeader) {4683if (!$first) {4684$copiedHeaderFields .= static::$LE . ' |';4685}4686//Fold long values4687if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {4688$copiedHeaderFields .= substr(4689chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS),46900,4691-strlen(static::$LE . self::FWS)4692);4693} else {4694$copiedHeaderFields .= $copiedHeader;4695}4696$first = false;4697}4698$copiedHeaderFields .= ';' . static::$LE;4699}4700$headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE;4701$headerValues = implode(static::$LE, $headersToSign);4702$body = $this->DKIM_BodyC($body);4703$DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body4704$ident = '';4705if ('' !== $this->DKIM_identity) {4706$ident = ' i=' . $this->DKIM_identity . ';' . static::$LE;4707}4708//The DKIM-Signature header is included in the signature *except for* the value of the `b` tag4709//which is appended after calculating the signature4710//https://tools.ietf.org/html/rfc6376#section-3.54711$dkimSignatureHeader = 'DKIM-Signature: v=1;' .4712' d=' . $this->DKIM_domain . ';' .4713' s=' . $this->DKIM_selector . ';' . static::$LE .4714' a=' . $DKIMsignatureType . ';' .4715' q=' . $DKIMquery . ';' .4716' t=' . $DKIMtime . ';' .4717' c=' . $DKIMcanonicalization . ';' . static::$LE .4718$headerKeys .4719$ident .4720$copiedHeaderFields .4721' bh=' . $DKIMb64 . ';' . static::$LE .4722' b=';4723//Canonicalize the set of headers4724$canonicalizedHeaders = $this->DKIM_HeaderC(4725$headerValues . static::$LE . $dkimSignatureHeader4726);4727$signature = $this->DKIM_Sign($canonicalizedHeaders);4728$signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS));47294730return static::normalizeBreaks($dkimSignatureHeader . $signature);4731}47324733/**4734* Detect if a string contains a line longer than the maximum line length4735* allowed by RFC 2822 section 2.1.1.4736*4737* @param string $str4738*4739* @return bool4740*/4741public static function hasLineLongerThanMax($str)4742{4743return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);4744}47454746/**4747* If a string contains any "special" characters, double-quote the name,4748* and escape any double quotes with a backslash.4749*4750* @param string $str4751*4752* @return string4753*4754* @see RFC822 3.4.14755*/4756public static function quotedString($str)4757{4758if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) {4759//If the string contains any of these chars, it must be double-quoted4760//and any double quotes must be escaped with a backslash4761return '"' . str_replace('"', '\\"', $str) . '"';4762}47634764//Return the string untouched, it doesn't need quoting4765return $str;4766}47674768/**4769* Allows for public read access to 'to' property.4770* Before the send() call, queued addresses (i.e. with IDN) are not yet included.4771*4772* @return array4773*/4774public function getToAddresses()4775{4776return $this->to;4777}47784779/**4780* Allows for public read access to 'cc' property.4781* Before the send() call, queued addresses (i.e. with IDN) are not yet included.4782*4783* @return array4784*/4785public function getCcAddresses()4786{4787return $this->cc;4788}47894790/**4791* Allows for public read access to 'bcc' property.4792* Before the send() call, queued addresses (i.e. with IDN) are not yet included.4793*4794* @return array4795*/4796public function getBccAddresses()4797{4798return $this->bcc;4799}48004801/**4802* Allows for public read access to 'ReplyTo' property.4803* Before the send() call, queued addresses (i.e. with IDN) are not yet included.4804*4805* @return array4806*/4807public function getReplyToAddresses()4808{4809return $this->ReplyTo;4810}48114812/**4813* Allows for public read access to 'all_recipients' property.4814* Before the send() call, queued addresses (i.e. with IDN) are not yet included.4815*4816* @return array4817*/4818public function getAllRecipientAddresses()4819{4820return $this->all_recipients;4821}48224823/**4824* Perform a callback.4825*4826* @param bool $isSent4827* @param array $to4828* @param array $cc4829* @param array $bcc4830* @param string $subject4831* @param string $body4832* @param string $from4833* @param array $extra4834*/4835protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)4836{4837if (!empty($this->action_function) && is_callable($this->action_function)) {4838call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);4839}4840}48414842/**4843* Get the OAuth instance.4844*4845* @return OAuth4846*/4847public function getOAuth()4848{4849return $this->oauth;4850}48514852/**4853* Set an OAuth instance.4854*/4855public function setOAuth(OAuth $oauth)4856{4857$this->oauth = $oauth;4858}4859}486048614862