pre 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.3@pre --create
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ TODO
2
+ ====
3
+
4
+ * ActiveModel integration
5
+ * Simple DNS resolution caching
6
+ * Configurable additional strategies
7
+
@@ -0,0 +1,5 @@
1
+ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}")
2
+ require "pre/validator"
3
+ module Pre
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,5 @@
1
+ require "pre/cache_store/null"
2
+ module Pre
3
+ class CacheStore
4
+ end
5
+ end
@@ -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,13 @@
1
+ module Pre
2
+ class CacheStore
3
+ class Null
4
+ def read key
5
+ end
6
+ def write key, val
7
+ end
8
+ def fetch key
9
+ yield
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,11 @@
1
+ module Pre
2
+ module Validators
3
+ module Format
4
+ def valid_format?
5
+ return false unless parsed.respond_to? :domain
6
+ parsed.domain.dot_atom_text.elements.size > 1
7
+ end
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,3 @@
1
+ module Pre
2
+ VERSION = "0.0.1"
3
+ end
File without changes
@@ -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
+
@@ -0,0 +1,7 @@
1
+ require_relative "../lib/pre"
2
+ require_relative "../lib/pre/fake_validation"
3
+ require_relative "../lib/pre/cache_store/fake"
4
+
5
+ RSpec.configure do |config|
6
+ config.mock_framework = :mocha
7
+ end
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