email_address 0.1.19 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|