Skip to content

Commit

Permalink
Merge branch 'master' of github.com:FGRibreau/mailchecker
Browse files Browse the repository at this point in the history
* 'master' of github.com:FGRibreau/mailchecker:
  refactor(rust): prefer once_cell over lazy_static:
  Revert "test(clojure): [workaround] npm run test:clojure"
  feat(clojure): add add-custom-domains
  test(clojure): [workaround] npm run test:clojure
  refactor(clojure): prefer built-in last method
  style(rust): fix warning: unused #[macro_use] import:
  perf(rust): support concurrent readers (Mutax -> RwLock):
  feat(rust): add add_custom_domains :
  refacor(elixir): automatic start_link for backword-compatibility
  feat(elixir): add add_custom_domains
  feat(go): add AddCustomDomains
  feat(python): add add_custom_domains
  feat(node): add addCustomDomains
  feat(ruby): add add_custom_domains
  test(php): test addCustomDomains
  feat(php): add addCustomDomains
  • Loading branch information
François-Guillaume Ribreau committed Nov 19, 2021
2 parents f12d885 + 3b36a11 commit 49ac2ed
Show file tree
Hide file tree
Showing 27 changed files with 382 additions and 35 deletions.
23 changes: 14 additions & 9 deletions platform/clojure/mailchecker.clj

Large diffs are not rendered by default.

21 changes: 13 additions & 8 deletions platform/clojure/mailchecker.tmpl.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
(ns mailchecker)

(require '[clojure.string :as str])
(require '[clojure.set :as set])

(def ^:const blacklist (set [{{& listSTR }}]))

(def custom_domains (atom #{}))

(defn is-email?
"Returns true if email is an email address"
[email]
Expand All @@ -18,16 +21,10 @@
[email]
(str/split email #"@"))

(defn last-element
"Returns the last element of the arr"
[arr]
(first
(take-last 1 arr)))

(defn domain-part
"Returns the domain part from email"
[email]
(last-element (at-split email)))
(last (at-split email)))

(defn dot-join
"Returns string from arr joined with dot char"
Expand All @@ -48,7 +45,10 @@
(defn in-blacklist?
"Returns true if any suffix of the email domain is in the blacklist"
[email]
(some (partial contains? blacklist) (all-domain-suffixes email)))
(let [domains (all-domain-suffixes email)]
(or
(some (partial contains? @custom_domains) domains)
(some (partial contains? blacklist) domains))))

(defn valid?
"Returns true if the email is valid"
Expand All @@ -57,3 +57,8 @@
(is-email? email)
(not
(in-blacklist? email))))

(defn add-custom-domains
"Add more domains to the blacklist"
[domains]
(swap! custom_domains (set/union @custom_domains domains)))
45 changes: 41 additions & 4 deletions platform/elixir/mail_checker.ex

Large diffs are not rendered by default.

45 changes: 41 additions & 4 deletions platform/elixir/mail_checker.tmpl.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
defmodule MailChecker do
use Agent

@blacklist Enum.into([{{& listSTR }}], MapSet.new)
@initial_config %{custom_domains: MapSet.new}

def start_link do
Agent.start_link(fn -> @initial_config end, name: __MODULE__)
end

def add_custom_domains(new_domains) do
ensure_started()

Agent.update(__MODULE__, fn config ->
Map.update(config, :custom_domains, @initial_config[:custom_domains], fn existing_custom_domains ->
new_domains
|> Enum.reduce(existing_custom_domains, &(MapSet.put(&2, &1)))
end)
end)
end

def blacklist, do: @blacklist

Expand All @@ -8,18 +26,37 @@ defmodule MailChecker do
end

def in_blacklist?(email) do
Enum.any?(extract_domain_suffixes(email), fn domain -> MapSet.member?(blacklist(), domain) end)
Enum.any?(extract_domain_suffixes(email), fn domain ->
MapSet.member?(blacklist(), domain) || contain_custom_domain?(domain)
end)
end

def extract_domain_suffixes(email) do
domain = String.split(email, "@") |> List.last
domain_parts = String.split(domain, ".")
range = Range.new(0, (length domain_parts) - 1)
domain = String.split(email, "@") |> List.last()
domain_parts = String.split(domain, ".")
range = Range.new(0, length(domain_parts) - 1)

Enum.map(range, fn n -> Enum.join(Enum.drop(domain_parts, n), ".") end)
end

def valid_address?(email) do
Regex.match?(~r/\A{{& unanchoredRegexpString }}\z/i, email)
end

defp ensure_started do
case start_link() do
{:ok, pid} -> pid
{:error, {:already_started, pid}} -> pid
end
end

defp custom_domains do
ensure_started()

Agent.get(__MODULE__, & &1[:custom_domains])
end

defp contain_custom_domain?(domain) do
MapSet.member?(custom_domains(), domain)
end
end
8 changes: 8 additions & 0 deletions platform/go/mail_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,11 @@ func IsBlacklisted(email string) bool {

return false
}

func AddCustomDomains(domains []string) map[string]struct{} {
for _, domain := range domains {
blacklist[domain] = itemExists
}

return blacklist
}
8 changes: 8 additions & 0 deletions platform/go/mail_checker.tmpl._go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,11 @@ func IsBlacklisted(email string) bool {

return false
}

func AddCustomDomains(domains []string) map[string]struct{} {
for _, domain := range domains {
blacklist[domain] = itemExists
}

return blacklist
}
60 changes: 60 additions & 0 deletions platform/go/mail_checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,63 @@ func TestReturnFalseForBlacklistedDomainsAndTheirSubdomains(t *testing.T) {
}
}
}

func TestAddCustomDomains(t *testing.T) {
testCasesBefore := []struct {
email string
valid bool
}{
{
email: "foo@youtube.com",
valid: true,
},
{
email: "foo@google.com",
valid: true,
},
{
email: "ok@gmail.com",
valid: true,
},
}

for i, testCase := range testCasesBefore {
result := IsValid(testCase.email)

if result != testCase.valid {
t.Errorf("Expected result for email %s (test case %d) is %t, got %t", testCase.email, i, testCase.valid, result)
}
}

domains := []string{
"youtube.com",
"google.com",
}
AddCustomDomains(domains)

testCasesAfter := []struct {
email string
valid bool
}{
{
email: "foo@youtube.com",
valid: false,
},
{
email: "foo@google.com",
valid: false,
},
{
email: "ok@gmail.com",
valid: true,
},
}

for i, testCase := range testCasesAfter {
result := IsValid(testCase.email)

if result != testCase.valid {
t.Errorf("Expected result for email %s (test case %d) is %t, got %t", testCase.email, i, testCase.valid, result)
}
}
}
3 changes: 3 additions & 0 deletions platform/javascript/MailChecker.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions platform/javascript/MailChecker.tmpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
},
blacklist: function () {
return blacklist;
},
addCustomDomains: function (domains = []) {
blacklist.push(...domains)
}
};
})(window);
Expand Down
3 changes: 3 additions & 0 deletions platform/node/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions platform/node/index.tmpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,8 @@ module.exports = {
},
blacklist: function () {
return blacklist;
},
addCustomDomains: function (domains = []) {
blacklist.push(...domains)
}
};
7 changes: 7 additions & 0 deletions platform/php/MailChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ public static function init(): void
self::$blacklist = require __DIR__.'/blacklist.php';
}

public static function addCustomDomains(array $domains): void
{
foreach ($domains as $domain) {
self::$blacklist[$domain] = true;
}
}

public static function isValid(string $email): bool
{
$email = strtolower($email);
Expand Down
4 changes: 4 additions & 0 deletions platform/python/MailChecker.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ def is_blacklisted(cls, email):
@classmethod
def is_valid_email_format(cls, email):
return bool(email) and cls.valid_matcher.search(email) is not None

@classmethod
def add_custom_domains(cls, domains = []):
cls.blacklist.update(domains)
4 changes: 4 additions & 0 deletions platform/python/MailChecker.tmpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ def is_blacklisted(cls, email):
@classmethod
def is_valid_email_format(cls, email):
return bool(email) and cls.valid_matcher.search(email) is not None

@classmethod
def add_custom_domains(cls, domains = []):
cls.blacklist.update(domains)
6 changes: 6 additions & 0 deletions platform/ruby/mail_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ def self.blacklisted?(email)
extract_all_domain_suffixes(email).any? { |domain| BLACKLIST.include?(domain) }
end

def self.add_custom_domains(domains)
domains.each do |domain|
BLACKLIST.add(domain)
end
end

def self.extract_all_domain_suffixes(email)
domain = email.to_s.gsub(/.+@([^.]+)/, '\1').downcase

Expand Down
6 changes: 6 additions & 0 deletions platform/ruby/mail_checker.tmpl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ def self.blacklisted?(email)
extract_all_domain_suffixes(email).any? { |domain| BLACKLIST.include?(domain) }
end

def self.add_custom_domains(domains)
domains.each do |domain|
BLACKLIST.add(domain)
end
end

def self.extract_all_domain_suffixes(email)
domain = email.to_s.gsub(/.+@([^.]+)/, '\1').downcase

Expand Down
7 changes: 7 additions & 0 deletions platform/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions platform/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ include = ["src/lib.rs", "Cargo.toml"]

[dependencies]
fast_chemail = "0.9.5"
once_cell = "1.8.0"

[dev-dependencies]
pretty_assertions = "0.1.2"
Expand Down
34 changes: 30 additions & 4 deletions platform/rust/src/lib.rs

Large diffs are not rendered by default.

34 changes: 30 additions & 4 deletions platform/rust/src/lib.tmpl.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;

extern crate once_cell;
extern crate fast_chemail;

use once_cell::sync::Lazy;
use std::collections::HashSet;
use std::sync::RwLock;

static BLACKLIST: &'static [&'static str] = &[{{ &listSTR }}];

static CUSTOM_DOMAINS: Lazy<RwLock<HashSet<&'static str>>> = Lazy::new(|| RwLock::new(HashSet::new()));

/// # Usage
///
Expand Down Expand Up @@ -83,7 +87,7 @@ fn all_domain_suffixes(email: &str) -> Vec<String> {


fn suffix_is_blacklisted(domain: &str) -> bool{
return BLACKLIST.contains(&domain)
return BLACKLIST.contains(&domain) || CUSTOM_DOMAINS.read().unwrap().contains(&domain)
}

/// # Usage
Expand All @@ -98,11 +102,33 @@ fn suffix_is_blacklisted(domain: &str) -> bool{
///
/// assert!(mailchecker::blacklist().len() > 2000, "blacklist should at least contain 2000 items");
/// ```

pub fn blacklist() -> Vec<&'static str> {
return BLACKLIST.to_vec();
}

/// # Usage
///
///
/// `add_custom_domains` allow more domains to be add to the blacklist
///
///
///
/// ```
/// extern crate mailchecker;
///
/// assert_eq!(true, mailchecker::is_valid("foo@youtube.com"));
/// assert_eq!(true, mailchecker::is_valid("foo@google.com"));
/// assert_eq!(true, mailchecker::is_valid("ok@gmail.com"));

/// mailchecker::add_custom_domains(["youtube.com", "google.com"].to_vec());

/// assert_eq!(false, mailchecker::is_valid("foo@youtube.com"));
/// assert_eq!(false, mailchecker::is_valid("foo@google.com"));
/// assert_eq!(true, mailchecker::is_valid("ok@gmail.com"));
/// ```
pub fn add_custom_domains(domains: Vec<&'static str>) -> () {
return CUSTOM_DOMAINS.write().unwrap().extend(domains.iter().copied());
}

// Helpers
// https://gist.github.com/FGRibreau/9bab6501c13367e787b5f31dc1d670f4
Expand Down

0 comments on commit 49ac2ed

Please sign in to comment.