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 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