pre 0.0.1
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.
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +49 -0
- data/README.md +90 -0
- data/TODO.md +7 -0
- data/lib/pre.rb +5 -0
- data/lib/pre/cache_store.rb +5 -0
- data/lib/pre/cache_store/fake.rb +18 -0
- data/lib/pre/cache_store/null.rb +13 -0
- data/lib/pre/fake_validation.rb +25 -0
- data/lib/pre/rfc2822.treetop +412 -0
- data/lib/pre/rfc2822_obsolete.treetop +241 -0
- data/lib/pre/validator.rb +90 -0
- data/lib/pre/validators/domain.rb +21 -0
- data/lib/pre/validators/format.rb +11 -0
- data/lib/pre/version.rb +3 -0
- data/lib/tasks/.gitkeep +0 -0
- data/pre.gemspec +25 -0
- data/spec/lib/pre/validator_spec.rb +118 -0
- data/spec/lib/pre/validators/domain_spec.rb +33 -0
- data/spec/spec_helper.rb +7 -0
- metadata +143 -0
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-1.9.3@pre --create
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
pre (0.0.1)
|
5
|
+
active_support
|
6
|
+
dalli
|
7
|
+
treetop
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
active_support (3.0.0)
|
13
|
+
activesupport (= 3.0.0)
|
14
|
+
activesupport (3.0.0)
|
15
|
+
coderay (1.0.7)
|
16
|
+
dalli (2.1.0)
|
17
|
+
diff-lcs (1.1.3)
|
18
|
+
metaclass (0.0.1)
|
19
|
+
method_source (0.8)
|
20
|
+
mocha (0.12.3)
|
21
|
+
metaclass (~> 0.0.1)
|
22
|
+
polyglot (0.3.3)
|
23
|
+
pry (0.9.10)
|
24
|
+
coderay (~> 1.0.5)
|
25
|
+
method_source (~> 0.8)
|
26
|
+
slop (~> 3.3.1)
|
27
|
+
pry-nav (0.2.2)
|
28
|
+
pry (~> 0.9.10)
|
29
|
+
rspec (2.11.0)
|
30
|
+
rspec-core (~> 2.11.0)
|
31
|
+
rspec-expectations (~> 2.11.0)
|
32
|
+
rspec-mocks (~> 2.11.0)
|
33
|
+
rspec-core (2.11.1)
|
34
|
+
rspec-expectations (2.11.2)
|
35
|
+
diff-lcs (~> 1.1.3)
|
36
|
+
rspec-mocks (2.11.1)
|
37
|
+
slop (3.3.2)
|
38
|
+
treetop (1.4.10)
|
39
|
+
polyglot
|
40
|
+
polyglot (>= 0.3.1)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
ruby
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
mocha
|
47
|
+
pre!
|
48
|
+
pry-nav
|
49
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Pretty Reliable Email
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Why?
|
5
|
+
----
|
6
|
+
|
7
|
+
Regexes are not enough sometimes. We should go all the way and fully
|
8
|
+
parse the email and knock on the server's door to see if they really
|
9
|
+
have a mailbox.
|
10
|
+
|
11
|
+
How?
|
12
|
+
---
|
13
|
+
|
14
|
+
### Basics
|
15
|
+
require 'pre'
|
16
|
+
validator = Pre::Validator.new
|
17
|
+
validator.valid? "me@apple.com"
|
18
|
+
|
19
|
+
### With basic config
|
20
|
+
|
21
|
+
Pre by default has built in validators for RFC2822 and DNS MX record verification. Validators can be passed through the `:validators` option.
|
22
|
+
|
23
|
+
require 'pre'
|
24
|
+
# validate with no domain validation
|
25
|
+
validator = Pre::Validator.new :validators => :format
|
26
|
+
validator.valid? "foo@nonexistent201233.com" # => true
|
27
|
+
|
28
|
+
### Advanced config
|
29
|
+
|
30
|
+
Pre can take blocks for custom validators
|
31
|
+
|
32
|
+
require 'pre'
|
33
|
+
no_gmail = lambda do |address|
|
34
|
+
address !~ /gmail.com$/
|
35
|
+
end
|
36
|
+
validator = Pre::Validator.new :validators => [:format, no_gmail]
|
37
|
+
validator.valid? "foo@gmail.com" # => false
|
38
|
+
|
39
|
+
Pre can take any object that implements the valid? method
|
40
|
+
|
41
|
+
require 'pre'
|
42
|
+
class ComplexValidator
|
43
|
+
def valid? address
|
44
|
+
return true unless address =~ /gmail.com$/
|
45
|
+
# do not allow user+label@gmail.com
|
46
|
+
address !~ /\+.+@/
|
47
|
+
end
|
48
|
+
end
|
49
|
+
validator = Pre::Validator.new :validators => [:domain, ComplexValidator.new]
|
50
|
+
validator.valid? "john+spam@gmail.com" # => false
|
51
|
+
|
52
|
+
Pre can also take alternate configuration for a single address
|
53
|
+
|
54
|
+
require 'pre'
|
55
|
+
validator = Pre::Validator.new :validators => :format
|
56
|
+
validator.valid? "john@example.co.uk", :validators => lambda { |address|
|
57
|
+
address =~ /example.co.nz$/
|
58
|
+
} # => false
|
59
|
+
|
60
|
+
### Caching
|
61
|
+
|
62
|
+
Certain strategies may be more "intense" than others. MX Record lookup and other expensive operations can benefit from providing Pre with a cache store. A cache store must respond to `:write(key, val)` and `:read(key)` methods. The cache abstraction layer provided by Rails' [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html) fits this interface.
|
63
|
+
|
64
|
+
require 'pre'
|
65
|
+
memcache = ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
66
|
+
validator = Pre::Validator.new :cache_store => memcache
|
67
|
+
validator.valid? "foo@gmail.com" # => true
|
68
|
+
|
69
|
+
|
70
|
+
Contributing
|
71
|
+
---
|
72
|
+
|
73
|
+
1. Fork
|
74
|
+
2. Make tests
|
75
|
+
3. Make changes to lib
|
76
|
+
4. Ensure tests pass on 1.8/1.9
|
77
|
+
5. Submit request
|
78
|
+
6. Smile
|
79
|
+
|
80
|
+
Roadmap
|
81
|
+
---
|
82
|
+
|
83
|
+
* ActiveModel, Mongoid::Document, etc.. integration
|
84
|
+
* DSL for configuring Pre validation configuration sets
|
85
|
+
|
86
|
+
Ack
|
87
|
+
---
|
88
|
+
|
89
|
+
The RFC treetop grammars are pulled from the fantastic [mail](https://github.com/mikel/mail) gem.
|
90
|
+
|
data/TODO.md
ADDED
data/lib/pre.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Pre
|
2
|
+
class Cache
|
3
|
+
module Fake
|
4
|
+
def cache
|
5
|
+
@cache ||= {}
|
6
|
+
end
|
7
|
+
def cache_read key
|
8
|
+
cache[key]
|
9
|
+
end
|
10
|
+
def cache_write key, value
|
11
|
+
cache[key] = value
|
12
|
+
end
|
13
|
+
def cache_fetch key, &block
|
14
|
+
cache[key] ||= block.call
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Pre
|
2
|
+
module FakeValidation
|
3
|
+
|
4
|
+
def stub_validator strategy, result
|
5
|
+
stubbed_validators[strategy] = result
|
6
|
+
end
|
7
|
+
|
8
|
+
def stub_validators *stubs
|
9
|
+
stubs.each_slice(2) do |strategy, result|
|
10
|
+
stub_validator strategy, result
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def stubbed_validators
|
15
|
+
@stubbed_validators ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate strategy
|
19
|
+
return super unless stubbed_validators.has_key? strategy
|
20
|
+
result = stubbed_validators[strategy]
|
21
|
+
return result.call self if result.respond_to? :call
|
22
|
+
result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,412 @@
|
|
1
|
+
# Originally from https://github.com/mikel/mail lib/mail/parsers/rfc2822.treetop
|
2
|
+
|
3
|
+
module Pre
|
4
|
+
grammar RFC2822
|
5
|
+
|
6
|
+
include RFC2822Obsolete
|
7
|
+
|
8
|
+
rule ALPHA
|
9
|
+
[a-zA-Z]
|
10
|
+
end
|
11
|
+
|
12
|
+
rule DIGIT
|
13
|
+
[0-9]
|
14
|
+
end
|
15
|
+
|
16
|
+
rule DQUOTE
|
17
|
+
'"'
|
18
|
+
end
|
19
|
+
|
20
|
+
rule LF
|
21
|
+
"\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
rule CR
|
25
|
+
"\r"
|
26
|
+
end
|
27
|
+
|
28
|
+
rule CRLF
|
29
|
+
"\r\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
rule WSP
|
33
|
+
[\x09\x20]
|
34
|
+
end
|
35
|
+
|
36
|
+
rule FWS # Folding white space
|
37
|
+
(WSP* CRLF WSP+) / (CRLF WSP+) / obs_FWS
|
38
|
+
end
|
39
|
+
|
40
|
+
rule CFWS
|
41
|
+
(FWS* comment)* FWS?
|
42
|
+
end
|
43
|
+
|
44
|
+
rule NO_WS_CTL
|
45
|
+
[\x01-\x08] / # US-ASCII control characters
|
46
|
+
[\x0B-\x0C] / # that do not include the
|
47
|
+
[\x0E-\x1F] / # carriage return, line feed,
|
48
|
+
[\x7f] # and white space characters
|
49
|
+
end
|
50
|
+
|
51
|
+
rule specials
|
52
|
+
"(" / ")" / # Special characters used in
|
53
|
+
"<" / ">" / # other parts of the syntax
|
54
|
+
"[" / "]" /
|
55
|
+
":" / ";" /
|
56
|
+
"@" / '\\' /
|
57
|
+
"," / "." /
|
58
|
+
DQUOTE
|
59
|
+
end
|
60
|
+
|
61
|
+
rule ctext
|
62
|
+
NO_WS_CTL / # Non white space controls
|
63
|
+
[\x21-\x27] / # The rest of the US-ASCII
|
64
|
+
[\x2a-\x5b] / # characters not including "(",
|
65
|
+
[\x5d-\x7e] # ")", or "\"
|
66
|
+
end
|
67
|
+
|
68
|
+
rule ccontent
|
69
|
+
ctext / quoted_pair / comment
|
70
|
+
end
|
71
|
+
|
72
|
+
rule comment
|
73
|
+
"(" ( FWS? ccontent )* FWS? ")"
|
74
|
+
end
|
75
|
+
|
76
|
+
rule atext
|
77
|
+
ALPHA / DIGIT / # Any character except controls,
|
78
|
+
"!" / "#" / # SP, and specials.
|
79
|
+
"$" / "%" / # Used for atoms
|
80
|
+
"&" / "'" /
|
81
|
+
"*" / "+" /
|
82
|
+
"-" / "/" /
|
83
|
+
"=" / "?" /
|
84
|
+
"^" / "_" /
|
85
|
+
"`" / "{" /
|
86
|
+
"|" / "}" /
|
87
|
+
"~"
|
88
|
+
end
|
89
|
+
|
90
|
+
rule mtext
|
91
|
+
(atext / ".")+
|
92
|
+
end
|
93
|
+
|
94
|
+
rule atom
|
95
|
+
CFWS? atext+ CFWS?
|
96
|
+
end
|
97
|
+
|
98
|
+
rule dot_atom
|
99
|
+
CFWS? dot_atom_text CFWS?
|
100
|
+
end
|
101
|
+
|
102
|
+
rule local_dot_atom
|
103
|
+
CFWS? local_dot_atom_text CFWS?
|
104
|
+
end
|
105
|
+
|
106
|
+
rule message_id_text
|
107
|
+
mtext+
|
108
|
+
end
|
109
|
+
|
110
|
+
rule dot_atom_text
|
111
|
+
(domain_text "."?)+
|
112
|
+
end
|
113
|
+
|
114
|
+
rule local_dot_atom_text
|
115
|
+
("."? domain_text)+
|
116
|
+
end
|
117
|
+
|
118
|
+
rule domain_text
|
119
|
+
(DQUOTE (FWS? quoted_domain)+ FWS? DQUOTE) / atext+
|
120
|
+
end
|
121
|
+
|
122
|
+
rule quoted_domain
|
123
|
+
qdcontent / "\\" text
|
124
|
+
end
|
125
|
+
|
126
|
+
rule qdcontent
|
127
|
+
NO_WS_CTL / # Non white space controls
|
128
|
+
[\x21] / # The rest of the US-ASCII
|
129
|
+
[\x23-\x45] / # characters not including "\"
|
130
|
+
[\x47-\x5b] / # or the "." or the
|
131
|
+
[\x5d-\x7e] # double quote character
|
132
|
+
end
|
133
|
+
|
134
|
+
rule phrase
|
135
|
+
obs_phrase / word+
|
136
|
+
end
|
137
|
+
|
138
|
+
rule word
|
139
|
+
atom / quoted_string
|
140
|
+
end
|
141
|
+
|
142
|
+
rule phrase_list
|
143
|
+
first_phrase:phrase other_phrases:("," FWS* phrase_value:phrase)*
|
144
|
+
end
|
145
|
+
|
146
|
+
rule domain_literal
|
147
|
+
CFWS? "[" (FWS? dcontent)* FWS? "]" CFWS?
|
148
|
+
end
|
149
|
+
|
150
|
+
rule dcontent
|
151
|
+
dtext / quoted_pair
|
152
|
+
end
|
153
|
+
|
154
|
+
rule dtext
|
155
|
+
NO_WS_CTL / # Non white space controls
|
156
|
+
[\x21-\x5a] / # The rest of the US-ASCII characters
|
157
|
+
[\x5e-\x7e] # not including "[", "]", or "\"
|
158
|
+
end
|
159
|
+
|
160
|
+
rule angle_addr
|
161
|
+
CFWS? "<" addr_spec ">" CFWS? / obs_angle_addr
|
162
|
+
end
|
163
|
+
|
164
|
+
rule addr_spec
|
165
|
+
(local_part "@" domain) / local_part
|
166
|
+
end
|
167
|
+
|
168
|
+
rule local_part
|
169
|
+
local_dot_atom / quoted_string / obs_local_part
|
170
|
+
end
|
171
|
+
|
172
|
+
rule domain
|
173
|
+
dot_atom / domain_literal / obs_domain
|
174
|
+
end
|
175
|
+
|
176
|
+
rule group
|
177
|
+
group_name:display_name ":" group_list:(mailbox_list_group / CFWS)? ";" CFWS?
|
178
|
+
end
|
179
|
+
|
180
|
+
rule mailbox_list_group
|
181
|
+
mailbox_list {
|
182
|
+
def addresses
|
183
|
+
[first_addr] + other_addr.elements.map { |o| o.addr_value }
|
184
|
+
end
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
rule quoted_string
|
189
|
+
CFWS? DQUOTE quoted_content:(FWS? qcontent)+ FWS? DQUOTE CFWS?
|
190
|
+
end
|
191
|
+
|
192
|
+
rule qcontent
|
193
|
+
qtext / quoted_pair
|
194
|
+
end
|
195
|
+
|
196
|
+
rule quoted_pair
|
197
|
+
("\\" text) / obs_qp
|
198
|
+
end
|
199
|
+
|
200
|
+
rule qtext
|
201
|
+
NO_WS_CTL / # Non white space controls
|
202
|
+
[\x21] / # The rest of the US-ASCII
|
203
|
+
[\x23-\x5b] / # characters not including "\"
|
204
|
+
[\x5d-\x7e] # or the quote character
|
205
|
+
end
|
206
|
+
|
207
|
+
rule text
|
208
|
+
[\x01-\x09] / # Characters excluding CR and LF
|
209
|
+
[\x0b-\x0c] /
|
210
|
+
[\x0e-\x7e] /
|
211
|
+
obs_text
|
212
|
+
end
|
213
|
+
|
214
|
+
rule display_name
|
215
|
+
phrase
|
216
|
+
end
|
217
|
+
|
218
|
+
rule name_addr
|
219
|
+
display_name angle_addr / angle_addr
|
220
|
+
end
|
221
|
+
|
222
|
+
rule mailbox_list
|
223
|
+
(first_addr:mailbox other_addr:("," addr_value:mailbox)*) / obs_mbox_list
|
224
|
+
end
|
225
|
+
|
226
|
+
rule mailbox
|
227
|
+
name_addr / addr_spec
|
228
|
+
end
|
229
|
+
|
230
|
+
rule address
|
231
|
+
group {
|
232
|
+
|
233
|
+
def dig_comments(comments, elements)
|
234
|
+
elements.each { |elem|
|
235
|
+
if elem.respond_to?(:comment)
|
236
|
+
comments << elem.comment
|
237
|
+
end
|
238
|
+
dig_comments(comments, elem.elements) if elem.elements
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
def comments
|
243
|
+
comments = []
|
244
|
+
dig_comments(comments, elements)
|
245
|
+
comments
|
246
|
+
end
|
247
|
+
|
248
|
+
} /
|
249
|
+
mailbox {
|
250
|
+
|
251
|
+
def dig_comments(comments, elements)
|
252
|
+
elements.each { |elem|
|
253
|
+
if elem.respond_to?(:comment)
|
254
|
+
comments << elem.comment
|
255
|
+
end
|
256
|
+
dig_comments(comments, elem.elements) if elem.elements
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
def comments
|
261
|
+
comments = []
|
262
|
+
dig_comments(comments, elements)
|
263
|
+
comments
|
264
|
+
end
|
265
|
+
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
269
|
+
rule address_list
|
270
|
+
first_addr:address? other_addr:(FWS* "," FWS* addr_value:address?)*
|
271
|
+
end
|
272
|
+
|
273
|
+
rule date_time
|
274
|
+
( day_of_week ",")? date FWS time CFWS?
|
275
|
+
end
|
276
|
+
|
277
|
+
rule day_of_week
|
278
|
+
(FWS? day_name) / obs_day_of_week
|
279
|
+
end
|
280
|
+
|
281
|
+
rule day_name
|
282
|
+
"Mon" / "Tue" / "Wed" / "Thu" /
|
283
|
+
"Fri" / "Sat" / "Sun"
|
284
|
+
end
|
285
|
+
|
286
|
+
rule date
|
287
|
+
day month year
|
288
|
+
end
|
289
|
+
|
290
|
+
rule year
|
291
|
+
DIGIT DIGIT DIGIT DIGIT / obs_year
|
292
|
+
end
|
293
|
+
|
294
|
+
rule month
|
295
|
+
(FWS month_name FWS) / obs_month
|
296
|
+
end
|
297
|
+
|
298
|
+
rule month_name
|
299
|
+
"Jan" / "Feb" / "Mar" / "Apr" /
|
300
|
+
"May" / "Jun" / "Jul" / "Aug" /
|
301
|
+
"Sep" / "Oct" / "Nov" / "Dec"
|
302
|
+
end
|
303
|
+
|
304
|
+
rule day
|
305
|
+
(FWS? DIGIT DIGIT?) / obs_day
|
306
|
+
end
|
307
|
+
|
308
|
+
rule time
|
309
|
+
time_of_day FWS zone
|
310
|
+
end
|
311
|
+
|
312
|
+
rule time_of_day
|
313
|
+
hour ":" minute ( ":" second )?
|
314
|
+
end
|
315
|
+
|
316
|
+
rule hour
|
317
|
+
DIGIT DIGIT / obs_hour
|
318
|
+
end
|
319
|
+
|
320
|
+
rule minute
|
321
|
+
DIGIT DIGIT / obs_minute
|
322
|
+
end
|
323
|
+
|
324
|
+
rule second
|
325
|
+
DIGIT DIGIT / obs_second
|
326
|
+
end
|
327
|
+
|
328
|
+
rule zone
|
329
|
+
(( "+" / "-" ) DIGIT DIGIT DIGIT DIGIT) / obs_zone
|
330
|
+
end
|
331
|
+
|
332
|
+
rule return
|
333
|
+
path CRLF
|
334
|
+
end
|
335
|
+
|
336
|
+
rule path
|
337
|
+
((CFWS)? "<" ((CFWS)? / addr_spec) ">" (CFWS)?) / obs_path
|
338
|
+
end
|
339
|
+
|
340
|
+
rule received
|
341
|
+
name_val_list ";" date_time CRLF
|
342
|
+
end
|
343
|
+
|
344
|
+
rule name_val_list
|
345
|
+
(CFWS)? (name_val_pair (CFWS name_val_pair)*)
|
346
|
+
end
|
347
|
+
|
348
|
+
rule name_val_pair
|
349
|
+
item_name CFWS item_value
|
350
|
+
end
|
351
|
+
|
352
|
+
rule item_name
|
353
|
+
ALPHA (("-")? (ALPHA / DIGIT))*
|
354
|
+
end
|
355
|
+
|
356
|
+
rule item_value
|
357
|
+
(angle_addr)+ / addr_spec / atom / domain / msg_id
|
358
|
+
end
|
359
|
+
|
360
|
+
rule message_ids
|
361
|
+
first_msg_id:msg_id other_msg_ids:( CFWS msg_id_value:msg_id )*
|
362
|
+
end
|
363
|
+
|
364
|
+
rule msg_id
|
365
|
+
(CFWS)? "<" msg_id_value ">" (CFWS)?
|
366
|
+
end
|
367
|
+
|
368
|
+
rule msg_id_value
|
369
|
+
id_left "@" id_right
|
370
|
+
end
|
371
|
+
|
372
|
+
rule id_left
|
373
|
+
message_id_text / no_fold_quote / obs_id_left
|
374
|
+
end
|
375
|
+
|
376
|
+
rule id_right
|
377
|
+
msg_id_dot_atom_text / no_fold_literal / obs_id_right
|
378
|
+
end
|
379
|
+
|
380
|
+
rule msg_id_dot_atom_text
|
381
|
+
(msg_id_domain_text "."?)+
|
382
|
+
end
|
383
|
+
|
384
|
+
rule msg_id_domain_text
|
385
|
+
(DQUOTE (FWS? quoted_domain)+ FWS? DQUOTE) / msg_id_atext+
|
386
|
+
end
|
387
|
+
|
388
|
+
rule msg_id_atext
|
389
|
+
ALPHA / DIGIT / # Any character except controls,
|
390
|
+
"!" / "#" / # SP, and specials.
|
391
|
+
"$" / "%" / # Used for atoms
|
392
|
+
"&" / "'" /
|
393
|
+
"*" / "+" /
|
394
|
+
"-" / "/" /
|
395
|
+
"=" / "?" /
|
396
|
+
"^" / "_" /
|
397
|
+
"`" / "{" /
|
398
|
+
"|" / "}" /
|
399
|
+
"~" / "@"
|
400
|
+
end
|
401
|
+
|
402
|
+
rule no_fold_quote
|
403
|
+
DQUOTE (qtext / quoted_pair)+ DQUOTE
|
404
|
+
end
|
405
|
+
|
406
|
+
rule no_fold_literal
|
407
|
+
"[" (dtext / quoted_pair)+ "]"
|
408
|
+
end
|
409
|
+
|
410
|
+
|
411
|
+
end
|
412
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
module Pre
|
2
|
+
grammar RFC2822Obsolete
|
3
|
+
|
4
|
+
rule obs_qp
|
5
|
+
"\\" [\x00-\x7F]
|
6
|
+
end
|
7
|
+
|
8
|
+
rule obs_text
|
9
|
+
LF* CR* (obs_char LF* CR*)*
|
10
|
+
end
|
11
|
+
|
12
|
+
rule obs_char
|
13
|
+
[\x00-\x09] / # %d0-127 except CR and
|
14
|
+
[\x0B-\x0C] / # LF
|
15
|
+
[\x0E-\x7F]
|
16
|
+
end
|
17
|
+
|
18
|
+
rule obs_utext
|
19
|
+
obs_text
|
20
|
+
end
|
21
|
+
|
22
|
+
rule obs_phrase
|
23
|
+
(word / "." / "@")+
|
24
|
+
end
|
25
|
+
|
26
|
+
rule obs_phrase_list
|
27
|
+
phrase / (phrase? CFWS? "," CFWS?)+ phrase?
|
28
|
+
end
|
29
|
+
|
30
|
+
rule obs_FWS
|
31
|
+
WSP+ (CRLF WSP+)*
|
32
|
+
end
|
33
|
+
|
34
|
+
rule obs_day_of_week
|
35
|
+
CFWS? day_name CFWS?
|
36
|
+
end
|
37
|
+
|
38
|
+
rule obs_year
|
39
|
+
CFWS? (DIGIT DIGIT) CFWS?
|
40
|
+
end
|
41
|
+
|
42
|
+
rule obs_month
|
43
|
+
CFWS month_name CFWS
|
44
|
+
end
|
45
|
+
|
46
|
+
rule obs_day
|
47
|
+
CFWS? (DIGIT / (DIGIT DIGIT)) CFWS?
|
48
|
+
end
|
49
|
+
|
50
|
+
rule obs_hour
|
51
|
+
CFWS? (DIGIT DIGIT) CFWS?
|
52
|
+
end
|
53
|
+
|
54
|
+
rule obs_minute
|
55
|
+
CFWS? (DIGIT DIGIT) CFWS?
|
56
|
+
end
|
57
|
+
|
58
|
+
rule obs_second
|
59
|
+
CFWS? (DIGIT DIGIT) CFWS?
|
60
|
+
end
|
61
|
+
|
62
|
+
rule obs_zone
|
63
|
+
"UT" / "GMT" / # Universal Time
|
64
|
+
# North American UT
|
65
|
+
# offsets
|
66
|
+
"EST" / "EDT" / # Eastern: - 5/ - 4
|
67
|
+
"CST" / "CDT" / # Central: - 6/ - 5
|
68
|
+
"MST" / "MDT" / # Mountain: - 7/ - 6
|
69
|
+
"PST" / "PDT" / # Pacific: - 8/ - 7
|
70
|
+
#
|
71
|
+
[\x41-\x49] / # Military zones - "A"
|
72
|
+
[\x4B-\x5A] / # through "I" and "K"
|
73
|
+
[\x61-\x69] / # through "Z", both
|
74
|
+
[\x6B-\x7A] # upper and lower case
|
75
|
+
end
|
76
|
+
|
77
|
+
rule obs_angle_addr
|
78
|
+
CFWS? "<" obs_route? addr_spec ">" CFWS?
|
79
|
+
end
|
80
|
+
|
81
|
+
rule obs_route
|
82
|
+
CFWS? obs_domain_list ":" CFWS?
|
83
|
+
end
|
84
|
+
|
85
|
+
rule obs_domain_list
|
86
|
+
"@" domain (("," )* CFWS? "@" domain)*
|
87
|
+
end
|
88
|
+
|
89
|
+
rule obs_local_part
|
90
|
+
word ("." word)*
|
91
|
+
end
|
92
|
+
|
93
|
+
rule obs_domain
|
94
|
+
atom ("." atom)*
|
95
|
+
end
|
96
|
+
|
97
|
+
rule obs_mbox_list
|
98
|
+
(mailbox? CFWS? "," CFWS?)+ mailbox?
|
99
|
+
end
|
100
|
+
|
101
|
+
rule obs_addr_list
|
102
|
+
(address? CFWS? "," CFWS?)+ address?
|
103
|
+
end
|
104
|
+
|
105
|
+
rule obs_fields
|
106
|
+
(obs_return /
|
107
|
+
obs_received /
|
108
|
+
obs_orig_date /
|
109
|
+
obs_from /
|
110
|
+
obs_sender /
|
111
|
+
obs_reply_to /
|
112
|
+
obs_to /
|
113
|
+
obs_cc /
|
114
|
+
obs_bcc /
|
115
|
+
obs_message_id /
|
116
|
+
obs_in_reply_to /
|
117
|
+
obs_references /
|
118
|
+
obs_subject /
|
119
|
+
obs_comments /
|
120
|
+
obs_keywords /
|
121
|
+
obs_resent_date /
|
122
|
+
obs_resent_from /
|
123
|
+
obs_resent_send /
|
124
|
+
obs_resent_rply /
|
125
|
+
obs_resent_to /
|
126
|
+
obs_resent_cc /
|
127
|
+
obs_resent_bcc /
|
128
|
+
obs_resent_mid /
|
129
|
+
obs_optional)*
|
130
|
+
end
|
131
|
+
|
132
|
+
rule obs_orig_date
|
133
|
+
"Date" WSP* ":" date_time CRLF
|
134
|
+
end
|
135
|
+
|
136
|
+
rule obs_from
|
137
|
+
"From" WSP* ":" mailbox_list CRLF
|
138
|
+
end
|
139
|
+
|
140
|
+
rule obs_sender
|
141
|
+
"Sender" WSP* ":" mailbox CRLF
|
142
|
+
end
|
143
|
+
|
144
|
+
rule obs_reply_to
|
145
|
+
"Reply-To" WSP* ":" mailbox_list CRLF
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
rule obs_to
|
150
|
+
"To" WSP* ":" address_list CRLF
|
151
|
+
end
|
152
|
+
|
153
|
+
rule obs_cc
|
154
|
+
"Cc" WSP* ":" address_list CRLF
|
155
|
+
end
|
156
|
+
|
157
|
+
rule obs_bcc
|
158
|
+
"Bcc" WSP* ":" (address_list / CFWS?) CRLF
|
159
|
+
end
|
160
|
+
|
161
|
+
rule obs_message_id
|
162
|
+
"Message-ID" WSP* ":" msg_id CRLF
|
163
|
+
end
|
164
|
+
|
165
|
+
rule obs_in_reply_to
|
166
|
+
"In-Reply-To" WSP* ":" (phrase / msg_id)* CRLF
|
167
|
+
end
|
168
|
+
|
169
|
+
rule obs_references
|
170
|
+
"References" WSP* ":" (phrase / msg_id)* CRLF
|
171
|
+
end
|
172
|
+
|
173
|
+
rule obs_id_left
|
174
|
+
local_part
|
175
|
+
end
|
176
|
+
|
177
|
+
rule obs_id_right
|
178
|
+
domain
|
179
|
+
end
|
180
|
+
|
181
|
+
rule obs_subject
|
182
|
+
"Subject" WSP* ":" unstructured CRLF
|
183
|
+
end
|
184
|
+
|
185
|
+
rule obs_comments
|
186
|
+
"Comments" WSP* ":" unstructured CRLF
|
187
|
+
end
|
188
|
+
|
189
|
+
rule obs_keywords
|
190
|
+
"Keywords" WSP* ":" obs_phrase_list CRLF
|
191
|
+
end
|
192
|
+
|
193
|
+
rule obs_resent_from
|
194
|
+
"Resent-From" WSP* ":" mailbox_list CRLF
|
195
|
+
end
|
196
|
+
|
197
|
+
rule obs_resent_send
|
198
|
+
"Resent-Sender" WSP* ":" mailbox CRLF
|
199
|
+
end
|
200
|
+
|
201
|
+
rule obs_resent_date
|
202
|
+
"Resent-Date" WSP* ":" date_time CRLF
|
203
|
+
end
|
204
|
+
|
205
|
+
rule obs_resent_to
|
206
|
+
"Resent-To" WSP* ":" address_list CRLF
|
207
|
+
end
|
208
|
+
|
209
|
+
rule obs_resent_cc
|
210
|
+
"Resent-Cc" WSP* ":" address_list CRLF
|
211
|
+
end
|
212
|
+
|
213
|
+
rule obs_resent_bcc
|
214
|
+
"Resent-Bcc" WSP* ":" (address_list / CFWS?) CRLF
|
215
|
+
end
|
216
|
+
|
217
|
+
rule obs_resent_mid
|
218
|
+
"Resent-Message-ID" WSP* ":" msg_id CRLF
|
219
|
+
end
|
220
|
+
|
221
|
+
rule obs_resent_rply
|
222
|
+
"Resent-Reply-To" WSP* ":" address_list CRLF
|
223
|
+
end
|
224
|
+
|
225
|
+
rule obs_return
|
226
|
+
"Return-Path" WSP* ":" path CRLF
|
227
|
+
end
|
228
|
+
|
229
|
+
rule obs_received
|
230
|
+
"Received" WSP* ":" name_val_list CRLF
|
231
|
+
end
|
232
|
+
|
233
|
+
rule obs_path
|
234
|
+
obs_angle_addr
|
235
|
+
end
|
236
|
+
|
237
|
+
rule obs_optional
|
238
|
+
field_name WSP* ":" unstructured CRLF
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'treetop'
|
2
|
+
require 'pre/validators/format'
|
3
|
+
require 'pre/validators/domain'
|
4
|
+
require 'pre/cache_store'
|
5
|
+
|
6
|
+
module Pre
|
7
|
+
class Validator
|
8
|
+
include Validators::Format
|
9
|
+
include Validators::Domain
|
10
|
+
|
11
|
+
attr_accessor :raw_address
|
12
|
+
|
13
|
+
def initialize options = {}
|
14
|
+
@options = defaults options
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid? address, options = {}
|
18
|
+
self.address= address
|
19
|
+
validators(options[:validators]).all? do |strategy|
|
20
|
+
validate strategy
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def address= new_address
|
25
|
+
@parsed = nil
|
26
|
+
@raw_address = new_address
|
27
|
+
end
|
28
|
+
|
29
|
+
def cache_read key
|
30
|
+
@options[:cache_store].read key
|
31
|
+
end
|
32
|
+
|
33
|
+
def cache_write key, value
|
34
|
+
@options[:cache_store].write key, value
|
35
|
+
end
|
36
|
+
|
37
|
+
def cache_fetch key, &block
|
38
|
+
@options[:cache_store].fetch key, &block
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def domain
|
44
|
+
parsed.domain.text_value
|
45
|
+
end
|
46
|
+
|
47
|
+
def address
|
48
|
+
@raw_address
|
49
|
+
end
|
50
|
+
|
51
|
+
def validators validators
|
52
|
+
Array(validators || @options[:validators])
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate strategy
|
56
|
+
internal_method = "valid_#{strategy}?".to_sym
|
57
|
+
return send(internal_method) if respond_to? internal_method
|
58
|
+
return strategy.call @raw_address if strategy.respond_to? :call
|
59
|
+
return delegated strategy if strategy.respond_to? :delegate=
|
60
|
+
return strategy.valid? @raw_address if strategy.respond_to? :valid?
|
61
|
+
raise ArgumentError, "Could not identify strategy #{strategy}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def delegated strategy
|
65
|
+
strategy.delegate = self
|
66
|
+
strategy.valid? address
|
67
|
+
end
|
68
|
+
|
69
|
+
def defaults options
|
70
|
+
options[:validators] ||= [:format, :domain]
|
71
|
+
options[:cache_store] ||= Pre::CacheStore::Null.new
|
72
|
+
@options = options
|
73
|
+
end
|
74
|
+
|
75
|
+
def rfc2822_parser(treetop = Treetop)
|
76
|
+
['rfc2822_obsolete', 'rfc2822'].each do |grammar|
|
77
|
+
treetop.require File.join(File.dirname(__FILE__), grammar)
|
78
|
+
end
|
79
|
+
RFC2822Parser.new
|
80
|
+
end
|
81
|
+
|
82
|
+
def parsed
|
83
|
+
return @parsed if @parsed
|
84
|
+
@parser = rfc2822_parser
|
85
|
+
@parser.parse @raw_address
|
86
|
+
@parsed = @parser._nt_address#.tap {|t| binding.pry}
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'resolv'
|
3
|
+
module Pre
|
4
|
+
module Validators
|
5
|
+
module Domain
|
6
|
+
def self.expiry
|
7
|
+
3600
|
8
|
+
end
|
9
|
+
|
10
|
+
def cache_key key
|
11
|
+
"resolv_dns_result_#{key}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid_domain?(resolution_provider = Resolv::DNS.new)
|
15
|
+
cache_fetch cache_key(domain) do
|
16
|
+
resolution_provider.getresources(domain, Resolv::DNS::Resource::IN::MX).any?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/pre/version.rb
ADDED
data/lib/tasks/.gitkeep
ADDED
File without changes
|
data/pre.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "pre/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "pre"
|
7
|
+
s.version = Pre::VERSION
|
8
|
+
s.authors = ["Thomas Devol"]
|
9
|
+
s.email = ["thomas.devol@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Pretty reliable email validator}
|
12
|
+
s.description = %q{Checks email using RFC2822 compliant parser, Requests MX records for server validation}
|
13
|
+
|
14
|
+
s.rubyforge_project = "pre"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
["rspec", "mocha", "pry-nav"].each {|lib| s.add_development_dependency lib}
|
22
|
+
s.add_runtime_dependency "treetop"
|
23
|
+
s.add_runtime_dependency "active_support"
|
24
|
+
s.add_runtime_dependency "dalli"
|
25
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require 'pry'
|
3
|
+
describe Pre::Validator do
|
4
|
+
def test_validator options={}
|
5
|
+
@validator = described_class.new options
|
6
|
+
@validator.extend Pre::FakeValidation
|
7
|
+
end
|
8
|
+
|
9
|
+
before do
|
10
|
+
@validator = test_validator
|
11
|
+
end
|
12
|
+
|
13
|
+
context "default configuration" do
|
14
|
+
it "indicates a basic email address is valid" do
|
15
|
+
@validator.valid?("vajrapani666@gmail.com").should be_true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "invalidates email for invalid format" do
|
19
|
+
@validator.valid?("vajrapani666-gmail.com").should be_false
|
20
|
+
end
|
21
|
+
|
22
|
+
it "invalidates email for non-existent server" do
|
23
|
+
@validator.stub_validator :domain, false
|
24
|
+
@validator.valid?("support@revolution.gov").should be_false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "configuration" do
|
29
|
+
|
30
|
+
it "allows server validation to be toggled" do
|
31
|
+
@validator = test_validator :validators => :format
|
32
|
+
@validator.stub_validators(
|
33
|
+
:server, -> validator do
|
34
|
+
raise "Should not have called format"
|
35
|
+
end,
|
36
|
+
:format, true
|
37
|
+
)
|
38
|
+
@validator.valid?("support@revolution.gov").should be_true
|
39
|
+
end
|
40
|
+
|
41
|
+
it "allows one-time configuration" do
|
42
|
+
@validator = test_validator :validators => []
|
43
|
+
@validator.stub_validator :format, false
|
44
|
+
@validator.valid?("support@revolution.gov", :validators => :format).should be_false
|
45
|
+
end
|
46
|
+
|
47
|
+
it "allows lambda for validators" do
|
48
|
+
@validator = test_validator(:validators => -> address do
|
49
|
+
address !~ /^i.*gov$/
|
50
|
+
end)
|
51
|
+
@validator.valid?("info@revolution.gov").should be_false
|
52
|
+
@validator.valid?("contact@revolution.org").should be_true
|
53
|
+
end
|
54
|
+
|
55
|
+
it "allows any object responding to valid? method for validator" do
|
56
|
+
validating_stub = stub(:valid? => false)
|
57
|
+
@validator = test_validator :validators => validating_stub
|
58
|
+
@validator.valid?("info@revolution.gov").should be_false
|
59
|
+
end
|
60
|
+
|
61
|
+
it "allows a delegate to be specified" do
|
62
|
+
validating_delegate = mock()
|
63
|
+
@validator = test_validator :validators => validating_delegate
|
64
|
+
validating_delegate.expects(:delegate=).with(@validator)
|
65
|
+
validating_delegate.expects(:valid?).returns(false)
|
66
|
+
@validator.valid?("any@address.com").should be_false
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
context "caching config" do
|
71
|
+
context "allows a cache store to be configured" do
|
72
|
+
it "delegates cache_read to cache store" do
|
73
|
+
mock_cache = mock()
|
74
|
+
mock_cache.expects(:read).with("test_key")
|
75
|
+
@validator = test_validator :validators => [], :cache_store => mock_cache
|
76
|
+
@validator.cache_read "test_key"
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
it "delegates cache_read to cache store" do
|
81
|
+
mock_cache = mock()
|
82
|
+
mock_cache.expects(:write).with("test_key", "test_value")
|
83
|
+
@validator = test_validator :validators => [], :cache_store => mock_cache
|
84
|
+
@validator.cache_write "test_key", "test_value"
|
85
|
+
end
|
86
|
+
|
87
|
+
it "exposes caching ability to collaborators" do
|
88
|
+
validating_delegate = Object.new
|
89
|
+
class << validating_delegate
|
90
|
+
|
91
|
+
def delegate=(validator)
|
92
|
+
@validator = validator
|
93
|
+
end
|
94
|
+
|
95
|
+
def expensive_method
|
96
|
+
:expensive_result
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid? address
|
100
|
+
existing_value = @validator.cache_read "foo"
|
101
|
+
return existing_value if existing_value
|
102
|
+
result = expensive_method
|
103
|
+
@validator.cache_write "foo", result
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
mock_cache = mock()
|
109
|
+
mock_cache.expects(:read).with("foo")
|
110
|
+
mock_cache.expects(:write).with("foo", :expensive_result)
|
111
|
+
|
112
|
+
@validator = test_validator :validators => validating_delegate, :cache_store => mock_cache
|
113
|
+
@validator.valid? "test@example.com"
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
describe Pre::Validators::Domain do
|
4
|
+
let (:validator) do
|
5
|
+
class << fake_validator = Object.new
|
6
|
+
include Pre::Cache::Fake
|
7
|
+
define_method(:domain) { "gmail.com" }
|
8
|
+
define_method(:cache_key) { |key| key }
|
9
|
+
end
|
10
|
+
fake_validator.extend Pre::Validators::Domain
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "when dns resolution returns no mx records" do
|
14
|
+
it "returns false" do
|
15
|
+
result = validator.valid_domain?(stub(:getresources => []))
|
16
|
+
result.should be_false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "when dns resolution returns mx records" do
|
21
|
+
it "returns true" do
|
22
|
+
result = validator.valid_domain?(stub(:getresources => ["mx"]))
|
23
|
+
result.should be_true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "caches mx lookups" do
|
28
|
+
result = validator.valid_domain?(stub(:getresources => ["mx"]))
|
29
|
+
result.should be_true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pre
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Thomas Devol
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70142524558780 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70142524558780
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mocha
|
27
|
+
requirement: &70142524558020 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70142524558020
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: pry-nav
|
38
|
+
requirement: &70142524557260 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70142524557260
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: treetop
|
49
|
+
requirement: &70142524556180 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70142524556180
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: active_support
|
60
|
+
requirement: &70142524555000 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70142524555000
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: dalli
|
71
|
+
requirement: &70142524552500 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70142524552500
|
80
|
+
description: Checks email using RFC2822 compliant parser, Requests MX records for
|
81
|
+
server validation
|
82
|
+
email:
|
83
|
+
- thomas.devol@gmail.com
|
84
|
+
executables: []
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- .rspec
|
89
|
+
- .rvmrc
|
90
|
+
- Gemfile
|
91
|
+
- Gemfile.lock
|
92
|
+
- README.md
|
93
|
+
- TODO.md
|
94
|
+
- lib/pre.rb
|
95
|
+
- lib/pre/cache_store.rb
|
96
|
+
- lib/pre/cache_store/fake.rb
|
97
|
+
- lib/pre/cache_store/null.rb
|
98
|
+
- lib/pre/fake_validation.rb
|
99
|
+
- lib/pre/rfc2822.treetop
|
100
|
+
- lib/pre/rfc2822_obsolete.treetop
|
101
|
+
- lib/pre/validator.rb
|
102
|
+
- lib/pre/validators/domain.rb
|
103
|
+
- lib/pre/validators/format.rb
|
104
|
+
- lib/pre/version.rb
|
105
|
+
- lib/tasks/.gitkeep
|
106
|
+
- pre.gemspec
|
107
|
+
- spec/lib/pre/validator_spec.rb
|
108
|
+
- spec/lib/pre/validators/domain_spec.rb
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
homepage: ''
|
111
|
+
licenses: []
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
hash: -3663766737597231915
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
segments:
|
132
|
+
- 0
|
133
|
+
hash: -3663766737597231915
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project: pre
|
136
|
+
rubygems_version: 1.8.17
|
137
|
+
signing_key:
|
138
|
+
specification_version: 3
|
139
|
+
summary: Pretty reliable email validator
|
140
|
+
test_files:
|
141
|
+
- spec/lib/pre/validator_spec.rb
|
142
|
+
- spec/lib/pre/validators/domain_spec.rb
|
143
|
+
- spec/spec_helper.rb
|