spf-query 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ module SPF
2
+ module Query
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'spf/query/mechanism'
3
+
4
+ describe SPF::Query::Mechanism do
5
+ let(:name) { :all }
6
+ let(:qualifier) { :soft_fail }
7
+
8
+ subject { described_class.new(name, qualifier: qualifier) }
9
+
10
+ describe "#initialize" do
11
+ it "should set the name" do
12
+ expect(subject.name).to be :all
13
+ end
14
+
15
+ it "should set qualifier" do
16
+ expect(subject.qualifier).to be :soft_fail
17
+ end
18
+
19
+ context "when qualifier is omitted" do
20
+ subject { described_class.new(:all) }
21
+
22
+ it "should default qualifier to :pass" do
23
+ expect(subject.qualifier).to be :pass
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "#pass?" do
29
+ subject { described_class.new(name, qualifier: :pass) }
30
+
31
+ it "should check if qualifier is :pass" do
32
+ expect(subject.pass?).to be true
33
+ end
34
+ end
35
+
36
+ describe "#fail?" do
37
+ subject { described_class.new(name, qualifier: :fail) }
38
+
39
+ it "should check if qualifier is :fail" do
40
+ expect(subject.fail?).to be true
41
+ end
42
+ end
43
+
44
+ describe "#soft_fail?" do
45
+ subject { described_class.new(name, qualifier: :soft_fail) }
46
+
47
+ it "should check if qualifier is :soft_fail" do
48
+ expect(subject.soft_fail?).to be true
49
+ end
50
+ end
51
+
52
+ describe "#neutral?" do
53
+ subject { described_class.new(name, qualifier: :neutral) }
54
+
55
+ it "should check if qualifier is :neutral" do
56
+ expect(subject.neutral?).to be true
57
+ end
58
+ end
59
+
60
+ describe "#to_s" do
61
+ it "should map the qualifier back to a Symbol" do
62
+ expect(subject.to_s).to be == "~#{name}"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,608 @@
1
+ require 'spec_helper'
2
+ require 'spf/query/parser'
3
+
4
+ describe Parser do
5
+ describe "rules" do
6
+ describe "record" do
7
+ subject { super().record }
8
+
9
+ it "should parse a version then multiple terms" do
10
+ expect(subject.parse("v=spf1 -all redirect=_spf.example.com")).to be == {
11
+ version: 'spf1',
12
+
13
+ rules: [
14
+ {
15
+ directive: {
16
+ qualifier: '-',
17
+ name: "all"
18
+ }
19
+ },
20
+
21
+ {
22
+ modifier: {
23
+ name: 'redirect',
24
+ value: {macro_string: [{literal: '_spf.example.com'}]}
25
+ }
26
+ }
27
+ ]
28
+ }
29
+ end
30
+ end
31
+
32
+ describe "version" do
33
+ subject { super().version }
34
+
35
+ it "should match v=spf1" do
36
+ expect(subject.parse('v=spf1')).to be == {version: 'spf1'}
37
+ end
38
+ end
39
+
40
+ describe "terms" do
41
+ subject { super().terms }
42
+
43
+ it "should parse a single term" do
44
+ expect(subject.parse("-all")).to be == {
45
+ directive: {
46
+ qualifier: '-',
47
+ name: "all"
48
+ }
49
+ }
50
+ end
51
+
52
+ it "should parse multiple terms separated by one or more spaces" do
53
+ expect(subject.parse("-all redirect=_spf.example.com")).to be == [
54
+ {
55
+ directive: {
56
+ qualifier: '-',
57
+ name: "all"
58
+ }
59
+ },
60
+
61
+ {
62
+ modifier: {
63
+ name: 'redirect',
64
+ value: {macro_string: [{literal: '_spf.example.com'}]}
65
+ }
66
+ }
67
+ ]
68
+ end
69
+ end
70
+
71
+ describe "term" do
72
+ subject { super().term }
73
+
74
+ it "should parse a directive" do
75
+ expect(subject.parse("-all")).to be == {
76
+ directive: {
77
+ qualifier: '-',
78
+ name: "all"
79
+ }
80
+ }
81
+ end
82
+
83
+ it "should also parse a modifier" do
84
+ expect(subject.parse('redirect=_spf.example.com')).to be == {
85
+ modifier: {
86
+ name: 'redirect',
87
+ value: {macro_string: [{literal: '_spf.example.com'}]}
88
+ }
89
+ }
90
+ end
91
+ end
92
+
93
+ describe "directive" do
94
+ subject { super().directive }
95
+
96
+ it "should parse a mechanism" do
97
+ expect(subject.parse("all")).to be == {directive: {name: "all"}}
98
+ end
99
+
100
+ it "should parse a mechanism with a qualifier" do
101
+ expect(subject.parse("-all")).to be == {
102
+ directive: {
103
+ qualifier: '-',
104
+ name: "all"
105
+ }
106
+ }
107
+ end
108
+ end
109
+
110
+ describe "qualifier" do
111
+ subject { super().qualifier }
112
+
113
+ %w[+ - ~ ?].each do |char|
114
+ it "should match '#{char}'" do
115
+ expect(subject.parse(char)).to be == {qualifier: char}
116
+ end
117
+ end
118
+
119
+ it "should match other characters" do
120
+ expect { subject.parse('x') }.to raise_error(Parslet::ParseFailed)
121
+ end
122
+ end
123
+
124
+ describe "mechanism" do
125
+ subject { super().mechanism }
126
+
127
+ it "should parse a mechanism" do
128
+ expect(subject.parse('all')).to be == {name: 'all'}
129
+ end
130
+ end
131
+
132
+ describe "all" do
133
+ subject { super().all }
134
+
135
+ it "should parse \"all\"" do
136
+ expect(subject.parse('all')).to be == {name: 'all'}
137
+ end
138
+ end
139
+
140
+ describe "include" do
141
+ subject { super().include }
142
+
143
+ let(:domain) { 'example.com' }
144
+
145
+ it "should parse \"include:domain\"" do
146
+ expect(subject.parse("include:#{domain}")).to be == {
147
+ name: 'include',
148
+ value: {macro_string: [{literal: domain}]}
149
+ }
150
+ end
151
+ end
152
+
153
+ describe "a" do
154
+ subject { super().a }
155
+
156
+ let(:domain) { 'example.com' }
157
+
158
+ it "should parse \"a:domain\"" do
159
+ expect(subject.parse("a:#{domain}")).to be == {
160
+ name: 'a',
161
+ value: {
162
+ macro_string: [{literal: domain}],
163
+ }
164
+ }
165
+ end
166
+
167
+ let(:cidr_length) { '30' }
168
+
169
+ it "should parse \"a:domain/cidr-length\"" do
170
+ expect(subject.parse("a:#{domain}/#{cidr_length}")).to be == {
171
+ name: 'a',
172
+ value: {
173
+ macro_string: [{literal: "#{domain}/#{cidr_length}"}]
174
+ }
175
+ }
176
+ end
177
+
178
+ it "should parse \"a:/cidr-length\"" do
179
+ expect(subject.parse("a:/#{cidr_length}")).to be == {
180
+ name: 'a',
181
+ value: {
182
+ macro_string: [{literal: "/#{cidr_length}"}]
183
+ }
184
+ }
185
+ end
186
+ end
187
+
188
+ describe "mx" do
189
+ subject { super().mx }
190
+
191
+ let(:domain) { 'example.com' }
192
+
193
+ it "should parse \"mx:domain\"" do
194
+ expect(subject.parse("mx:#{domain}")).to be == {
195
+ name: 'mx',
196
+ value: {
197
+ macro_string: [{literal: domain}]
198
+ }
199
+ }
200
+ end
201
+
202
+ let(:cidr_length) { '30' }
203
+
204
+ it "should parse \"mx:domain/cidr-length\"" do
205
+ expect(subject.parse("mx:#{domain}/#{cidr_length}")).to be == {
206
+ name: 'mx',
207
+ value: {
208
+ macro_string: [{literal: "#{domain}/#{cidr_length}"}]
209
+ }
210
+ }
211
+ end
212
+
213
+ it "should parse \"mx:/cidr-length\"" do
214
+ expect(subject.parse("mx:/#{cidr_length}")).to be == {
215
+ name: 'mx',
216
+ value: {
217
+ macro_string: [{literal: "/#{cidr_length}"}]
218
+ }
219
+ }
220
+ end
221
+ end
222
+
223
+ describe "ip4" do
224
+ subject { super().ip4 }
225
+
226
+ let(:ip) { '1.2.3.4' }
227
+
228
+ it "should parse \"ip4:ip\"" do
229
+ expect(subject.parse("ip4:#{ip}")).to be == {
230
+ name: 'ip4',
231
+ value: {ip: ip}
232
+ }
233
+ end
234
+
235
+ let(:cidr_length) { '24' }
236
+
237
+ it "should parse \"ip4:ip/cidr-length\"" do
238
+ expect(subject.parse("ip4:#{ip}/#{cidr_length}")).to be == {
239
+ name: 'ip4',
240
+ value: {ip: ip, cidr_length: cidr_length}
241
+ }
242
+ end
243
+ end
244
+
245
+ describe "ip6" do
246
+ subject { super().ip6 }
247
+
248
+ let(:ip) { '2001:0db8:85a3:0000:0000:8a2e:0370:7334' }
249
+
250
+ it "should parse \"ip6:ip\"" do
251
+ expect(subject.parse("ip6:#{ip}")).to be == {
252
+ name: 'ip6',
253
+ value: {ip: ip}
254
+ }
255
+ end
256
+
257
+ let(:cidr_length) { '32' }
258
+
259
+ it "should parse \"ip6:ip/cidr-length\"" do
260
+ expect(subject.parse("ip6:#{ip}/#{cidr_length}")).to be == {
261
+ name: 'ip6',
262
+ value: {ip: ip, cidr_length: cidr_length}
263
+ }
264
+ end
265
+ end
266
+
267
+ describe "ipv4_cidr_length" do
268
+ subject { super().ipv4_cidr_length }
269
+
270
+ it "should not match \"/\"" do
271
+ expect { subject.parse("/") }.to raise_error(Parslet::ParseFailed)
272
+ end
273
+
274
+ it "should match \"/1\"" do
275
+ expect(subject.parse("/1")).to be == {cidr_length: '1'}
276
+ end
277
+
278
+ it "should match \"/123\"" do
279
+ expect(subject.parse("/123")).to be == {cidr_length: '123'}
280
+ end
281
+ end
282
+
283
+ describe "ipv6_cidr_length" do
284
+ subject { super().ipv6_cidr_length }
285
+
286
+ it "should not match \"/\"" do
287
+ expect { subject.parse("/") }.to raise_error(Parslet::ParseFailed)
288
+ end
289
+
290
+ it "should match \"/1\"" do
291
+ expect(subject.parse("/1")).to be == {cidr_length: '1'}
292
+ end
293
+
294
+ it "should match \"/123\"" do
295
+ expect(subject.parse("/123")).to be == {cidr_length: '123'}
296
+ end
297
+ end
298
+
299
+ describe "exists" do
300
+ subject { super().exists }
301
+
302
+ it "should parse \"exists:domain\"" do
303
+ expect(subject.parse('exists:%{ir}.sbl.spamhaus.example.org')).to be == {
304
+ name: 'exists',
305
+ value: {macro_string: [
306
+ {macro: {letter: 'i', reverse: 'r'}},
307
+ {literal: '.sbl.spamhaus.example.org'}
308
+ ]}
309
+ }
310
+ end
311
+ end
312
+
313
+ describe "modifier" do
314
+ subject { super().modifier }
315
+
316
+ it "should parse a modifier" do
317
+ expect(subject.parse('redirect=_spf.example.com')).to be == {
318
+ modifier: {
319
+ name: 'redirect',
320
+ value: {macro_string: [{literal: '_spf.example.com'}]}
321
+ }
322
+ }
323
+ end
324
+ end
325
+
326
+ describe "redirect" do
327
+ subject { super().redirect }
328
+
329
+ it "should parse \"redirect=domain\"" do
330
+ expect(subject.parse('redirect=_spf.example.com')).to be == {
331
+ name: 'redirect',
332
+ value: {macro_string: [{literal: '_spf.example.com'}]}
333
+ }
334
+ end
335
+ end
336
+
337
+ describe "explanation" do
338
+ subject { super().explanation }
339
+
340
+ it "should parse \"exp=domain\"" do
341
+ expect(subject.parse("exp=explain._spf.%{d}")).to be == {
342
+ name: 'exp',
343
+ value: {macro_string: [
344
+ {literal: 'explain._spf.'},
345
+ {macro: {letter: 'd'}}
346
+ ]}
347
+ }
348
+ end
349
+ end
350
+
351
+ describe "unknown_modifier" do
352
+ subject { super().unknown_modifier }
353
+
354
+ it "should parse \"name=\"" do
355
+ expect(subject.parse("foo=")).to be == {name: 'foo', value: nil}
356
+ end
357
+
358
+ it "should parse \"name=value\"" do
359
+ expect(subject.parse("foo=bar")).to be == {
360
+ name: 'foo',
361
+ value: {macro_string: [{literal: 'bar'}]}
362
+ }
363
+ end
364
+ end
365
+
366
+ describe "domain_spec" do
367
+ subject { super().domain_spec }
368
+
369
+ it "should not parse \"\"" do
370
+ expect { subject.parse('') }.to raise_error(Parslet::ParseFailed)
371
+ end
372
+
373
+ it "should parse macro_literals" do
374
+ expect(subject.parse('AAA')).to be == {macro_string: [{literal: 'AAA'}]}
375
+ end
376
+
377
+ it "should parse macro_expands" do
378
+ expect(subject.parse('%{s}%{d}')).to be == {macro_string: [
379
+ {macro: {letter: 's'}},
380
+ {macro: {letter: 'd'}}
381
+ ]}
382
+ end
383
+
384
+ it "should parse a mixture of macro_literals and macro_expands" do
385
+ expect(subject.parse('foo.%{s}.bar.%{d}')).to be == {macro_string: [
386
+ {literal: 'foo.'},
387
+ {macro: {letter: 's'}},
388
+ {literal: '.bar.'},
389
+ {macro: {letter: 'd'}}
390
+ ]}
391
+ end
392
+ end
393
+
394
+ describe "name" do
395
+ subject { super().name }
396
+
397
+ %w[A AAA A123 A_123 A-123 A.123 A123_ A123- A123.].each do |str|
398
+ it "should parse #{str.inspect}" do
399
+ expect(subject.parse(str)).to be == str
400
+ end
401
+ end
402
+ end
403
+
404
+ describe "macro_string" do
405
+ subject { super().macro_string }
406
+
407
+ it "should not parse ''" do
408
+ expect { subject.parse('') }.to raise_error(Parslet::ParseFailed)
409
+ end
410
+
411
+ it "should parse macro_literals" do
412
+ expect(subject.parse('AAA')).to be == {macro_string: [{literal: 'AAA'}]}
413
+ end
414
+
415
+ it "should parse macro_expands" do
416
+ expect(subject.parse('%{s}%{d}')).to be == {macro_string: [
417
+ {macro: {letter: 's'}},
418
+ {macro: {letter: 'd'}}
419
+ ]}
420
+ end
421
+
422
+ it "should parse a mixture of macro_literals and macro_expands" do
423
+ expect(subject.parse('foo.%{s}.bar.%{d}')).to be == {macro_string: [
424
+ {literal: 'foo.'},
425
+ {macro: {letter: 's'}},
426
+ {literal: '.bar.'},
427
+ {macro: {letter: 'd'}}
428
+ ]}
429
+ end
430
+ end
431
+
432
+ describe "macro_string?" do
433
+ subject { super().macro_string? }
434
+
435
+ it "should parse ''" do
436
+ expect(subject.parse('')).to be == ''
437
+ end
438
+ end
439
+
440
+ describe "macro_expand" do
441
+ subject { super().macro_expand }
442
+
443
+ %w[%% %_ %-].each do |str|
444
+ it "should parse #{str.inspect}" do
445
+ expect(subject.parse(str)).to be == {macro: str}
446
+ end
447
+ end
448
+
449
+ it "should parse \"%{s}\"" do
450
+ expect(subject.parse("%{s}")).to be == {
451
+ macro: {letter: 's'}
452
+ }
453
+ end
454
+
455
+ it "should parse \"%{d4}\"" do
456
+ expect(subject.parse("%{d4}")).to be == {
457
+ macro: {
458
+ letter: 'd',
459
+ digits: '4'
460
+ }
461
+ }
462
+ end
463
+
464
+ it "should parse \"%{dr}\"" do
465
+ expect(subject.parse("%{dr}")).to be == {
466
+ macro: {
467
+ letter: 'd',
468
+ reverse: 'r'
469
+ }
470
+ }
471
+ end
472
+
473
+ it "should parse \"%{d2r}\"" do
474
+ expect(subject.parse("%{d2r}")).to be == {
475
+ macro: {
476
+ letter: 'd',
477
+ digits: '2',
478
+ reverse: 'r'
479
+ }
480
+ }
481
+ end
482
+
483
+ it "should parse \"%{l-}\"" do
484
+ expect(subject.parse("%{l-}")).to be == {
485
+ macro: {
486
+ letter: 'l',
487
+ delimiters: [{char: '-'}]
488
+ }
489
+ }
490
+ end
491
+
492
+ it "should parse \"%{lr-}\"" do
493
+ expect(subject.parse("%{lr-}")).to be == {
494
+ macro: {
495
+ letter: 'l',
496
+ reverse: 'r',
497
+ delimiters: [{char: '-'}]
498
+ }
499
+ }
500
+ end
501
+
502
+ it "should parse \"%{l1r-}\"" do
503
+ expect(subject.parse("%{l1r-}")).to be == {
504
+ macro: {
505
+ letter: 'l',
506
+ digits: '1',
507
+ reverse: 'r',
508
+ delimiters: [{char: '-'}]
509
+ }
510
+ }
511
+ end
512
+ end
513
+
514
+ describe "macro_literal" do
515
+ subject { super().macro_literal }
516
+
517
+ [*"\x21".."\x24", *"\x26".."\x7e"].each do |char|
518
+ it "should recognize the #{char.inspect} char" do
519
+ expect(subject.parse(char)).to be == char
520
+ end
521
+ end
522
+ end
523
+
524
+ describe "macro_letter" do
525
+ subject { super().macro_letter }
526
+
527
+ %w[s l o d i p h c r t].each do |char|
528
+ it "should recognize the '#{char}' char" do
529
+ expect(subject.parse(char)).to be == char
530
+ end
531
+ end
532
+ end
533
+
534
+ describe "transformers" do
535
+ subject { super().transformers }
536
+
537
+ it "should parse \"\"" do
538
+ expect(subject.parse("")).to be == ""
539
+ end
540
+
541
+ it "should parse a single digit" do
542
+ expect(subject.parse("1")).to be == {digits: "1"}
543
+ end
544
+
545
+ it "should parse a multiple digits" do
546
+ expect(subject.parse("123")).to be == {digits: "123"}
547
+ end
548
+
549
+ it "should parse 'r'" do
550
+ expect(subject.parse("r")).to be == {reverse: "r"}
551
+ end
552
+
553
+ it "should parse a single digit then 'r'" do
554
+ expect(subject.parse("1r")).to be == {digits: "1", reverse: "r"}
555
+ end
556
+
557
+ it "should parse a multiple digits then 'r'" do
558
+ expect(subject.parse("123r")).to be == {digits: "123", reverse: "r"}
559
+ end
560
+ end
561
+
562
+ describe "delimiter" do
563
+ subject { super().delimiter }
564
+
565
+ %w[- . + , / _ =].each do |char|
566
+ it "should match '#{char}'" do
567
+ expect(subject.parse(char)).to be == {char: char}
568
+ end
569
+ end
570
+
571
+ it "should not match other characters" do
572
+ expect { subject.parse('x') }.to raise_error(Parslet::ParseFailed)
573
+ end
574
+ end
575
+ end
576
+
577
+ describe Parser::Transform do
578
+ describe "directive" do
579
+ subject do
580
+ super().apply(directive: {qualifier: '~', name: 'all'})
581
+ end
582
+
583
+ it "should map directives to Mechanism objects" do
584
+ expect(subject).to be_kind_of(Mechanism)
585
+ end
586
+
587
+ it "should set the name" do
588
+ expect(subject.name).to be == :all
589
+ end
590
+
591
+ it "should map qualifier to a Symbol" do
592
+ expect(subject.qualifier).to be :soft_fail
593
+ end
594
+ end
595
+ end
596
+
597
+ describe ".parse" do
598
+ let(:spf) do
599
+ %{v=spf1 ip4:199.16.156.0/22 ip4:199.59.148.0/22 ip4:8.25.194.0/23 ip4:8.25.196.0/23 ip4:204.92.114.203 ip4:204.92.114.204/31 ip4:107.20.52.15 ip4:23.21.83.90 include:_spf.google.com include:_thirdparty.twitter.com all}
600
+ end
601
+
602
+ subject { described_class.parse(spf) }
603
+
604
+ it "should return a Record" do
605
+ expect(subject).to be_kind_of(Record)
606
+ end
607
+ end
608
+ end