Changeset 50397 for trunk/src/wp-includes/PHPMailer/PHPMailer.php
- Timestamp:
- 02/21/2021 09:32:41 AM (4 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/PHPMailer/PHPMailer.php
r49713 r50397 749 749 * @var string 750 750 */ 751 const VERSION = '6. 2.0';751 const VERSION = '6.3.0'; 752 752 753 753 /** … … 863 863 } 864 864 //Calling mail() with null params breaks 865 $this->edebug('Sending with mail()'); 866 $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); 867 $this->edebug("Envelope sender: {$this->Sender}"); 868 $this->edebug("To: {$to}"); 869 $this->edebug("Subject: {$subject}"); 870 $this->edebug("Headers: {$header}"); 865 871 if (!$this->UseSendmailOptions || null === $params) { 866 872 $result = @mail($to, $subject, $body, $header); 867 873 } else { 874 $this->edebug("Additional params: {$params}"); 868 875 $result = @mail($to, $subject, $body, $header, $params); 869 876 } 870 877 $this->edebug('Result: ' . ($result ? 'true' : 'false')); 871 878 return $result; 872 879 } 873 880 874 881 /** 875 * Output debugging info via user-defined method.876 * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).882 * Output debugging info via a user-defined method. 883 * Only generates output if debug output is enabled. 877 884 * 878 885 * @see PHPMailer::$Debugoutput … … 1071 1078 $pos = strrpos($address, '@'); 1072 1079 if (false === $pos) { 1073 // 1080 //At-sign is missing. 1074 1081 $error_message = sprintf( 1075 1082 '%s (%s): %s', … … 1087 1094 } 1088 1095 $params = [$kind, $address, $name]; 1089 // 1096 //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. 1090 1097 if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { 1091 1098 if ('Reply-To' !== $kind) { … … 1104 1111 } 1105 1112 1106 // 1113 //Immediately add standard addresses without IDN. 1107 1114 return call_user_func_array([$this, 'addAnAddress'], $params); 1108 1115 } … … 1192 1199 ) 1193 1200 ) { 1201 //Decode the name part if it's present and encoded 1202 if (property_exists($address, 'personal') && preg_match('/^=\?.*\?=$/', $address->personal)) { 1203 $address->personal = mb_decode_mimeheader($address->personal); 1204 } 1205 1194 1206 $addresses[] = [ 1195 1207 'name' => (property_exists($address, 'personal') ? $address->personal : ''), … … 1215 1227 list($name, $email) = explode('<', $address); 1216 1228 $email = trim(str_replace('>', '', $email)); 1229 $name = trim($name); 1217 1230 if (static::validateAddress($email)) { 1231 //If this name is encoded, decode it 1232 if (preg_match('/^=\?.*\?=$/', $name)) { 1233 $name = mb_decode_mimeheader($name); 1234 } 1218 1235 $addresses[] = [ 1219 'name' => trim(str_replace(['"', "'"], '', $name)), 1236 //Remove any surrounding quotes and spaces from the name 1237 'name' => trim($name, '\'" '), 1220 1238 'address' => $email, 1221 1239 ]; … … 1243 1261 $address = trim($address); 1244 1262 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim 1245 // 1263 //Don't validate now addresses with IDN. Will be done in send(). 1246 1264 $pos = strrpos($address, '@'); 1247 1265 if ( … … 1396 1414 public function punyencodeAddress($address) 1397 1415 { 1398 // 1416 //Verify we have required functions, CharSet, and at-sign. 1399 1417 $pos = strrpos($address, '@'); 1400 1418 if ( … … 1404 1422 ) { 1405 1423 $domain = substr($address, ++$pos); 1406 // 1424 //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. 1407 1425 if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { 1408 $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); 1426 //Convert the domain from whatever charset it's in to UTF-8 1427 $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); 1409 1428 //Ignore IDE complaints about this line - method signature changed in PHP 5.4 1410 1429 $errorcode = 0; 1411 1430 if (defined('INTL_IDNA_VARIANT_UTS46')) { 1412 $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46); 1431 //Use the current punycode standard (appeared in PHP 7.2) 1432 $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_UTS46); 1413 1433 } elseif (defined('INTL_IDNA_VARIANT_2003')) { 1434 //Fall back to this old, deprecated/removed encoding 1414 1435 // phpcs:ignore PHPCompatibility.Constants.RemovedConstants.intl_idna_variant_2003Deprecated 1415 $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003);1436 $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); 1416 1437 } else { 1438 //Fall back to a default we don't know about 1417 1439 // phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet 1418 1440 $punycode = idn_to_ascii($domain, $errorcode); … … 1465 1487 if ( 1466 1488 'smtp' === $this->Mailer 1467 || ('mail' === $this->Mailer && ( PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0))1489 || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) 1468 1490 ) { 1469 1491 //SMTP mandates RFC-compliant line endings … … 1477 1499 if ( 1478 1500 'mail' === $this->Mailer 1479 && (( PHP_VERSION_ID >= 70000 &&PHP_VERSION_ID < 70017)1480 || ( PHP_VERSION_ID >= 70100 &&PHP_VERSION_ID < 70103))1501 && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) 1502 || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) 1481 1503 && ini_get('mail.add_x_header') === '1' 1482 1504 && stripos(PHP_OS, 'WIN') === 0 … … 1491 1513 1492 1514 try { 1493 $this->error_count = 0; // 1515 $this->error_count = 0; //Reset errors 1494 1516 $this->mailHeader = ''; 1495 1517 1496 // 1518 //Dequeue recipient and Reply-To addresses with IDN 1497 1519 foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { 1498 1520 $params[1] = $this->punyencodeAddress($params[1]); … … 1503 1525 } 1504 1526 1505 // 1527 //Validate From, Sender, and ConfirmReadingTo addresses 1506 1528 foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { 1507 1529 $this->$address_kind = trim($this->$address_kind); … … 1527 1549 } 1528 1550 1529 // 1551 //Set whether the message is multipart/alternative 1530 1552 if ($this->alternativeExists()) { 1531 1553 $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; … … 1533 1555 1534 1556 $this->setMessageType(); 1535 // 1557 //Refuse to send an empty message unless we are specifically allowing it 1536 1558 if (!$this->AllowEmpty && empty($this->Body)) { 1537 1559 throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); … … 1540 1562 //Trim subject consistently 1541 1563 $this->Subject = trim($this->Subject); 1542 // 1564 //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) 1543 1565 $this->MIMEHeader = ''; 1544 1566 $this->MIMEBody = $this->createBody(); 1545 // 1567 //createBody may have added some headers, so retain them 1546 1568 $tempheaders = $this->MIMEHeader; 1547 1569 $this->MIMEHeader = $this->createHeader(); 1548 1570 $this->MIMEHeader .= $tempheaders; 1549 1571 1550 // 1551 // 1572 //To capture the complete message when using mail(), create 1573 //an extra header list which createHeader() doesn't fold in 1552 1574 if ('mail' === $this->Mailer) { 1553 1575 if (count($this->to) > 0) { … … 1562 1584 } 1563 1585 1564 // 1586 //Sign with DKIM if enabled 1565 1587 if ( 1566 1588 !empty($this->DKIM_domain) … … 1603 1625 { 1604 1626 try { 1605 // 1627 //Choose the mailer and send through it 1606 1628 switch ($this->Mailer) { 1607 1629 case 'sendmail': … … 1648 1670 protected function sendmailSend($header, $body) 1649 1671 { 1672 if ($this->Mailer === 'qmail') { 1673 $this->edebug('Sending with qmail'); 1674 } else { 1675 $this->edebug('Sending with sendmail'); 1676 } 1650 1677 $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; 1651 1652 // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. 1653 if (!empty($this->Sender) && self::isShellSafe($this->Sender)) { 1654 if ('qmail' === $this->Mailer) { 1678 //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver 1679 //A space after `-f` is optional, but there is a long history of its presence 1680 //causing problems, so we don't use one 1681 //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html 1682 //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html 1683 //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html 1684 //Example problem: https://www.drupal.org/node/1057954 1685 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. 1686 if ('' === $this->Sender) { 1687 $this->Sender = $this->From; 1688 } 1689 if (empty($this->Sender) && !empty(ini_get('sendmail_from'))) { 1690 //PHP config has a sender address we can use 1691 $this->Sender = ini_get('sendmail_from'); 1692 } 1693 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. 1694 //But sendmail requires this param, so fail without it 1695 if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { 1696 if ($this->Mailer === 'qmail') { 1655 1697 $sendmailFmt = '%s -f%s'; 1656 1698 } else { 1657 1699 $sendmailFmt = '%s -oi -f%s -t'; 1658 1700 } 1659 } elseif ('qmail' === $this->Mailer) {1660 $sendmailFmt = '%s';1661 1701 } else { 1662 $sendmailFmt = '%s -oi -t'; 1702 $this->edebug('Sender address unusable or missing: ' . $this->Sender); 1703 return false; 1663 1704 } 1664 1705 1665 1706 $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); 1707 $this->edebug('Sendmail path: ' . $this->Sendmail); 1708 $this->edebug('Sendmail command: ' . $sendmail); 1709 $this->edebug('Envelope sender: ' . $this->Sender); 1710 $this->edebug("Headers: {$header}"); 1666 1711 1667 1712 if ($this->SingleTo) { … … 1671 1716 throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1672 1717 } 1718 $this->edebug("To: {$toAddr}"); 1673 1719 fwrite($mail, 'To: ' . $toAddr . "\n"); 1674 1720 fwrite($mail, $header); … … 1685 1731 [] 1686 1732 ); 1733 $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); 1687 1734 if (0 !== $result) { 1688 1735 throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); … … 1707 1754 [] 1708 1755 ); 1756 $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); 1709 1757 if (0 !== $result) { 1710 1758 throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); … … 1727 1775 protected static function isShellSafe($string) 1728 1776 { 1729 // 1777 //Future-proof 1730 1778 if ( 1731 1779 escapeshellcmd($string) !== $string … … 1740 1788 $c = $string[$i]; 1741 1789 1742 // 1743 // 1744 // 1790 //All other characters have a special meaning in at least one common shell, including = and +. 1791 //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. 1792 //Note that this does permit non-Latin alphanumeric characters based on the current locale. 1745 1793 if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { 1746 1794 return false; … … 1812 1860 //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html 1813 1861 //Example problem: https://www.drupal.org/node/1057954 1814 // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. 1815 if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { 1816 $params = sprintf('-f%s', $this->Sender); 1862 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. 1863 if ('' === $this->Sender) { 1864 $this->Sender = $this->From; 1865 } 1866 if (empty($this->Sender) && !empty(ini_get('sendmail_from'))) { 1867 //PHP config has a sender address we can use 1868 $this->Sender = ini_get('sendmail_from'); 1817 1869 } 1818 1870 if (!empty($this->Sender) && static::validateAddress($this->Sender)) { 1871 if (self::isShellSafe($this->Sender)) { 1872 $params = sprintf('-f%s', $this->Sender); 1873 } 1819 1874 $old_from = ini_get('sendmail_from'); 1820 1875 ini_set('sendmail_from', $this->Sender); … … 1902 1957 1903 1958 $callbacks = []; 1904 // 1959 //Attempt to send to all recipients 1905 1960 foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { 1906 1961 foreach ($togroup as $to) { … … 1917 1972 } 1918 1973 1919 // 1974 //Only send the DATA command if we have viable recipients 1920 1975 if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { 1921 1976 throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); … … 1979 2034 } 1980 2035 1981 // 2036 //Already connected? 1982 2037 if ($this->smtp->connected()) { 1983 2038 return true; … … 2001 2056 ) { 2002 2057 $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); 2003 // 2058 //Not a valid host entry 2004 2059 continue; 2005 2060 } 2006 // 2007 // 2008 // 2009 // 2010 // 2061 //$hostinfo[1]: optional ssl or tls prefix 2062 //$hostinfo[2]: the hostname 2063 //$hostinfo[3]: optional port number 2064 //The host string prefix can temporarily override the current setting for SMTPSecure 2065 //If it's not specified, the default value is used 2011 2066 2012 2067 //Check the host name is a valid name or IP address before trying to use it … … 2020 2075 if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { 2021 2076 $prefix = 'ssl://'; 2022 $tls = false; // 2077 $tls = false; //Can't have SSL and TLS at the same time 2023 2078 $secure = static::ENCRYPTION_SMTPS; 2024 2079 } elseif ('tls' === $hostinfo[1]) { 2025 2080 $tls = true; 2026 // tlsdoesn't use a prefix2081 //TLS doesn't use a prefix 2027 2082 $secure = static::ENCRYPTION_STARTTLS; 2028 2083 } … … 2054 2109 $this->smtp->hello($hello); 2055 2110 //Automatically enable TLS encryption if: 2056 // 2057 // 2058 // 2059 // 2111 //* it's not disabled 2112 //* we have openssl extension 2113 //* we are not already using SSL 2114 //* the server offers STARTTLS 2060 2115 if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { 2061 2116 $tls = true; … … 2065 2120 throw new Exception($this->lang('connect_host')); 2066 2121 } 2067 // 2122 //We must resend EHLO after TLS negotiation 2068 2123 $this->smtp->hello($hello); 2069 2124 } … … 2083 2138 $lastexception = $exc; 2084 2139 $this->edebug($exc->getMessage()); 2085 // 2140 //We must have connected, but then failed TLS or Auth, so close connection nicely 2086 2141 $this->smtp->quit(); 2087 2142 } 2088 2143 } 2089 2144 } 2090 // 2145 //If we get here, all connection attempts have failed, so close connection hard 2091 2146 $this->smtp->close(); 2092 // 2147 //As we've caught all exceptions, just report whatever the last one was 2093 2148 if ($this->exceptions && null !== $lastexception) { 2094 2149 throw $lastexception; … … 2121 2176 public function setLanguage($langcode = 'en', $lang_path = '') 2122 2177 { 2123 // 2178 //Backwards compatibility for renamed language codes 2124 2179 $renamed_langcodes = [ 2125 2180 'br' => 'pt_br', … … 2137 2192 } 2138 2193 2139 // 2194 //Define full set of translatable strings in English 2140 2195 $PHPMAILER_LANG = [ 2141 2196 'authenticate' => 'SMTP Error: Could not authenticate.', … … 2162 2217 ]; 2163 2218 if (empty($lang_path)) { 2164 // 2219 //Calculate an absolute path so it can work if CWD is not here 2165 2220 $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; 2166 2221 } … … 2171 2226 $foundlang = true; 2172 2227 $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; 2173 // 2228 //There is no English translation file 2174 2229 if ('en' !== $langcode) { 2175 // 2230 //Make sure language file path is readable 2176 2231 if (!static::fileIsAccessible($lang_file)) { 2177 2232 $foundlang = false; 2178 2233 } else { 2179 // 2180 // 2234 //Overwrite language-specific strings. 2235 //This way we'll never have missing translation keys. 2181 2236 $foundlang = include $lang_file; 2182 2237 } … … 2184 2239 $this->language = $PHPMAILER_LANG; 2185 2240 2186 return (bool) $foundlang; // 2241 return (bool) $foundlang; //Returns false if language not found 2187 2242 } 2188 2243 … … 2228 2283 public function addrFormat($addr) 2229 2284 { 2230 if (empty($addr[1])) { // 2285 if (empty($addr[1])) { //No name provided 2231 2286 return $this->secureHeader($addr[0]); 2232 2287 } … … 2255 2310 $soft_break = static::$LE; 2256 2311 } 2257 // 2258 // 2312 //If utf-8 encoding is used, we will need to make sure we don't 2313 //split multibyte characters when we wrap 2259 2314 $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet); 2260 2315 $lelen = strlen(static::$LE); … … 2356 2411 $encodedCharPos = strpos($lastChunk, '='); 2357 2412 if (false !== $encodedCharPos) { 2358 // 2359 // 2413 //Found start of encoded character byte within $lookBack block. 2414 //Check the encoded byte value (the 2 chars after the '=') 2360 2415 $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); 2361 2416 $dec = hexdec($hex); 2362 2417 if ($dec < 128) { 2363 // 2364 // 2365 // 2418 //Single byte character. 2419 //If the encoded char was found at pos 0, it will fit 2420 //otherwise reduce maxLength to start of the encoded char 2366 2421 if ($encodedCharPos > 0) { 2367 2422 $maxLength -= $lookBack - $encodedCharPos; … … 2369 2424 $foundSplitPos = true; 2370 2425 } elseif ($dec >= 192) { 2371 // 2372 // 2426 //First byte of a multi byte character 2427 //Reduce maxLength to split at start of character 2373 2428 $maxLength -= $lookBack - $encodedCharPos; 2374 2429 $foundSplitPos = true; 2375 2430 } elseif ($dec < 192) { 2376 // 2431 //Middle byte of a multi byte character, look further back 2377 2432 $lookBack += 3; 2378 2433 } 2379 2434 } else { 2380 // 2435 //No encoded character found 2381 2436 $foundSplitPos = true; 2382 2437 } … … 2422 2477 $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate); 2423 2478 2424 // 2479 //The To header is created automatically by mail(), so needs to be omitted here 2425 2480 if ('mail' !== $this->Mailer) { 2426 2481 if ($this->SingleTo) { … … 2436 2491 $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]); 2437 2492 2438 // 2493 //sendmail and mail() extract Cc from the header before sending 2439 2494 if (count($this->cc) > 0) { 2440 2495 $result .= $this->addrAppend('Cc', $this->cc); 2441 2496 } 2442 2497 2443 // 2498 //sendmail and mail() extract Bcc from the header before sending 2444 2499 if ( 2445 2500 ( … … 2455 2510 } 2456 2511 2457 // 2512 //mail() sets the subject itself 2458 2513 if ('mail' !== $this->Mailer) { 2459 2514 $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); 2460 2515 } 2461 2516 2462 // 2463 // 2517 //Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 2518 //https://tools.ietf.org/html/rfc5322#section-3.6.4 2464 2519 if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) { 2465 2520 $this->lastMessageID = $this->MessageID; … … 2487 2542 } 2488 2543 2489 // 2544 //Add custom headers 2490 2545 foreach ($this->CustomHeader as $header) { 2491 2546 $result .= $this->headerLine( … … 2529 2584 break; 2530 2585 default: 2531 // 2586 //Catches case 'plain': and case '': 2532 2587 $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); 2533 2588 $ismultipart = false; 2534 2589 break; 2535 2590 } 2536 // 2591 //RFC1341 part 5 says 7bit is assumed if not specified 2537 2592 if (static::ENCODING_7BIT !== $this->Encoding) { 2538 // 2593 //RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE 2539 2594 if ($ismultipart) { 2540 2595 if (static::ENCODING_8BIT === $this->Encoding) { 2541 2596 $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT); 2542 2597 } 2543 // 2598 //The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible 2544 2599 } else { 2545 2600 $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); 2546 2601 } 2547 }2548 2549 if ('mail' !== $this->Mailer) {2550 // $result .= static::$LE;2551 2602 } 2552 2603 … … 2819 2870 break; 2820 2871 default: 2821 // 2872 //Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types 2822 2873 //Reset the `Encoding` property in case we changed it for line length reasons 2823 2874 $this->Encoding = $bodyEncoding; … … 2910 2961 $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); 2911 2962 $result .= static::$LE; 2912 // 2963 //RFC1341 part 5 says 7bit is assumed if not specified 2913 2964 if (static::ENCODING_7BIT !== $encoding) { 2914 2965 $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); … … 3008 3059 } 3009 3060 3010 // 3061 //If a MIME type is not specified, try to work it out from the file name 3011 3062 if ('' === $type) { 3012 3063 $type = static::filenameToType($path); … … 3027 3078 3 => $encoding, 3028 3079 4 => $type, 3029 5 => false, // 3080 5 => false, //isStringAttachment 3030 3081 6 => $disposition, 3031 3082 7 => $name, … … 3067 3118 protected function attachAll($disposition_type, $boundary) 3068 3119 { 3069 // 3120 //Return text of body 3070 3121 $mime = []; 3071 3122 $cidUniq = []; 3072 3123 $incl = []; 3073 3124 3074 // 3125 //Add all attachments 3075 3126 foreach ($this->attachment as $attachment) { 3076 // 3127 //Check if it is a valid disposition_filter 3077 3128 if ($attachment[6] === $disposition_type) { 3078 // 3129 //Check for string attachment 3079 3130 $string = ''; 3080 3131 $path = ''; … … 3117 3168 ); 3118 3169 } 3119 // 3170 //RFC1341 part 5 says 7bit is assumed if not specified 3120 3171 if (static::ENCODING_7BIT !== $encoding) { 3121 3172 $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE); … … 3127 3178 } 3128 3179 3129 // 3180 //Allow for bypassing the Content-Disposition header 3130 3181 if (!empty($disposition)) { 3131 3182 $encoded_name = $this->encodeHeader($this->secureHeader($name)); … … 3148 3199 } 3149 3200 3150 // 3201 //Encode as string attachment 3151 3202 if ($bString) { 3152 3203 $mime[] = $this->encodeString($string, $encoding); … … 3224 3275 case static::ENCODING_8BIT: 3225 3276 $encoded = static::normalizeBreaks($str); 3226 // 3277 //Make sure it ends with a line break 3227 3278 if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) { 3228 3279 $encoded .= static::$LE; … … 3262 3313 case 'phrase': 3263 3314 if (!preg_match('/[\200-\377]/', $str)) { 3264 // 3315 //Can't use addslashes as we don't know the value of magic_quotes_sybase 3265 3316 $encoded = addcslashes($str, "\0..\37\177\\\""); 3266 3317 if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { … … 3288 3339 } 3289 3340 3290 // 3341 //Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<content>?=`"). 3291 3342 $overhead = 8 + strlen($charset); 3292 3343 … … 3297 3348 } 3298 3349 3299 // 3350 //Select the encoding that produces the shortest output and/or prevents corruption. 3300 3351 if ($matchcount > strlen($str) / 3) { 3301 // 3352 //More than 1/3 of the content needs encoding, use B-encode. 3302 3353 $encoding = 'B'; 3303 3354 } elseif ($matchcount > 0) { 3304 // 3355 //Less than 1/3 of the content needs encoding, use Q-encode. 3305 3356 $encoding = 'Q'; 3306 3357 } elseif (strlen($str) > $maxlen) { 3307 // 3358 //No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption. 3308 3359 $encoding = 'Q'; 3309 3360 } else { 3310 // 3361 //No reformatting needed 3311 3362 $encoding = false; 3312 3363 } … … 3315 3366 case 'B': 3316 3367 if ($this->hasMultiBytes($str)) { 3317 // 3318 // 3368 //Use a custom function which correctly encodes and wraps long 3369 //multibyte strings without breaking lines within a character 3319 3370 $encoded = $this->base64EncodeWrapMB($str, "\n"); 3320 3371 } else { … … 3351 3402 } 3352 3403 3353 // 3404 //Assume no multibytes (we can't handle without mbstring functions anyway) 3354 3405 return false; 3355 3406 } … … 3389 3440 3390 3441 $mb_length = mb_strlen($str, $this->CharSet); 3391 // 3442 //Each line must have length <= 75, including $start and $end 3392 3443 $length = 75 - strlen($start) - strlen($end); 3393 // 3444 //Average multi-byte ratio 3394 3445 $ratio = $mb_length / strlen($str); 3395 // 3446 //Base64 has a 4:3 ratio 3396 3447 $avgLength = floor($length * $ratio * .75); 3397 3448 … … 3408 3459 } 3409 3460 3410 // 3461 //Chomp the last linefeed 3411 3462 return substr($encoded, 0, -strlen($linebreak)); 3412 3463 } … … 3437 3488 public function encodeQ($str, $position = 'text') 3438 3489 { 3439 // 3490 //There should not be any EOL in the string 3440 3491 $pattern = ''; 3441 3492 $encoded = str_replace(["\r", "\n"], '', $str); 3442 3493 switch (strtolower($position)) { 3443 3494 case 'phrase': 3444 // 3495 //RFC 2047 section 5.3 3445 3496 $pattern = '^A-Za-z0-9!*+\/ -'; 3446 3497 break; … … 3455 3506 case 'text': 3456 3507 default: 3457 // 3458 // 3508 //RFC 2047 section 5.1 3509 //Replace every high ascii, control, =, ? and _ characters 3459 3510 $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; 3460 3511 break; … … 3462 3513 $matches = []; 3463 3514 if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { 3464 // 3465 // 3515 //If the string contains an '=', make sure it's the first thing we replace 3516 //so as to avoid double-encoding 3466 3517 $eqkey = array_search('=', $matches[0], true); 3467 3518 if (false !== $eqkey) { … … 3473 3524 } 3474 3525 } 3475 // 3476 // 3526 //Replace spaces with _ (more readable than =20) 3527 //RFC 2047 section 4.2(2) 3477 3528 return str_replace(' ', '_', $encoded); 3478 3529 } … … 3501 3552 ) { 3502 3553 try { 3503 // 3554 //If a MIME type is not specified, try to work it out from the file name 3504 3555 if ('' === $type) { 3505 3556 $type = static::filenameToType($filename); … … 3510 3561 } 3511 3562 3512 // 3563 //Append to $attachment array 3513 3564 $this->attachment[] = [ 3514 3565 0 => $string, … … 3517 3568 3 => $encoding, 3518 3569 4 => $type, 3519 5 => true, // 3570 5 => true, //isStringAttachment 3520 3571 6 => $disposition, 3521 3572 7 => 0, … … 3568 3619 } 3569 3620 3570 // 3621 //If a MIME type is not specified, try to work it out from the file name 3571 3622 if ('' === $type) { 3572 3623 $type = static::filenameToType($path); … … 3582 3633 } 3583 3634 3584 // 3635 //Append to $attachment array 3585 3636 $this->attachment[] = [ 3586 3637 0 => $path, … … 3589 3640 3 => $encoding, 3590 3641 4 => $type, 3591 5 => false, // 3642 5 => false, //isStringAttachment 3592 3643 6 => $disposition, 3593 3644 7 => $cid, … … 3634 3685 ) { 3635 3686 try { 3636 // 3687 //If a MIME type is not specified, try to work it out from the name 3637 3688 if ('' === $type && !empty($name)) { 3638 3689 $type = static::filenameToType($name); … … 3643 3694 } 3644 3695 3645 // 3696 //Append to $attachment array 3646 3697 $this->attachment[] = [ 3647 3698 0 => $string, … … 3650 3701 3 => $encoding, 3651 3702 4 => $type, 3652 5 => true, // 3703 5 => true, //isStringAttachment 3653 3704 6 => $disposition, 3654 3705 7 => $cid, … … 3870 3921 public static function rfcDate() 3871 3922 { 3872 // 3873 // 3923 //Set the time zone to whatever the default is to avoid 500 errors 3924 //Will default to UTC if it's not set properly in php.ini 3874 3925 date_default_timezone_set(@date_default_timezone_get()); 3875 3926 … … 3949 4000 { 3950 4001 if (count($this->language) < 1) { 3951 $this->setLanguage(); // set the default language4002 $this->setLanguage(); //Set the default language 3952 4003 } 3953 4004 3954 4005 if (array_key_exists($key, $this->language)) { 3955 4006 if ('smtp_connect_failed' === $key) { 3956 //Include a link to troubleshooting docs on SMTP connection failure 3957 // this is by far the biggest cause of support questions4007 //Include a link to troubleshooting docs on SMTP connection failure. 4008 //This is by far the biggest cause of support questions 3958 4009 //but it's usually not PHPMailer's fault. 3959 4010 return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; … … 3990 4041 { 3991 4042 if (null === $value && strpos($name, ':') !== false) { 3992 // 4043 //Value passed in as name:value 3993 4044 list($name, $value) = explode(':', $name, 2); 3994 4045 } … … 4044 4095 if (array_key_exists(2, $images)) { 4045 4096 if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) { 4046 // 4097 //Ensure $basedir has a trailing / 4047 4098 $basedir .= '/'; 4048 4099 } 4049 4100 foreach ($images[2] as $imgindex => $url) { 4050 // 4101 //Convert data URIs into embedded images 4051 4102 //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" 4052 4103 $match = []; … … 4062 4113 //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places 4063 4114 //will only be embedded once, even if it used a different encoding 4064 $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // 4115 $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; //RFC2392 S 2 4065 4116 4066 4117 if (!$this->cidExists($cid)) { … … 4081 4132 } 4082 4133 if ( 4083 // 4134 //Only process relative URLs if a basedir is provided (i.e. no absolute local paths) 4084 4135 !empty($basedir) 4085 // 4136 //Ignore URLs containing parent dir traversal (..) 4086 4137 && (strpos($url, '..') === false) 4087 // 4138 //Do not change urls that are already inline images 4088 4139 && 0 !== strpos($url, 'cid:') 4089 // 4140 //Do not change absolute URLs, including anonymous protocol 4090 4141 && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url) 4091 4142 ) { … … 4095 4146 $directory = ''; 4096 4147 } 4097 // 4148 //RFC2392 S 2 4098 4149 $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0'; 4099 4150 if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) { … … 4122 4173 } 4123 4174 $this->isHTML(); 4124 // 4175 //Convert all message body line breaks to LE, makes quoted-printable encoding work much better 4125 4176 $this->Body = static::normalizeBreaks($message); 4126 4177 $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced)); … … 4141 4192 * 4142 4193 * ```php 4143 * // 4194 * //Use default conversion 4144 4195 * $plain = $mail->html2text($html); 4145 * // 4196 * //Use your own custom converter 4146 4197 * $plain = $mail->html2text($html, function($html) { 4147 4198 * $converter = new MyHtml2text($html); … … 4310 4361 public static function filenameToType($filename) 4311 4362 { 4312 // 4363 //In case the path is a URL, strip any query string before getting extension 4313 4364 $qpos = strpos($filename, '?'); 4314 4365 if (false !== $qpos) { … … 4421 4472 $breaktype = static::$LE; 4422 4473 } 4423 // 4474 //Normalise to \n 4424 4475 $text = str_replace([self::CRLF, "\r"], "\n", $text); 4425 // 4476 //Now convert LE as needed 4426 4477 if ("\n" !== $breaktype) { 4427 4478 $text = str_replace("\n", $breaktype, $text); … … 4529 4580 } 4530 4581 if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { 4531 if ( PHP_MAJOR_VERSION < 8) {4582 if (\PHP_MAJOR_VERSION < 8) { 4532 4583 openssl_pkey_free($privKey); 4533 4584 } … … 4535 4586 return base64_encode($signature); 4536 4587 } 4537 if ( PHP_MAJOR_VERSION < 8) {4588 if (\PHP_MAJOR_VERSION < 8) { 4538 4589 openssl_pkey_free($privKey); 4539 4590 } … … 4602 4653 return self::CRLF; 4603 4654 } 4604 // 4655 //Normalize line endings to CRLF 4605 4656 $body = static::normalizeBreaks($body, self::CRLF); 4606 4657 … … 4622 4673 public function DKIM_Add($headers_line, $subject, $body) 4623 4674 { 4624 $DKIMsignatureType = 'rsa-sha256'; // 4625 $DKIMcanonicalization = 'relaxed/simple'; // 4626 $DKIMquery = 'dns/txt'; // 4675 $DKIMsignatureType = 'rsa-sha256'; //Signature & hash algorithms 4676 $DKIMcanonicalization = 'relaxed/simple'; //Canonicalization methods of header & body 4677 $DKIMquery = 'dns/txt'; //Query method 4627 4678 $DKIMtime = time(); 4628 4679 //Always sign these headers without being asked … … 4725 4776 $headerValues = implode(static::$LE, $headersToSign); 4726 4777 $body = $this->DKIM_BodyC($body); 4727 $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body 4778 //Base64 of packed binary SHA-256 hash of body 4779 $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); 4728 4780 $ident = ''; 4729 4781 if ('' !== $this->DKIM_identity) {
Note: See TracChangeset
for help on using the changeset viewer.