nondisposable 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +123 -0
- data/Rakefile +5 -0
- data/data/email_providers.txt +276 -0
- data/data/iana_tlds.txt +1439 -0
- data/lib/generators/nondisposable/templates/nondisposable.rb +44 -0
- data/lib/nondisposable/email_validator.rb +52 -1
- data/lib/nondisposable/suggestion.rb +166 -0
- data/lib/nondisposable/tld.rb +185 -0
- data/lib/nondisposable/tld_list_updater.rb +101 -0
- data/lib/nondisposable/version.rb +1 -1
- data/lib/nondisposable.rb +92 -0
- data/lib/tasks/nondisposable.rake +37 -0
- metadata +8 -2
data/lib/nondisposable.rb
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "nondisposable/version"
|
|
4
4
|
require_relative "nondisposable/engine"
|
|
5
|
+
require_relative "nondisposable/tld"
|
|
6
|
+
require_relative "nondisposable/suggestion"
|
|
5
7
|
require_relative "nondisposable/email_validator"
|
|
6
8
|
require_relative "nondisposable/domain_list_updater"
|
|
9
|
+
require_relative "nondisposable/tld_list_updater"
|
|
7
10
|
|
|
8
11
|
module Nondisposable
|
|
9
12
|
class Error < StandardError; end
|
|
@@ -30,6 +33,29 @@ module Nondisposable
|
|
|
30
33
|
DisposableDomain.disposable?(domain.downcase)
|
|
31
34
|
end
|
|
32
35
|
|
|
36
|
+
# Does this address end in a TLD that actually exists?
|
|
37
|
+
#
|
|
38
|
+
# A different question from #disposable?, catching a different kind of bad
|
|
39
|
+
# address: `user@gmail.con` is not a throwaway, it is a typo that produces an
|
|
40
|
+
# account nobody can ever reach. Answers true for anything we cannot judge —
|
|
41
|
+
# no domain, no dot, an IP literal — so it is safe to call on any input; pair
|
|
42
|
+
# it with a format validation if you also care about shape.
|
|
43
|
+
def self.valid_tld?(email)
|
|
44
|
+
return true unless Tld.judgeable?(email)
|
|
45
|
+
|
|
46
|
+
tld = Tld.extract(email)
|
|
47
|
+
return false if tld.nil? # A domain with no TLD can't end in a real one
|
|
48
|
+
|
|
49
|
+
Tld.valid?(tld) && !Tld.blocked?(tld)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# "someone@gmial.com" => "someone@gmail.com", or nil when we have nothing
|
|
53
|
+
# useful to say. Never blocks anything by itself — hand it to a user and let
|
|
54
|
+
# them decide. See Nondisposable::Suggestion for how it decides.
|
|
55
|
+
def self.suggestion_for(email)
|
|
56
|
+
Suggestion.for(email)
|
|
57
|
+
end
|
|
58
|
+
|
|
33
59
|
class Configuration
|
|
34
60
|
ON_CHECK_FAILURE_MODES = [:allow, :reject].freeze
|
|
35
61
|
|
|
@@ -40,12 +66,78 @@ module Nondisposable
|
|
|
40
66
|
attr_accessor :check_parent_domains
|
|
41
67
|
attr_reader :on_check_failure
|
|
42
68
|
|
|
69
|
+
# ---- TLD validity (Nondisposable::Tld) ----------------------------------
|
|
70
|
+
|
|
71
|
+
# OFF by default, and it stays off: upgrading a gem must never silently
|
|
72
|
+
# start rejecting addresses that were fine yesterday. Turn it on to reject
|
|
73
|
+
# addresses whose TLD is not in the IANA root zone — `user@gmail.con` and
|
|
74
|
+
# friends, which are typos rather than throwaways but produce an account
|
|
75
|
+
# nobody can ever reach.
|
|
76
|
+
attr_accessor :check_tld
|
|
77
|
+
|
|
78
|
+
# Your escape hatch from a stale snapshot. A TLD delegated after this gem's
|
|
79
|
+
# release is unknown to the bundled list; naming it here accepts it
|
|
80
|
+
# immediately, with no release to wait for. Dots optional: "app" == ".app".
|
|
81
|
+
attr_accessor :additional_tlds
|
|
82
|
+
|
|
83
|
+
# TLDs to refuse even though they are perfectly real. The usual reason is
|
|
84
|
+
# abuse economics rather than validity — the historically free Freenom set
|
|
85
|
+
# (%w[tk ml ga cf gq]) is the classic example.
|
|
86
|
+
attr_accessor :blocked_tlds
|
|
87
|
+
|
|
88
|
+
# Allowlist mode: when set, ONLY these TLDs are accepted and every other one
|
|
89
|
+
# is refused. nil (the default) means "any TLD in the root zone". ⚠️ This is
|
|
90
|
+
# a blunt instrument — `%w[es]` turns away every customer who happens to use
|
|
91
|
+
# a .com address. Reach for blocked_tlds first.
|
|
92
|
+
attr_accessor :allowed_tlds
|
|
93
|
+
|
|
94
|
+
# ---- Lookalike domains (Nondisposable::Suggestion) ----------------------
|
|
95
|
+
|
|
96
|
+
# Reject addresses one edit away from a well-known provider — `gmail.co`,
|
|
97
|
+
# `gmial.com`, `hotmial.com` — with a message naming the correction.
|
|
98
|
+
#
|
|
99
|
+
# OFF by default and worth leaving off unless you have thought about it: a
|
|
100
|
+
# suggestion is a guess about intent, and a wrong guess here stops a real
|
|
101
|
+
# person signing up with their real address. `Nondisposable.suggestion_for`
|
|
102
|
+
# is always available and blocks nothing, which is the gentler way to use
|
|
103
|
+
# this: show the hint, let the human decide.
|
|
104
|
+
attr_accessor :reject_lookalike_domains
|
|
105
|
+
|
|
106
|
+
# How many edits still count as "a typo". 1 (the default) is the distance at
|
|
107
|
+
# which acting on a guess is safe; at 2, genuinely different domains start
|
|
108
|
+
# colliding with each other. 0 disables suggestions entirely.
|
|
109
|
+
attr_accessor :lookalike_distance
|
|
110
|
+
|
|
111
|
+
# Providers to add to the bundled list — your own domain, a regional
|
|
112
|
+
# provider we missed. Adding one both makes it a suggestion candidate and,
|
|
113
|
+
# more importantly, stops its users being told they made a typo.
|
|
114
|
+
attr_accessor :additional_email_providers
|
|
115
|
+
|
|
116
|
+
# ---- Error messages -----------------------------------------------------
|
|
117
|
+
|
|
118
|
+
attr_accessor :invalid_tld_error_message, :blocked_tld_error_message
|
|
119
|
+
# %{suggestion} is replaced with the full corrected address.
|
|
120
|
+
attr_accessor :lookalike_error_message
|
|
121
|
+
|
|
43
122
|
def initialize
|
|
44
123
|
@error_message = "provider is not allowed"
|
|
45
124
|
@additional_domains = []
|
|
46
125
|
@excluded_domains = []
|
|
47
126
|
@on_check_failure = :allow
|
|
48
127
|
@check_parent_domains = true
|
|
128
|
+
|
|
129
|
+
@check_tld = false
|
|
130
|
+
@additional_tlds = []
|
|
131
|
+
@blocked_tlds = []
|
|
132
|
+
@allowed_tlds = nil
|
|
133
|
+
|
|
134
|
+
@reject_lookalike_domains = false
|
|
135
|
+
@lookalike_distance = 1
|
|
136
|
+
@additional_email_providers = []
|
|
137
|
+
|
|
138
|
+
@invalid_tld_error_message = "doesn't look like a real email address"
|
|
139
|
+
@blocked_tld_error_message = "domain ending is not allowed"
|
|
140
|
+
@lookalike_error_message = "looks like a typo. Did you mean %{suggestion}?"
|
|
49
141
|
end
|
|
50
142
|
|
|
51
143
|
# What the validator does when the disposable check itself raises
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :nondisposable do
|
|
4
|
+
namespace :tlds do
|
|
5
|
+
desc "Refresh the vendored IANA TLD snapshot (maintenance task — run in a gem checkout before a release)"
|
|
6
|
+
task :update do
|
|
7
|
+
# Deliberately NOT `require "nondisposable"`: that loads the Engine, which
|
|
8
|
+
# needs Rails, which a bare `rake` in a gem checkout doesn't have. Nothing
|
|
9
|
+
# this task touches reads configuration — only `Tld.valid?`/`blocked?` do,
|
|
10
|
+
# and it calls neither.
|
|
11
|
+
require_relative "../nondisposable/tld"
|
|
12
|
+
require_relative "../nondisposable/tld_list_updater"
|
|
13
|
+
|
|
14
|
+
before = begin
|
|
15
|
+
Nondisposable::Tld.all.size
|
|
16
|
+
rescue StandardError
|
|
17
|
+
0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
count = Nondisposable::TldListUpdater.update
|
|
21
|
+
|
|
22
|
+
if count.nil?
|
|
23
|
+
abort "[nondisposable] TLD list unchanged. See the error above."
|
|
24
|
+
else
|
|
25
|
+
added = count - before
|
|
26
|
+
change =
|
|
27
|
+
if added.positive? then "+#{added}"
|
|
28
|
+
elsif added.negative? then added.to_s
|
|
29
|
+
else "no change"
|
|
30
|
+
end
|
|
31
|
+
puts "[nondisposable] TLD snapshot now holds #{count} entries (#{change})."
|
|
32
|
+
puts "[nondisposable] IANA version: #{Nondisposable::Tld.version}"
|
|
33
|
+
puts "[nondisposable] Commit data/iana_tlds.txt if it changed."
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nondisposable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-08-
|
|
10
|
+
date: 2026-08-27 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|
|
@@ -43,6 +43,8 @@ files:
|
|
|
43
43
|
- app/models/nondisposable/disposable_domain.rb
|
|
44
44
|
- context7.json
|
|
45
45
|
- data/disposable_email_blocklist.conf
|
|
46
|
+
- data/email_providers.txt
|
|
47
|
+
- data/iana_tlds.txt
|
|
46
48
|
- gemfiles/rails_7.2.gemfile
|
|
47
49
|
- gemfiles/rails_8.0.gemfile
|
|
48
50
|
- gemfiles/rails_8.1.gemfile
|
|
@@ -54,7 +56,11 @@ files:
|
|
|
54
56
|
- lib/nondisposable/domain_list_updater.rb
|
|
55
57
|
- lib/nondisposable/email_validator.rb
|
|
56
58
|
- lib/nondisposable/engine.rb
|
|
59
|
+
- lib/nondisposable/suggestion.rb
|
|
60
|
+
- lib/nondisposable/tld.rb
|
|
61
|
+
- lib/nondisposable/tld_list_updater.rb
|
|
57
62
|
- lib/nondisposable/version.rb
|
|
63
|
+
- lib/tasks/nondisposable.rake
|
|
58
64
|
- sig/nondisposable.rbs
|
|
59
65
|
homepage: https://github.com/rameerez/nondisposable
|
|
60
66
|
licenses:
|