1183 * |
1183 * |
1184 * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation |
1184 * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation |
1185 * |
1185 * |
1186 * @param string $addrstr The address list string |
1186 * @param string $addrstr The address list string |
1187 * @param bool $useimap Whether to use the IMAP extension to parse the list |
1187 * @param bool $useimap Whether to use the IMAP extension to parse the list |
|
1188 * @param string $charset The charset to use when decoding the address list string. |
1188 * |
1189 * |
1189 * @return array |
1190 * @return array |
1190 */ |
1191 */ |
1191 public static function parseAddresses($addrstr, $useimap = true) |
1192 public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) |
1192 { |
1193 { |
1193 $addresses = []; |
1194 $addresses = []; |
1194 if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { |
1195 if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { |
1195 //Use this built-in parser if it's available |
1196 //Use this built-in parser if it's available |
1196 $list = imap_rfc822_parse_adrlist($addrstr, ''); |
1197 $list = imap_rfc822_parse_adrlist($addrstr, ''); |
|
1198 // Clear any potential IMAP errors to get rid of notices being thrown at end of script. |
|
1199 imap_errors(); |
1197 foreach ($list as $address) { |
1200 foreach ($list as $address) { |
1198 if ( |
1201 if ( |
1199 ('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress( |
1202 '.SYNTAX-ERROR.' !== $address->host && |
1200 $address->mailbox . '@' . $address->host |
1203 static::validateAddress($address->mailbox . '@' . $address->host) |
1201 ) |
|
1202 ) { |
1204 ) { |
1203 //Decode the name part if it's present and encoded |
1205 //Decode the name part if it's present and encoded |
1204 if ( |
1206 if ( |
1205 property_exists($address, 'personal') && |
1207 property_exists($address, 'personal') && |
1206 extension_loaded('mbstring') && |
1208 //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled |
1207 preg_match('/^=\?.*\?=$/', $address->personal) |
1209 defined('MB_CASE_UPPER') && |
|
1210 preg_match('/^=\?.*\?=$/s', $address->personal) |
1208 ) { |
1211 ) { |
|
1212 $origCharset = mb_internal_encoding(); |
|
1213 mb_internal_encoding($charset); |
|
1214 //Undo any RFC2047-encoded spaces-as-underscores |
|
1215 $address->personal = str_replace('_', '=20', $address->personal); |
|
1216 //Decode the name |
1209 $address->personal = mb_decode_mimeheader($address->personal); |
1217 $address->personal = mb_decode_mimeheader($address->personal); |
|
1218 mb_internal_encoding($origCharset); |
1210 } |
1219 } |
1211 |
1220 |
1212 $addresses[] = [ |
1221 $addresses[] = [ |
1213 'name' => (property_exists($address, 'personal') ? $address->personal : ''), |
1222 'name' => (property_exists($address, 'personal') ? $address->personal : ''), |
1214 'address' => $address->mailbox . '@' . $address->host, |
1223 'address' => $address->mailbox . '@' . $address->host, |
1232 } else { |
1241 } else { |
1233 list($name, $email) = explode('<', $address); |
1242 list($name, $email) = explode('<', $address); |
1234 $email = trim(str_replace('>', '', $email)); |
1243 $email = trim(str_replace('>', '', $email)); |
1235 $name = trim($name); |
1244 $name = trim($name); |
1236 if (static::validateAddress($email)) { |
1245 if (static::validateAddress($email)) { |
|
1246 //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled |
1237 //If this name is encoded, decode it |
1247 //If this name is encoded, decode it |
1238 if (preg_match('/^=\?.*\?=$/', $name)) { |
1248 if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) { |
|
1249 $origCharset = mb_internal_encoding(); |
|
1250 mb_internal_encoding($charset); |
|
1251 //Undo any RFC2047-encoded spaces-as-underscores |
|
1252 $name = str_replace('_', '=20', $name); |
|
1253 //Decode the name |
1239 $name = mb_decode_mimeheader($name); |
1254 $name = mb_decode_mimeheader($name); |
|
1255 mb_internal_encoding($origCharset); |
1240 } |
1256 } |
1241 $addresses[] = [ |
1257 $addresses[] = [ |
1242 //Remove any surrounding quotes and spaces from the name |
1258 //Remove any surrounding quotes and spaces from the name |
1243 'name' => trim($name, '\'" '), |
1259 'name' => trim($name, '\'" '), |
1244 'address' => $email, |
1260 'address' => $email, |
1434 $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); |
1450 $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); |
1435 //Ignore IDE complaints about this line - method signature changed in PHP 5.4 |
1451 //Ignore IDE complaints about this line - method signature changed in PHP 5.4 |
1436 $errorcode = 0; |
1452 $errorcode = 0; |
1437 if (defined('INTL_IDNA_VARIANT_UTS46')) { |
1453 if (defined('INTL_IDNA_VARIANT_UTS46')) { |
1438 //Use the current punycode standard (appeared in PHP 7.2) |
1454 //Use the current punycode standard (appeared in PHP 7.2) |
1439 $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_UTS46); |
1455 $punycode = idn_to_ascii( |
|
1456 $domain, |
|
1457 \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | |
|
1458 \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, |
|
1459 \INTL_IDNA_VARIANT_UTS46 |
|
1460 ); |
1440 } elseif (defined('INTL_IDNA_VARIANT_2003')) { |
1461 } elseif (defined('INTL_IDNA_VARIANT_2003')) { |
1441 //Fall back to this old, deprecated/removed encoding |
1462 //Fall back to this old, deprecated/removed encoding |
1442 // phpcs:ignore PHPCompatibility.Constants.RemovedConstants.intl_idna_variant_2003Deprecated |
1463 // phpcs:ignore PHPCompatibility.Constants.RemovedConstants.intl_idna_variant_2003Deprecated |
1443 $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); |
1464 $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); |
1444 } else { |
1465 } else { |
1687 //causing problems, so we don't use one |
1703 //causing problems, so we don't use one |
1688 //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html |
1704 //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html |
1689 //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html |
1705 //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html |
1690 //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html |
1706 //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html |
1691 //Example problem: https://www.drupal.org/node/1057954 |
1707 //Example problem: https://www.drupal.org/node/1057954 |
1692 if (empty($this->Sender) && !empty(ini_get('sendmail_from'))) { |
1708 |
|
1709 //PHP 5.6 workaround |
|
1710 $sendmail_from_value = ini_get('sendmail_from'); |
|
1711 if (empty($this->Sender) && !empty($sendmail_from_value)) { |
1693 //PHP config has a sender address we can use |
1712 //PHP config has a sender address we can use |
1694 $this->Sender = ini_get('sendmail_from'); |
1713 $this->Sender = ini_get('sendmail_from'); |
1695 } |
1714 } |
1696 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. |
1715 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. |
1697 if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { |
1716 if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { |
1869 //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html |
1894 //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html |
1870 //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html |
1895 //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html |
1871 //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html |
1896 //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html |
1872 //Example problem: https://www.drupal.org/node/1057954 |
1897 //Example problem: https://www.drupal.org/node/1057954 |
1873 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. |
1898 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. |
1874 if (empty($this->Sender) && !empty(ini_get('sendmail_from'))) { |
1899 |
|
1900 //PHP 5.6 workaround |
|
1901 $sendmail_from_value = ini_get('sendmail_from'); |
|
1902 if (empty($this->Sender) && !empty($sendmail_from_value)) { |
1875 //PHP config has a sender address we can use |
1903 //PHP config has a sender address we can use |
1876 $this->Sender = ini_get('sendmail_from'); |
1904 $this->Sender = ini_get('sendmail_from'); |
1877 } |
1905 } |
1878 if (!empty($this->Sender) && static::validateAddress($this->Sender)) { |
1906 if (!empty($this->Sender) && static::validateAddress($this->Sender)) { |
1879 if (self::isShellSafe($this->Sender)) { |
1907 if (self::isShellSafe($this->Sender)) { |
2211 } |
2245 } |
2212 |
2246 |
2213 //Define full set of translatable strings in English |
2247 //Define full set of translatable strings in English |
2214 $PHPMAILER_LANG = [ |
2248 $PHPMAILER_LANG = [ |
2215 'authenticate' => 'SMTP Error: Could not authenticate.', |
2249 'authenticate' => 'SMTP Error: Could not authenticate.', |
|
2250 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . |
|
2251 ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . |
|
2252 ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', |
2216 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', |
2253 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', |
2217 'data_not_accepted' => 'SMTP Error: data not accepted.', |
2254 'data_not_accepted' => 'SMTP Error: data not accepted.', |
2218 'empty_message' => 'Message body empty', |
2255 'empty_message' => 'Message body empty', |
2219 'encoding' => 'Unknown encoding: ', |
2256 'encoding' => 'Unknown encoding: ', |
2220 'execute' => 'Could not execute: ', |
2257 'execute' => 'Could not execute: ', |
|
2258 'extension_missing' => 'Extension missing: ', |
2221 'file_access' => 'Could not access file: ', |
2259 'file_access' => 'Could not access file: ', |
2222 'file_open' => 'File Error: Could not open file: ', |
2260 'file_open' => 'File Error: Could not open file: ', |
2223 'from_failed' => 'The following From address failed: ', |
2261 'from_failed' => 'The following From address failed: ', |
2224 'instantiate' => 'Could not instantiate mail function.', |
2262 'instantiate' => 'Could not instantiate mail function.', |
2225 'invalid_address' => 'Invalid address: ', |
2263 'invalid_address' => 'Invalid address: ', |
|
2264 'invalid_header' => 'Invalid header name or value', |
2226 'invalid_hostentry' => 'Invalid hostentry: ', |
2265 'invalid_hostentry' => 'Invalid hostentry: ', |
2227 'invalid_host' => 'Invalid host: ', |
2266 'invalid_host' => 'Invalid host: ', |
2228 'mailer_not_supported' => ' mailer is not supported.', |
2267 'mailer_not_supported' => ' mailer is not supported.', |
2229 'provide_address' => 'You must provide at least one recipient email address.', |
2268 'provide_address' => 'You must provide at least one recipient email address.', |
2230 'recipients_failed' => 'SMTP Error: The following recipients failed: ', |
2269 'recipients_failed' => 'SMTP Error: The following recipients failed: ', |
2231 'signing' => 'Signing Error: ', |
2270 'signing' => 'Signing Error: ', |
|
2271 'smtp_code' => 'SMTP code: ', |
|
2272 'smtp_code_ex' => 'Additional SMTP info: ', |
2232 'smtp_connect_failed' => 'SMTP connect() failed.', |
2273 'smtp_connect_failed' => 'SMTP connect() failed.', |
|
2274 'smtp_detail' => 'Detail: ', |
2233 'smtp_error' => 'SMTP server error: ', |
2275 'smtp_error' => 'SMTP server error: ', |
2234 'variable_set' => 'Cannot set or reset variable: ', |
2276 'variable_set' => 'Cannot set or reset variable: ', |
2235 'extension_missing' => 'Extension missing: ', |
|
2236 ]; |
2277 ]; |
2237 if (empty($lang_path)) { |
2278 if (empty($lang_path)) { |
2238 //Calculate an absolute path so it can work if CWD is not here |
2279 //Calculate an absolute path so it can work if CWD is not here |
2239 $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; |
2280 $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; |
2240 } |
2281 } |
|
2282 |
2241 //Validate $langcode |
2283 //Validate $langcode |
2242 if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { |
2284 $foundlang = true; |
|
2285 $langcode = strtolower($langcode); |
|
2286 if ( |
|
2287 !preg_match('/^(?P<lang>[a-z]{2})(?P<script>_[a-z]{4})?(?P<country>_[a-z]{2})?$/', $langcode, $matches) |
|
2288 && $langcode !== 'en' |
|
2289 ) { |
|
2290 $foundlang = false; |
2243 $langcode = 'en'; |
2291 $langcode = 'en'; |
2244 } |
2292 } |
2245 $foundlang = true; |
2293 |
2246 $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; |
|
2247 //There is no English translation file |
2294 //There is no English translation file |
2248 if ('en' !== $langcode) { |
2295 if ('en' !== $langcode) { |
2249 //Make sure language file path is readable |
2296 $langcodes = []; |
2250 if (!static::fileIsAccessible($lang_file)) { |
2297 if (!empty($matches['script']) && !empty($matches['country'])) { |
|
2298 $langcodes[] = $matches['lang'] . $matches['script'] . $matches['country']; |
|
2299 } |
|
2300 if (!empty($matches['country'])) { |
|
2301 $langcodes[] = $matches['lang'] . $matches['country']; |
|
2302 } |
|
2303 if (!empty($matches['script'])) { |
|
2304 $langcodes[] = $matches['lang'] . $matches['script']; |
|
2305 } |
|
2306 $langcodes[] = $matches['lang']; |
|
2307 |
|
2308 //Try and find a readable language file for the requested language. |
|
2309 $foundFile = false; |
|
2310 foreach ($langcodes as $code) { |
|
2311 $lang_file = $lang_path . 'phpmailer.lang-' . $code . '.php'; |
|
2312 if (static::fileIsAccessible($lang_file)) { |
|
2313 $foundFile = true; |
|
2314 break; |
|
2315 } |
|
2316 } |
|
2317 |
|
2318 if ($foundFile === false) { |
2251 $foundlang = false; |
2319 $foundlang = false; |
2252 } else { |
2320 } else { |
2253 //$foundlang = include $lang_file; |
|
2254 $lines = file($lang_file); |
2321 $lines = file($lang_file); |
2255 foreach ($lines as $line) { |
2322 foreach ($lines as $line) { |
2256 //Translation file lines look like this: |
2323 //Translation file lines look like this: |
2257 //$PHPMAILER_LANG['authenticate'] = 'SMTP-Fehler: Authentifizierung fehlgeschlagen.'; |
2324 //$PHPMAILER_LANG['authenticate'] = 'SMTP-Fehler: Authentifizierung fehlgeschlagen.'; |
2258 //These files are parsed as text and not PHP so as to avoid the possibility of code injection |
2325 //These files are parsed as text and not PHP so as to avoid the possibility of code injection |
2551 $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); |
2622 $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); |
2552 } |
2623 } |
2553 |
2624 |
2554 //Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 |
2625 //Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 |
2555 //https://tools.ietf.org/html/rfc5322#section-3.6.4 |
2626 //https://tools.ietf.org/html/rfc5322#section-3.6.4 |
2556 if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) { |
2627 if ( |
|
2628 '' !== $this->MessageID && |
|
2629 preg_match( |
|
2630 '/^<((([a-z\d!#$%&\'*+\/=?^_`{|}~-]+(\.[a-z\d!#$%&\'*+\/=?^_`{|}~-]+)*)' . |
|
2631 '|("(([\x01-\x08\x0B\x0C\x0E-\x1F\x7F]|[\x21\x23-\x5B\x5D-\x7E])' . |
|
2632 '|(\\[\x01-\x09\x0B\x0C\x0E-\x7F]))*"))@(([a-z\d!#$%&\'*+\/=?^_`{|}~-]+' . |
|
2633 '(\.[a-z\d!#$%&\'*+\/=?^_`{|}~-]+)*)|(\[(([\x01-\x08\x0B\x0C\x0E-\x1F\x7F]' . |
|
2634 '|[\x21-\x5A\x5E-\x7E])|(\\[\x01-\x09\x0B\x0C\x0E-\x7F]))*\])))>$/Di', |
|
2635 $this->MessageID |
|
2636 ) |
|
2637 ) { |
2557 $this->lastMessageID = $this->MessageID; |
2638 $this->lastMessageID = $this->MessageID; |
2558 } else { |
2639 } else { |
2559 $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); |
2640 $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); |
2560 } |
2641 } |
2561 $result .= $this->headerLine('Message-ID', $this->lastMessageID); |
2642 $result .= $this->headerLine('Message-ID', $this->lastMessageID); |
2562 if (null !== $this->Priority) { |
2643 if (null !== $this->Priority) { |
2563 $result .= $this->headerLine('X-Priority', $this->Priority); |
2644 $result .= $this->headerLine('X-Priority', $this->Priority); |
2564 } |
2645 } |
2565 if ('' === $this->XMailer) { |
2646 if ('' === $this->XMailer) { |
|
2647 //Empty string for default X-Mailer header |
2566 $result .= $this->headerLine( |
2648 $result .= $this->headerLine( |
2567 'X-Mailer', |
2649 'X-Mailer', |
2568 'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)' |
2650 'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)' |
2569 ); |
2651 ); |
2570 } else { |
2652 } elseif (is_string($this->XMailer) && trim($this->XMailer) !== '') { |
2571 $myXmailer = trim($this->XMailer); |
2653 //Some string |
2572 if ($myXmailer) { |
2654 $result .= $this->headerLine('X-Mailer', trim($this->XMailer)); |
2573 $result .= $this->headerLine('X-Mailer', $myXmailer); |
2655 } //Other values result in no X-Mailer header |
2574 } |
|
2575 } |
|
2576 |
2656 |
2577 if ('' !== $this->ConfirmReadingTo) { |
2657 if ('' !== $this->ConfirmReadingTo) { |
2578 $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); |
2658 $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); |
2579 } |
2659 } |
2580 |
2660 |
4079 if (null === $value && strpos($name, ':') !== false) { |
4179 if (null === $value && strpos($name, ':') !== false) { |
4080 //Value passed in as name:value |
4180 //Value passed in as name:value |
4081 list($name, $value) = explode(':', $name, 2); |
4181 list($name, $value) = explode(':', $name, 2); |
4082 } |
4182 } |
4083 $name = trim($name); |
4183 $name = trim($name); |
4084 $value = trim($value); |
4184 $value = (null === $value) ? '' : trim($value); |
4085 //Ensure name is not empty, and that neither name nor value contain line breaks |
4185 //Ensure name is not empty, and that neither name nor value contain line breaks |
4086 if (empty($name) || strpbrk($name . $value, "\r\n") !== false) { |
4186 if (empty($name) || strpbrk($name . $value, "\r\n") !== false) { |
4087 if ($this->exceptions) { |
4187 if ($this->exceptions) { |
4088 throw new Exception('Invalid header name or value'); |
4188 throw new Exception($this->lang('invalid_header')); |
4089 } |
4189 } |
4090 |
4190 |
4091 return false; |
4191 return false; |
4092 } |
4192 } |
4093 $this->CustomHeader[] = [$name, $value]; |
4193 $this->CustomHeader[] = [$name, $value]; |