cms_scanner 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +14 -0
  6. data/Gemfile +6 -0
  7. data/README.md +20 -0
  8. data/Rakefile +9 -0
  9. data/app/app.rb +4 -0
  10. data/app/controllers.rb +2 -0
  11. data/app/controllers/core.rb +46 -0
  12. data/app/controllers/core/cli_options.rb +68 -0
  13. data/app/controllers/interesting_files.rb +12 -0
  14. data/app/finders.rb +1 -0
  15. data/app/finders/interesting_files.rb +21 -0
  16. data/app/finders/interesting_files/fantastico_fileslist.rb +23 -0
  17. data/app/finders/interesting_files/headers.rb +15 -0
  18. data/app/finders/interesting_files/robots_txt.rb +22 -0
  19. data/app/finders/interesting_files/search_replace_db_2.rb +28 -0
  20. data/app/finders/interesting_files/xml_rpc.rb +62 -0
  21. data/app/formatters.rb +3 -0
  22. data/app/formatters/cli.rb +18 -0
  23. data/app/formatters/cli_no_colour.rb +15 -0
  24. data/app/formatters/json.rb +12 -0
  25. data/app/models.rb +5 -0
  26. data/app/models/fantastico_fileslist.rb +20 -0
  27. data/app/models/headers.rb +37 -0
  28. data/app/models/interesting_file.rb +30 -0
  29. data/app/models/robots_txt.rb +20 -0
  30. data/app/models/xml_rpc.rb +35 -0
  31. data/app/views/cli/core/finished.erb +4 -0
  32. data/app/views/cli/core/started.erb +3 -0
  33. data/app/views/cli/interesting_files/findings.erb +19 -0
  34. data/app/views/cli/scan_aborted.erb +4 -0
  35. data/app/views/json/core/finished.erb +3 -0
  36. data/app/views/json/core/started.erb +3 -0
  37. data/app/views/json/interesting_files/findings.erb +1 -0
  38. data/app/views/json/scan_aborted.erb +4 -0
  39. data/cms_scanner.gemspec +37 -0
  40. data/examples/views/cli/wp_custom/test.erb +1 -0
  41. data/examples/views/json/wp_custom/test.erb +1 -0
  42. data/examples/wpscan.rb +29 -0
  43. data/lib/cms_scanner.rb +71 -0
  44. data/lib/cms_scanner/browser.rb +68 -0
  45. data/lib/cms_scanner/browser/actions.rb +48 -0
  46. data/lib/cms_scanner/browser/options.rb +53 -0
  47. data/lib/cms_scanner/cache/file_store.rb +75 -0
  48. data/lib/cms_scanner/cache/typhoeus.rb +21 -0
  49. data/lib/cms_scanner/controller.rb +90 -0
  50. data/lib/cms_scanner/controllers.rb +34 -0
  51. data/lib/cms_scanner/errors/auth_errors.rb +15 -0
  52. data/lib/cms_scanner/finders.rb +5 -0
  53. data/lib/cms_scanner/finders/finder.rb +27 -0
  54. data/lib/cms_scanner/finders/finding.rb +32 -0
  55. data/lib/cms_scanner/finders/findings.rb +25 -0
  56. data/lib/cms_scanner/finders/independent_finder.rb +30 -0
  57. data/lib/cms_scanner/finders/independent_finders.rb +41 -0
  58. data/lib/cms_scanner/formatter.rb +118 -0
  59. data/lib/cms_scanner/formatter/buffer.rb +15 -0
  60. data/lib/cms_scanner/target.rb +33 -0
  61. data/lib/cms_scanner/target/platform.rb +2 -0
  62. data/lib/cms_scanner/target/platform/php.rb +39 -0
  63. data/lib/cms_scanner/target/platform/wordpress.rb +35 -0
  64. data/lib/cms_scanner/target/platform/wordpress/custom_directories.rb +62 -0
  65. data/lib/cms_scanner/target/server.rb +3 -0
  66. data/lib/cms_scanner/target/server/apache.rb +43 -0
  67. data/lib/cms_scanner/target/server/generic.rb +34 -0
  68. data/lib/cms_scanner/target/server/iis.rb +48 -0
  69. data/lib/cms_scanner/version.rb +4 -0
  70. data/lib/cms_scanner/web_site.rb +68 -0
  71. data/lib/helper.rb +24 -0
  72. data/spec/app/controllers/core_spec.rb +152 -0
  73. data/spec/app/controllers/interesting_files_spec.rb +50 -0
  74. data/spec/app/finders/interesting_files/fantastico_fileslist_spec.rb +68 -0
  75. data/spec/app/finders/interesting_files/headers_spec.rb +38 -0
  76. data/spec/app/finders/interesting_files/robots_txt_spec.rb +56 -0
  77. data/spec/app/finders/interesting_files/search_replace_db_2_spec.rb +55 -0
  78. data/spec/app/finders/interesting_files/xml_rpc_spec.rb +138 -0
  79. data/spec/app/finders/interesting_files_spec.rb +13 -0
  80. data/spec/app/formatters/cli_no_colour_spec.rb +17 -0
  81. data/spec/app/formatters/cli_spec.rb +21 -0
  82. data/spec/app/formatters/json_spec.rb +33 -0
  83. data/spec/app/models/fantastico_fileslist_spec.rb +32 -0
  84. data/spec/app/models/headers_spec.rb +52 -0
  85. data/spec/app/models/interesting_file_spec.rb +51 -0
  86. data/spec/app/models/robots_txt_spec.rb +28 -0
  87. data/spec/app/models/xml_rpc_spec.rb +47 -0
  88. data/spec/cache/.gitignore +4 -0
  89. data/spec/dummy_finders.rb +41 -0
  90. data/spec/fixtures/interesting_files/fantastico_fileslist/fantastico_fileslist.txt +12 -0
  91. data/spec/fixtures/interesting_files/file.txt +4 -0
  92. data/spec/fixtures/interesting_files/headers/interesting.txt +14 -0
  93. data/spec/fixtures/interesting_files/headers/no_interesting.txt +12 -0
  94. data/spec/fixtures/interesting_files/robots_txt/robots.txt +10 -0
  95. data/spec/fixtures/interesting_files/search_replace_db_2/searchreplacedb2.php +188 -0
  96. data/spec/fixtures/interesting_files/xml_rpc/homepage_in_scope_pingback.html +7 -0
  97. data/spec/fixtures/interesting_files/xml_rpc/homepage_out_of_scope_pingback.html +7 -0
  98. data/spec/fixtures/interesting_files/xml_rpc/xmlrpc.php +1 -0
  99. data/spec/fixtures/output.txt +0 -0
  100. data/spec/fixtures/target/platform/php/debug_log/debug.log +2 -0
  101. data/spec/fixtures/target/platform/php/fpd/wp_rss_functions.php +2 -0
  102. data/spec/fixtures/target/platform/wordpress/custom_directories/custom_w_spaces.html +10 -0
  103. data/spec/fixtures/target/platform/wordpress/custom_directories/default.html +14 -0
  104. data/spec/fixtures/target/platform/wordpress/custom_directories/https.html +12 -0
  105. data/spec/fixtures/target/platform/wordpress/detection/default.html +4 -0
  106. data/spec/fixtures/target/platform/wordpress/detection/not_wp.html +8 -0
  107. data/spec/fixtures/target/platform/wordpress/detection/wp_includes.html +3 -0
  108. data/spec/fixtures/target/server/apache/directory_listing/2.2.16.html +15 -0
  109. data/spec/fixtures/target/server/generic/server/apache/basic.txt +5 -0
  110. data/spec/fixtures/target/server/generic/server/iis/basic.txt +6 -0
  111. data/spec/fixtures/target/server/generic/server/not_detected.txt +3 -0
  112. data/spec/fixtures/target/server/iis/directory_listing/no_parent.html +3 -0
  113. data/spec/fixtures/target/server/iis/directory_listing/with_parent.html +3 -0
  114. data/spec/fixtures/views/base/ctrl/local.erb +1 -0
  115. data/spec/fixtures/views/base/ctrl/test.erb +3 -0
  116. data/spec/fixtures/views/base/global.erb +1 -0
  117. data/spec/fixtures/views/base/test.erb +2 -0
  118. data/spec/fixtures/views/based_format/test.erb +1 -0
  119. data/spec/fixtures/views/json/render_me.erb +4 -0
  120. data/spec/lib/browser_spec.rb +141 -0
  121. data/spec/lib/cache/file_store_spec.rb +101 -0
  122. data/spec/lib/cache/typhoeus_spec.rb +30 -0
  123. data/spec/lib/cms_scanner_spec.rb +45 -0
  124. data/spec/lib/controller_spec.rb +23 -0
  125. data/spec/lib/controllers_spec.rb +52 -0
  126. data/spec/lib/finders/findings_spec.rb +49 -0
  127. data/spec/lib/finders/independent_finders_spec.rb +98 -0
  128. data/spec/lib/formatter_spec.rb +136 -0
  129. data/spec/lib/sub_scanner_spec.rb +27 -0
  130. data/spec/lib/target/platforms_spec.rb +13 -0
  131. data/spec/lib/target/servers_spec.rb +13 -0
  132. data/spec/lib/target_spec.rb +50 -0
  133. data/spec/lib/web_site_spec.rb +124 -0
  134. data/spec/shared_examples.rb +11 -0
  135. data/spec/shared_examples/browser_actions.rb +32 -0
  136. data/spec/shared_examples/finding.rb +20 -0
  137. data/spec/shared_examples/formatter_buffer.rb +8 -0
  138. data/spec/shared_examples/formatter_class_methods.rb +26 -0
  139. data/spec/shared_examples/independent_finder.rb +33 -0
  140. data/spec/shared_examples/target/platform/php.rb +58 -0
  141. data/spec/shared_examples/target/platform/wordpress.rb +41 -0
  142. data/spec/shared_examples/target/platform/wordpress/custom_directories.rb +50 -0
  143. data/spec/shared_examples/target/server/apache.rb +33 -0
  144. data/spec/shared_examples/target/server/generic.rb +34 -0
  145. data/spec/shared_examples/target/server/iis.rb +38 -0
  146. data/spec/spec_helper.rb +41 -0
  147. metadata +432 -0
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'dummy_finders'
3
+
4
+ describe CMSScanner::Finders::Findings do
5
+
6
+ subject(:findings) { described_class.new }
7
+ let(:dummy) { CMSScanner::DummyFinding }
8
+
9
+ describe '#<<' do
10
+ after { expect(findings).to eq @expected }
11
+
12
+ context 'when empty array' do
13
+ it 'adds it' do
14
+ findings << 'empty-test'
15
+ @expected = %w(empty-test)
16
+ end
17
+ end
18
+
19
+ context 'when not empty' do
20
+ let(:confirmed) { dummy.new('confirmed') }
21
+
22
+ before { findings << dummy.new('test') << confirmed }
23
+
24
+ it 'adds a confirmed result correctly' do
25
+ confirmed_dup = confirmed.dup
26
+ confirmed_dup.confidence = 100
27
+
28
+ findings << dummy.new('test2')
29
+ findings << confirmed_dup
30
+
31
+ confirmed.confirmed_by = confirmed_dup
32
+
33
+ @expected = [] << dummy.new('test') << confirmed << dummy.new('test2')
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '#+' do
39
+ after { expect(findings).to eq @expected }
40
+
41
+ it 'adds it/them' do
42
+ # Dummy assignement to avoid the 'Operator used in void context'
43
+ _ = findings + %w(test1 test2)
44
+
45
+ @expected = %w(test1 test2)
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+ require 'dummy_finders'
3
+
4
+ describe CMSScanner::Finders::IndependentFinders do
5
+
6
+ subject(:finders) { described_class.new }
7
+
8
+ describe '#run' do
9
+ let(:target) { 'target' }
10
+ let(:finding) { CMSScanner::DummyFinding }
11
+ let(:expected_aggressive) { finding.new('test', found_by: 'override', confidence: 100) }
12
+ let(:expected_passive) do
13
+ [
14
+ finding.new('test', found_by: 'DummyFinder (passive detection)'),
15
+ finding.new('spotted', found_by: 'NoAggressiveResult (passive detection)', confidence: 10)
16
+ ]
17
+ end
18
+
19
+ before do
20
+ finders << CMSScanner::Finders::DummyFinder.new(target) <<
21
+ CMSScanner::Finders::NoAggressiveResult.new(target)
22
+
23
+ @found = finders.run(mode: mode)
24
+
25
+ expect(@found).to be_a(CMSScanner::Finders::Findings)
26
+
27
+ @found.each { |f| expect(f).to be_a finding }
28
+ end
29
+
30
+ context 'when :passive mode' do
31
+ let(:mode) { :passive }
32
+
33
+ it 'returns 2 results' do
34
+ expect(@found.size).to eq 2
35
+ expect(@found.first).to eql expected_passive.first
36
+ expect(@found.last).to eql expected_passive.last
37
+ end
38
+ end
39
+
40
+ context 'when :aggressive mode' do
41
+ let(:mode) { :aggressive }
42
+
43
+ it 'returns 1 result' do
44
+ expect(@found.size).to eq 1
45
+ expect(@found.first).to eql expected_aggressive
46
+ end
47
+ end
48
+
49
+ context 'when :mixed mode' do
50
+ let(:mode) { :mixed }
51
+
52
+ it 'returns 2 results' do
53
+ expect(@found.size).to eq 2
54
+ expect(@found.first).to eql expected_passive.first
55
+ expect(@found.first.confirmed_by).to eql [expected_aggressive]
56
+ expect(@found.last).to eql expected_passive.last
57
+ end
58
+ end
59
+
60
+ context 'when multiple results returned' do
61
+
62
+ end
63
+ end
64
+
65
+ describe '#symbols_from_mode' do
66
+ after { expect(finders.symbols_from_mode(@mode)).to eq @expected }
67
+
68
+ context 'when :mixed' do
69
+ it 'returns [:passive, :aggressive]' do
70
+ @mode = :mixed
71
+ @expected = [:passive, :aggressive]
72
+ end
73
+ end
74
+
75
+ context 'when :passive or :aggresssive' do
76
+ [:passive, :aggressive].each do |symbol|
77
+ it 'returns it in an array' do
78
+ @mode = symbol
79
+ @expected = [*symbol]
80
+ end
81
+ end
82
+ end
83
+
84
+ context 'otherwise' do
85
+ it 'returns []' do
86
+ @mode = :unallowed
87
+ @expected = []
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '#findings' do
93
+ it 'returns a Findings object' do
94
+ expect(finders.findings).to be_a CMSScanner::Finders::Findings
95
+ end
96
+ end
97
+
98
+ end
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+
3
+ # Test Module to check the correct inclusion of
4
+ # the class methods
5
+ module OtherFormatter
6
+ include CMSScanner::Formatter
7
+ end
8
+
9
+ [CMSScanner::Formatter, OtherFormatter].each do |f|
10
+ describe "#{f}" do
11
+ subject(:formatter) { f }
12
+ it_behaves_like CMSScanner::Formatter::ClassMethods
13
+ end
14
+ end
15
+
16
+ module CMSScanner
17
+ module Formatter
18
+ module Spec
19
+ # Base Format Test Class
20
+ class BasedFormat < Base
21
+ def base_format
22
+ 'base'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ describe CMSScanner::Formatter::Base do
30
+
31
+ subject(:formatter) { described_class.new }
32
+
33
+ describe '#format' do
34
+ its(:format) { should eq 'base' }
35
+ end
36
+
37
+ describe '#render, output' do
38
+ before { formatter.views_directories << FIXTURES_VIEWS }
39
+
40
+ it 'renders the global template and does not override the @views_directories' do
41
+ expect($stdout).to receive(:puts)
42
+ .with("It Works!\nViews Dirs: #{formatter.views_directories}")
43
+
44
+ formatter.output('@test', test: 'Works!', views_directories: 'owned')
45
+ end
46
+
47
+ context 'when global and local rendering are used inside a template' do
48
+ it 'renders them correcly' do
49
+ rendered = formatter.render('test', { var: 'Works' }, 'ctrl')
50
+
51
+ expect(rendered).to eq "Test: Works\nLocal View\nGlobal View"
52
+ end
53
+ end
54
+
55
+ it 'raises an error if the controller_name is nil and tpl is not a global one' do
56
+ expect { formatter.output('test') }.to raise_error('The controller_name can not be nil')
57
+ end
58
+ end
59
+
60
+ describe '#view_path' do
61
+ before do
62
+ formatter.views_directories << FIXTURES_VIEWS
63
+ formatter.render('local', {}, 'ctrl') # Used to set the @controller_name
64
+ end
65
+
66
+ context 'when the tpl format is invalid' do
67
+ let(:tpl) { '../try-this' }
68
+
69
+ it 'raises an error' do
70
+ expect { formatter.view_path(tpl) }.to raise_error("Wrong tpl format: 'ctrl/#{tpl}'")
71
+ end
72
+ end
73
+
74
+ context 'when the tpl is not found' do
75
+ let(:tpl) { 'not_there' }
76
+
77
+ it 'raises an error' do
78
+ expect { formatter.view_path(tpl) }.to raise_error("View not found for base/ctrl/#{tpl}")
79
+ end
80
+ end
81
+
82
+ context 'when the tpl is found' do
83
+ after { expect(formatter.view_path(@tpl)).to eq @expected }
84
+
85
+ context 'if it\'s a global tpl' do
86
+ it 'returns its path' do
87
+ @expected = File.join(FIXTURES_VIEWS, 'base', 'test.erb')
88
+ @tpl = '@test'
89
+ end
90
+ end
91
+
92
+ context 'if it\s a local tpl' do
93
+ it 'retuns its path' do
94
+ @expected = File.join(FIXTURES_VIEWS, 'base', 'ctrl', 'local.erb')
95
+ @tpl = 'local'
96
+ end
97
+ end
98
+ end
99
+
100
+ context 'when base_format' do
101
+ subject(:formatter) { CMSScanner::Formatter::Spec::BasedFormat.new }
102
+
103
+ after { expect(formatter.view_path(@tpl)).to eq @expected }
104
+
105
+ context 'when the ovverided view exists' do
106
+ it 'returns it' do
107
+ @expected = File.join(FIXTURES_VIEWS, 'based_format', 'test.erb')
108
+ @tpl = '@test'
109
+ end
110
+ end
111
+
112
+ it 'returns the base views otherwise' do
113
+ @expected = File.join(FIXTURES_VIEWS, 'base', 'ctrl', 'local.erb')
114
+ @tpl = 'local'
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ describe '#views_directories' do
121
+ let(:default_directories) { [APP_VIEWS] }
122
+
123
+ context 'when default directories' do
124
+ its(:views_directories) { should eq(default_directories) }
125
+ end
126
+
127
+ context 'when adding directories' do
128
+ it 'adds them' do
129
+ formatter.views_directories << 'testing'
130
+
131
+ expect(formatter.views_directories).to eq(default_directories << 'testing')
132
+ end
133
+ end
134
+ end
135
+
136
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ # Module included the CMSScanner to test its correct inclusion
4
+ module SubScanner
5
+ include CMSScanner
6
+
7
+ # This Target class should be called in the CMSScanner::Controller::Base
8
+ # instead of the CMSScanner::Target
9
+ class Target < CMSScanner::Target
10
+ def new_method
11
+ 'working'
12
+ end
13
+ end
14
+ end
15
+
16
+ describe SubScanner::Scan do
17
+ subject(:scanner) { described_class.new }
18
+ let(:controller) { SubScanner::Controller }
19
+
20
+ it 'loads the overrided Target class' do
21
+ target = scanner.controllers.first.target
22
+
23
+ expect(target).to be_a SubScanner::Target
24
+ expect(target.respond_to?(:new_method)).to eq true
25
+ expect(target.new_method).to eq 'working'
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ [:WordPress, :PHP].each do |platform|
4
+ describe CMSScanner::Target do
5
+ subject(:target) do
6
+ described_class.new(url).extend(described_class::Platform.const_get(platform))
7
+ end
8
+ let(:url) { 'http://ex.lo' }
9
+ let(:fixtures) { File.join(FIXTURES, 'target', 'platform', platform.to_s.downcase) }
10
+
11
+ it_behaves_like described_class::Platform.const_get(platform)
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ [:Generic, :Apache, :IIS].each do |server|
4
+ describe CMSScanner::Target do
5
+ subject(:target) do
6
+ described_class.new(url).extend(described_class::Server.const_get(server))
7
+ end
8
+ let(:url) { 'http://ex.lo' }
9
+ let(:fixtures) { File.join(FIXTURES, 'target', 'server', server.to_s.downcase) }
10
+
11
+ it_behaves_like described_class::Server.const_get(server)
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe CMSScanner::Target do
4
+
5
+ subject(:target) { described_class.new(url) }
6
+ let(:url) { 'http://ex.lo' }
7
+
8
+ describe '#in_scope?' do
9
+ after { expect(target.in_scope?(@url)).to eq @expected }
10
+
11
+ [nil, '', 'http://out-of-scope.com', '//jquery.com/j.js'].each do |url|
12
+ it "returns false for #{url}" do
13
+ @url = url
14
+ @expected = false
15
+ end
16
+ end
17
+
18
+ %w(https://ex.lo/file.txt http://ex.lo/ /relative).each do |url|
19
+ it "returns true for #{url}" do
20
+ @url = url
21
+ @expected = true
22
+ end
23
+ end
24
+ end
25
+
26
+ describe '#interesting_files' do
27
+ before do
28
+ expect(CMSScanner::Finders::InterestingFiles).to receive(:find).and_return(stubbed)
29
+ end
30
+
31
+ context 'when no findings' do
32
+ let(:stubbed) { [] }
33
+
34
+ its(:interesting_files) { should eq stubbed }
35
+ end
36
+
37
+ context 'when findings' do
38
+ let(:stubbed) { ['yolo'] }
39
+
40
+ it 'allows findings to be added with <<' do
41
+ expect(target.interesting_files).to eq stubbed
42
+
43
+ target.interesting_files << 'other-finding'
44
+
45
+ expect(target.interesting_files).to eq(stubbed << 'other-finding')
46
+ end
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+
3
+ describe CMSScanner::WebSite do
4
+
5
+ subject(:web_site) { described_class.new(url) }
6
+ let(:url) { 'http://ex.lo' }
7
+
8
+ describe '#url=' do
9
+ context 'when the url is incorrect' do
10
+
11
+ after do
12
+ expect { web_site.url = @url }.to raise_error Addressable::URI::InvalidURIError
13
+ end
14
+
15
+ it 'raises an error if empty' do
16
+ @url = ''
17
+ end
18
+
19
+ it 'raises an error if wrong format' do
20
+ @url = 'jj'
21
+ end
22
+ end
23
+
24
+ context 'when valid' do
25
+ it 'creates an Addressable object and adds a traling slash' do
26
+ web_site.url = 'http://site.com'
27
+
28
+ expect(web_site.url).to eq('http://site.com/')
29
+ expect(web_site.uri).to be_a Addressable::URI
30
+ end
31
+ end
32
+ end
33
+
34
+ describe '#url' do
35
+ context 'when no path argument' do
36
+ its(:url) { should eql 'http://ex.lo/' }
37
+ end
38
+
39
+ context 'when a path argument' do
40
+ it 'appends the path' do
41
+ expect(web_site.url('file.txt')).to eql "#{url}/file.txt"
42
+ end
43
+
44
+ context 'when relative path' do
45
+ let(:url) { 'http://ex.lo/dir/' }
46
+
47
+ it 'appends it from the host/domain' do
48
+ expect(web_site.url('/sub/file.txt')).to eql 'http://ex.lo/sub/file.txt'
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '#online?' do
55
+ context 'when online' do
56
+ before { stub_request(:get, url).to_return(status: 200) }
57
+
58
+ it { should be_online }
59
+ end
60
+
61
+ context 'when offline' do
62
+ before { stub_request(:get, url).to_return(status: 0) }
63
+
64
+ it { should_not be_online }
65
+ end
66
+ end
67
+
68
+ describe '#http_auth?' do
69
+ context 'when http auth' do
70
+ before { stub_request(:get, url).to_return(status: 401) }
71
+
72
+ it { should be_http_auth }
73
+ end
74
+
75
+ context 'when no http auth' do
76
+ before { stub_request(:get, url).to_return(status: 200) }
77
+
78
+ it { should_not be_http_auth }
79
+ end
80
+ end
81
+
82
+ describe '#proxy_auth?' do
83
+ # Handled in app/controllers/core_spec
84
+ end
85
+
86
+ describe '#redirection' do
87
+ it 'returns nil if no redirection detected' do
88
+ stub_request(:get, web_site.url).to_return(status: 200, body: '')
89
+
90
+ expect(web_site.redirection).to be_nil
91
+ end
92
+
93
+ [301, 302].each do |status_code|
94
+ it "returns http://new-location.com if the status code is #{status_code}" do
95
+ new_location = 'http://new-location.com'
96
+
97
+ stub_request(:get, web_site.url)
98
+ .to_return(status: status_code, headers: { location: new_location })
99
+
100
+ stub_request(:get, new_location).to_return(status: 200)
101
+
102
+ expect(web_site.redirection).to eq new_location
103
+ end
104
+ end
105
+
106
+ context 'when multiple redirections' do
107
+ it 'returns the last redirection' do
108
+ first_redirection = 'www.redirection.com'
109
+ last_redirection = 'redirection.com'
110
+
111
+ stub_request(:get, web_site.url)
112
+ .to_return(status: 301, headers: { location: first_redirection })
113
+
114
+ stub_request(:get, first_redirection)
115
+ .to_return(status: 302, headers: { location: last_redirection })
116
+
117
+ stub_request(:get, last_redirection).to_return(status: 200)
118
+
119
+ expect(web_site.redirection).to eq last_redirection
120
+ end
121
+ end
122
+ end
123
+
124
+ end