cms_scanner 0.0.18 → 0.0.19

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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/core.rb +4 -3
  3. data/app/views/cli/core/finished.erb +1 -0
  4. data/app/views/json/core/finished.erb +1 -0
  5. data/cms_scanner.gemspec +13 -3
  6. data/lib/cms_scanner.rb +14 -2
  7. data/lib/cms_scanner/finders/finder.rb +16 -7
  8. data/lib/cms_scanner/finders/finder/enumerator.rb +4 -28
  9. data/lib/cms_scanner/finders/finder/fingerprinter.rb +60 -0
  10. data/lib/cms_scanner/finders/finding.rb +1 -1
  11. data/lib/cms_scanner/target/scope.rb +4 -1
  12. data/lib/cms_scanner/target/server/generic.rb +1 -1
  13. data/lib/cms_scanner/typhoeus/hydra.rb +10 -0
  14. data/lib/cms_scanner/version.rb +1 -1
  15. metadata +5 -203
  16. data/.gitignore +0 -7
  17. data/.rspec +0 -2
  18. data/.rubocop.yml +0 -10
  19. data/.travis.yml +0 -17
  20. data/Gemfile +0 -6
  21. data/Rakefile +0 -9
  22. data/spec/app/controllers/core_spec.rb +0 -167
  23. data/spec/app/controllers/interesting_files_spec.rb +0 -70
  24. data/spec/app/finders/interesting_files/fantastico_fileslist_spec.rb +0 -66
  25. data/spec/app/finders/interesting_files/headers_spec.rb +0 -36
  26. data/spec/app/finders/interesting_files/robots_txt_spec.rb +0 -54
  27. data/spec/app/finders/interesting_files/search_replace_db_2_spec.rb +0 -53
  28. data/spec/app/finders/interesting_files/xml_rpc_spec.rb +0 -136
  29. data/spec/app/finders/interesting_files_spec.rb +0 -12
  30. data/spec/app/formatters/cli_no_colour_spec.rb +0 -14
  31. data/spec/app/formatters/cli_spec.rb +0 -30
  32. data/spec/app/formatters/json_spec.rb +0 -30
  33. data/spec/app/models/fantastico_fileslist_spec.rb +0 -31
  34. data/spec/app/models/headers_spec.rb +0 -51
  35. data/spec/app/models/interesting_file_spec.rb +0 -69
  36. data/spec/app/models/robots_txt_spec.rb +0 -27
  37. data/spec/app/models/version_spec.rb +0 -51
  38. data/spec/app/models/xml_rpc_spec.rb +0 -46
  39. data/spec/app/views_spec.rb +0 -35
  40. data/spec/cache/.gitignore +0 -4
  41. data/spec/dummy_finding.rb +0 -25
  42. data/spec/dummy_independent_finders.rb +0 -26
  43. data/spec/dummy_unique_finders.rb +0 -33
  44. data/spec/fixtures/finders/interesting_files/fantastico_fileslist/fantastico_fileslist.txt +0 -12
  45. data/spec/fixtures/finders/interesting_files/file.txt +0 -4
  46. data/spec/fixtures/finders/interesting_files/headers/interesting.txt +0 -16
  47. data/spec/fixtures/finders/interesting_files/headers/no_interesting.txt +0 -12
  48. data/spec/fixtures/finders/interesting_files/robots_txt/robots.txt +0 -10
  49. data/spec/fixtures/finders/interesting_files/search_replace_db_2/searchreplacedb2.php +0 -188
  50. data/spec/fixtures/finders/interesting_files/xml_rpc/homepage_in_scope_pingback.html +0 -7
  51. data/spec/fixtures/finders/interesting_files/xml_rpc/homepage_out_of_scope_pingback.html +0 -7
  52. data/spec/fixtures/finders/interesting_files/xml_rpc/xmlrpc.php +0 -1
  53. data/spec/fixtures/output.txt +0 -0
  54. data/spec/fixtures/target/comments.html +0 -29
  55. data/spec/fixtures/target/platform/php/debug_log/debug.log +0 -2
  56. data/spec/fixtures/target/platform/php/fpd/wp_rss_functions.php +0 -2
  57. data/spec/fixtures/target/scope/index.html +0 -23
  58. data/spec/fixtures/target/server/apache/directory_listing/2.2.16.html +0 -15
  59. data/spec/fixtures/target/server/generic/server/apache/basic.txt +0 -5
  60. data/spec/fixtures/target/server/generic/server/iis/basic.txt +0 -6
  61. data/spec/fixtures/target/server/generic/server/not_detected.txt +0 -3
  62. data/spec/fixtures/target/server/iis/directory_listing/no_parent.html +0 -3
  63. data/spec/fixtures/target/server/iis/directory_listing/with_parent.html +0 -3
  64. data/spec/fixtures/views/base/ctrl/local.erb +0 -1
  65. data/spec/fixtures/views/base/ctrl/test.erb +0 -3
  66. data/spec/fixtures/views/base/global.erb +0 -1
  67. data/spec/fixtures/views/base/test.erb +0 -2
  68. data/spec/fixtures/views/based_format/test.erb +0 -1
  69. data/spec/fixtures/views/json/render_me.erb +0 -4
  70. data/spec/lib/browser_spec.rb +0 -140
  71. data/spec/lib/cache/file_store_spec.rb +0 -100
  72. data/spec/lib/cache/typhoeus_spec.rb +0 -28
  73. data/spec/lib/cms_scanner_spec.rb +0 -49
  74. data/spec/lib/controller_spec.rb +0 -30
  75. data/spec/lib/controllers_spec.rb +0 -48
  76. data/spec/lib/finders/confidence_spec.rb +0 -39
  77. data/spec/lib/finders/finder/enumerator_spec.rb +0 -89
  78. data/spec/lib/finders/finder/smart_url_checker/findings_spec.rb +0 -39
  79. data/spec/lib/finders/finder/smart_url_checker_spec.rb +0 -50
  80. data/spec/lib/finders/finder_spec.rb +0 -11
  81. data/spec/lib/finders/findings_spec.rb +0 -36
  82. data/spec/lib/finders/independent_finders_spec.rb +0 -134
  83. data/spec/lib/finders/same_type_finder_spec.rb +0 -24
  84. data/spec/lib/finders/same_type_finders_spec.rb +0 -126
  85. data/spec/lib/finders/unique_finder_spec.rb +0 -24
  86. data/spec/lib/finders/unique_finders_spec.rb +0 -222
  87. data/spec/lib/formatter_spec.rb +0 -145
  88. data/spec/lib/public_suffix/domain_spec.rb +0 -49
  89. data/spec/lib/sub_scanner_spec.rb +0 -45
  90. data/spec/lib/target/hashes_spec.rb +0 -90
  91. data/spec/lib/target/platforms_spec.rb +0 -13
  92. data/spec/lib/target/scope_spec.rb +0 -103
  93. data/spec/lib/target/servers_spec.rb +0 -13
  94. data/spec/lib/target_spec.rb +0 -69
  95. data/spec/lib/vulnerability/references_spec.rb +0 -75
  96. data/spec/lib/vulnerability_spec.rb +0 -27
  97. data/spec/lib/web_site_spec.rb +0 -121
  98. data/spec/output/core/finished.cli_no_colour +0 -3
  99. data/spec/output/core/finished.json +0 -5
  100. data/spec/output/core/started.cli_no_colour +0 -3
  101. data/spec/output/core/started.json +0 -5
  102. data/spec/output/interesting_files/empty.cli_no_colour +0 -2
  103. data/spec/output/interesting_files/empty.json +0 -5
  104. data/spec/output/interesting_files/findings.cli_no_colour +0 -30
  105. data/spec/output/interesting_files/findings.json +0 -75
  106. data/spec/shared_examples.rb +0 -11
  107. data/spec/shared_examples/browser_actions.rb +0 -30
  108. data/spec/shared_examples/finding.rb +0 -54
  109. data/spec/shared_examples/formatter_buffer.rb +0 -6
  110. data/spec/shared_examples/formatter_class_methods.rb +0 -26
  111. data/spec/shared_examples/independent_finder.rb +0 -31
  112. data/spec/shared_examples/target/platform/php.rb +0 -56
  113. data/spec/shared_examples/target/server/apache.rb +0 -32
  114. data/spec/shared_examples/target/server/generic.rb +0 -33
  115. data/spec/shared_examples/target/server/iis.rb +0 -37
  116. data/spec/shared_examples/views/core.rb +0 -26
  117. data/spec/shared_examples/views/interesting_files.rb +0 -36
  118. data/spec/spec_helper.rb +0 -43
@@ -1,49 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe PublicSuffix::Domain do
4
- describe '#match' do
5
- it 'returns true' do
6
- expect(PublicSuffix.parse('g.com').match('g.com')).to eql true
7
- end
8
-
9
- it 'returns true' do
10
- expect(PublicSuffix.parse('s.g.com').match('*.g.com')).to eql true
11
- end
12
-
13
- it 'returns false' do
14
- expect(PublicSuffix.parse('a.b.g.com').match('*.g.com')).to eql false
15
- end
16
-
17
- it 'returns true' do
18
- expect(PublicSuffix.parse('a.b.g.com').match('*.b.g.com')).to eql true
19
- end
20
-
21
- it 'returns true' do
22
- expect(PublicSuffix.parse('a.b.g.com').match('**.g.com')).to eql true
23
- end
24
-
25
- it 'returns false' do
26
- expect(PublicSuffix.parse('a.b.y.g.com').match('**.b.g.com')).to eql false
27
- end
28
-
29
- it 'returns false' do
30
- expect(PublicSuffix.parse('w.g.com').match('*.g2.com')).to eql false
31
- end
32
-
33
- it 'returns true' do
34
- expect(PublicSuffix.parse('a.b.g.com').match('a.b.g.com')).to eql true
35
- end
36
-
37
- it 'returns false' do
38
- expect(PublicSuffix.parse('a.b.g.com').match('a.y.g.com')).to eql false
39
- end
40
-
41
- it 'returns true' do
42
- expect(PublicSuffix.parse('a.b.c.d.g.com').match('**.c.d.g.com')).to eql true
43
- end
44
-
45
- it 'returns true' do
46
- expect(PublicSuffix.parse('a.b.c.d.g.com').match('*.b.c.d.g.com')).to eql true
47
- end
48
- end
49
- end
@@ -1,45 +0,0 @@
1
- require 'spec_helper'
2
-
3
- # Module including 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
-
15
- # Custom method for all formatters
16
- module Formatter
17
- include CMSScanner::Formatter
18
-
19
- # Implements a #custom method which should be available in all formatters
20
- module InstanceMethods
21
- def custom
22
- 'It Works!'
23
- end
24
- end
25
- end
26
- end
27
-
28
- describe SubScanner::Scan do
29
- subject(:scanner) { described_class.new }
30
- let(:formatter_class) { SubScanner::Formatter }
31
-
32
- it 'loads the overrided Target class' do
33
- target = scanner.controllers.first.target
34
-
35
- expect(target).to be_a SubScanner::Target
36
- expect(target).to respond_to(:new_method)
37
- expect(target.new_method).to eq 'working'
38
- end
39
-
40
- it 'adds the #custom method for all formatters' do
41
- formatter_class.availables.each do |format|
42
- expect(formatter_class.load(format).custom).to eql 'It Works!'
43
- end
44
- end
45
- end
@@ -1,90 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe CMSScanner::Target do
4
- subject(:target) { described_class.new(url) }
5
- let(:url) { 'http://e.org' }
6
-
7
- def md5sum(body)
8
- Digest::MD5.hexdigest(body)
9
- end
10
-
11
- describe '#page_hash' do
12
- after { expect(described_class.page_hash(page)).to eql @expected }
13
-
14
- context 'when the page is an url' do
15
- let(:page) { 'http://e.org/somepage.php' }
16
-
17
- it 'returns the MD5 hash of the page' do
18
- body = 'Hello World !'
19
-
20
- stub_request(:get, page).to_return(body: body)
21
-
22
- @expected = md5sum(body)
23
- end
24
- end
25
-
26
- context 'when the page is a Typhoeus::Response' do
27
- let(:page) { Typhoeus::Response.new(body: 'Hello Example!') }
28
-
29
- it 'returns the correct hash' do
30
- @expected = md5sum('Hello Example!')
31
- end
32
- end
33
-
34
- context 'when there are comments' do
35
- let(:page) do
36
- body = "yolo\n\n<!--I should <script>no longer be</script> there -->\nworld!"
37
- Typhoeus::Response.new(body: body)
38
- end
39
-
40
- it 'removes them' do
41
- @expected = md5sum("yolo\n\n\nworld!")
42
- end
43
- end
44
- end
45
-
46
- describe '#homepage_hash' do
47
- it 'returns the MD5 hash of the homepage' do
48
- body = 'Hello World'
49
-
50
- stub_request(:get, target.url).to_return(body: body)
51
-
52
- expect(target.homepage_hash).to eql md5sum(body)
53
- end
54
- end
55
-
56
- describe '#error_404_hash' do
57
- it 'returns the md5sum of the 404 page' do
58
- stub_request(:any, /.*/).to_return(status: 404, body: '404 page !')
59
-
60
- expect(target.error_404_hash).to eql md5sum('404 page !')
61
- end
62
- end
63
-
64
- describe '#homepage_or_404?' do
65
- let(:page_url) { target.url('page') }
66
-
67
- before do
68
- expect(target).to receive(:homepage_hash).and_return(md5sum('Home'))
69
- expect(target).to receive(:error_404_hash).and_return(md5sum('Custom 404'))
70
-
71
- stub_request(:get, page_url).to_return(body: body)
72
- end
73
-
74
- context 'when hashes do not match' do
75
- let(:body) { 'Page!' }
76
-
77
- it 'returns false' do
78
- expect(target.homepage_or_404?(page_url)).to eql false
79
- end
80
- end
81
-
82
- context 'when hashes match' do
83
- let(:body) { 'Custom 404' }
84
-
85
- it 'returns true' do
86
- expect(target.homepage_or_404?(page_url)).to eql true
87
- end
88
- end
89
- end
90
- end
@@ -1,13 +0,0 @@
1
- require 'spec_helper'
2
-
3
- [: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://e.org' }
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
@@ -1,103 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe CMSScanner::Target do
4
- subject(:target) { described_class.new(url, opts) }
5
- let(:url) { 'http://e.org' }
6
- let(:fixtures) { File.join(FIXTURES, 'target', 'scope') }
7
- let(:opts) { { scope: nil } }
8
-
9
- describe '#scope' do
10
- let(:default_domains) { [PublicSuffix.parse('e.org')] }
11
-
12
- context 'when none supplied' do
13
- its('scope.domains') { should eq default_domains }
14
- end
15
-
16
- context 'when scope provided' do
17
- let(:opts) { super().merge(scope: ['*.e.org']) }
18
-
19
- its('scope.domains') { should eq default_domains << PublicSuffix.parse(opts[:scope].first) }
20
-
21
- context 'when invalid domains provided' do
22
- let(:opts) { super().merge(scope: ['wp-lamp', '192.168.1.12']) }
23
-
24
- it 'adds them in the invalid_domains attribute' do
25
- expect(target.scope.domains).to eq default_domains
26
- expect(target.scope.invalid_domains).to eq opts[:scope]
27
- end
28
- end
29
- end
30
- end
31
-
32
- describe '#in_scope?' do
33
- context 'when default scope (target domain)' do
34
- [nil, '', 'http://out-of-scope.com', '//jquery.com/j.js',
35
- 'javascript:alert(3)', 'mailto:p@g.com'
36
- ].each do |url|
37
- it "returns false for #{url}" do
38
- expect(target.in_scope?(url)).to eql false
39
- end
40
- end
41
-
42
- %w(https://e.org/file.txt http://e.org/ //e.org).each do |url|
43
- it "returns true for #{url}" do
44
- expect(target.in_scope?(url)).to eql true
45
- end
46
- end
47
- end
48
-
49
- context 'when custom scope' do
50
- let(:opts) { { scope: ['*.e.org', '192.168.1.12'] } }
51
-
52
- [nil, '', 'http://out-of-scope.com', '//jquery.com/j.js', 'http://192.168.1.2/'].each do |url|
53
- it "returns false for #{url}" do
54
- expect(target.in_scope?(url)).to eql false
55
- end
56
- end
57
-
58
- %w(http://e.org //cdn.e.org/f.txt http://s.e.org/ https://192.168.1.12/h).each do |url|
59
- it "returns true for #{url}" do
60
- expect(target.in_scope?(url)).to eql true
61
- end
62
- end
63
- end
64
- end
65
-
66
- describe '#in_scope_urls' do
67
- let(:res) { Typhoeus::Response.new(body: File.open(File.join(fixtures, 'index.html'))) }
68
-
69
- context 'when block given' do
70
- it 'yield the url' do
71
- expect { |b| target.in_scope_urls(res, &b) }
72
- .to yield_successive_args('http://e.org/f.txt', 'http://e.org/script/s.js', 'http://e.org/feed')
73
- end
74
- end
75
-
76
- context 'when xpath argument given' do
77
- it 'returns the expected array' do
78
- xpath = '//link[@rel="alternate" and @type="application/rss+xml"]'
79
-
80
- expect(target.in_scope_urls(res, xpath)).to eql(%w(http://e.org/feed))
81
- end
82
- end
83
-
84
- context 'when no block given' do
85
- after { expect(target.in_scope_urls(res)).to eql @expected }
86
-
87
- context 'when default scope' do
88
- it 'returns the expected array' do
89
- @expected = %w(http://e.org/f.txt http://e.org/script/s.js http://e.org/feed)
90
- end
91
- end
92
-
93
- context 'when supplied scope' do
94
- let(:opts) { super().merge(scope: ['*.e.org', 'wp-lamp']) }
95
-
96
- it 'returns the expected array' do
97
- @expected = %w(http://e.org/f.txt https://cdn.e.org/f2.js http://e.org/script/s.js
98
- http://wp-lamp/robots.txt http://e.org/feed)
99
- end
100
- end
101
- end
102
- end
103
- end
@@ -1,13 +0,0 @@
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://e.org' }
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
@@ -1,69 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe CMSScanner::Target do
4
- subject(:target) { described_class.new(url) }
5
- let(:url) { 'http://e.org' }
6
-
7
- describe '#interesting_files' do
8
- before do
9
- expect(CMSScanner::Finders::InterestingFiles::Base).to receive(:find).and_return(stubbed)
10
- end
11
-
12
- context 'when no findings' do
13
- let(:stubbed) { [] }
14
-
15
- its(:interesting_files) { should eq stubbed }
16
- end
17
-
18
- context 'when findings' do
19
- let(:stubbed) { ['yolo'] }
20
-
21
- it 'allows findings to be added with <<' do
22
- expect(target.interesting_files).to eq stubbed
23
-
24
- target.interesting_files << 'other-finding'
25
-
26
- expect(target.interesting_files).to eq(stubbed << 'other-finding')
27
- end
28
- end
29
- end
30
-
31
- describe '#comments_from_page' do
32
- let(:fixture) { File.join(FIXTURES, 'target', 'comments.html') }
33
- let(:page) { Typhoeus::Response.new(body: File.read(fixture)) }
34
-
35
- context 'when the pattern does not match anything' do
36
- it 'returns an empty array' do
37
- expect(target.comments_from_page(/none/, page)).to eql([])
38
- end
39
- end
40
-
41
- context 'when the pattern matches' do
42
- let(:pattern) { /all in one seo pack/i }
43
- let(:s1) { 'All in One SEO Pack 2.2.5.1 by Michael Torbert of Semper Fi Web Design' }
44
- let(:s2) { '/all in one seo pack' }
45
-
46
- context 'when no block given' do
47
- it 'returns the expected matches' do
48
- results = target.comments_from_page(pattern, page)
49
-
50
- [s1, s2].each_with_index do |s, i|
51
- expect(results[i].first).to eql s.match(pattern)
52
- expect(results[i].last.to_s).to eql "<!-- #{s} -->"
53
- end
54
- end
55
- end
56
-
57
- # The below doesn't work, dunno why
58
- context 'when block given' do
59
- it 'yield the MatchData' do
60
- expect { |b| target.comments_from_page(pattern, page, &b) }
61
- .to yield_successive_args(
62
- [MatchData, Nokogiri::XML::Comment],
63
- [MatchData, Nokogiri::XML::Comment]
64
- )
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,75 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe CMSScanner::Vulnerability do
4
- subject(:vuln) { described_class.new(title, references) }
5
- let(:title) { 'Test Vuln' }
6
- let(:references) { {} }
7
-
8
- describe '#new' do
9
- context 'when no references' do
10
- [:cves, :secunia_ids, :osvdb_ids, :exploitdb_ids, :urls,
11
- :msf_modules, :packetstorm_ids
12
- ].each do |attribute|
13
- its(attribute) { should eql([]) }
14
- end
15
-
16
- [:cve_urls, :secunia_urls, :osvdb_urls, :exploitdb_urls, :msf_urls,
17
- :packetstorm_urls
18
- ].each do |attribute|
19
- its(attribute) { should eql([]) }
20
- end
21
-
22
- its(:references_urls) { should eql([]) }
23
- end
24
-
25
- context 'when references provided as string' do
26
- let(:references) do
27
- {
28
- cve: 11,
29
- secunia: 12,
30
- osvdb: 13,
31
- exploitdb: 14,
32
- url: 'single-url',
33
- metasploit: '/exploit/yolo',
34
- packetstorm: 15
35
- }
36
- end
37
-
38
- its(:cves) { should eql %w(11) }
39
- its(:cve_urls) { should eql %w(http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-11) }
40
-
41
- its(:secunia_ids) { should eql %w(12) }
42
- its(:secunia_urls) { should eql %w(https://secunia.com/advisories/12) }
43
-
44
- its(:osvdb_ids) { should eql %w(13) }
45
- its(:osvdb_urls) { should eql %w(http://osvdb.org/13) }
46
-
47
- its(:exploitdb_ids) { should eql %w(14) }
48
- its(:exploitdb_urls) { should eql %w(http://www.exploit-db.com/exploits/14/) }
49
-
50
- its(:urls) { should eql %w(single-url) }
51
-
52
- its(:msf_modules) { should eql %w(/exploit/yolo) }
53
- its(:msf_urls) { should eql %w(http://www.rapid7.com/db/modules/exploit/yolo) }
54
-
55
- its(:packetstorm_ids) { should eq %w(15) }
56
- its(:packetstorm_urls) { should eql %w(http://packetstormsecurity.com/files/15/) }
57
-
58
- its(:references_urls) do
59
- should eql [
60
- 'http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-11',
61
- 'https://secunia.com/advisories/12',
62
- 'http://osvdb.org/13',
63
- 'http://www.exploit-db.com/exploits/14/',
64
- 'single-url',
65
- 'http://www.rapid7.com/db/modules/exploit/yolo',
66
- 'http://packetstormsecurity.com/files/15/'
67
- ]
68
- end
69
- end
70
-
71
- context 'when references provided as array' do
72
- xit
73
- end
74
- end
75
- end