mirror of
https://github.com/processwire/processwire.git
synced 2025-08-08 07:47:00 +02:00
Add support for email blacklists via $config->wireMail('blacklist') property
This commit is contained in:
@@ -1066,12 +1066,39 @@ $config->substituteModules = array(
|
|||||||
* Note you can add any other properties to the wireMail array that are supported by WireMail settings
|
* Note you can add any other properties to the wireMail array that are supported by WireMail settings
|
||||||
* like we’ve done with from, fromName and headers here. Any values set here become defaults for the
|
* like we’ve done with from, fromName and headers here. Any values set here become defaults for the
|
||||||
* WireMail module.
|
* WireMail module.
|
||||||
|
*
|
||||||
|
* Blacklist property
|
||||||
|
* ==================
|
||||||
|
* The blacklist property lets you specify email addresses, domains, partial host names or regular
|
||||||
|
* expressions that prevent sending to certain email addresses. This is demonstrated by example:
|
||||||
|
* ~~~~~
|
||||||
|
* // Example of blacklist definition
|
||||||
|
* $config->wireMail('blacklist', [
|
||||||
|
* 'email@domain.com', // blacklist this email address
|
||||||
|
* '@host.domain.com', // blacklist all emails ending with @host.domain.com
|
||||||
|
* '@domain.com', // blacklist all emails ending with @domain.com
|
||||||
|
* 'domain.com', // blacklist any email address ending with domain.com (would include mydomain.com too).
|
||||||
|
* '.domain.com', // blacklist any email address at any host off domain.com (domain.com, my.domain.com, but NOT mydomain.com).
|
||||||
|
* '/something/', // blacklist any email containing "something". PCRE regex assumed when "/" is used as opening/closing delimiter.
|
||||||
|
* '/.+@really\.bad\.com$/', // another example of using a PCRE regular expression (blocks all "@really.bad.com").
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* // Test out the blacklist
|
||||||
|
* $email = 'somebody@bad-domain.com';
|
||||||
|
* $result = $mail->isBlacklistEmail($email, [ 'why' => true ]);
|
||||||
|
* if($result === false) {
|
||||||
|
* echo "<p>Email address is not blacklisted</p>";
|
||||||
|
* } else {
|
||||||
|
* echo "<p>Email is blacklisted by rule: $result</p>";
|
||||||
|
* }
|
||||||
|
* ~~~~~
|
||||||
*
|
*
|
||||||
* #property string module Name of WireMail module to use or blank to auto-detect. (default='')
|
* #property string module Name of WireMail module to use or blank to auto-detect. (default='')
|
||||||
* #property string from Default from email address, when none provided at runtime. (default=$config->adminEmail)
|
* #property string from Default from email address, when none provided at runtime. (default=$config->adminEmail)
|
||||||
* #property string fromName Default from name string, when none provided at runtime. (default='')
|
* #property string fromName Default from name string, when none provided at runtime. (default='')
|
||||||
* #property string newline What to use for newline if different from RFC standard of "\r\n" (optional).
|
* #property string newline What to use for newline if different from RFC standard of "\r\n" (optional).
|
||||||
* #property array headers Default additional headers to send in email, key=value. (default=[])
|
* #property array headers Default additional headers to send in email, key=value. (default=[])
|
||||||
|
* #property array blacklist Email blacklist addresses or rules. (default=[])
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*
|
*
|
||||||
@@ -1081,6 +1108,7 @@ $config->wireMail = array(
|
|||||||
'from' => '',
|
'from' => '',
|
||||||
'fromName' => '',
|
'fromName' => '',
|
||||||
'headers' => array(),
|
'headers' => array(),
|
||||||
|
'blacklist' => array()
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProcessWire WireMail
|
* ProcessWire WireMail
|
||||||
*
|
*
|
||||||
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
|
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||||
* https://processwire.com
|
* https://processwire.com
|
||||||
*
|
*
|
||||||
* #pw-summary A module type that handles sending of email in ProcessWire
|
* #pw-summary A module type that handles sending of email in ProcessWire
|
||||||
@@ -91,17 +91,37 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
'attachments' => array(),
|
'attachments' => array(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->mail['header']['X-Mailer'] = "ProcessWire/" . $this->className();
|
$this->mail['header']['X-Mailer'] = "ProcessWire/" . $this->className();
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get property
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed|null
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function get($key) {
|
public function get($key) {
|
||||||
if($key === 'headers') $key = 'header';
|
if($key === 'headers') $key = 'header';
|
||||||
if(array_key_exists($key, $this->mail)) return $this->mail[$key];
|
if(array_key_exists($key, $this->mail)) return $this->mail[$key];
|
||||||
return parent::get($key);
|
return parent::get($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set property
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value
|
||||||
|
*
|
||||||
|
* @return $this|WireData
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function set($key, $value) {
|
public function set($key, $value) {
|
||||||
if($key === 'headers' || $key === 'header') {
|
if($key === 'headers' || $key === 'header') {
|
||||||
if(is_array($value)) $this->headers($value);
|
if(is_array($value)) $this->headers($value);
|
||||||
@@ -117,7 +137,7 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
public function __set($key, $value) { return $this->set($key, $value); }
|
public function __set($key, $value) { return $this->set($key, $value); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize an email address or throw WireException if invalid
|
* Sanitize an email address or throw WireException if invalid or in blacklist
|
||||||
*
|
*
|
||||||
* @param string $email
|
* @param string $email
|
||||||
* @return string
|
* @return string
|
||||||
@@ -127,9 +147,13 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
protected function sanitizeEmail($email) {
|
protected function sanitizeEmail($email) {
|
||||||
$email = strtolower(trim($email));
|
$email = strtolower(trim($email));
|
||||||
$clean = $this->wire('sanitizer')->email($email);
|
$clean = $this->wire('sanitizer')->email($email);
|
||||||
if($email != $clean) {
|
if($email !== $clean) {
|
||||||
$clean = $this->wire('sanitizer')->entities($email);
|
throw new WireException("Invalid email address: " . $this->wire('sanitizer')->entities($email));
|
||||||
throw new WireException("Invalid email address ($clean)");
|
}
|
||||||
|
/** @var WireMailTools $mail */
|
||||||
|
$mail = $this->wire('mail');
|
||||||
|
if($mail && $mail->isBlacklistEmail($email)) {
|
||||||
|
throw new WireException("Email address not allowed: " . $this->wire('sanitizer')->entities($email));
|
||||||
}
|
}
|
||||||
return $clean;
|
return $clean;
|
||||||
}
|
}
|
||||||
@@ -204,7 +228,7 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
* @param string $name Optionally provide a TO name, applicable
|
* @param string $name Optionally provide a TO name, applicable
|
||||||
* only when specifying #1 (single email) for the first argument.
|
* only when specifying #1 (single email) for the first argument.
|
||||||
* @return $this
|
* @return $this
|
||||||
* @throws WireException if any provided emails were invalid
|
* @throws WireException if any provided emails were invalid or in blacklist
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function to($email = null, $name = null) {
|
public function to($email = null, $name = null) {
|
||||||
@@ -238,8 +262,10 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
|
|
||||||
if(empty($toName)) $toName = $name; // use function arg if not overwritten
|
if(empty($toName)) $toName = $name; // use function arg if not overwritten
|
||||||
$toEmail = $this->sanitizeEmail($toEmail);
|
$toEmail = $this->sanitizeEmail($toEmail);
|
||||||
$this->mail['to'][$toEmail] = $toEmail;
|
if(strlen($toEmail)) {
|
||||||
$this->mail['toName'][$toEmail] = $this->sanitizeHeader($toName);
|
$this->mail['to'][$toEmail] = $toEmail;
|
||||||
|
$this->mail['toName'][$toEmail] = $this->sanitizeHeader($toName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -272,7 +298,7 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
* @param string $email Must be a single email address or "User Name <user@example.com>" string.
|
* @param string $email Must be a single email address or "User Name <user@example.com>" string.
|
||||||
* @param string|null An optional FROM name (same as setting/calling fromName)
|
* @param string|null An optional FROM name (same as setting/calling fromName)
|
||||||
* @return $this
|
* @return $this
|
||||||
* @throws WireException if provided email was invalid
|
* @throws WireException if provided email was invalid or in blacklist
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function from($email, $name = null) {
|
public function from($email, $name = null) {
|
||||||
@@ -307,7 +333,7 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
* @param string $email Must be a single email address or "User Name <user@example.com>" string.
|
* @param string $email Must be a single email address or "User Name <user@example.com>" string.
|
||||||
* @param string|null An optional Reply-To name (same as setting/calling replyToName method)
|
* @param string|null An optional Reply-To name (same as setting/calling replyToName method)
|
||||||
* @return $this
|
* @return $this
|
||||||
* @throws WireException if provided email was invalid
|
* @throws WireException if provided email was invalid or in blacklist
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function replyTo($email, $name = null) {
|
public function replyTo($email, $name = null) {
|
||||||
@@ -827,4 +853,5 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
public function quotedPrintableString($text) {
|
public function quotedPrintableString($text) {
|
||||||
return '=?utf-8?Q?' . quoted_printable_encode($text) . '?=';
|
return '=?utf-8?Q?' . quoted_printable_encode($text) . '?=';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProcessWire Mail Tools ($mail API variable)
|
* ProcessWire Mail Tools ($mail API variable)
|
||||||
*
|
*
|
||||||
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
|
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||||
* https://processwire.com
|
* https://processwire.com
|
||||||
*
|
*
|
||||||
* #pw-summary Provides an API interface to email and WireMail.
|
* #pw-summary Provides an API interface to email and WireMail.
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
* #pw-body
|
* #pw-body
|
||||||
*
|
*
|
||||||
* @method WireMail new($options = array()) Create a new WireMail() instance
|
* @method WireMail new($options = array()) Create a new WireMail() instance
|
||||||
|
* @method bool|string isBlacklistEmail($email, array $options = array())
|
||||||
* @property WireMail new Get a new WireMail() instance (same as method version)
|
* @property WireMail new Get a new WireMail() instance (same as method version)
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
@@ -386,5 +387,107 @@ class WireMailTools extends Wire {
|
|||||||
if($key === 'new') return $this->new();
|
if($key === 'new') return $this->new();
|
||||||
return parent::__get($key);
|
return parent::__get($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is given email address in the blacklist?
|
||||||
|
*
|
||||||
|
* - Returns boolean false if not blacklisted, true if it is.
|
||||||
|
* - Uses `$config->wireMail['blacklist']` array unless given another blacklist array in $options.
|
||||||
|
* - Always independently verify that your blacklist rules are working before assuming they do.
|
||||||
|
* - Specify true for the `why` option if you want to return the matching rule when email is in blacklist.
|
||||||
|
* - Specify true for the `throw` option if you want a WireException thrown when email is blacklisted.
|
||||||
|
*
|
||||||
|
* ~~~~~
|
||||||
|
* // Define blacklist in /site/config.php
|
||||||
|
* $config->wireMail('blacklist', [
|
||||||
|
* 'email@domain.com', // blacklist this email address
|
||||||
|
* '@host.domain.com', // blacklist all emails ending with @host.domain.com
|
||||||
|
* '@domain.com', // blacklist all emails ending with @domain.com
|
||||||
|
* 'domain.com', // blacklist any email address ending with domain.com (would include mydomain.com too).
|
||||||
|
* '.domain.com', // blacklist any email address at any host off domain.com (domain.com, my.domain.com, but NOT mydomain.com).
|
||||||
|
* '/something/', // blacklist any email containing "something". PCRE regex assumed when "/" is used as opening/closing delimiter.
|
||||||
|
* '/.+@really\.bad\.com$/', // another example of using a PCRE regular expression (blocks all "@really.bad.com").
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* // Test if email in blacklist
|
||||||
|
* $email = 'somebody@domain.com';
|
||||||
|
* $result = $mail->isBlacklistEmail($email, [ 'why' => true ]);
|
||||||
|
* if($result === false) {
|
||||||
|
* echo "<p>Email address is not blacklisted</p>";
|
||||||
|
* } else {
|
||||||
|
* echo "<p>Email is blacklisted by rule: $result</p>";
|
||||||
|
* }
|
||||||
|
* ~~~~~
|
||||||
|
*
|
||||||
|
* @param string $email Email to check
|
||||||
|
* @param array $options
|
||||||
|
* - `blacklist` (array): Use this blacklist rather than `$config->emailBlacklist` (default=[])
|
||||||
|
* - `throw` (bool): Throw WireException if email is blacklisted? (default=false)
|
||||||
|
* - `why` (bool): Return string containing matching rule when email is blacklisted? (default=false)
|
||||||
|
* @return bool|string Returns true if email is blacklisted, false if not. Returns string if `why` option specified + email blacklisted.
|
||||||
|
* @throws WireException if given a blacklist that is not an array, or if requested to via `throw` option.
|
||||||
|
* @since 3.0.129
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function ___isBlacklistEmail($email, array $options = array()) {
|
||||||
|
|
||||||
|
$defaults = array(
|
||||||
|
'blacklist' => array(),
|
||||||
|
'throw' => false,
|
||||||
|
'why' => false,
|
||||||
|
);
|
||||||
|
|
||||||
|
$options = count($options) ? array_merge($defaults, $options) : $defaults;
|
||||||
|
$blacklist = $options['blacklist'];
|
||||||
|
if(empty($blacklist)) $blacklist = $this->wire('config')->wireMail('blacklist');
|
||||||
|
if(empty($blacklist)) return false;
|
||||||
|
if(!is_array($blacklist)) throw new WireException("Email blacklist must be array");
|
||||||
|
|
||||||
|
$inBlacklist = false;
|
||||||
|
$tt = $this->wire('sanitizer')->getTextTools();
|
||||||
|
$email = trim($tt->strtolower($email));
|
||||||
|
|
||||||
|
foreach($blacklist as $line) {
|
||||||
|
$line = $tt->strtolower(trim($line));
|
||||||
|
if(!strlen($line)) continue;
|
||||||
|
if(strpos($line, '/') === 0) {
|
||||||
|
// perform a regex match
|
||||||
|
if(preg_match($line, $email)) $inBlacklist = $line;
|
||||||
|
} else if(strpos($line, '@')) {
|
||||||
|
// full email (@ is present and is not first char)
|
||||||
|
if($email === $line) $inBlacklist = $line;
|
||||||
|
} else if(strpos($line, '.') === 0) {
|
||||||
|
// any hostname at domain (.domain.com)
|
||||||
|
list(,$emailDomain) = explode('@', $email);
|
||||||
|
if($emailDomain === ltrim($line, '.')) {
|
||||||
|
$inBlacklist = $line;
|
||||||
|
} else if($tt->substr($emailDomain, -1 * $tt->strlen($line)) === $line ) {
|
||||||
|
$inBlacklist = $line;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// match ending string, host or domain name (host.domain.com, domain.com)
|
||||||
|
if($tt->substr($email, -1 * $tt->strlen($line)) === $line) $inBlacklist = $line;
|
||||||
|
}
|
||||||
|
if($inBlacklist) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$inBlacklist && strpos($email, '+')) {
|
||||||
|
// leading part of email contains a plus, so check again without the "+portion"
|
||||||
|
// i.e. ryan+test@domain.com
|
||||||
|
list($prefix, $rest) = explode('+', $email, 2);
|
||||||
|
list(,$hostname) = explode('@', $rest, 2);
|
||||||
|
$email = "$prefix@$hostname";
|
||||||
|
$inBlacklist = $this->isBlacklistEmail($email, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($inBlacklist !== false && $options['throw']) {
|
||||||
|
throw new WireException("Email matches blacklist" . ($options['why'] ? " ($inBlacklist)" : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$options['why'] && $inBlacklist !== false) $inBlacklist = true;
|
||||||
|
|
||||||
|
return $inBlacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user