spf-query 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.
@@ -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