email_address 0.1.19 → 0.2.2
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/.github/workflows/ci.yml +18 -0
- data/Gemfile +1 -1
- data/README.md +27 -2
- data/Rakefile +2 -2
- data/email_address.gemspec +23 -23
- data/lib/email_address/active_record_validator.rb +5 -9
- data/lib/email_address/address.rb +29 -19
- data/lib/email_address/config.rb +13 -2
- data/lib/email_address/exchanger.rb +5 -19
- data/lib/email_address/host.rb +29 -45
- data/lib/email_address/local.rb +104 -105
- data/lib/email_address/rewriter.rb +28 -31
- data/lib/email_address/version.rb +1 -1
- data/lib/email_address.rb +8 -9
- data/test/activerecord/test_ar.rb +17 -13
- data/test/activerecord/user.rb +31 -30
- data/test/email_address/test_address.rb +46 -25
- data/test/email_address/test_config.rb +8 -8
- data/test/email_address/test_exchanger.rb +6 -7
- data/test/email_address/test_host.rb +2 -1
- data/test/email_address/test_local.rb +39 -35
- data/test/email_address/test_rewriter.rb +2 -5
- data/test/test_aliasing.rb +1 -2
- data/test/test_email_address.rb +14 -18
- data/test/test_helper.rb +9 -8
- metadata +29 -18
- data/.travis.yml +0 -9
data/lib/email_address/local.rb
CHANGED
@@ -67,50 +67,51 @@ module EmailAddress
|
|
67
67
|
# [CFWS]
|
68
68
|
############################################################################
|
69
69
|
class Local
|
70
|
-
attr_reader
|
70
|
+
attr_reader :local
|
71
71
|
attr_accessor :mailbox, :comment, :tag, :config, :original
|
72
|
-
attr_accessor :syntax
|
72
|
+
attr_accessor :syntax, :locale
|
73
73
|
|
74
74
|
# RFC-2142: MAILBOX NAMES FOR COMMON SERVICES, ROLES AND FUNCTIONS
|
75
|
-
BUSINESS_MAILBOXES = %w
|
76
|
-
NETWORK_MAILBOXES
|
77
|
-
SERVICE_MAILBOXES
|
78
|
-
SYSTEM_MAILBOXES
|
79
|
-
ROLE_MAILBOXES
|
80
|
-
SPECIAL_MAILBOXES
|
81
|
-
|
82
|
-
STANDARD_MAX_SIZE
|
75
|
+
BUSINESS_MAILBOXES = %w[info marketing sales support]
|
76
|
+
NETWORK_MAILBOXES = %w[abuse noc security]
|
77
|
+
SERVICE_MAILBOXES = %w[postmaster hostmaster usenet news webmaster www uucp ftp]
|
78
|
+
SYSTEM_MAILBOXES = %w[help mailer-daemon root] # Not from RFC-2142
|
79
|
+
ROLE_MAILBOXES = %w[staff office orders billing careers jobs] # Not from RFC-2142
|
80
|
+
SPECIAL_MAILBOXES = BUSINESS_MAILBOXES + NETWORK_MAILBOXES + SERVICE_MAILBOXES +
|
81
|
+
SYSTEM_MAILBOXES + ROLE_MAILBOXES
|
82
|
+
STANDARD_MAX_SIZE = 64
|
83
83
|
|
84
84
|
# Conventional : word([.-+'_]word)*
|
85
|
-
CONVENTIONAL_MAILBOX_REGEX
|
86
|
-
CONVENTIONAL_MAILBOX_WITHIN = /[\p{L}\p{N}_]+ (?: [
|
85
|
+
CONVENTIONAL_MAILBOX_REGEX = /\A [\p{L}\p{N}_]+ (?: [.\-+'_] [\p{L}\p{N}_]+ )* \z/x
|
86
|
+
CONVENTIONAL_MAILBOX_WITHIN = /[\p{L}\p{N}_]+ (?: [.\-+'_] [\p{L}\p{N}_]+ )*/x
|
87
87
|
|
88
88
|
# Relaxed: same characters, relaxed order
|
89
|
-
RELAXED_MAILBOX_WITHIN = /[\p{L}\p{N}_]+ (?: [
|
90
|
-
RELAXED_MAILBOX_REGEX = /\A [\p{L}\p{N}_]+ (?: [
|
89
|
+
RELAXED_MAILBOX_WITHIN = /[\p{L}\p{N}_]+ (?: [.\-+'_]+ [\p{L}\p{N}_]+ )*/x
|
90
|
+
RELAXED_MAILBOX_REGEX = /\A [\p{L}\p{N}_]+ (?: [.\-+'_]+ [\p{L}\p{N}_]+ )* \z/x
|
91
91
|
|
92
92
|
# RFC5322 Token: token."token".token (dot-separated tokens)
|
93
93
|
# Quoted Token can also have: SPACE \" \\ ( ) , : ; < > @ [ \ ] .
|
94
94
|
STANDARD_LOCAL_WITHIN = /
|
95
|
-
(?: [\p{L}\p{N}
|
96
|
-
|
|
97
|
-
(?: \. (?: [\p{L}\p{N}
|
98
|
-
|
|
95
|
+
(?: [\p{L}\p{N}!\#$%&'*+\-\/=?\^_`{|}~()]+
|
96
|
+
| " (?: \\[" \\] | [\x20-\x21\x23-\x2F\x3A-\x40\x5B\x5D-\x60\x7B-\x7E\p{L}\p{N}] )+ " )
|
97
|
+
(?: \. (?: [\p{L}\p{N}!\#$%&'*+\-\/=?\^_`{|}~()]+
|
98
|
+
| " (?: \\[" \\] | [\x20-\x21\x23-\x2F\x3A-\x40\x5B\x5D-\x60\x7B-\x7E\p{L}\p{N}] )+ " ) )* /x
|
99
99
|
|
100
100
|
STANDARD_LOCAL_REGEX = /\A #{STANDARD_LOCAL_WITHIN} \z/x
|
101
101
|
|
102
102
|
REDACTED_REGEX = /\A \{ [0-9a-f]{40} \} \z/x # {sha1}
|
103
103
|
|
104
|
-
CONVENTIONAL_TAG_REGEX
|
105
|
-
%r
|
106
|
-
RELAXED_TAG_REGEX
|
107
|
-
%r/^([\w
|
104
|
+
CONVENTIONAL_TAG_REGEX = # AZaz09_!'+-/=
|
105
|
+
%r{^([\w!'+\-/=.]+)$}i
|
106
|
+
RELAXED_TAG_REGEX = # AZaz09_!#$%&'*+-/=?^`{|}~
|
107
|
+
%r/^([\w.!\#$%&'*+\-\/=?\^`{|}~]+)$/i
|
108
108
|
|
109
|
-
def initialize(local, config={}, host=nil)
|
109
|
+
def initialize(local, config = {}, host = nil, locale = "en")
|
110
110
|
@config = config.is_a?(Hash) ? Config.new(config) : config
|
111
|
-
self.local
|
112
|
-
@host
|
113
|
-
@
|
111
|
+
self.local = local
|
112
|
+
@host = host
|
113
|
+
@locale = locale
|
114
|
+
@error = @error_message = nil
|
114
115
|
end
|
115
116
|
|
116
117
|
def local=(raw)
|
@@ -121,23 +122,23 @@ module EmailAddress
|
|
121
122
|
if @config[:local_parse].is_a?(Proc)
|
122
123
|
self.mailbox, self.tag, self.comment = @config[:local_parse].call(raw)
|
123
124
|
else
|
124
|
-
self.mailbox, self.tag, self.comment =
|
125
|
+
self.mailbox, self.tag, self.comment = parse(raw)
|
125
126
|
end
|
126
127
|
|
127
128
|
self.format
|
128
129
|
end
|
129
130
|
|
130
131
|
def parse(raw)
|
131
|
-
if raw =~ /\A
|
132
|
+
if raw =~ /\A"(.*)"\z/ # Quoted
|
132
133
|
raw = $1
|
133
134
|
raw = raw.gsub(/\\(.)/, '\1') # Unescape
|
134
135
|
elsif @config[:local_fix] && @config[:local_format] != :standard
|
135
|
-
raw = raw.
|
136
|
-
raw = raw.
|
137
|
-
#raw.gsub!(/([^\p{L}\p{N}]{2,10})/) {|s| s[0] } # Stutter punctuation typo
|
136
|
+
raw = raw.delete(" ")
|
137
|
+
raw = raw.tr(",", ".")
|
138
|
+
# raw.gsub!(/([^\p{L}\p{N}]{2,10})/) {|s| s[0] } # Stutter punctuation typo
|
138
139
|
end
|
139
|
-
raw, comment =
|
140
|
-
mailbox, tag =
|
140
|
+
raw, comment = parse_comment(raw)
|
141
|
+
mailbox, tag = parse_tag(raw)
|
141
142
|
mailbox ||= ""
|
142
143
|
[mailbox, tag, comment]
|
143
144
|
end
|
@@ -156,28 +157,28 @@ module EmailAddress
|
|
156
157
|
end
|
157
158
|
|
158
159
|
def parse_tag(raw)
|
159
|
-
separator = @config[:tag_separator] ||=
|
160
|
+
separator = @config[:tag_separator] ||= "+"
|
160
161
|
raw.split(separator, 2)
|
161
162
|
end
|
162
163
|
|
163
164
|
# True if the the value contains only Latin characters (7-bit ASCII)
|
164
165
|
def ascii?
|
165
|
-
!
|
166
|
+
!unicode?
|
166
167
|
end
|
167
168
|
|
168
169
|
# True if the the value contains non-Latin Unicde characters
|
169
170
|
def unicode?
|
170
|
-
|
171
|
+
/[^\p{InBasicLatin}]/.match?(local)
|
171
172
|
end
|
172
173
|
|
173
174
|
# Returns true if the value matches the Redacted format
|
174
175
|
def redacted?
|
175
|
-
|
176
|
+
REDACTED_REGEX.match?(local)
|
176
177
|
end
|
177
178
|
|
178
179
|
# Returns true if the value matches the Redacted format
|
179
180
|
def self.redacted?(local)
|
180
|
-
|
181
|
+
REDACTED_REGEX.match?(local)
|
181
182
|
end
|
182
183
|
|
183
184
|
# Is the address for a common system or business role account?
|
@@ -190,81 +191,80 @@ module EmailAddress
|
|
190
191
|
end
|
191
192
|
|
192
193
|
# Builds the local string according to configurations
|
193
|
-
def format(form
|
194
|
+
def format(form = @config[:local_format] || :conventional)
|
194
195
|
if @config[:local_format].is_a?(Proc)
|
195
196
|
@config[:local_format].call(self)
|
196
197
|
elsif form == :conventional
|
197
|
-
|
198
|
+
conventional
|
198
199
|
elsif form == :canonical
|
199
|
-
|
200
|
+
canonical
|
200
201
|
elsif form == :relaxed
|
201
|
-
|
202
|
+
relax
|
202
203
|
elsif form == :standard
|
203
|
-
|
204
|
+
standard
|
204
205
|
end
|
205
206
|
end
|
206
207
|
|
207
208
|
# Returns a conventional form of the address
|
208
209
|
def conventional
|
209
|
-
if
|
210
|
-
[
|
210
|
+
if tag
|
211
|
+
[mailbox, tag].join(@config[:tag_separator])
|
211
212
|
else
|
212
|
-
|
213
|
+
mailbox
|
213
214
|
end
|
214
215
|
end
|
215
216
|
|
216
217
|
# Returns a canonical form of the address
|
217
218
|
def canonical
|
218
219
|
if @config[:mailbox_canonical]
|
219
|
-
@config[:mailbox_canonical].call(
|
220
|
+
@config[:mailbox_canonical].call(mailbox)
|
220
221
|
else
|
221
|
-
|
222
|
+
mailbox.downcase
|
222
223
|
end
|
223
224
|
end
|
224
225
|
|
225
226
|
# Relaxed format: mailbox and tag, no comment, no extended character set
|
226
227
|
def relax
|
227
|
-
form =
|
228
|
-
form += @config[:tag_separator] +
|
229
|
-
form
|
230
|
-
form
|
228
|
+
form = mailbox
|
229
|
+
form += @config[:tag_separator] + tag if tag
|
230
|
+
form.gsub(/[ "(),:<>@\[\]\\]/, "")
|
231
231
|
end
|
232
232
|
|
233
233
|
# Returns a normalized version of the standard address parts.
|
234
234
|
def standard
|
235
|
-
form =
|
236
|
-
form += @config[:tag_separator] +
|
237
|
-
form += "(" +
|
238
|
-
form = form.gsub(/([
|
239
|
-
if
|
240
|
-
form = %
|
235
|
+
form = mailbox
|
236
|
+
form += @config[:tag_separator] + tag if tag
|
237
|
+
form += "(" + comment + ")" if comment
|
238
|
+
form = form.gsub(/([\\"])/, '\\\1') # Escape \ and "
|
239
|
+
if /[ "(),:<>@\[\\\]]/.match?(form) # Space and "(),:;<>@[\]
|
240
|
+
form = %("#{form}")
|
241
241
|
end
|
242
242
|
form
|
243
243
|
end
|
244
244
|
|
245
245
|
# Sets the part to be the conventional form
|
246
246
|
def conventional!
|
247
|
-
self.local =
|
247
|
+
self.local = conventional
|
248
248
|
end
|
249
249
|
|
250
250
|
# Sets the part to be the canonical form
|
251
251
|
def canonical!
|
252
|
-
self.local =
|
252
|
+
self.local = canonical
|
253
253
|
end
|
254
254
|
|
255
255
|
# Dropps unusual parts of Standard form to form a relaxed version.
|
256
256
|
def relax!
|
257
|
-
self.local =
|
257
|
+
self.local = relax
|
258
258
|
end
|
259
259
|
|
260
260
|
# Returns the munged form of the address, like "ma*****"
|
261
261
|
def munge
|
262
|
-
|
262
|
+
to_s.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] }
|
263
263
|
end
|
264
264
|
|
265
265
|
# Mailbox with trailing numbers removed
|
266
266
|
def root_name
|
267
|
-
|
267
|
+
mailbox =~ /\A(.+?)\d+\z/ ? $1 : mailbox
|
268
268
|
end
|
269
269
|
|
270
270
|
############################################################################
|
@@ -272,19 +272,19 @@ module EmailAddress
|
|
272
272
|
############################################################################
|
273
273
|
|
274
274
|
# True if the part is valid according to the configurations
|
275
|
-
def valid?(format
|
275
|
+
def valid?(format = @config[:local_format] || :conventional)
|
276
276
|
if @config[:mailbox_validator].is_a?(Proc)
|
277
|
-
@config[:mailbox_validator].call(
|
277
|
+
@config[:mailbox_validator].call(mailbox, tag)
|
278
278
|
elsif format.is_a?(Proc)
|
279
279
|
format.call(self)
|
280
280
|
elsif format == :conventional
|
281
|
-
|
281
|
+
conventional?
|
282
282
|
elsif format == :relaxed
|
283
|
-
|
283
|
+
relaxed?
|
284
284
|
elsif format == :redacted
|
285
|
-
|
285
|
+
redacted?
|
286
286
|
elsif format == :standard
|
287
|
-
|
287
|
+
standard?
|
288
288
|
elsif format == :none
|
289
289
|
true
|
290
290
|
else
|
@@ -295,13 +295,13 @@ module EmailAddress
|
|
295
295
|
# Returns the format of the address
|
296
296
|
def format?
|
297
297
|
# if :custom
|
298
|
-
if
|
298
|
+
if conventional?
|
299
299
|
:conventional
|
300
|
-
elsif
|
300
|
+
elsif relaxed?
|
301
301
|
:relax
|
302
|
-
elsif
|
302
|
+
elsif redacted?
|
303
303
|
:redacted
|
304
|
-
elsif
|
304
|
+
elsif standard?
|
305
305
|
:standard
|
306
306
|
else
|
307
307
|
:invalid
|
@@ -309,38 +309,38 @@ module EmailAddress
|
|
309
309
|
end
|
310
310
|
|
311
311
|
def valid_size?
|
312
|
-
return set_error(:local_size_long) if
|
313
|
-
if @host
|
312
|
+
return set_error(:local_size_long) if local.size > STANDARD_MAX_SIZE
|
313
|
+
if @host&.hosted_service?
|
314
314
|
return false if @config[:local_private_size] && !valid_size_checks(@config[:local_private_size])
|
315
|
-
|
316
|
-
return false
|
315
|
+
elsif @config[:local_size] && !valid_size_checks(@config[:local_size])
|
316
|
+
return false
|
317
317
|
end
|
318
318
|
return false if @config[:mailbox_size] && !valid_size_checks(@config[:mailbox_size])
|
319
319
|
true
|
320
320
|
end
|
321
321
|
|
322
322
|
def valid_size_checks(range)
|
323
|
-
return set_error(:local_size_short) if
|
324
|
-
return set_error(:local_size_long)
|
323
|
+
return set_error(:local_size_short) if mailbox.size < range.first
|
324
|
+
return set_error(:local_size_long) if mailbox.size > range.last
|
325
325
|
true
|
326
326
|
end
|
327
327
|
|
328
|
-
def valid_encoding?(enc
|
329
|
-
return false if enc == :ascii &&
|
328
|
+
def valid_encoding?(enc = @config[:local_encoding] || :ascii)
|
329
|
+
return false if enc == :ascii && unicode?
|
330
330
|
true
|
331
331
|
end
|
332
332
|
|
333
333
|
# True if the part matches the conventional format
|
334
334
|
def conventional?
|
335
335
|
self.syntax = :invalid
|
336
|
-
if
|
337
|
-
return false unless
|
338
|
-
|
336
|
+
if tag
|
337
|
+
return false unless mailbox =~ CONVENTIONAL_MAILBOX_REGEX &&
|
338
|
+
tag =~ CONVENTIONAL_TAG_REGEX
|
339
339
|
else
|
340
|
-
return false unless
|
340
|
+
return false unless CONVENTIONAL_MAILBOX_REGEX.match?(local)
|
341
341
|
end
|
342
|
-
|
343
|
-
|
342
|
+
valid_size? or return false
|
343
|
+
valid_encoding? or return false
|
344
344
|
self.syntax = :conventional
|
345
345
|
true
|
346
346
|
end
|
@@ -348,12 +348,14 @@ module EmailAddress
|
|
348
348
|
# Relaxed conventional is not so strict about character order.
|
349
349
|
def relaxed?
|
350
350
|
self.syntax = :invalid
|
351
|
-
|
352
|
-
|
353
|
-
if
|
354
|
-
return false unless
|
355
|
-
|
356
|
-
|
351
|
+
valid_size? or return false
|
352
|
+
valid_encoding? or return false
|
353
|
+
if tag
|
354
|
+
return false unless RELAXED_MAILBOX_REGEX.match?(mailbox) &&
|
355
|
+
RELAXED_TAG_REGEX.match?(tag)
|
356
|
+
self.syntax = :relaxed
|
357
|
+
true
|
358
|
+
elsif RELAXED_MAILBOX_REGEX.match?(local)
|
357
359
|
self.syntax = :relaxed
|
358
360
|
true
|
359
361
|
else
|
@@ -364,9 +366,9 @@ module EmailAddress
|
|
364
366
|
# True if the part matches the RFC standard format
|
365
367
|
def standard?
|
366
368
|
self.syntax = :invalid
|
367
|
-
|
368
|
-
|
369
|
-
if
|
369
|
+
valid_size? or return false
|
370
|
+
valid_encoding? or return false
|
371
|
+
if STANDARD_LOCAL_REGEX.match?(local)
|
370
372
|
self.syntax = :standard
|
371
373
|
true
|
372
374
|
else
|
@@ -379,26 +381,23 @@ module EmailAddress
|
|
379
381
|
def matches?(*rules)
|
380
382
|
rules.flatten.each do |r|
|
381
383
|
if r =~ /(.+)@\z/
|
382
|
-
return r if File.fnmatch?($1,
|
384
|
+
return r if File.fnmatch?($1, local)
|
383
385
|
end
|
384
386
|
end
|
385
387
|
false
|
386
388
|
end
|
387
389
|
|
388
|
-
def set_error(err, reason=nil)
|
390
|
+
def set_error(err, reason = nil)
|
389
391
|
@error = err
|
390
|
-
@reason= reason
|
391
|
-
@error_message = Config.error_message(err)
|
392
|
+
@reason = reason
|
393
|
+
@error_message = Config.error_message(err, locale)
|
392
394
|
false
|
393
395
|
end
|
394
396
|
|
395
|
-
|
396
|
-
@error_message
|
397
|
-
end
|
397
|
+
attr_reader :error_message
|
398
398
|
|
399
399
|
def error
|
400
|
-
|
400
|
+
valid? ? nil : (@error || :local_invalid)
|
401
401
|
end
|
402
|
-
|
403
402
|
end
|
404
403
|
end
|
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "base64"
|
4
4
|
|
5
5
|
module EmailAddress::Rewriter
|
6
|
-
|
7
|
-
SRS_FORMAT_REGEX = /\ASRS0=(....)=(\w\w)=(.+?)=(.+?)@(.+)\z/
|
6
|
+
SRS_FORMAT_REGEX = /\ASRS0=(....)=(\w\w)=(.+?)=(.+?)@(.+)\z/
|
8
7
|
|
9
8
|
def parse_rewritten(e)
|
10
9
|
@rewrite_scheme = nil
|
11
|
-
@rewrite_error
|
12
|
-
|
10
|
+
@rewrite_error = nil
|
11
|
+
parse_srs(e)
|
13
12
|
# e = parse_batv(e)
|
14
|
-
e
|
15
13
|
end
|
16
14
|
|
17
15
|
#---------------------------------------------------------------------------
|
@@ -24,9 +22,9 @@ module EmailAddress::Rewriter
|
|
24
22
|
# a different domain.
|
25
23
|
# Format: SRS0=HHH=TT=domain=local@sending-domain.com
|
26
24
|
#---------------------------------------------------------------------------
|
27
|
-
def srs(sending_domain, options={}, &block)
|
28
|
-
tt = srs_tt
|
29
|
-
a = [tt,
|
25
|
+
def srs(sending_domain, options = {}, &block)
|
26
|
+
tt = srs_tt
|
27
|
+
a = [tt, hostname, local.to_s].join("=") + "@" + sending_domain
|
30
28
|
hhh = srs_hash(a, options, &block)
|
31
29
|
|
32
30
|
["SRS0", hhh, a].join("=")
|
@@ -36,11 +34,11 @@ module EmailAddress::Rewriter
|
|
36
34
|
email.match(SRS_FORMAT_REGEX) ? true : false
|
37
35
|
end
|
38
36
|
|
39
|
-
def parse_srs(email, options={}, &block)
|
40
|
-
if email
|
37
|
+
def parse_srs(email, options = {}, &block)
|
38
|
+
if email&.match(SRS_FORMAT_REGEX)
|
41
39
|
@rewrite_scheme = :srs
|
42
40
|
hhh, tt, domain, local, sending_domain = [$1, $2, $3, $4, $5]
|
43
|
-
hhh = tt = sending_domain if false && hhh # Hide warnings for now :-)
|
41
|
+
# hhh = tt = sending_domain if false && hhh # Hide warnings for now :-)
|
44
42
|
a = [tt, domain, local].join("=") + "@" + sending_domain
|
45
43
|
unless srs_hash(a, options, &block) === hhh
|
46
44
|
@rewrite_error = "Invalid SRS Email Address: Possibly altered"
|
@@ -58,16 +56,16 @@ module EmailAddress::Rewriter
|
|
58
56
|
# Returns a 2-character code for the day. After a few days the code will roll.
|
59
57
|
# TT has a one-day resolution in order to make the address invalid after a few days.
|
60
58
|
# The cycle period is 3.5 years. Used to control late bounces and harvesting.
|
61
|
-
def srs_tt(t=Time.now.utc)
|
62
|
-
Base64.encode64((t.to_i / (60*60*24) %
|
59
|
+
def srs_tt(t = Time.now.utc)
|
60
|
+
Base64.encode64((t.to_i / (60 * 60 * 24) % 210).to_s)[0, 2]
|
63
61
|
end
|
64
62
|
|
65
|
-
def srs_hash(email, options={}, &block)
|
63
|
+
def srs_hash(email, options = {}, &block)
|
66
64
|
key = options[:key] || @config[:key] || email.reverse
|
67
|
-
if
|
68
|
-
block.call(email)[0,4]
|
65
|
+
if block
|
66
|
+
block.call(email)[0, 4]
|
69
67
|
else
|
70
|
-
Base64.encode64(Digest::SHA1.digest(email + key))[0,4]
|
68
|
+
Base64.encode64(Digest::SHA1.digest(email + key))[0, 4]
|
71
69
|
end
|
72
70
|
end
|
73
71
|
|
@@ -85,17 +83,17 @@ module EmailAddress::Rewriter
|
|
85
83
|
# * SSSSSS: sha1( KDDD + orig-mailfrom + key)[0,6]
|
86
84
|
# See: https://tools.ietf.org/html/draft-levine-smtp-batv-01
|
87
85
|
#---------------------------------------------------------------------------
|
88
|
-
def batv_prvs(options={})
|
86
|
+
def batv_prvs(options = {})
|
89
87
|
k = options[:prvs_key_id] || "0"
|
90
88
|
prvs_days = options[:prvs_days] || @config[:prvs_days] || 30
|
91
89
|
ddd = prvs_day(prvs_days)
|
92
|
-
ssssss = prvs_sign(k, ddd,
|
93
|
-
["prvs=", k, ddd, ssssss,
|
90
|
+
ssssss = prvs_sign(k, ddd, to_s, options)
|
91
|
+
["prvs=", k, ddd, ssssss, "=", to_s].join("")
|
94
92
|
end
|
95
93
|
|
96
94
|
PRVS_REGEX = /\Aprvs=(\d)(\d{3})(\w{6})=(.+)\z/
|
97
95
|
|
98
|
-
def parse_prvs(email, options={})
|
96
|
+
def parse_prvs(email, options = {})
|
99
97
|
if email.match(PRVS_REGEX)
|
100
98
|
@rewrite_scheme = :prvs
|
101
99
|
k, ddd, ssssss, email = [$1, $2, $3, $4]
|
@@ -119,13 +117,13 @@ module EmailAddress::Rewriter
|
|
119
117
|
end
|
120
118
|
|
121
119
|
def prvs_day(days)
|
122
|
-
((Time.now.to_i + (days*24*60*60)) / (24*60*60)).to_s[-3,3]
|
120
|
+
((Time.now.to_i + (days * 24 * 60 * 60)) / (24 * 60 * 60)).to_s[-3, 3]
|
123
121
|
end
|
124
122
|
|
125
|
-
def prvs_sign(k, ddd, email, options={})
|
126
|
-
str = [ddd, ssssss,
|
123
|
+
def prvs_sign(k, ddd, email, options = {})
|
124
|
+
str = [ddd, ssssss, "=", to_s].join("")
|
127
125
|
key = options["key_#{k}".to_i] || @config["key_#{k}".to_i] || str.reverse
|
128
|
-
Digest::SHA1.hexdigest([k,ddd, email, key].join(
|
126
|
+
Digest::SHA1.hexdigest([k, ddd, email, key].join(""))[0, 6]
|
129
127
|
end
|
130
128
|
|
131
129
|
#---------------------------------------------------------------------------
|
@@ -136,12 +134,11 @@ module EmailAddress::Rewriter
|
|
136
134
|
# To handle incoming verp, the "tag" is the recipient email address,
|
137
135
|
# remember to convert the last '=' into a '@' to reconstruct it.
|
138
136
|
#---------------------------------------------------------------------------
|
139
|
-
def verp(recipient, split_char=
|
140
|
-
|
141
|
-
split_char + recipient.
|
142
|
-
"@" +
|
137
|
+
def verp(recipient, split_char = "+")
|
138
|
+
local.to_s +
|
139
|
+
split_char + recipient.tr("@", "=") +
|
140
|
+
"@" + hostname
|
143
141
|
end
|
144
142
|
|
145
143
|
# NEXT: DMARC, SPF Validation
|
146
|
-
|
147
144
|
end
|
data/lib/email_address.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
# EmailAddress parses and validates email addresses against RFC standard,
|
4
4
|
# conventional, canonical, formats and other special uses.
|
5
5
|
module EmailAddress
|
6
|
-
|
7
6
|
require "email_address/config"
|
8
7
|
require "email_address/exchanger"
|
9
8
|
require "email_address/host"
|
@@ -49,21 +48,21 @@ module EmailAddress
|
|
49
48
|
|
50
49
|
# Creates an instance of this email address.
|
51
50
|
# This is a short-cut to EmailAddress::Address.new
|
52
|
-
def new(email_address, config={})
|
53
|
-
Address.new(email_address, config)
|
51
|
+
def new(email_address, config = {}, locale = "en")
|
52
|
+
Address.new(email_address, config, locale)
|
54
53
|
end
|
55
54
|
|
56
|
-
def new_redacted(email_address, config={})
|
57
|
-
Address.new(Address.new(email_address, config).redact)
|
55
|
+
def new_redacted(email_address, config = {}, locale = "en")
|
56
|
+
Address.new(Address.new(email_address, config, locale).redact)
|
58
57
|
end
|
59
58
|
|
60
|
-
def new_canonical(email_address, config={})
|
61
|
-
Address.new(Address.new(email_address, config).canonical, config)
|
59
|
+
def new_canonical(email_address, config = {}, locale = "en")
|
60
|
+
Address.new(Address.new(email_address, config, locale).canonical, config)
|
62
61
|
end
|
63
62
|
|
64
63
|
# Does the email address match any of the given rules
|
65
|
-
def matches?(email_address, rules, config={})
|
66
|
-
Address.new(email_address, config).matches?(rules)
|
64
|
+
def matches?(email_address, rules, config = {}, locale = "en")
|
65
|
+
Address.new(email_address, config, locale).matches?(rules)
|
67
66
|
end
|
68
67
|
end
|
69
68
|
end
|
@@ -1,23 +1,27 @@
|
|
1
|
-
|
2
|
-
require_relative '../test_helper'
|
1
|
+
require_relative "../test_helper"
|
3
2
|
|
4
3
|
class TestAR < MiniTest::Test
|
5
|
-
require_relative
|
4
|
+
require_relative "user"
|
6
5
|
|
7
6
|
def test_validation
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
# Disabled JRuby checks... weird CI failures. Hopefully someone can help?
|
8
|
+
if RUBY_PLATFORM != "java" # jruby
|
9
|
+
user = User.new(email: "Pat.Jones+ASDF#GMAIL.com")
|
10
|
+
assert_equal false, user.valid?
|
11
|
+
assert user.errors.messages[:email].first
|
12
|
+
user = User.new(email: "Pat.Jones+ASDF@GMAIL.com")
|
13
|
+
assert_equal true, user.valid?
|
14
|
+
end
|
13
15
|
end
|
14
16
|
|
15
17
|
def test_datatype
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
# Disabled JRuby checks... weird CI failures. Hopefully someone can help?
|
19
|
+
if RUBY_PLATFORM != "java" # jruby
|
20
|
+
if defined?(ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5
|
21
|
+
user = User.new(email: "Pat.Jones+ASDF@GMAIL.com")
|
22
|
+
assert_equal "pat.jones+asdf@gmail.com", user.email
|
23
|
+
assert_equal "patjones@gmail.com", user.canonical_email
|
24
|
+
end
|
20
25
|
end
|
21
26
|
end
|
22
|
-
|
23
27
|
end
|