arachni 1.0.5 → 1.0.6

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +9 -2
  4. data/components/checks/active/code_injection.rb +5 -5
  5. data/components/checks/active/code_injection_timing.rb +3 -3
  6. data/components/checks/active/no_sql_injection_differential.rb +3 -2
  7. data/components/checks/active/os_cmd_injection.rb +11 -5
  8. data/components/checks/active/os_cmd_injection_timing.rb +11 -4
  9. data/components/checks/active/path_traversal.rb +2 -2
  10. data/components/checks/active/sql_injection.rb +1 -1
  11. data/components/checks/active/sql_injection/patterns/mssql +1 -0
  12. data/components/checks/active/sql_injection_differential.rb +3 -2
  13. data/components/checks/active/unvalidated_redirect.rb +3 -3
  14. data/components/checks/passive/common_directories/directories.txt +2 -0
  15. data/components/checks/passive/common_files/filenames.txt +1 -0
  16. data/lib/arachni/browser.rb +17 -1
  17. data/lib/arachni/check/auditor.rb +5 -2
  18. data/lib/arachni/check/base.rb +30 -5
  19. data/lib/arachni/element/capabilities/analyzable/differential.rb +2 -5
  20. data/lib/arachni/element/capabilities/auditable.rb +3 -1
  21. data/lib/arachni/element/capabilities/with_dom.rb +1 -0
  22. data/lib/arachni/element/capabilities/with_node.rb +1 -1
  23. data/lib/arachni/element/cookie.rb +2 -2
  24. data/lib/arachni/element/form.rb +1 -1
  25. data/lib/arachni/element/header.rb +2 -2
  26. data/lib/arachni/element/link_template.rb +1 -1
  27. data/lib/arachni/framework.rb +21 -1144
  28. data/lib/arachni/framework/parts/audit.rb +282 -0
  29. data/lib/arachni/framework/parts/browser.rb +132 -0
  30. data/lib/arachni/framework/parts/check.rb +86 -0
  31. data/lib/arachni/framework/parts/data.rb +158 -0
  32. data/lib/arachni/framework/parts/platform.rb +34 -0
  33. data/lib/arachni/framework/parts/plugin.rb +61 -0
  34. data/lib/arachni/framework/parts/report.rb +128 -0
  35. data/lib/arachni/framework/parts/scope.rb +40 -0
  36. data/lib/arachni/framework/parts/state.rb +457 -0
  37. data/lib/arachni/http/client.rb +33 -30
  38. data/lib/arachni/http/request.rb +6 -2
  39. data/lib/arachni/issue.rb +55 -1
  40. data/lib/arachni/platform/manager.rb +25 -21
  41. data/lib/arachni/state/framework.rb +7 -1
  42. data/lib/arachni/utilities.rb +10 -0
  43. data/lib/version +1 -1
  44. data/spec/arachni/browser_spec.rb +13 -0
  45. data/spec/arachni/check/auditor_spec.rb +1 -0
  46. data/spec/arachni/check/base_spec.rb +80 -0
  47. data/spec/arachni/element/cookie_spec.rb +2 -2
  48. data/spec/arachni/framework/parts/audit_spec.rb +391 -0
  49. data/spec/arachni/framework/parts/browser_spec.rb +26 -0
  50. data/spec/arachni/framework/parts/check_spec.rb +24 -0
  51. data/spec/arachni/framework/parts/data_spec.rb +187 -0
  52. data/spec/arachni/framework/parts/platform_spec.rb +62 -0
  53. data/spec/arachni/framework/parts/plugin_spec.rb +41 -0
  54. data/spec/arachni/framework/parts/report_spec.rb +66 -0
  55. data/spec/arachni/framework/parts/scope_spec.rb +86 -0
  56. data/spec/arachni/framework/parts/state_spec.rb +528 -0
  57. data/spec/arachni/framework_spec.rb +17 -1344
  58. data/spec/arachni/http/client_spec.rb +12 -7
  59. data/spec/arachni/issue_spec.rb +35 -0
  60. data/spec/arachni/platform/manager_spec.rb +2 -3
  61. data/spec/arachni/state/framework_spec.rb +15 -0
  62. data/spec/components/checks/active/code_injection_timing_spec.rb +5 -5
  63. data/spec/components/checks/active/no_sql_injection_differential_spec.rb +4 -0
  64. data/spec/components/checks/active/os_cmd_injection_spec.rb +20 -7
  65. data/spec/components/checks/active/os_cmd_injection_timing_spec.rb +5 -5
  66. data/spec/components/checks/active/sql_injection_differential_spec.rb +4 -0
  67. data/spec/components/checks/active/sql_injection_spec.rb +2 -3
  68. data/spec/support/servers/arachni/browser.rb +31 -0
  69. data/spec/support/servers/checks/active/code_injection.rb +1 -1
  70. data/spec/support/servers/checks/active/no_sql_injection_differential.rb +36 -34
  71. data/spec/support/servers/checks/active/os_cmd_injection.rb +6 -12
  72. data/spec/support/servers/checks/active/os_cmd_injection_timing.rb +9 -4
  73. data/spec/support/servers/checks/active/sql_injection.rb +1 -1
  74. data/spec/support/servers/checks/active/sql_injection_differential.rb +37 -34
  75. data/spec/support/shared/element/capabilities/with_node.rb +25 -0
  76. data/spec/support/shared/framework.rb +26 -0
  77. data/ui/cli/output.rb +2 -0
  78. data/ui/cli/rpc/server/dispatcher/option_parser.rb +1 -1
  79. metadata +32 -4
  80. data/components/checks/active/sql_injection/patterns/coldfusion +0 -1
@@ -1,30 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Arachni::Framework do
4
-
5
- before( :all ) do
6
- @url = web_server_url_for( :auditor )
7
- @f_url = web_server_url_for( :framework )
8
-
9
- @options = Arachni::Options.instance
10
- end
11
-
12
- before( :each ) do
13
- reset_options
14
- @options.paths.reporters = fixtures_path + '/reporters/manager_spec/'
15
- @options.paths.checks = fixtures_path + '/taint_check/'
16
-
17
- @f = Arachni::Framework.new
18
- @f.options.url = @url
19
- end
20
- after( :each ) do
21
- File.delete( @snapshot ) rescue nil
22
-
23
- @f.clean_up
24
- @f.reset
25
- end
26
-
27
- subject { @f }
4
+ include_examples 'framework'
28
5
 
29
6
  describe '#initialize' do
30
7
  context 'when passed a block' do
@@ -62,241 +39,10 @@ describe Arachni::Framework do
62
39
  end
63
40
  end
64
41
 
65
- describe '#browser_cluster' do
66
- it "returns #{Arachni::BrowserCluster}" do
67
- subject.browser_cluster.should be_kind_of Arachni::BrowserCluster
68
- end
69
-
70
- context "when #{Arachni::OptionGroups::BrowserCluster}#pool_size" do
71
- it 'returns nil' do
72
- subject.options.browser_cluster.pool_size = 0
73
- subject.browser_cluster.should be_nil
74
- end
75
- end
76
-
77
- context "when #{Arachni::OptionGroups::Scope}#dom_depth_limit" do
78
- it 'returns nil' do
79
- subject.options.scope.dom_depth_limit = 0
80
- subject.browser_cluster.should be_nil
81
- end
82
- end
83
- end
84
-
85
- describe '#state' do
86
- it "returns #{Arachni::State::Framework}" do
87
- subject.state.should be_kind_of Arachni::State::Framework
88
- end
89
- end
90
-
91
- describe '#data' do
92
- it "returns #{Arachni::Data::Framework}" do
93
- subject.data.should be_kind_of Arachni::Data::Framework
94
- end
95
- end
96
-
97
- describe '#on_page_audit' do
98
- it 'calls the given block before each page is audited' do
99
- ok = false
100
- Arachni::Framework.new do |f|
101
- f.options.url = @url
102
- f.on_page_audit { ok = true }
103
-
104
- f.audit_page Arachni::Page.from_url( @url + '/link' )
105
- end
106
- ok.should be_true
107
- end
108
- end
109
-
110
- describe '#after_page_audit' do
111
- it 'calls the given block before each page is audited' do
112
- ok = false
113
- Arachni::Framework.new do |f|
114
- f.options.url = @url
115
- f.after_page_audit { ok = true }
116
-
117
- f.audit_page Arachni::Page.from_url( @url + '/link' )
118
- end
119
- ok.should be_true
120
- end
121
- end
122
-
123
- context 'when unable to get a response for the given URL' do
124
- context 'due to a network error' do
125
- it 'returns an empty sitemap and have failures' do
126
- @options.url = 'http://blahaha'
127
- @options.scope.restrict_paths = [@options.url]
128
-
129
- subject.checks.load :taint
130
- subject.run
131
- subject.failures.should be_any
132
- end
133
- end
134
-
135
- context 'due to a server error' do
136
- it 'returns an empty sitemap and have failures' do
137
- @options.url = @f_url + '/fail'
138
- @options.scope.restrict_paths = [@options.url]
139
-
140
- subject.checks.load :taint
141
- subject.run
142
- subject.failures.should be_any
143
- end
144
- end
145
-
146
- it "retries #{Arachni::Framework::AUDIT_PAGE_MAX_TRIES} times" do
147
- @options.url = @f_url + '/fail_4_times'
148
- @options.scope.restrict_paths = [@options.url]
149
-
150
- subject.checks.load :taint
151
- subject.run
152
- subject.failures.should be_empty
153
- end
154
- end
155
-
156
- describe '#failures' do
157
- context 'when there are no failed requests' do
158
- it 'returns an empty array' do
159
- @options.url = @f_url
160
- @options.scope.restrict_paths = [@options.url]
161
-
162
- subject.checks.load :taint
163
- subject.run
164
- subject.failures.should be_empty
165
- end
166
- end
167
- context 'when there are failed requests' do
168
- it 'returns an array containing the failed URLs' do
169
- @options.url = @f_url + '/fail'
170
- @options.scope.restrict_paths = [@options.url]
171
-
172
- subject.checks.load :taint
173
- subject.run
174
- subject.failures.should be_any
175
- end
176
- end
177
- end
178
-
179
42
  describe '#options' do
180
43
  it "provides access to #{Arachni::Options}" do
181
44
  subject.options.should be_kind_of Arachni::Options
182
45
  end
183
-
184
- describe "#{Arachni::OptionGroups::Scope}#exclude_binaries" do
185
- it 'excludes binary pages from the scan' do
186
- audited = []
187
- Arachni::Framework.new do |f|
188
- f.options.url = @url
189
- f.options.scope.restrict_paths << @url + '/binary'
190
- f.options.audit.elements :links, :forms, :cookies
191
- f.checks.load :taint
192
-
193
- f.on_page_audit { |p| audited << p.url }
194
- f.run
195
- end
196
- audited.sort.should == [@url + '/binary'].sort
197
-
198
- audited = []
199
- Arachni::Framework.new do |f|
200
- f.options.url = @url
201
- f.options.scope.restrict_paths << @url + '/binary'
202
- f.options.scope.exclude_binaries = true
203
- f.checks.load :taint
204
-
205
- f.on_page_audit { |p| audited << p.url }
206
- f.run
207
- end
208
- audited.should be_empty
209
- end
210
- end
211
-
212
- describe "#{Arachni::OptionGroups::Scope}#extend_paths" do
213
- it 'extends the crawl scope' do
214
- Arachni::Framework.new do |f|
215
- f.options.url = "#{@url}/elem_combo"
216
- f.options.scope.extend_paths = %w(/some/stuff /more/stuff)
217
- f.options.audit.elements :links, :forms, :cookies
218
- f.checks.load :taint
219
-
220
- f.run
221
-
222
- f.report.sitemap.should include "#{@url}/some/stuff"
223
- f.report.sitemap.should include "#{@url}/more/stuff"
224
- f.report.sitemap.size.should > 3
225
- end
226
- end
227
- end
228
-
229
- describe "#{Arachni::OptionGroups::Scope}#restrict_paths" do
230
- it 'serves as a replacement to crawling' do
231
- Arachni::Framework.new do |f|
232
- f.options.url = "#{@url}/elem_combo"
233
- f.options.scope.restrict_paths = %w(/log_remote_file_if_exists/true)
234
- f.options.audit.elements :links, :forms, :cookies
235
- f.checks.load :taint
236
-
237
- f.run
238
-
239
- sitemap = f.report.sitemap.map { |u, _| u.split( '?' ).first }
240
- sitemap.sort.uniq.should == f.options.scope.restrict_paths.
241
- map { |p| f.to_absolute( p ) }.sort
242
- end
243
- end
244
- end
245
- end
246
-
247
- describe '#sitemap' do
248
- it 'returns a hash with covered URLs and HTTP status codes' do
249
- Arachni::Framework.new do |f|
250
- f.options.url = "#{@url}/"
251
- f.options.audit.elements :links, :forms, :cookies
252
- f.checks.load :taint
253
-
254
- f.run
255
- f.sitemap.should == { "#{@url}/" => 200 }
256
- end
257
- end
258
- end
259
-
260
- describe '#reporters' do
261
- it 'provides access to the reporter manager' do
262
- subject.reporters.is_a?( Arachni::Reporter::Manager ).should be_true
263
- subject.reporters.available.sort.should == %w(afr foo).sort
264
- end
265
- end
266
-
267
- describe '#checks' do
268
- it 'provides access to the check manager' do
269
- subject.checks.is_a?( Arachni::Check::Manager ).should be_true
270
- subject.checks.available.should == %w(taint)
271
- end
272
- end
273
-
274
- describe '#plugins' do
275
- it 'provides access to the plugin manager' do
276
- subject.plugins.is_a?( Arachni::Plugin::Manager ).should be_true
277
- subject.plugins.available.sort.should ==
278
- %w(wait bad with_options distributable loop default suspendable).sort
279
- end
280
- end
281
-
282
- describe '#http' do
283
- it 'provides access to the HTTP interface' do
284
- subject.http.is_a?( Arachni::HTTP::Client ).should be_true
285
- end
286
- end
287
-
288
- describe '#scanning?' do
289
- it "delegates to #{Arachni::State::Framework}#scanning?" do
290
- subject.state.stub(:scanning?) { :stuff }
291
- subject.scanning?.should == :stuff
292
- end
293
- end
294
-
295
- describe '#paused?' do
296
- it "delegates to #{Arachni::State::Framework}#paused?" do
297
- subject.state.stub(:paused?) { :stuff }
298
- subject.paused?.should == :stuff
299
- end
300
46
  end
301
47
 
302
48
  describe '#run' do
@@ -336,7 +82,7 @@ describe Arachni::Framework do
336
82
  end
337
83
 
338
84
  it 'handles heavy load' do
339
- @options.paths.checks = fixtures_path + '/taint_check/'
85
+ @options.paths.checks = fixtures_path + '/taint_check/'
340
86
 
341
87
  Arachni::Framework.new do |f|
342
88
  f.options.url = web_server_url_for :framework_multi
@@ -417,1104 +163,31 @@ describe Arachni::Framework do
417
163
  end
418
164
  end
419
165
 
420
- describe '#abort' do
421
- it 'aborts the system' do
422
- @options.paths.checks = fixtures_path + '/taint_check/'
423
-
424
- described_class.new do |f|
425
- f.options.url = web_server_url_for :framework_multi
426
- f.options.audit.elements :links
427
-
428
- f.plugins.load :wait
429
- f.checks.load :taint
430
-
431
- t = Thread.new do
432
- f.run
433
- end
434
-
435
- sleep 0.1 while Arachni::Data.issues.size < 2
436
-
437
- f.abort
438
- t.join
439
-
440
- Arachni::Data.issues.size.should < 500
441
- end
442
- end
443
-
444
- it 'sets #status to :aborted' do
445
- described_class.new do |f|
446
- f.options.url = web_server_url_for :framework_multi
447
- f.options.audit.elements :links
448
- f.checks.load :taint
449
-
450
- t = Thread.new do
451
- f.run
452
- end
453
- sleep 0.1 while f.status != :scanning
454
-
455
- f.abort
456
- f.status.should == :aborted
457
-
458
- t.join
459
- f.status.should == :aborted
460
- end
461
- end
462
- end
463
-
464
- describe '#suspend' do
465
- it 'suspends the system' do
466
- @options.paths.checks = fixtures_path + '/taint_check/'
467
-
468
- described_class.new do |f|
469
- f.options.url = web_server_url_for :framework_multi
470
- f.options.audit.elements :links
471
-
472
- f.plugins.load :wait
473
- f.checks.load :taint
474
-
475
- t = Thread.new do
476
- f.run
477
- end
478
-
479
- sleep 0.1 while Arachni::Data.issues.size < 2
480
-
481
- @snapshot = f.suspend
482
- t.join
483
-
484
- Arachni::Data.issues.size.should < 500
485
- end
486
-
487
- Arachni::Snapshot.load( @snapshot ).should be_true
488
- end
489
-
490
- it 'sets #status to :suspended' do
491
- described_class.new do |f|
492
- f.options.url = web_server_url_for :framework_multi
493
- f.options.audit.elements :links
494
- f.checks.load :taint
495
-
496
- t = Thread.new do
497
- f.run
498
- end
499
- sleep 0.1 while f.status != :scanning
500
-
501
- @snapshot = f.suspend
502
- f.status.should == :suspended
503
-
504
- t.join
505
- f.status.should == :suspended
506
- end
507
- end
508
-
509
- it 'suspends plugins' do
510
- Arachni::Options.plugins['suspendable'] = {
511
- 'my_option' => 'my value'
512
- }
513
-
514
- described_class.new do |f|
515
- f.options.url = web_server_url_for :framework_multi
516
- f.options.audit.elements :links
517
-
518
- f.checks.load :taint
519
- f.plugins.load :suspendable
520
-
521
- t = Thread.new do
522
- f.run
523
- end
524
-
525
- sleep 0.1 while f.status != :scanning
526
-
527
- f.suspend
528
- t.join
166
+ describe '#statistics' do
167
+ let(:statistics) { subject.statistics }
529
168
 
530
- Arachni::State.plugins.runtime[:suspendable][:data].should == 1
531
- end
169
+ it 'includes http statistics' do
170
+ statistics[:http].should == subject.http.statistics
532
171
  end
533
172
 
534
- it 'waits for the BrowserCluster jobs to finish'
535
-
536
- context "when #{Arachni::OptionGroups::Snapshot}#save_path" do
537
- context 'is a directory' do
538
- it 'stores the snapshot under it' do
539
- @options.paths.checks = fixtures_path + '/taint_check/'
540
- @options.snapshot.save_path = Dir.tmpdir
541
-
542
- described_class.new do |f|
543
- f.options.url = web_server_url_for :framework_multi
544
- f.options.audit.elements :links
545
-
546
- f.plugins.load :wait
547
- f.checks.load :taint
548
-
549
- t = Thread.new do
550
- f.run
551
- end
552
-
553
- sleep 0.1 while Arachni::Data.issues.size < 2
554
-
555
- @snapshot = f.suspend
556
- t.join
557
-
558
- Arachni::Data.issues.size.should < 500
559
- end
560
-
561
- File.dirname( @snapshot ).should == Dir.tmpdir
562
- Arachni::Snapshot.load( @snapshot ).should be_true
563
- end
564
- end
565
-
566
- context 'is a file path' do
567
- it 'stores the snapshot there' do
568
- @options.paths.checks = fixtures_path + '/taint_check/'
569
- @options.snapshot.save_path = "#{Dir.tmpdir}/snapshot"
570
-
571
- described_class.new do |f|
572
- f.options.url = web_server_url_for :framework_multi
573
- f.options.audit.elements :links
574
-
575
- f.plugins.load :wait
576
- f.checks.load :taint
577
-
578
- t = Thread.new do
579
- f.run
580
- end
581
-
582
- sleep 0.1 while Arachni::Data.issues.size < 2
583
-
584
- @snapshot = f.suspend
585
- t.join
586
-
587
- Arachni::Data.issues.size.should < 500
588
- end
589
-
590
- @snapshot.should == "#{Dir.tmpdir}/snapshot"
591
- Arachni::Snapshot.load( @snapshot ).should be_true
592
- end
173
+ [:found_pages, :audited_pages, :current_page].each do |k|
174
+ it "includes #{k}" do
175
+ statistics.should include k
593
176
  end
594
177
  end
595
- end
596
-
597
- describe '#restore' do
598
- it 'restores a suspended scan' do
599
- @options.paths.checks = fixtures_path + '/taint_check/'
600
-
601
- logged_issues = 0
602
- described_class.new do |f|
603
- f.options.url = web_server_url_for :framework_multi
604
- f.options.audit.elements :links
605
-
606
- f.plugins.load :wait
607
- f.checks.load :taint
608
-
609
- Arachni::Data.issues.on_new do
610
- logged_issues += 1
611
- end
612
178
 
613
- t = Thread.new do
614
- f.run
179
+ describe :runtime do
180
+ context 'when the scan has been running' do
181
+ it 'returns the runtime in seconds' do
182
+ subject.run
183
+ statistics[:runtime].should > 0
615
184
  end
616
-
617
- sleep 0.1 while logged_issues < 200
618
-
619
- @snapshot = f.suspend
620
- t.join
621
-
622
- logged_issues.should < 500
623
185
  end
624
186
 
625
- reset_options
626
- @options.paths.checks = fixtures_path + '/taint_check/'
627
-
628
- described_class.new do |f|
629
- f.restore @snapshot
630
-
631
- Arachni::Data.issues.on_new do
632
- logged_issues += 1
187
+ context 'when no scan has been running' do
188
+ it 'returns 0' do
189
+ statistics[:runtime].should == 0
633
190
  end
634
- f.run
635
-
636
- # logged_issues.should == 500
637
- Arachni::Data.issues.size.should == 500
638
-
639
- f.report.plugins[:wait][:results].should == { 'stuff' => true }
640
- end
641
- end
642
-
643
- it 'restores options' do
644
- options_hash = nil
645
-
646
- described_class.new do |f|
647
- f.options.url = @url + '/with_ajax'
648
- f.options.audit.elements :links, :forms, :cookies
649
- f.options.datastore.my_custom_option = 'my custom value'
650
- options_hash = f.options.update( f.options.to_rpc_data ).to_h.deep_clone
651
-
652
- f.checks.load :taint
653
-
654
- t = Thread.new { f.run }
655
-
656
- sleep 0.1 while f.browser_cluster.done?
657
- @snapshot = f.suspend
658
-
659
- t.join
660
- end
661
-
662
- described_class.restore( @snapshot ) do |f|
663
- f.options.to_h.should == options_hash.merge( checks: ['taint'] )
664
- f.browser_job_skip_states.should be_any
665
- end
666
- end
667
-
668
- it 'restores BrowserCluster skip states' do
669
- described_class.new do |f|
670
- f.options.url = @url + '/with_ajax'
671
- f.options.audit.elements :links, :forms, :cookies
672
-
673
- f.checks.load :taint
674
-
675
- t = Thread.new { f.run }
676
-
677
- sleep 0.1 while f.browser_cluster.done?
678
- @snapshot = f.suspend
679
-
680
- t.join
681
- end
682
-
683
- described_class.restore( @snapshot ) do |f|
684
- f.browser_job_skip_states.should be_any
685
- end
686
- end
687
-
688
- it 'restores loaded checks' do
689
- described_class.new do |f|
690
- f.options.url = @url
691
- f.checks.load :taint
692
-
693
- t = Thread.new { f.run }
694
- sleep 0.1 while f.status != :scanning
695
-
696
- @snapshot = f.suspend
697
-
698
- t.join
699
- end
700
-
701
- described_class.restore( @snapshot ) do |f|
702
- f.checks.loaded.should == ['taint']
703
- end
704
- end
705
-
706
- it 'restores loaded plugins' do
707
- described_class.new do |f|
708
- f.options.url = @url
709
- f.plugins.load :wait
710
-
711
- t = Thread.new { f.run }
712
- sleep 0.1 while f.status != :scanning
713
-
714
- @snapshot = f.suspend
715
- t.join
716
- end
717
-
718
- described_class.restore( @snapshot ) do |f|
719
- f.plugins.loaded.should == ['wait']
720
- end
721
- end
722
-
723
- it 'restores plugin states' do
724
- Arachni::Options.plugins['suspendable'] = {
725
- 'my_option' => 'my value'
726
- }
727
-
728
- described_class.new do |f|
729
- f.options.url = web_server_url_for :framework_multi
730
- f.options.audit.elements :links
731
-
732
- f.checks.load :taint
733
- f.plugins.load :suspendable
734
-
735
- t = Thread.new do
736
- f.run
737
- end
738
-
739
- sleep 0.1 while f.status != :scanning
740
-
741
- @snapshot = f.suspend
742
- t.join
743
-
744
- Arachni::State.plugins.runtime[:suspendable][:data].should == 1
745
- end
746
-
747
- described_class.restore( @snapshot ) do |f|
748
- t = Thread.new do
749
- f.run
750
- end
751
-
752
- sleep 0.1 while f.status != :scanning
753
-
754
- f.plugins.jobs[:suspendable][:instance].counter.should == 2
755
-
756
- f.abort
757
- t.join
758
- end
759
- end
760
- end
761
-
762
- describe '#pause' do
763
- it 'pauses the system' do
764
- described_class.new do |f|
765
- f.options.url = @url + '/elem_combo'
766
- f.options.audit.elements :links, :forms, :cookies
767
- f.checks.load :taint
768
-
769
- t = Thread.new do
770
- f.run
771
- end
772
-
773
- f.pause
774
-
775
- sleep 10
776
-
777
- f.running?.should be_true
778
- t.kill
779
- end
780
- end
781
-
782
- it 'returns an Integer request ID' do
783
- described_class.new do |f|
784
- f.options.url = @url + '/elem_combo'
785
- f.options.audit.elements :links, :forms, :cookies
786
- f.checks.load :taint
787
-
788
- t = Thread.new do
789
- f.run
790
- end
791
-
792
- f.pause.should be_kind_of Integer
793
-
794
- sleep 10
795
-
796
- f.running?.should be_true
797
- t.kill
798
- end
799
- end
800
-
801
- it 'sets #status to :paused' do
802
- described_class.new do |f|
803
- f.options.url = @url + '/elem_combo'
804
- f.options.audit.elements :links, :forms, :cookies
805
- f.checks.load :taint
806
-
807
- t = Thread.new do
808
- f.run
809
- end
810
- sleep 0.1 while f.status != :scanning
811
-
812
- f.pause
813
- f.status.should == :paused
814
-
815
- t.kill
816
- end
817
- end
818
- end
819
-
820
- describe '#resume' do
821
- it 'resumes the system' do
822
- described_class.new do |f|
823
- f.options.url = @url + '/elem_combo'
824
- f.options.audit.elements :links, :forms, :cookies
825
- f.checks.load :taint
826
-
827
- t = Thread.new do
828
- f.run
829
- end
830
-
831
- id = f.pause
832
-
833
- sleep 10
834
-
835
- f.running?.should be_true
836
- f.resume id
837
- t.join
838
- end
839
- end
840
-
841
- it 'sets #status to scanning' do
842
- described_class.new do |f|
843
- f.options.url = @url + '/elem_combo'
844
- f.options.audit.elements :links, :forms, :cookies
845
- f.checks.load :taint
846
-
847
- t = Thread.new do
848
- f.run
849
- end
850
-
851
- id = f.pause
852
- f.status.should == :paused
853
-
854
- f.resume id
855
- Timeout.timeout( 5 ) do
856
- sleep 0.1 while f.status != :scanning
857
- end
858
- t.join
859
- end
860
- end
861
- end
862
-
863
- describe '#clean_up' do
864
- it 'stops the #plugins' do
865
- described_class.new do |f|
866
- f.options.url = @url + '/elem_combo'
867
- f.plugins.load :wait
868
-
869
- f.plugins.run
870
- f.clean_up
871
- f.plugins.jobs.should be_empty
872
- end
873
- end
874
-
875
- it 'sets the status to cleanup' do
876
- described_class.new do |f|
877
- f.options.url = @url + '/elem_combo'
878
-
879
- f.clean_up
880
- f.status.should == :cleanup
881
- end
882
- end
883
-
884
- it 'clears the page queue' do
885
- described_class.new do |f|
886
- f.options.url = @url + '/elem_combo'
887
- f.push_to_page_queue Arachni::Page.from_url( f.options.url )
888
-
889
- f.data.page_queue.should_not be_empty
890
- f.clean_up
891
- f.data.page_queue.should be_empty
892
- end
893
- end
894
-
895
- it 'clears the URL queue' do
896
- described_class.new do |f|
897
- f.options.url = @url + '/elem_combo'
898
- f.push_to_url_queue f.options.url
899
-
900
- f.data.url_queue.should_not be_empty
901
- f.clean_up
902
- f.data.url_queue.should be_empty
903
- end
904
- end
905
-
906
- it 'sets #running? to false' do
907
- described_class.new do |f|
908
- f.options.url = @url + '/elem_combo'
909
- f.clean_up
910
- f.should_not be_running
911
- end
912
- end
913
- end
914
-
915
- describe '#report_as' do
916
- before( :each ) do
917
- reset_options
918
- @new_framework = Arachni::Framework.new
919
- end
920
-
921
- context 'when passed a valid reporter name' do
922
- it 'returns the reporter as a string' do
923
- json = @new_framework.report_as( :json )
924
- JSON.load( json )['issues'].size.should == @new_framework.report.issues.size
925
- end
926
-
927
- context 'which does not support the \'outfile\' option' do
928
- it 'raises Arachni::Component::Options::Error::Invalid' do
929
- expect { @new_framework.report_as( :stdout ) }.to raise_error Arachni::Component::Options::Error::Invalid
930
- end
931
- end
932
- end
933
-
934
- context 'when passed an invalid reporter name' do
935
- it 'raises Arachni::Component::Error::NotFound' do
936
- expect { @new_framework.report_as( :blah ) }.to raise_error Arachni::Component::Error::NotFound
937
- end
938
- end
939
- end
940
-
941
- describe '#audit_page' do
942
- it 'updates the #sitemap with the DOM URL' do
943
- subject.options.audit.elements :links, :forms, :cookies
944
- subject.checks.load :taint
945
-
946
- subject.sitemap.should be_empty
947
-
948
- page = Arachni::Page.from_url( @url + '/link' )
949
- page.dom.url = @url + '/link/#/stuff'
950
-
951
- subject.audit_page page
952
- subject.sitemap.should include @url + '/link/#/stuff'
953
- end
954
-
955
- it "runs #{Arachni::Check::Manager}#without_platforms before #{Arachni::Check::Manager}#with_platforms" do
956
- @options.paths.checks = fixtures_path + '/checks/'
957
-
958
- described_class.new do |f|
959
- f.checks.load_all
960
-
961
- page = Arachni::Page.from_url( @url + '/link' )
962
-
963
- responses = []
964
- f.http.on_complete do |response|
965
- responses << response.url
966
- end
967
-
968
- f.audit_page page
969
-
970
- responses.sort.should ==
971
- %w(http://localhost/test3 http://localhost/test
972
- http://localhost/test2).sort
973
- end
974
- end
975
-
976
- context 'when checks were' do
977
- context 'ran against the page' do
978
- it 'returns true' do
979
- subject.checks.load :taint
980
- subject.audit_page( Arachni::Page.from_url( @url + '/link' ) ).should be_true
981
- end
982
- end
983
-
984
- context 'not ran against the page' do
985
- it 'returns false' do
986
- subject.audit_page( Arachni::Page.from_url( @url + '/link' ) ).should be_false
987
- end
988
- end
989
- end
990
-
991
- context 'when the page contains JavaScript code' do
992
- it 'analyzes the DOM and pushes new pages to the page queue' do
993
- Arachni::Framework.new do |f|
994
- f.options.audit.elements :links, :forms, :cookies
995
- f.checks.load :taint
996
-
997
- f.page_queue_total_size.should == 0
998
-
999
- f.audit_page( Arachni::Page.from_url( @url + '/with_javascript' ) )
1000
-
1001
- sleep 0.1 while f.wait_for_browser?
1002
-
1003
- f.page_queue_total_size.should > 0
1004
- end
1005
- end
1006
-
1007
- it 'analyzes the DOM and pushes new paths to the url queue' do
1008
- Arachni::Framework.new do |f|
1009
- f.options.url = @url
1010
- f.options.audit.elements :links, :forms, :cookies
1011
- f.checks.load :taint
1012
-
1013
- f.url_queue_total_size.should == 0
1014
-
1015
- f.audit_page( Arachni::Page.from_url( @url + '/with_javascript' ) )
1016
-
1017
- sleep 0.1 while f.wait_for_browser?
1018
-
1019
- f.url_queue_total_size.should == 2
1020
- end
1021
- end
1022
-
1023
- context 'when the DOM depth limit has been reached' do
1024
- it 'does not analyze the DOM' do
1025
- Arachni::Framework.new do |f|
1026
- f.options.url = @url
1027
-
1028
- f.options.audit.elements :links, :forms, :cookies
1029
- f.checks.load :taint
1030
- f.options.scope.dom_depth_limit = 1
1031
- f.url_queue_total_size.should == 0
1032
- f.audit_page( Arachni::Page.from_url( @url + '/with_javascript' ) ).should be_true
1033
- sleep 0.1 while f.wait_for_browser?
1034
- f.url_queue_total_size.should == 2
1035
-
1036
- f.reset
1037
-
1038
- f.options.audit.elements :links, :forms, :cookies
1039
- f.checks.load :taint
1040
- f.options.scope.dom_depth_limit = 1
1041
- f.url_queue_total_size.should == 0
1042
-
1043
- page = Arachni::Page.from_url( @url + '/with_javascript' )
1044
- page.dom.push_transition Arachni::Page::DOM::Transition.new( :page, :load )
1045
-
1046
- f.audit_page( page ).should be_true
1047
- sleep 0.1 while f.wait_for_browser?
1048
- f.url_queue_total_size.should == 0
1049
- end
1050
- end
1051
-
1052
- it 'returns false' do
1053
- page = Arachni::Page.from_data(
1054
- url: @url,
1055
- dom: {
1056
- transitions: [
1057
- { page: :load },
1058
- { "<a href='javascript:click();'>" => :click },
1059
- { "<button dblclick='javascript:doubleClick();'>" => :ondblclick }
1060
- ].map { |t| Arachni::Page::DOM::Transition.new *t.first }
1061
- }
1062
- )
1063
-
1064
- Arachni::Framework.new do |f|
1065
- f.checks.load :taint
1066
-
1067
- f.options.scope.dom_depth_limit = 10
1068
- f.audit_page( page ).should be_true
1069
-
1070
- f.options.scope.dom_depth_limit = 2
1071
- f.audit_page( page ).should be_false
1072
- end
1073
- end
1074
- end
1075
- end
1076
-
1077
- context 'when the page matches exclusion criteria' do
1078
- it 'does not audit it' do
1079
- subject.options.scope.exclude_path_patterns << /link/
1080
- subject.options.audit.elements :links, :forms, :cookies
1081
-
1082
- subject.checks.load :taint
1083
-
1084
- subject.audit_page( Arachni::Page.from_url( @url + '/link' ) )
1085
- subject.report.issues.size.should == 0
1086
- end
1087
-
1088
- it 'returns false' do
1089
- subject.options.scope.exclude_path_patterns << /link/
1090
- subject.audit_page( Arachni::Page.from_url( @url + '/link' ) ).should be_false
1091
- end
1092
- end
1093
-
1094
- context "when #{Arachni::Check::Auditor}.has_timeout_candidates?" do
1095
- it "calls #{Arachni::Check::Auditor}.timeout_audit_run" do
1096
- Arachni::Check::Auditor.stub(:has_timeout_candidates?){ true }
1097
-
1098
- Arachni::Check::Auditor.should receive(:timeout_audit_run)
1099
- subject.audit_page( Arachni::Page.from_url( @url + '/link' ) )
1100
- end
1101
- end
1102
-
1103
- context 'when the page contains elements seen in previous pages' do
1104
- it 'removes them from the page'
1105
- end
1106
-
1107
- context 'when a check fails with an exception' do
1108
- it 'moves to the next one' do
1109
- @options.paths.checks = fixtures_path + '/checks/'
1110
-
1111
- described_class.new do |f|
1112
- f.checks.load_all
1113
-
1114
- f.checks[:test].any_instance.stub(:run) { raise }
1115
-
1116
- page = Arachni::Page.from_url( @url + '/link' )
1117
-
1118
- responses = []
1119
- f.http.on_complete do |response|
1120
- responses << response.url
1121
- end
1122
-
1123
- f.audit_page page
1124
-
1125
- responses.should == %w(http://localhost/test3 http://localhost/test2)
1126
- end
1127
- end
1128
- end
1129
- end
1130
-
1131
- describe '#page_limit_reached?' do
1132
- context "when the #{Arachni::OptionGroups::Scope}#page_limit has" do
1133
- context 'been reached' do
1134
- it 'returns true' do
1135
- Arachni::Framework.new do |f|
1136
- f.options.url = web_server_url_for :framework_multi
1137
- f.options.audit.elements :links
1138
- f.options.scope.page_limit = 10
1139
-
1140
- f.page_limit_reached?.should be_false
1141
- f.run
1142
- f.page_limit_reached?.should be_true
1143
-
1144
- f.sitemap.size.should == 10
1145
- end
1146
- end
1147
- end
1148
-
1149
- context 'not been reached' do
1150
- it 'returns false' do
1151
- Arachni::Framework.new do |f|
1152
- f.options.url = web_server_url_for :framework
1153
- f.options.audit.elements :links
1154
- f.options.scope.page_limit = 100
1155
-
1156
- f.checks.load :taint
1157
-
1158
- f.page_limit_reached?.should be_false
1159
- f.run
1160
- f.page_limit_reached?.should be_false
1161
- end
1162
- end
1163
- end
1164
-
1165
- context 'not been set' do
1166
- it 'returns false' do
1167
- Arachni::Framework.new do |f|
1168
- f.options.url = web_server_url_for :framework
1169
- f.options.audit.elements :links
1170
-
1171
- f.checks.load :taint
1172
-
1173
- f.page_limit_reached?.should be_false
1174
- f.run
1175
- f.page_limit_reached?.should be_false
1176
- end
1177
- end
1178
- end
1179
- end
1180
- end
1181
-
1182
- describe '#accepts_more_pages?' do
1183
- context 'when #page_limit_reached? and #crawl?' do
1184
- it 'return true' do
1185
- subject.stub(:page_limit_reached?) { false }
1186
- subject.stub(:crawl?) { true }
1187
-
1188
- subject.accepts_more_pages?.should be_true
1189
- end
1190
- end
1191
-
1192
- context 'when #page_limit_reached?' do
1193
- context true do
1194
- it 'returns false' do
1195
- subject.stub(:page_limit_reached?) { true }
1196
- subject.accepts_more_pages?.should be_false
1197
- end
1198
- end
1199
- end
1200
-
1201
- context 'when #crawl?' do
1202
- context false do
1203
- it 'returns false' do
1204
- subject.stub(:crawl?) { false }
1205
- subject.accepts_more_pages?.should be_false
1206
- end
1207
- end
1208
- end
1209
- end
1210
-
1211
- describe '#push_to_page_queue' do
1212
- let(:page) { Arachni::Page.from_url( @url + '/train/true' ) }
1213
-
1214
- it 'pushes it to the page audit queue and returns true' do
1215
- subject.options.audit.elements :links, :forms, :cookies
1216
- subject.checks.load :taint
1217
-
1218
- subject.page_queue_total_size.should == 0
1219
- subject.push_to_page_queue( page ).should be_true
1220
- subject.run
1221
-
1222
- subject.report.issues.size.should == 1
1223
- subject.page_queue_total_size.should > 0
1224
- end
1225
-
1226
- it 'updates the #sitemap with the DOM URL' do
1227
- subject.options.audit.elements :links, :forms, :cookies
1228
- subject.checks.load :taint
1229
-
1230
- subject.sitemap.should be_empty
1231
-
1232
- page = Arachni::Page.from_url( @url + '/link' )
1233
- page.dom.url = @url + '/link/#/stuff'
1234
-
1235
- subject.push_to_page_queue page
1236
- subject.sitemap.should include @url + '/link/#/stuff'
1237
- end
1238
-
1239
- it "passes it to #{Arachni::ElementFilter}#update_from_page_cache" do
1240
- page = Arachni::Page.from_url( @url + '/link' )
1241
-
1242
- Arachni::ElementFilter.should receive(:update_from_page_cache).with(page)
1243
-
1244
- subject.push_to_page_queue page
1245
- end
1246
-
1247
- context 'when the page has already been seen' do
1248
- it 'ignores it' do
1249
- page = Arachni::Page.from_url( @url + '/stuff' )
1250
-
1251
- subject.page_queue_total_size.should == 0
1252
- subject.push_to_page_queue( page )
1253
- subject.push_to_page_queue( page )
1254
- subject.push_to_page_queue( page )
1255
- subject.page_queue_total_size.should == 1
1256
- end
1257
-
1258
- it 'returns false' do
1259
- page = Arachni::Page.from_url( @url + '/stuff' )
1260
-
1261
- subject.page_queue_total_size.should == 0
1262
- subject.push_to_page_queue( page ).should be_true
1263
- subject.push_to_page_queue( page ).should be_false
1264
- subject.push_to_page_queue( page ).should be_false
1265
- subject.page_queue_total_size.should == 1
1266
- end
1267
- end
1268
-
1269
- context 'when #accepts_more_pages?' do
1270
- context false do
1271
- it 'returns false' do
1272
- subject.stub(:accepts_more_pages?) { false }
1273
- subject.push_to_page_queue( page ).should be_false
1274
- end
1275
- end
1276
-
1277
- context true do
1278
- it 'returns true' do
1279
- subject.stub(:accepts_more_pages?) { true }
1280
- subject.push_to_page_queue( page ).should be_true
1281
- end
1282
- end
1283
- end
1284
-
1285
- context "when #{Arachni::Page::Scope}#out? is true" do
1286
- it 'returns false' do
1287
- Arachni::Page::Scope.any_instance.stub(:out?) { true }
1288
- subject.push_to_page_queue( page ).should be_false
1289
- end
1290
- end
1291
-
1292
- context "when #{Arachni::URI::Scope}#redundant? is true" do
1293
- it 'returns false' do
1294
- Arachni::Page::Scope.any_instance.stub(:redundant?) { true }
1295
- subject.push_to_page_queue( page ).should be_false
1296
- end
1297
- end
1298
-
1299
- context "when #{Arachni::Page::Scope}#auto_redundant? is true" do
1300
- it 'returns false' do
1301
- Arachni::Page::Scope.any_instance.stub(:auto_redundant?) { true }
1302
- subject.push_to_page_queue( page ).should be_false
1303
- end
1304
- end
1305
- end
1306
-
1307
- describe '#push_to_url_queue' do
1308
- it 'pushes a URL to the URL audit queue' do
1309
- subject.options.audit.elements :links, :forms, :cookies
1310
- subject.checks.load :taint
1311
-
1312
- subject.url_queue_total_size.should == 0
1313
- subject.push_to_url_queue( @url + '/link' ).should be_true
1314
- subject.run
1315
-
1316
- subject.report.issues.size.should == 1
1317
- subject.url_queue_total_size.should == 3
1318
- end
1319
-
1320
- context 'when the URL has already been seen' do
1321
- it 'returns false' do
1322
- subject.push_to_url_queue( @url + '/link' ).should be_true
1323
- subject.push_to_url_queue( @url + '/link' ).should be_false
1324
- end
1325
-
1326
- it 'ignores it' do
1327
- subject.url_queue_total_size.should == 0
1328
- subject.push_to_url_queue( @url + '/link' )
1329
- subject.push_to_url_queue( @url + '/link' )
1330
- subject.push_to_url_queue( @url + '/link' )
1331
- subject.url_queue_total_size.should == 1
1332
- end
1333
- end
1334
-
1335
- context 'when #accepts_more_pages?' do
1336
- context false do
1337
- it 'returns false' do
1338
- subject.stub(:accepts_more_pages?) { false }
1339
- subject.push_to_url_queue( @url ).should be_false
1340
- end
1341
- end
1342
-
1343
- context true do
1344
- it 'returns true' do
1345
- subject.stub(:accepts_more_pages?) { true }
1346
- subject.push_to_url_queue( @url ).should be_true
1347
- end
1348
- end
1349
- end
1350
-
1351
- context "when #{Arachni::URI::Scope}#out? is true" do
1352
- it 'returns false' do
1353
- Arachni::URI::Scope.any_instance.stub(:out?) { true }
1354
- subject.push_to_url_queue( @url ).should be_false
1355
- end
1356
- end
1357
-
1358
- context "when #{Arachni::URI::Scope}#redundant? is true" do
1359
- it 'returns false' do
1360
- Arachni::URI::Scope.any_instance.stub(:redundant?) { true }
1361
- subject.push_to_url_queue( @url ).should be_false
1362
- end
1363
- end
1364
-
1365
- context "when #{Arachni::URI::Scope}#auto_redundant? is true" do
1366
- it 'returns false' do
1367
- Arachni::URI::Scope.any_instance.stub(:auto_redundant?) { true }
1368
- subject.push_to_url_queue( @url ).should be_false
1369
- end
1370
- end
1371
- end
1372
-
1373
- describe '#statistics' do
1374
- let(:statistics) { subject.statistics }
1375
-
1376
- it 'includes http statistics' do
1377
- statistics[:http].should == subject.http.statistics
1378
- end
1379
-
1380
- [:found_pages, :audited_pages, :current_page].each do |k|
1381
- it "includes #{k}" do
1382
- statistics.should include k
1383
- end
1384
- end
1385
-
1386
- describe :runtime do
1387
- context 'when the scan has been running' do
1388
- it 'returns the runtime in seconds' do
1389
- subject.run
1390
- statistics[:runtime].should > 0
1391
- end
1392
- end
1393
-
1394
- context 'when no scan has been running' do
1395
- it 'returns 0' do
1396
- statistics[:runtime].should == 0
1397
- end
1398
- end
1399
- end
1400
- end
1401
-
1402
- describe '#list_platforms' do
1403
- it 'returns information about all valid platforms' do
1404
- subject.list_platforms.should == {
1405
- 'Operating systems' => {
1406
- unix: 'Generic Unix family',
1407
- linux: 'Linux',
1408
- bsd: 'Generic BSD family',
1409
- aix: 'IBM AIX',
1410
- solaris: 'Solaris',
1411
- windows: 'MS Windows'
1412
- },
1413
- 'Databases' => {
1414
- access: 'MS Access',
1415
- coldfusion: 'ColdFusion',
1416
- db2: 'DB2',
1417
- emc: 'EMC',
1418
- firebird: 'Firebird',
1419
- frontbase: 'Frontbase',
1420
- hsqldb: 'HSQLDB',
1421
- informix: 'Informix',
1422
- ingres: 'IngresDB',
1423
- interbase: 'InterBase',
1424
- maxdb: 'SaP Max DB',
1425
- mssql: 'MSSQL',
1426
- mysql: 'MySQL',
1427
- oracle: 'Oracle',
1428
- pgsql: 'Postgresql',
1429
- sqlite: 'SQLite',
1430
- sybase: 'Sybase',
1431
- mongodb: 'MongoDB'
1432
- },
1433
- 'Web servers' => {
1434
- apache: 'Apache',
1435
- iis: 'IIS',
1436
- jetty: 'Jetty',
1437
- nginx: 'Nginx',
1438
- tomcat: 'TomCat'
1439
- },
1440
- 'Programming languages' => {
1441
- asp: 'ASP',
1442
- aspx: 'ASP.NET',
1443
- jsp: 'JSP',
1444
- perl: 'Perl',
1445
- php: 'PHP',
1446
- python: 'Python',
1447
- ruby: 'Ruby'
1448
- },
1449
- 'Frameworks' => {
1450
- rack: 'Rack'
1451
- }
1452
- }
1453
-
1454
- end
1455
- end
1456
-
1457
- describe '#list_checks' do
1458
- context 'when a pattern is given' do
1459
- it 'uses it to filter out checks that do not match it' do
1460
- subject.list_checks( 'boo' ).size == 0
1461
-
1462
- subject.list_checks( 'taint' ).should == subject.list_checks
1463
- subject.list_checks.size == 1
1464
- end
1465
- end
1466
- end
1467
-
1468
- describe '#list_plugins' do
1469
- it 'returns info on all plugins' do
1470
- subject.list_plugins.size.should == subject.plugins.available.size
1471
-
1472
- info = subject.list_plugins.find { |p| p[:options].any? }
1473
- plugin = subject.plugins[info[:shortname]]
1474
-
1475
- plugin.info.each do |k, v|
1476
- if k == :author
1477
- info[k].should == [v].flatten
1478
- next
1479
- end
1480
-
1481
- info[k].should == v
1482
- end
1483
-
1484
- info[:shortname].should == plugin.shortname
1485
- end
1486
-
1487
- context 'when a pattern is given' do
1488
- it 'uses it to filter out plugins that do not match it' do
1489
- subject.list_plugins( 'bad|foo' ).size == 2
1490
- subject.list_plugins( 'boo' ).size == 0
1491
- end
1492
- end
1493
- end
1494
-
1495
- describe '#list_reporters' do
1496
- it 'returns info on all reporters' do
1497
- subject.list_reporters.size.should == subject.reporters.available.size
1498
-
1499
- info = subject.list_reporters.find { |p| p[:options].any? }
1500
- report = subject.reporters[info[:shortname]]
1501
-
1502
- report.info.each do |k, v|
1503
- if k == :author
1504
- info[k].should == [v].flatten
1505
- next
1506
- end
1507
-
1508
- info[k].should == v
1509
- end
1510
-
1511
- info[:shortname].should == report.shortname
1512
- end
1513
-
1514
- context 'when a pattern is given' do
1515
- it 'uses it to filter out reporters that do not match it' do
1516
- subject.list_reporters( 'foo' ).size == 1
1517
- subject.list_reporters( 'boo' ).size == 0
1518
191
  end
1519
192
  end
1520
193
  end