email_address 0.1.2 → 0.2.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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EmailAddress
2
4
  ##############################################################################
3
5
  # EmailAddress Local part consists of
@@ -65,42 +67,51 @@ module EmailAddress
65
67
  # [CFWS]
66
68
  ############################################################################
67
69
  class Local
68
- attr_reader :local
70
+ attr_reader :local
69
71
  attr_accessor :mailbox, :comment, :tag, :config, :original
70
- attr_accessor :syntax
72
+ attr_accessor :syntax, :locale
71
73
 
72
74
  # RFC-2142: MAILBOX NAMES FOR COMMON SERVICES, ROLES AND FUNCTIONS
73
- BUSINESS_MAILBOXES = %w(info marketing sales support)
74
- NETWORK_MAILBOXES = %w(abuse noc security)
75
- SERVICE_MAILBOXES = %w(postmaster hostmaster usenet news webmaster www uucp ftp)
76
- SYSTEM_MAILBOXES = %w(help mailer-daemon root) # Not from RFC-2142
77
- ROLE_MAILBOXES = %w(staff office orders billing careers jobs) # Not from RFC-2142
78
- SPECIAL_MAILBOXES = BUSINESS_MAILBOXES + NETWORK_MAILBOXES + SERVICE_MAILBOXES +
79
- SYSTEM_MAILBOXES + ROLE_MAILBOXES
80
- STANDARD_MAX_SIZE = 64
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
81
83
 
82
84
  # Conventional : word([.-+'_]word)*
83
- CONVENTIONAL_MAILBOX_REGEX = /\A [\p{L}\p{N}]+ (?: [\.\-\+\'_] [\p{L}\p{N}]+ )* \z/x
84
- CONVENTIONAL_MAILBOX_WITHIN = /[\p{L}\p{N}]+ (?: [\.\-\+\'_] [\p{L}\p{N}]+ )*/x
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
85
87
 
86
88
  # Relaxed: same characters, relaxed order
87
- RELAXED_MAILBOX_WITHIN = /[\p{L}\p{N}]+ (?: [\.\-\+\'_]+ [\p{L}\p{N}]+ )*/x
88
- RELAXED_MAILBOX_REGEX = /\A [\p{L}\p{N}]+ (?: [\.\-\+\'_]+ [\p{L}\p{N}]+ )* \z/x
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
89
91
 
90
92
  # RFC5322 Token: token."token".token (dot-separated tokens)
91
93
  # Quoted Token can also have: SPACE \" \\ ( ) , : ; < > @ [ \ ] .
92
94
  STANDARD_LOCAL_WITHIN = /
93
- (?: [\p{L}\p{N}\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~\(\)]+
94
- | \" (?: \\[\" \\] | [\x20 \! \x23-\x5B \x5D-\x7E \p{L} \p{N}] )+ \" )
95
- (?: \. (?: [\p{L}\p{N}\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~\(\)]+
96
- | \" (?: \\[\" \\] | [\x20 \! \x23-\x5B \x5D-\x7E \p{L} \p{N}] )+ \" ) )* /x
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
+
97
100
  STANDARD_LOCAL_REGEX = /\A #{STANDARD_LOCAL_WITHIN} \z/x
98
101
 
99
102
  REDACTED_REGEX = /\A \{ [0-9a-f]{40} \} \z/x # {sha1}
100
103
 
101
- def initialize(local, config={})
102
- self.config = config.empty? ? EmailAddress::Config.all_settings : config
103
- self.local = local
104
+ CONVENTIONAL_TAG_REGEX = # AZaz09_!'+-/=
105
+ %r{^([\w!'+\-/=.]+)$}i
106
+ RELAXED_TAG_REGEX = # AZaz09_!#$%&'*+-/=?^`{|}~
107
+ %r/^([\w.!\#$%&'*+\-\/=?\^`{|}~]+)$/i
108
+
109
+ def initialize(local, config = {}, host = nil, locale = "en")
110
+ @config = config.is_a?(Hash) ? Config.new(config) : config
111
+ self.local = local
112
+ @host = host
113
+ @locale = locale
114
+ @error = @error_message = nil
104
115
  end
105
116
 
106
117
  def local=(raw)
@@ -111,23 +122,23 @@ module EmailAddress
111
122
  if @config[:local_parse].is_a?(Proc)
112
123
  self.mailbox, self.tag, self.comment = @config[:local_parse].call(raw)
113
124
  else
114
- self.mailbox, self.tag, self.comment = self.parse(raw)
125
+ self.mailbox, self.tag, self.comment = parse(raw)
115
126
  end
116
127
 
117
128
  self.format
118
129
  end
119
130
 
120
131
  def parse(raw)
121
- if raw =~ /\A\"(.*)\"\z/ # Quoted
132
+ if raw =~ /\A"(.*)"\z/ # Quoted
122
133
  raw = $1
123
- raw.gsub!(/\\(.)/, '\1') # Unescape
124
- elsif @config[:local_fix]
125
- raw.gsub!(' ','')
126
- raw.gsub!(',','.')
127
- raw.gsub!(/([^\p{L}\p{N}]{2,10})/) {|s| s[0] } # Stutter punctuation typo
134
+ raw = raw.gsub(/\\(.)/, '\1') # Unescape
135
+ elsif @config[:local_fix] && @config[:local_format] != :standard
136
+ raw = raw.delete(" ")
137
+ raw = raw.tr(",", ".")
138
+ # raw.gsub!(/([^\p{L}\p{N}]{2,10})/) {|s| s[0] } # Stutter punctuation typo
128
139
  end
129
- raw, comment = self.parse_comment(raw)
130
- mailbox, tag = self.parse_tag(raw)
140
+ raw, comment = parse_comment(raw)
141
+ mailbox, tag = parse_tag(raw)
131
142
  mailbox ||= ""
132
143
  [mailbox, tag, comment]
133
144
  end
@@ -146,28 +157,28 @@ module EmailAddress
146
157
  end
147
158
 
148
159
  def parse_tag(raw)
149
- separator = @config[:tag_separator] ||= '+'
160
+ separator = @config[:tag_separator] ||= "+"
150
161
  raw.split(separator, 2)
151
162
  end
152
163
 
153
164
  # True if the the value contains only Latin characters (7-bit ASCII)
154
165
  def ascii?
155
- ! self.unicode?
166
+ !unicode?
156
167
  end
157
168
 
158
169
  # True if the the value contains non-Latin Unicde characters
159
170
  def unicode?
160
- self.local =~ /[^\p{InBasicLatin}]/ ? true : false
171
+ /[^\p{InBasicLatin}]/.match?(local)
161
172
  end
162
173
 
163
174
  # Returns true if the value matches the Redacted format
164
175
  def redacted?
165
- self.local =~ REDACTED_REGEX ? true : false
176
+ REDACTED_REGEX.match?(local)
166
177
  end
167
178
 
168
179
  # Returns true if the value matches the Redacted format
169
180
  def self.redacted?(local)
170
- local =~ REDACTED_REGEX ? true : false
181
+ REDACTED_REGEX.match?(local)
171
182
  end
172
183
 
173
184
  # Is the address for a common system or business role account?
@@ -180,81 +191,80 @@ module EmailAddress
180
191
  end
181
192
 
182
193
  # Builds the local string according to configurations
183
- def format(form=@config[:local_format]||:conventional)
194
+ def format(form = @config[:local_format] || :conventional)
184
195
  if @config[:local_format].is_a?(Proc)
185
196
  @config[:local_format].call(self)
186
197
  elsif form == :conventional
187
- self.conventional
198
+ conventional
188
199
  elsif form == :canonical
189
- self.canonical
190
- elsif form == :relax
191
- self.relax
200
+ canonical
201
+ elsif form == :relaxed
202
+ relax
192
203
  elsif form == :standard
193
- self.standard
204
+ standard
194
205
  end
195
206
  end
196
207
 
197
208
  # Returns a conventional form of the address
198
209
  def conventional
199
- if self.tag
200
- [self.mailbox, self.tag].join(@config[:tag_separator])
210
+ if tag
211
+ [mailbox, tag].join(@config[:tag_separator])
201
212
  else
202
- self.mailbox
213
+ mailbox
203
214
  end
204
215
  end
205
216
 
206
217
  # Returns a canonical form of the address
207
218
  def canonical
208
219
  if @config[:mailbox_canonical]
209
- @config[:mailbox_canonical].call(self.mailbox)
220
+ @config[:mailbox_canonical].call(mailbox)
210
221
  else
211
- self.mailbox.downcase
222
+ mailbox.downcase
212
223
  end
213
224
  end
214
225
 
215
226
  # Relaxed format: mailbox and tag, no comment, no extended character set
216
227
  def relax
217
- form = self.mailbox
218
- form += @config[:tag_separator] + self.tag if self.tag
219
- form.gsub!(/[ \"\(\),:<>@\[\]\\]/,'')
220
- form
228
+ form = mailbox
229
+ form += @config[:tag_separator] + tag if tag
230
+ form.gsub(/[ "(),:<>@\[\]\\]/, "")
221
231
  end
222
232
 
223
233
  # Returns a normalized version of the standard address parts.
224
234
  def standard
225
- form = self.mailbox
226
- form += @config[:tag_separator] + self.tag if self.tag
227
- form += "(" + self.comment + ")" if self.comment
228
- form.gsub!(/([\\\"])/, '\\\1') # Escape \ and "
229
- if form =~ /[ \"\(\),:<>@\[\\\]]/ # Space and "(),:;<>@[\]
230
- form = %Q("#{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}")
231
241
  end
232
242
  form
233
243
  end
234
244
 
235
245
  # Sets the part to be the conventional form
236
246
  def conventional!
237
- self.local = self.conventional
247
+ self.local = conventional
238
248
  end
239
249
 
240
250
  # Sets the part to be the canonical form
241
251
  def canonical!
242
- self.local = self.canonical
252
+ self.local = canonical
243
253
  end
244
254
 
245
255
  # Dropps unusual parts of Standard form to form a relaxed version.
246
256
  def relax!
247
- self.local = self.relax
257
+ self.local = relax
248
258
  end
249
259
 
250
260
  # Returns the munged form of the address, like "ma*****"
251
261
  def munge
252
- self.to_s.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] }
262
+ to_s.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] }
253
263
  end
254
264
 
255
265
  # Mailbox with trailing numbers removed
256
266
  def root_name
257
- self.mailbox =~ /\A(.+?)\d+\z/ ? $1 : self.mailbox
267
+ mailbox =~ /\A(.+?)\d+\z/ ? $1 : mailbox
258
268
  end
259
269
 
260
270
  ############################################################################
@@ -262,19 +272,19 @@ module EmailAddress
262
272
  ############################################################################
263
273
 
264
274
  # True if the part is valid according to the configurations
265
- def valid?(format=@config[:local_format]||:conventional)
275
+ def valid?(format = @config[:local_format] || :conventional)
266
276
  if @config[:mailbox_validator].is_a?(Proc)
267
- @config[:mailbox_validator].call(self.mailbox, self.tag)
277
+ @config[:mailbox_validator].call(mailbox, tag)
268
278
  elsif format.is_a?(Proc)
269
279
  format.call(self)
270
280
  elsif format == :conventional
271
- self.conventional?
281
+ conventional?
272
282
  elsif format == :relaxed
273
- self.relaxed?
283
+ relaxed?
274
284
  elsif format == :redacted
275
- self.redacted?
285
+ redacted?
276
286
  elsif format == :standard
277
- self.standard?
287
+ standard?
278
288
  elsif format == :none
279
289
  true
280
290
  else
@@ -285,13 +295,13 @@ module EmailAddress
285
295
  # Returns the format of the address
286
296
  def format?
287
297
  # if :custom
288
- if self.conventional?
298
+ if conventional?
289
299
  :conventional
290
- elsif self.relaxed?
300
+ elsif relaxed?
291
301
  :relax
292
- elsif self.redacted?
302
+ elsif redacted?
293
303
  :redacted
294
- elsif self.standard?
304
+ elsif standard?
295
305
  :standard
296
306
  else
297
307
  :invalid
@@ -299,24 +309,38 @@ module EmailAddress
299
309
  end
300
310
 
301
311
  def valid_size?
302
- return false if @config[:local_size] && !@config[:local_size].include?(self.local.size)
303
- return false if @config[:mailbox_size] && !@config[:mailbox_size].include?(self.mailbox.size)
304
- return false if self.local.size > STANDARD_MAX_SIZE
312
+ return set_error(:local_size_long) if local.size > STANDARD_MAX_SIZE
313
+ if @host&.hosted_service?
314
+ return false if @config[:local_private_size] && !valid_size_checks(@config[:local_private_size])
315
+ elsif @config[:local_size] && !valid_size_checks(@config[:local_size])
316
+ return false
317
+ end
318
+ return false if @config[:mailbox_size] && !valid_size_checks(@config[:mailbox_size])
305
319
  true
306
320
  end
307
321
 
308
- def valid_encoding?(enc=@config[:local_encoding]||:ascii)
309
- return false if enc == :ascii && self.unicode?
310
- return false if enc == :unicode && self.ascii?
322
+ def valid_size_checks(range)
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
+ true
326
+ end
327
+
328
+ def valid_encoding?(enc = @config[:local_encoding] || :ascii)
329
+ return false if enc == :ascii && unicode?
311
330
  true
312
331
  end
313
332
 
314
333
  # True if the part matches the conventional format
315
334
  def conventional?
316
335
  self.syntax = :invalid
317
- self.local =~ CONVENTIONAL_MAILBOX_REGEX or return false
318
- self.valid_size? or return false
319
- self.valid_encoding? or return false
336
+ if tag
337
+ return false unless mailbox =~ CONVENTIONAL_MAILBOX_REGEX &&
338
+ tag =~ CONVENTIONAL_TAG_REGEX
339
+ else
340
+ return false unless CONVENTIONAL_MAILBOX_REGEX.match?(local)
341
+ end
342
+ valid_size? or return false
343
+ valid_encoding? or return false
320
344
  self.syntax = :conventional
321
345
  true
322
346
  end
@@ -324,9 +348,12 @@ module EmailAddress
324
348
  # Relaxed conventional is not so strict about character order.
325
349
  def relaxed?
326
350
  self.syntax = :invalid
327
- self.valid_size? or return false
328
- self.valid_encoding? or return false
329
- if self.local =~ RELAXED_MAILBOX_REGEX
351
+ valid_size? or return false
352
+ valid_encoding? or return false
353
+ if tag
354
+ return false unless mailbox =~ RELAXED_MAILBOX_REGEX &&
355
+ tag =~ RELAXED_TAG_REGEX
356
+ elsif RELAXED_MAILBOX_REGEX.match?(local)
330
357
  self.syntax = :relaxed
331
358
  true
332
359
  else
@@ -337,9 +364,9 @@ module EmailAddress
337
364
  # True if the part matches the RFC standard format
338
365
  def standard?
339
366
  self.syntax = :invalid
340
- self.valid_size? or return false
341
- self.valid_encoding? or return false
342
- if self.local =~ STANDARD_LOCAL_REGEX
367
+ valid_size? or return false
368
+ valid_encoding? or return false
369
+ if STANDARD_LOCAL_REGEX.match?(local)
343
370
  self.syntax = :standard
344
371
  true
345
372
  else
@@ -352,10 +379,23 @@ module EmailAddress
352
379
  def matches?(*rules)
353
380
  rules.flatten.each do |r|
354
381
  if r =~ /(.+)@\z/
355
- return r if File.fnmatch?($1, self.local)
382
+ return r if File.fnmatch?($1, local)
356
383
  end
357
384
  end
358
385
  false
359
386
  end
387
+
388
+ def set_error(err, reason = nil)
389
+ @error = err
390
+ @reason = reason
391
+ @error_message = Config.error_message(err, locale)
392
+ false
393
+ end
394
+
395
+ attr_reader :error_message
396
+
397
+ def error
398
+ valid? ? nil : (@error || :local_invalid)
399
+ end
360
400
  end
361
401
  end
@@ -0,0 +1,21 @@
1
+ en:
2
+ email_address:
3
+ address_unknown: "Unknown Email Address"
4
+ domain_does_not_accept_email: "This domain is not configured to accept email"
5
+ domain_invalid: "Invalid Domain Name"
6
+ domain_no_localhost: "localhost is not allowed for your domain name"
7
+ domain_unknown: "Domain name not registered"
8
+ exceeds_size: "Address too long"
9
+ incomplete_domain: "Domain name is incomplete"
10
+ invalid_address: "Invalid Email Address"
11
+ invalid_host: "Invalid Host/Domain Name"
12
+ invalid_mailbox: "Invalid Mailbox"
13
+ ip_address_forbidden: "IP Addresses are not allowed"
14
+ ip_address_no_localhost: "Localhost IP addresses are not allowed"
15
+ ipv4_address_invalid: "This is not a valid IPv4 address"
16
+ ipv6_address_invalid: "This is not a valid IPv6 address"
17
+ local_size_long: "Mailbox name too long"
18
+ local_size_short: "Mailbox name too short"
19
+ local_invalid: "Recipient is not valid"
20
+ not_allowed: "Address is not allowed"
21
+ server_not_available: "The remote email server is not available"
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module EmailAddress::Rewriter
6
+ SRS_FORMAT_REGEX = /\ASRS0=(....)=(\w\w)=(.+?)=(.+?)@(.+)\z/
7
+
8
+ def parse_rewritten(e)
9
+ @rewrite_scheme = nil
10
+ @rewrite_error = nil
11
+ parse_srs(e)
12
+ # e = parse_batv(e)
13
+ end
14
+
15
+ #---------------------------------------------------------------------------
16
+ # SRS (Sender Rewriting Scheme) allows an address to be forwarded from the
17
+ # original owner and encoded to be used with the domain name of the MTA (Mail
18
+ # Transport Agent). It encodes the original address within the local part of the
19
+ # sending email address and respects VERP. If example.com needs to forward a
20
+ # message from "sender@gmail.com", the SMTP envelope sender is used at this
21
+ # address. These methods respect DMARC and prevent spoofing email send using
22
+ # a different domain.
23
+ # Format: SRS0=HHH=TT=domain=local@sending-domain.com
24
+ #---------------------------------------------------------------------------
25
+ def srs(sending_domain, options = {}, &block)
26
+ tt = srs_tt
27
+ a = [tt, hostname, local.to_s].join("=") + "@" + sending_domain
28
+ hhh = srs_hash(a, options, &block)
29
+
30
+ ["SRS0", hhh, a].join("=")
31
+ end
32
+
33
+ def srs?(email)
34
+ email.match(SRS_FORMAT_REGEX) ? true : false
35
+ end
36
+
37
+ def parse_srs(email, options = {}, &block)
38
+ if email&.match(SRS_FORMAT_REGEX)
39
+ @rewrite_scheme = :srs
40
+ hhh, tt, domain, local, sending_domain = [$1, $2, $3, $4, $5]
41
+ # hhh = tt = sending_domain if false && hhh # Hide warnings for now :-)
42
+ a = [tt, domain, local].join("=") + "@" + sending_domain
43
+ unless srs_hash(a, options, &block) === hhh
44
+ @rewrite_error = "Invalid SRS Email Address: Possibly altered"
45
+ end
46
+ unless tt == srs_tt
47
+ @rewrite_error = "Invalid SRS Email Address: Too old"
48
+ end
49
+ [local, domain].join("@")
50
+ else
51
+ email
52
+ end
53
+ end
54
+
55
+ # SRS Timeout Token
56
+ # Returns a 2-character code for the day. After a few days the code will roll.
57
+ # TT has a one-day resolution in order to make the address invalid after a few days.
58
+ # The cycle period is 3.5 years. Used to control late bounces and harvesting.
59
+ def srs_tt(t = Time.now.utc)
60
+ Base64.encode64((t.to_i / (60 * 60 * 24) % 210).to_s)[0, 2]
61
+ end
62
+
63
+ def srs_hash(email, options = {}, &block)
64
+ key = options[:key] || @config[:key] || email.reverse
65
+ if block
66
+ block.call(email)[0, 4]
67
+ else
68
+ Base64.encode64(Digest::SHA1.digest(email + key))[0, 4]
69
+ end
70
+ end
71
+
72
+ #---------------------------------------------------------------------------
73
+ # Returns a BATV form email address with "Private Signature" (prvs).
74
+ # Options: key: 0-9 key digit to use
75
+ # key_0..key_9: secret key used to sign/verify
76
+ # prvs_days: number of days before address "expires"
77
+ #
78
+ # BATV - Bounce Address Tag Validation
79
+ # PRVS - Simple Private Signature
80
+ # Ex: prvs=KDDDSSSS=user@example.com
81
+ # * K: Digit for Key rotation
82
+ # * DDD: Expiry date, since 1970, low 3 digits
83
+ # * SSSSSS: sha1( KDDD + orig-mailfrom + key)[0,6]
84
+ # See: https://tools.ietf.org/html/draft-levine-smtp-batv-01
85
+ #---------------------------------------------------------------------------
86
+ def batv_prvs(options = {})
87
+ k = options[:prvs_key_id] || "0"
88
+ prvs_days = options[:prvs_days] || @config[:prvs_days] || 30
89
+ ddd = prvs_day(prvs_days)
90
+ ssssss = prvs_sign(k, ddd, to_s, options)
91
+ ["prvs=", k, ddd, ssssss, "=", to_s].join("")
92
+ end
93
+
94
+ PRVS_REGEX = /\Aprvs=(\d)(\d{3})(\w{6})=(.+)\z/
95
+
96
+ def parse_prvs(email, options = {})
97
+ if email.match(PRVS_REGEX)
98
+ @rewrite_scheme = :prvs
99
+ k, ddd, ssssss, email = [$1, $2, $3, $4]
100
+
101
+ unless ssssss == prvs_sign(k, ddd, email, options)
102
+ @rewrite_error = "Invalid BATV Address: Signature unverified"
103
+ end
104
+ exp = ddd.to_i
105
+ roll = 1000 - exp # rolling 1000 day window
106
+ today = prvs_day(0)
107
+ # I'm sure this is wrong
108
+ if exp > today && exp < roll
109
+ @rewrite_error = "Invalid SRS Email Address: Address expired"
110
+ elsif exp < today && (today - exp) > 0
111
+ @rewrite_error = "Invalid SRS Email Address: Address expired"
112
+ end
113
+ [local, domain].join("@")
114
+ else
115
+ email
116
+ end
117
+ end
118
+
119
+ def prvs_day(days)
120
+ ((Time.now.to_i + (days * 24 * 60 * 60)) / (24 * 60 * 60)).to_s[-3, 3]
121
+ end
122
+
123
+ def prvs_sign(k, ddd, email, options = {})
124
+ str = [ddd, ssssss, "=", to_s].join("")
125
+ key = options["key_#{k}".to_i] || @config["key_#{k}".to_i] || str.reverse
126
+ Digest::SHA1.hexdigest([k, ddd, email, key].join(""))[0, 6]
127
+ end
128
+
129
+ #---------------------------------------------------------------------------
130
+ # VERP Embeds a recipient email address into the bounce address
131
+ # Bounce Address: message-id@example.net
132
+ # Recipient Email: recipient@example.org
133
+ # VERP : message-id+recipient=example.org@example.net
134
+ # To handle incoming verp, the "tag" is the recipient email address,
135
+ # remember to convert the last '=' into a '@' to reconstruct it.
136
+ #---------------------------------------------------------------------------
137
+ def verp(recipient, split_char = "+")
138
+ local.to_s +
139
+ split_char + recipient.tr("@", "=") +
140
+ "@" + hostname
141
+ end
142
+
143
+ # NEXT: DMARC, SPF Validation
144
+ end
@@ -1,3 +1,3 @@
1
1
  module EmailAddress
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end