email_address 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f7c4c2225713bcb8fa68e8c07aba6de9db62e629
4
- data.tar.gz: 8c1f4a244a889254825d1325414536564a12abdf
2
+ SHA256:
3
+ metadata.gz: f54dab3992bf4e19436dd4771c376b5c4a770b263b6e91704bb8f4b4aa1c33c6
4
+ data.tar.gz: 64f8de302e904131bcc25be93674d8e976e6f7ee37d6f75b3c4fcaf362054256
5
5
  SHA512:
6
- metadata.gz: 4927cd30d0302715b8f96a9224f13a85742242c27a1f813ffecdf75eb1896e2954fb36590213cad533d325ba121e096c15f74cb9c416fef191fe2181d668e591
7
- data.tar.gz: c5c4426b8749dd1cdd4d8617da291c88d6dbca11040a3bcbb1d729e43b14dabf7a4f4c6bea244fec997bb0d0a21aa47462cf66056fdaf536f181e3702216d5dd
6
+ metadata.gz: e5f284c688160493c8d30170007b797179ed18c38132e3d9d96d7d1afb390f996ac4f4d68dd3d473706c9a7a8be19cbaa2de2cf64b603032408cc7f3a1d60253
7
+ data.tar.gz: e78d1280da5668f07bd101f6f83df0e48c8f07a93bfdfe23b8a837632b5c62c57f2e19e309999c3b05c39f9c05a387394efd32bfc6f3ac2cdb28f49d0d4d0cad
data/README.md CHANGED
@@ -28,8 +28,10 @@ To quickly validate email addresses, use the valid? and error helpers.
28
28
  `valid?` returns a boolean, and `error` returns nil if valid, otherwise
29
29
  a basic error message.
30
30
 
31
- EmailAddress.valid? "allen@google.com" #=> true
32
- EmailAddress.error "allen@bad-d0main.com" #=> "Invalid Host/Domain Name"
31
+ ```ruby
32
+ EmailAddress.valid? "allen@google.com" #=> true
33
+ EmailAddress.error "allen@bad-d0main.com" #=> "Invalid Host/Domain Name"
34
+ ```
33
35
 
34
36
  `EmailAddress` deeply validates your email addresses. It checks:
35
37
 
@@ -48,14 +50,17 @@ website on one provider (ISP, Heroku, etc.), and email on a different
48
50
  provider (such as Google Apps). Note that `example.com`, while
49
51
  a valid domain name, does not have MX records.
50
52
 
51
- EmailAddress.valid? "allen@example.com" #=> false
52
- EmailAddress.valid? "allen@example.com", host_validation: :syntax #=> true
53
+ ```ruby
54
+ EmailAddress.valid? "allen@example.com" #=> false
55
+ EmailAddress.valid? "allen@example.com", host_validation: :syntax #=> true
56
+ ```
53
57
 
54
58
  Most mail servers do not yet support Unicode mailboxes, so the default here is ASCII.
55
59
 
56
- EmailAddress.error "Pelé@google.com" #=> "Invalid Recipient/Mailbox"
57
- EmailAddress.valid? "Pelé@google.com", local_encoding: :unicode #=> true
58
-
60
+ ```ruby
61
+ EmailAddress.error "Pelé@google.com" #=> "Invalid Recipient/Mailbox"
62
+ EmailAddress.valid? "Pelé@google.com", local_encoding: :unicode #=> true
63
+ ```
59
64
 
60
65
  ## Background
61
66
 
@@ -89,15 +94,20 @@ introduces terms to distinguish types of email addresses.
89
94
 
90
95
  madness!."()<>[]:,;@\\\"!#$%&'*+-/=?^_`{}| ~.a(comment )"@example.org
91
96
 
97
+ * *Base* - A unique mailbox without tags. For gmail, is uses the incoming
98
+ punctation, essential when building an MD5 or SHA1 to match services
99
+ like Gravatar, and email address digest interchange.
100
+
92
101
  * *Canonical* - An unique account address, lower-cased, without the
93
102
  tag, and with irrelevant characters stripped.
94
103
 
95
104
  clark.kent+scoops@gmail.com => clarkkent@gmail.com
96
105
 
97
- * *Reference* - The MD5 of the Canonical format, used to share account
106
+ * *Reference* - The MD5 of the Base format, used to share account
98
107
  references without exposing the private email address directly.
99
108
 
100
- Clark.Kent+scoops@gmail.com => c5be3597c391169a5ad2870f9ca51901
109
+ Clark.Kent+scoops@gmail.com =>
110
+ clark.kent@gmail.com => 1429a1dfc797d6e93075fef011c373fb
101
111
 
102
112
  * *Redacted* - A form of the email address where it is replaced by
103
113
  a SHA1-based version to remove the original address from the
@@ -181,8 +191,10 @@ If you are not using Bundler, you need to install the gem yourself.
181
191
 
182
192
  Require the gem inside your script.
183
193
 
184
- require 'rubygems'
185
- require 'email_address'
194
+ ```ruby
195
+ require 'rubygems'
196
+ require 'email_address'
197
+ ```
186
198
 
187
199
  ## Usage
188
200
 
@@ -192,41 +204,47 @@ instantiate an object to inspect the address.
192
204
  These top-level helpers return edited email addresses and validation
193
205
  check.
194
206
 
195
- address = "Clark.Kent+scoops@gmail.com"
196
- EmailAddress.valid?(address) #=> true
197
- EmailAddress.normal(address) #=> "clark.kent+scoops@gmail.com"
198
- EmailAddress.canonical(address) #=> "clarkkent@gmail.com"
199
- EmailAddress.reference(address) #=> "c5be3597c391169a5ad2870f9ca51901"
200
- EmailAddress.redact(address) #=> "{bea3f3560a757f8142d38d212a931237b218eb5e}@gmail.com"
201
- EmailAddress.munge(address) #=> "cl*****@gm*****"
202
- EmailAddress.matches?(address, 'google') #=> 'google' (true)
203
- EmailAddress.error("#bad@example.com") #=> "Invalid Mailbox"
207
+ ```ruby
208
+ address = "Clark.Kent+scoops@gmail.com"
209
+ EmailAddress.valid?(address) #=> true
210
+ EmailAddress.normal(address) #=> "clark.kent+scoops@gmail.com"
211
+ EmailAddress.canonical(address) #=> "clarkkent@gmail.com"
212
+ EmailAddress.reference(address) #=> "c5be3597c391169a5ad2870f9ca51901"
213
+ EmailAddress.redact(address) #=> "{bea3f3560a757f8142d38d212a931237b218eb5e}@gmail.com"
214
+ EmailAddress.munge(address) #=> "cl*****@gm*****"
215
+ EmailAddress.matches?(address, 'google') #=> 'google' (true)
216
+ EmailAddress.error("#bad@example.com") #=> "Invalid Mailbox"
217
+ ```
204
218
 
205
219
  Or you can create an instance of the email address to work with it.
206
220
 
207
- email = EmailAddress.new(address) #=> #<EmailAddress::Address:0x007fe6ee150540 ...>
208
- email.normal #=> "clark.kent+scoops@gmail.com"
209
- email.canonical #=> "clarkkent@gmail.com"
210
- email.original #=> "Clark.Kent+scoops@gmail.com"
211
- email.valid? #=> true
221
+ ```ruby
222
+ email = EmailAddress.new(address) #=> #<EmailAddress::Address:0x007fe6ee150540 ...>
223
+ email.normal #=> "clark.kent+scoops@gmail.com"
224
+ email.canonical #=> "clarkkent@gmail.com"
225
+ email.original #=> "Clark.Kent+scoops@gmail.com"
226
+ email.valid? #=> true
227
+ ```
212
228
 
213
229
  Here are some other methods that are available.
214
230
 
215
- email.redact #=> "{bea3f3560a757f8142d38d212a931237b218eb5e}@gmail.com"
216
- email.sha1 #=> "bea3f3560a757f8142d38d212a931237b218eb5e"
217
- email.md5 #=> "c5be3597c391169a5ad2870f9ca51901"
218
- email.host_name #=> "gmail.com"
219
- email.provider #=> :google
220
- email.mailbox #=> "clark.kent"
221
- email.tag #=> "scoops"
231
+ ```ruby
232
+ email.redact #=> "{bea3f3560a757f8142d38d212a931237b218eb5e}@gmail.com"
233
+ email.sha1 #=> "bea3f3560a757f8142d38d212a931237b218eb5e"
234
+ email.md5 #=> "c5be3597c391169a5ad2870f9ca51901"
235
+ email.host_name #=> "gmail.com"
236
+ email.provider #=> :google
237
+ email.mailbox #=> "clark.kent"
238
+ email.tag #=> "scoops"
222
239
 
223
- email.host.exchanger.first[:ip] #=> "2a00:1450:400b:c02::1a"
224
- email.host.txt_hash #=> {:v=>"spf1", :redirect=>"\_spf.google.com"}
240
+ email.host.exchanger.first[:ip] #=> "2a00:1450:400b:c02::1a"
241
+ email.host.txt_hash #=> {:v=>"spf1", :redirect=>"\_spf.google.com"}
225
242
 
226
- EmailAddress.normal("HIRO@こんにちは世界.com")
227
- #=> "hiro@xn--28j2a3ar1pp75ovm7c.com"
228
- EmailAddress.normal("hiro@xn--28j2a3ar1pp75ovm7c.com", host_encoding: :unicode)
229
- #=> "hiro@こんにちは世界.com"
243
+ EmailAddress.normal("HIRO@こんにちは世界.com")
244
+ #=> "hiro@xn--28j2a3ar1pp75ovm7c.com"
245
+ EmailAddress.normal("hiro@xn--28j2a3ar1pp75ovm7c.com", host_encoding: :unicode)
246
+ #=> "hiro@こんにちは世界.com"
247
+ ```
230
248
 
231
249
  #### Rails Validator
232
250
 
@@ -235,9 +253,11 @@ Specify your email address attributes with `field: :user_email`, or
235
253
  `fields: [:email1, :email2]`. If neither is given, it assumes to use the
236
254
  `email` or `email_address` attribute.
237
255
 
238
- class User < ActiveRecord::Base
239
- validates_with EmailAddress::ActiveRecordValidator, field: :email
240
- end
256
+ ```ruby
257
+ class User < ActiveRecord::Base
258
+ validates_with EmailAddress::ActiveRecordValidator, field: :email
259
+ end
260
+ ```
241
261
 
242
262
  #### Rails Email Address Type Attribute
243
263
 
@@ -247,9 +267,11 @@ First, you need to register the type in
247
267
  `config/initializers/email_address.rb` along with any global
248
268
  configurations you want.
249
269
 
250
- ActiveRecord::Type.register(:email_address, EmailAddress::EmailAddressType)
251
- ActiveRecord::Type.register(:canonical_email_address,
252
- EmailAddress::CanonicalEmailAddressType)
270
+ ```ruby
271
+ ActiveRecord::Type.register(:email_address, EmailAddress::EmailAddressType)
272
+ ActiveRecord::Type.register(:canonical_email_address,
273
+ EmailAddress::CanonicalEmailAddressType)
274
+ ```
253
275
 
254
276
  Assume the Users table contains the columns "email" and "canonical_email".
255
277
  We want to normalize the address in "email" and store the canonical/unique
@@ -258,39 +280,42 @@ the email attribute is assigned. With the canonical_email column,
258
280
  we can look up the User, even it the given email address didn't exactly
259
281
  match the registered version.
260
282
 
261
- class User < ApplicationRecord
262
- attribute :email, :email_address
263
- attribute :canonical_email, :canonical_email_address
264
-
265
- validates_with EmailAddress::ActiveRecordValidator,
266
- fields: %i(email canonical_email)
267
-
268
- def email=(email_address)
269
- self[:canonical_email] = email_address
270
- self[:email] = email_address
271
- end
272
-
273
- def self.find_by_email(email)
274
- user = self.find_by(email: EmailAddress.normal(email))
275
- user ||= self.find_by(canonical_email: EmailAddress.canonical(email))
276
- user ||= self.find_by(canonical_email: EmailAddress.redacted(email))
277
- user
278
- end
279
-
280
- def redact!
281
- self[:canonical_email] = EmailAddress.redact(self.canonical_email)
282
- self[:email] = self[:canonical_email]
283
- end
284
- end
283
+ ```ruby
284
+ class User < ApplicationRecord
285
+ attribute :email, :email_address
286
+ attribute :canonical_email, :canonical_email_address
287
+
288
+ validates_with EmailAddress::ActiveRecordValidator,
289
+ fields: %i(email canonical_email)
290
+
291
+ def email=(email_address)
292
+ self[:canonical_email] = email_address
293
+ self[:email] = email_address
294
+ end
295
+
296
+ def self.find_by_email(email)
297
+ user = self.find_by(email: EmailAddress.normal(email))
298
+ user ||= self.find_by(canonical_email: EmailAddress.canonical(email))
299
+ user ||= self.find_by(canonical_email: EmailAddress.redacted(email))
300
+ user
301
+ end
302
+
303
+ def redact!
304
+ self[:canonical_email] = EmailAddress.redact(self.canonical_email)
305
+ self[:email] = self[:canonical_email]
306
+ end
307
+ end
308
+ ```
285
309
 
286
310
  Here is how the User model works:
287
311
 
288
- user = User.create(email:"Pat.Smith+registrations@gmail.com")
289
- user.email #=> "pat.smith+registrations@gmail.com"
290
- user.canonical_email #=> "patsmith@gmail.com"
291
- User.find_by_email("PAT.SMITH@GMAIL.COM")
292
- #=> #<User email="pat.smith+registrations@gmail.com">
293
-
312
+ ```ruby
313
+ user = User.create(email:"Pat.Smith+registrations@gmail.com")
314
+ user.email #=> "pat.smith+registrations@gmail.com"
315
+ user.canonical_email #=> "patsmith@gmail.com"
316
+ User.find_by_email("PAT.SMITH@GMAIL.COM")
317
+ #=> #<User email="pat.smith+registrations@gmail.com">
318
+ ```
294
319
 
295
320
  The `find_by_email` method looks up a given email address by the
296
321
  normalized form (lower case), then by the canonical form, then finally
@@ -313,17 +338,19 @@ which syntax and network validations to perform.
313
338
 
314
339
  You can compare email addresses:
315
340
 
316
- e1 = EmailAddress.new("Clark.Kent@Gmail.com")
317
- e2 = EmailAddress.new("clark.kent+Superman@Gmail.com")
318
- e3 = EmailAddress.new(e2.redact)
319
- e1.to_s #=> "clark.kent@gmail.com"
320
- e2.to_s #=> "clark.kent+superman@gmail.com"
321
- e3.to_s #=> "{bea3f3560a757f8142d38d212a931237b218eb5e}@gmail.com"
341
+ ```ruby
342
+ e1 = EmailAddress.new("Clark.Kent@Gmail.com")
343
+ e2 = EmailAddress.new("clark.kent+Superman@Gmail.com")
344
+ e3 = EmailAddress.new(e2.redact)
345
+ e1.to_s #=> "clark.kent@gmail.com"
346
+ e2.to_s #=> "clark.kent+superman@gmail.com"
347
+ e3.to_s #=> "{bea3f3560a757f8142d38d212a931237b218eb5e}@gmail.com"
322
348
 
323
- e1 == e2 #=> false (Matches by normalized address)
324
- e1.same_as?(e2) #=> true (Matches as canonical address)
325
- e1.same_as?(e3) #=> true (Matches as redacted address)
326
- e1 < e2 #=> true (Compares using normalized address)
349
+ e1 == e2 #=> false (Matches by normalized address)
350
+ e1.same_as?(e2) #=> true (Matches as canonical address)
351
+ e1.same_as?(e3) #=> true (Matches as redacted address)
352
+ e1 < e2 #=> true (Compares using normalized address)
353
+ ```
327
354
 
328
355
  #### Matching
329
356
 
@@ -340,13 +367,15 @@ Matching addresses by simple patterns:
340
367
 
341
368
  Usage:
342
369
 
343
- e = EmailAddress.new("Clark.Kent@Gmail.com")
344
- e.matches?("gmail.com") #=> true
345
- e.matches?("google") #=> true
346
- e.matches?(".org") #=> false
347
- e.matches?("g*com") #=> true
348
- e.matches?("gmail.") #=> true
349
- e.matches?("*kent*@") #=> true
370
+ ```ruby
371
+ e = EmailAddress.new("Clark.Kent@Gmail.com")
372
+ e.matches?("gmail.com") #=> true
373
+ e.matches?("google") #=> true
374
+ e.matches?(".org") #=> false
375
+ e.matches?("g*com") #=> true
376
+ e.matches?("gmail.") #=> true
377
+ e.matches?("*kent*@") #=> true
378
+ ```
350
379
 
351
380
  ### Configuration
352
381
 
@@ -354,26 +383,34 @@ You can pass an options hash on the `.new()` and helper class methods to
354
383
  control how the library treats that address. These can also be
355
384
  configured during initialization by provider and default (see below).
356
385
 
357
- EmailAddress.new("clark.kent@gmail.com",
358
- host_validation: :syntax, host_encoding: :unicode)
386
+ ```ruby
387
+ EmailAddress.new("clark.kent@gmail.com",
388
+ host_validation: :syntax, host_encoding: :unicode)
389
+ ```
359
390
 
360
391
  Globally, you can change and query configuration options:
361
392
 
362
- EmailAddress::Config.setting(:host_validation, :mx)
363
- EmailAddress::Config.setting(:host_validation) #=> :mx
393
+ ```ruby
394
+ EmailAddress::Config.setting(:host_validation, :mx)
395
+ EmailAddress::Config.setting(:host_validation) #=> :mx
396
+ ```
364
397
 
365
398
  Or set multiple settings at once:
366
399
 
367
- EmailAddress::Config.configure(local_downcase:false, host_validation: :syntax)
400
+ ```ruby
401
+ EmailAddress::Config.configure(local_downcase: false, host_validation: :syntax)
402
+ ```
368
403
 
369
404
  You can add special rules by domain or provider. It takes the options
370
405
  above and adds the :domain_match and :exchanger_match rules.
371
406
 
372
- EmailAddress.define_provider('google',
373
- domain_match: %w(gmail.com googlemail.com),
374
- exchanger_match: %w(google.com), # Requires host_validation==:mx
375
- local_size: 5..64,
376
- mailbox_canonical: ->(m) {m.gsub('.','')})
407
+ ```ruby
408
+ EmailAddress.define_provider('google',
409
+ domain_match: %w(gmail.com googlemail.com),
410
+ exchanger_match: %w(google.com), # Requires host_validation==:mx
411
+ local_size: 5..64,
412
+ mailbox_canonical: ->(m) {m.gsub('.','')})
413
+ ```
377
414
 
378
415
  The library ships with the most common set of provider rules. It is not meant
379
416
  to house a database of all providers, but a separate `email_address-providers`
@@ -391,26 +428,29 @@ DNS. If you specify an exchanger pattern, but requires a DNS MX lookup.
391
428
  For Rails application, create an initializer file with your default
392
429
  configuration options:
393
430
 
394
- # ./config/initializers/email_address.rb
395
- EmailAddress::Config.setting( local_format: :relaxed )
396
- EmailAddress::Config.provider(:github,
397
- host_match: %w(github.com), local_format: :standard)
431
+ ```ruby
432
+ # ./config/initializers/email_address.rb
433
+ EmailAddress::Config.setting( local_format: :relaxed )
434
+ EmailAddress::Config.provider(:github,
435
+ host_match: %w(github.com), local_format: :standard)
436
+ ```
398
437
 
399
438
  #### Override Error Messaegs
400
439
 
401
440
  You can override the default error messages as follows:
402
441
 
403
- EmailAddress::Config.error_messages(
404
- invalid_address: "Invalid Email Address",
405
- invalid_mailbox: "Invalid Recipient/Mailbox",
406
- invalid_host: "Invalid Host/Domain Name",
407
- exceeds_size: "Address too long",
408
- not_allowed: "Address is not allowed",
409
- incomplete_domain: "Domain name is incomplete")
442
+ ```ruby
443
+ EmailAddress::Config.error_messages(
444
+ invalid_address: "Invalid Email Address",
445
+ invalid_mailbox: "Invalid Recipient/Mailbox",
446
+ invalid_host: "Invalid Host/Domain Name",
447
+ exceeds_size: "Address too long",
448
+ not_allowed: "Address is not allowed",
449
+ incomplete_domain: "Domain name is incomplete")
450
+ ```
410
451
 
411
452
  Full translation support would be ideal though.
412
453
 
413
-
414
454
  ### Available Configuration Settings
415
455
 
416
456
  * sha1_secret -
@@ -513,7 +553,6 @@ Proper personal identity can still be provided using
513
553
  [MIME Encoded-Words](http://en.wikipedia.org/wiki/MIME#Encoded-Word)
514
554
  in Email headers.
515
555
 
516
-
517
556
  #### Email Addresses as Sensitive Data
518
557
 
519
558
  Like Social Security and Credit Card Numbers, email addresses are
@@ -31,5 +31,5 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "codeclimate-test-reporter"
32
32
 
33
33
  spec.add_dependency "simpleidn"
34
- spec.add_dependency "netaddr"
34
+ spec.add_dependency "netaddr", "~> 1.5.1"
35
35
  end
@@ -5,6 +5,7 @@ module EmailAddress
5
5
  require "email_address/exchanger"
6
6
  require "email_address/host"
7
7
  require "email_address/local"
8
+ require "email_address/rewriter"
8
9
  require "email_address/address"
9
10
  require "email_address/version"
10
11
  require "email_address/active_record_validator" if defined?(ActiveModel)
@@ -13,22 +14,29 @@ module EmailAddress
13
14
  require "email_address/canonical_email_address_type"
14
15
  end
15
16
 
16
- # @!method self.valid?(options={})
17
+ # @!method self.valid?(email_address, options={})
17
18
  # Proxy method to {EmailAddress::Address#valid?}
18
- # @!method self.error
19
+ # @!method self.error(email_address)
19
20
  # Proxy method to {EmailAddress::Address#error}
20
- # @!method self.normal
21
+ # @!method self.normal(email_address)
21
22
  # Proxy method to {EmailAddress::Address#normal}
22
- # @!method self.redact(digest=:sha1)
23
+ # @!method self.redact(email_address, options={})
23
24
  # Proxy method to {EmailAddress::Address#redact}
24
- # @!method self.munge
25
+ # @!method self.munge(email_address, options={})
25
26
  # Proxy method to {EmailAddress::Address#munge}
26
- # @!method self.canonical
27
+ # @!method self.base(email_address, options{})
28
+ # Returns the base form of the email address, the mailbox
29
+ # without optional puncuation removed, no tag, and the host name.
30
+ # @!method self.canonical(email_address, options{})
27
31
  # Proxy method to {EmailAddress::Address#canonical}
28
- # @!method self.reference
29
- # Proxy method to {EmailAddress::Address#reference}
32
+ # @!method self.reference(email_address, form=:base, options={})
33
+ # Returns the reference form of the email address, by default
34
+ # the MD5 digest of the Base Form the the address.
35
+ # @!method self.srs(email_address, sending_domain, options={})
36
+ # Returns the address encoded for SRS forwarding. Pass a local
37
+ # secret to use in options[:secret]
30
38
  class << self
31
- (%i[valid? error normal redact munge canonical reference] &
39
+ (%i[valid? error normal redact munge canonical reference base srs] &
32
40
  EmailAddress::Address.public_instance_methods
33
41
  ).each do |proxy_method|
34
42
  define_method(proxy_method) do |*args, &block|
@@ -6,6 +6,8 @@ module EmailAddress
6
6
  # (EmailAddress::Local) and Host (Email::AddressHost) parts.
7
7
  class Address
8
8
  include Comparable
9
+ include EmailAddress::Rewriter
10
+
9
11
  attr_accessor :original, :local, :host, :config, :reason
10
12
 
11
13
  CONVENTIONAL_REGEX = /\A#{::EmailAddress::Local::CONVENTIONAL_MAILBOX_WITHIN}
@@ -19,19 +21,26 @@ module EmailAddress
19
21
  # instance, and initializes the address to the "normalized" format of the
20
22
  # address. The original string is available in the #original method.
21
23
  def initialize(email_address, config={})
22
- email_address = email_address.strip if email_address
24
+ @config = config # This needs refactoring!
25
+ email_address = (email_address || "").strip
23
26
  @original = email_address
24
- email_address||= ""
25
- if lh = email_address.match(/(.+)@(.+)/)
26
- (_, local, host) = lh.to_a
27
- else
28
- (local, host) = [email_address, '']
29
- end
27
+ email_address = parse_rewritten(email_address) unless config[:skip_rewrite]
28
+ local, host = EmailAddress::Address.split_local_host(email_address)
29
+
30
30
  @host = EmailAddress::Host.new(host, config)
31
31
  @config = @host.config
32
32
  @local = EmailAddress::Local.new(local, @config, @host)
33
33
  end
34
34
 
35
+ # Given an email address, this returns an array of [local, host] parts
36
+ def self.split_local_host(email)
37
+ if lh = email.match(/(.+)@(.+)/)
38
+ lh.to_a[1,2]
39
+ else
40
+ [email, '']
41
+ end
42
+ end
43
+
35
44
  ############################################################################
36
45
  # Local Part (left of @) access
37
46
  # * local: Access full local part instance
@@ -121,6 +130,11 @@ module EmailAddress
121
130
  self.canonical == self.to_s
122
131
  end
123
132
 
133
+ # The base address is the mailbox, without tags, and host.
134
+ def base
135
+ self.mailbox + "@" + self.hostname
136
+ end
137
+
124
138
  # Returns the redacted form of the address
125
139
  # This format is defined by this libaray, and may change as usage increases.
126
140
  # Takes either :sha1 (default) or :md5 as the argument
@@ -147,15 +161,15 @@ module EmailAddress
147
161
  # use the email address MD5 instead of the actual address to refer to the
148
162
  # same shared user identity without exposing the actual address when it
149
163
  # is not known in common.
150
- def reference
151
- Digest::MD5.hexdigest(self.canonical)
164
+ def reference(form=:base)
165
+ Digest::MD5.hexdigest(self.send(form))
152
166
  end
153
167
  alias :md5 :reference
154
168
 
155
169
  # This returns the SHA1 digest (in a hex string) of the canonical email
156
170
  # address. See #md5 for more background.
157
- def sha1
158
- Digest::SHA1.hexdigest((canonical||"") + (@config[:sha1_secret]||""))
171
+ def sha1(form=:base)
172
+ Digest::SHA1.hexdigest((self.send(form)||"") + (@config[:sha1_secret]||""))
159
173
  end
160
174
 
161
175
  #---------------------------------------------------------------------------
@@ -0,0 +1,145 @@
1
+ require 'base64'
2
+
3
+ module EmailAddress::Rewriter
4
+
5
+ SRS_FORMAT_REGEX = /\ASRS0=(....)=(\w\w)=(.+?)=(.+?)@(.+)\z/
6
+
7
+ def parse_rewritten(e)
8
+ @rewrite_scheme = nil
9
+ @rewrite_error = nil
10
+ e = parse_srs(e)
11
+ # e = parse_batv(e)
12
+ 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, self.hostname, self.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 && 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_given?
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, self.to_s, options={})
91
+ ["prvs=", k, ddd, ssssss, '=', self.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, '=', self.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
+ self.local.to_s +
139
+ split_char + recipient.gsub("@","=") +
140
+ "@" + self.hostname
141
+ end
142
+
143
+ # NEXT: DMARC, SPF Validation
144
+
145
+ end
@@ -1,3 +1,3 @@
1
1
  module EmailAddress
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  end
@@ -29,10 +29,12 @@ class TestAddress < Minitest::Test
29
29
  def test_forms
30
30
  a = EmailAddress.new("User+tag@example.com")
31
31
  assert_equal "user+tag@example.com", a.to_s
32
+ assert_equal "user@example.com", a.base
32
33
  assert_equal "user@example.com", a.canonical
33
34
  assert_equal "{63a710569261a24b3766275b7000ce8d7b32e2f7}@example.com", a.redact
34
35
  assert_equal "{b58996c504c5638798eb6b511e6f49af}@example.com", a.redact(:md5)
35
36
  assert_equal "b58996c504c5638798eb6b511e6f49af", a.reference
37
+ assert_equal "6bdd00c53645790ad9bbcb50caa93880", EmailAddress.reference("Gmail.User+tag@gmail.com")
36
38
  end
37
39
 
38
40
  # COMPARISON & MATCHING
@@ -58,10 +60,10 @@ class TestAddress < Minitest::Test
58
60
 
59
61
  def test_empty_address
60
62
  a = EmailAddress.new("")
61
- assert_equal "{da39a3ee5e6b4b0d3255bfef95601890afd80709}", a.redact
63
+ assert_equal "{9a78211436f6d425ec38f5c4e02270801f3524f8}", a.redact
62
64
  assert_equal "", a.to_s
63
65
  assert_equal "", a.canonical
64
- assert_equal "d41d8cd98f00b204e9800998ecf8427e", a.reference
66
+ assert_equal "518ed29525738cebdac49c49e60ea9d3", a.reference
65
67
  end
66
68
 
67
69
  # VALIDATION
@@ -89,6 +91,14 @@ class TestAddress < Minitest::Test
89
91
  assert "aasdf-34-.z@example.com".match(EmailAddress::Address::RELAXED_REGEX)
90
92
  end
91
93
 
94
+ def test_srs
95
+ ea= "first.LAST+tag@gmail.com"
96
+ e = EmailAddress.new(ea)
97
+ s = e.srs("example.com")
98
+ assert s.match(EmailAddress::Address::SRS_FORMAT_REGEX)
99
+ assert EmailAddress.new(s).to_s == e.to_s
100
+ end
101
+
92
102
  # Quick Regression tests for addresses that should have been valid (but fixed)
93
103
  def test_issues
94
104
  assert true, EmailAddress.valid?('test@jiff.com', dns_lookup: :mx) # #7
@@ -0,0 +1,14 @@
1
+ #encoding: utf-8
2
+ require_relative '../test_helper'
3
+
4
+ class TestRewriter < Minitest::Test
5
+
6
+ def test_srs
7
+ ea= "first.LAST+tag@gmail.com"
8
+ e = EmailAddress.new(ea)
9
+ s = e.srs("example.com")
10
+ assert s.match(EmailAddress::Address::SRS_FORMAT_REGEX)
11
+ assert EmailAddress.new(s).to_s == e.to_s
12
+ end
13
+
14
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: email_address
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Allen Fair
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-06 00:00:00.000000000 Z
11
+ date: 2018-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -112,16 +112,16 @@ dependencies:
112
112
  name: netaddr
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: 1.5.1
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: 1.5.1
125
125
  description: The EmailAddress Gem to work with and validate email addresses.
126
126
  email:
127
127
  - allen.fair@gmail.com
@@ -145,6 +145,7 @@ files:
145
145
  - lib/email_address/exchanger.rb
146
146
  - lib/email_address/host.rb
147
147
  - lib/email_address/local.rb
148
+ - lib/email_address/rewriter.rb
148
149
  - lib/email_address/version.rb
149
150
  - test/activerecord/test_ar.rb
150
151
  - test/activerecord/user.rb
@@ -153,6 +154,7 @@ files:
153
154
  - test/email_address/test_exchanger.rb
154
155
  - test/email_address/test_host.rb
155
156
  - test/email_address/test_local.rb
157
+ - test/email_address/test_rewriter.rb
156
158
  - test/test_email_address.rb
157
159
  - test/test_helper.rb
158
160
  homepage: https://github.com/afair/email_address
@@ -175,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
177
  version: '0'
176
178
  requirements: []
177
179
  rubyforge_project:
178
- rubygems_version: 2.6.13
180
+ rubygems_version: 2.7.3
179
181
  signing_key:
180
182
  specification_version: 4
181
183
  summary: This gem provides a ruby language library for working with and validating
@@ -190,5 +192,6 @@ test_files:
190
192
  - test/email_address/test_exchanger.rb
191
193
  - test/email_address/test_host.rb
192
194
  - test/email_address/test_local.rb
195
+ - test/email_address/test_rewriter.rb
193
196
  - test/test_email_address.rb
194
197
  - test/test_helper.rb