ruby-feroxbuster 0.1.0

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,48 @@
1
+ require 'feroxbuster/response'
2
+
3
+ module Feroxbuster
4
+ module Parsers
5
+ #
6
+ # Parses single-line response data.
7
+ #
8
+ # @api semipublic
9
+ #
10
+ module TXT
11
+ #
12
+ # Parses each line of plain-text.
13
+ #
14
+ # @param [IO] io
15
+ # The IO stream to parse.
16
+ #
17
+ # @yield [response]
18
+ # The given block will be passed each parsed response.
19
+ #
20
+ # @yieldparam [Response] response
21
+ # The parsed response.
22
+ #
23
+ # @return [Enumerator]
24
+ # If no block is given, an Enumerator will be returned.
25
+ #
26
+ def self.parse(io)
27
+ return enum_for(__method__,io) unless block_given?
28
+
29
+ line_regexp = /^(\d{3})\s+(\w+)\s+(\d+)l\s+(\d+)w\s+(\d+)c\s+([^\s]+)/
30
+
31
+ io.each_line do |line|
32
+ line.chomp!
33
+
34
+ if (match = line.match(line_regexp))
35
+ yield Response.new(
36
+ status: match[1].to_i,
37
+ method: match[2],
38
+ line_count: match[3].to_i,
39
+ word_count: match[4].to_i,
40
+ content_length: match[5].to_i,
41
+ url: match[6]
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,117 @@
1
+ module Feroxbuster
2
+ #
3
+ # Represents response data, either parsed from `.txt` or `.json` output.
4
+ #
5
+ class Response
6
+
7
+ # The URL that was requested.
8
+ #
9
+ # @return [String]
10
+ attr_reader :url
11
+
12
+ # The "original" URL that was requested.
13
+ #
14
+ # @return [String]
15
+ attr_reader :original_url
16
+
17
+ # The path that was requested.
18
+ #
19
+ # @return [String, nil]
20
+ attr_reader :path
21
+
22
+ # @return [Boolean, nil]
23
+ attr_reader :wildcard
24
+
25
+ # The HTTP response status code.
26
+ #
27
+ # @return [Integer]
28
+ attr_reader :status
29
+
30
+ # The HTTP method used for the request.
31
+ #
32
+ # @return [String]
33
+ attr_reader :method
34
+
35
+ # The `Content-Length` of the response.
36
+ #
37
+ # @return [Integer]
38
+ attr_reader :content_length
39
+
40
+ # The line count of the response.
41
+ #
42
+ # @return [Integer]
43
+ attr_reader :line_count
44
+
45
+ # The word count of the response.
46
+ #
47
+ # @return [Integer]
48
+ attr_reader :word_count
49
+
50
+ # the HTTP headers of the response.
51
+ #
52
+ # @return [Hash{String => String}, nil]
53
+ attr_reader :headers
54
+
55
+ # @return [String, nil]
56
+ attr_reader :extension
57
+
58
+ #
59
+ # Initializes the response object.
60
+ #
61
+ # @param [String] url
62
+ # The URL that was requested.
63
+ #
64
+ # @param [String] original_url
65
+ # The "original" URL that was requested.
66
+ #
67
+ # @param [String, nil] path
68
+ # The path that was requested.
69
+ #
70
+ # @param [Boolean, nil] wildcard
71
+ #
72
+ # @param [Integer] status
73
+ # The HTTP response status code.
74
+ #
75
+ # @param [String] method
76
+ # The HTTP method used for the request.
77
+ #
78
+ # @param [Integer] content_length
79
+ # The `Content-Length` of the response.
80
+ #
81
+ # @param [Integer] line_count
82
+ # The line count of the response.
83
+ #
84
+ # @param [Integer] word_count
85
+ # The word count of the response.
86
+ #
87
+ # @param [Hash{String => String}, nil] headers
88
+ # the HTTP headers of the response.
89
+ #
90
+ # @param [String, nil] extension
91
+ #
92
+ def initialize(url: ,
93
+ original_url: nil,
94
+ path: nil,
95
+ wildcard: nil,
96
+ status: ,
97
+ method: ,
98
+ content_length: ,
99
+ line_count: ,
100
+ word_count: ,
101
+ headers: nil,
102
+ extension: nil)
103
+ @url = url
104
+ @original_url = original_url
105
+ @path = path
106
+ @wildcard = wildcard
107
+ @status = status
108
+ @method = method
109
+ @content_length = content_length
110
+ @line_count = line_count
111
+ @word_count = word_count
112
+ @headers = headers
113
+ @extension = extension
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,238 @@
1
+ module Feroxbuster
2
+ #
3
+ # Represents the statistics data, parsed from the last line of a `.json`
4
+ # output file.
5
+ #
6
+ class Statistics
7
+
8
+ # @return [Integer]
9
+ attr_reader :timeouts
10
+
11
+ # @return [Integer]
12
+ attr_reader :requests
13
+
14
+ # @return [Integer]
15
+ attr_reader :expected_per_scan
16
+
17
+ # @return [Integer]
18
+ attr_reader :total_expected
19
+
20
+ # @return [Integer]
21
+ attr_reader :errors
22
+
23
+ # @return [Integer]
24
+ attr_reader :successes
25
+
26
+ # @return [Integer]
27
+ attr_reader :redirects
28
+
29
+ # @return [Integer]
30
+ attr_reader :client_errors
31
+
32
+ # @return [Integer]
33
+ attr_reader :server_errors
34
+
35
+ # @return [Integer]
36
+ attr_reader :total_scans
37
+
38
+ # @return [Integer]
39
+ attr_reader :initial_targets
40
+
41
+ # @return [Integer]
42
+ attr_reader :links_extracted
43
+
44
+ # @return [Integer]
45
+ attr_reader :extensions_collected
46
+
47
+ # @return [Integer]
48
+ attr_reader :status_200s
49
+
50
+ # @return [Integer]
51
+ attr_reader :status_301s
52
+
53
+ # @return [Integer]
54
+ attr_reader :status_302s
55
+
56
+ # @return [Integer]
57
+ attr_reader :status_401s
58
+
59
+ # @return [Integer]
60
+ attr_reader :status_403s
61
+
62
+ # @return [Integer]
63
+ attr_reader :status_429s
64
+
65
+ # @return [Integer]
66
+ attr_reader :status_500s
67
+
68
+ # @return [Integer]
69
+ attr_reader :status_503s
70
+
71
+ # @return [Integer]
72
+ attr_reader :status_504s
73
+
74
+ # @return [Integer]
75
+ attr_reader :status_508s
76
+
77
+ # @return [Integer]
78
+ attr_reader :wildcards_filtered
79
+
80
+ # @return [Integer]
81
+ attr_reader :responses_filtered
82
+
83
+ # @return [Integer]
84
+ attr_reader :resources_discovered
85
+
86
+ # @return [Integer]
87
+ attr_reader :url_format_errors
88
+
89
+ # @return [Integer]
90
+ attr_reader :redirection_errors
91
+
92
+ # @return [Integer]
93
+ attr_reader :connection_errors
94
+
95
+ # @return [Integer]
96
+ attr_reader :request_errors
97
+
98
+ # @return [Array<Float>]
99
+ attr_reader :directory_scan_times
100
+
101
+ # @return [Array<Float>]
102
+ attr_reader :total_runtime
103
+
104
+ #
105
+ # Initializes the statistics object.
106
+ #
107
+ # @param [Integer] timeouts
108
+ #
109
+ # @param [Integer] requests
110
+ #
111
+ # @param [Integer] expected_per_scan
112
+ #
113
+ # @param [Integer] total_expected
114
+ #
115
+ # @param [Integer] errors
116
+ #
117
+ # @param [Integer] successes
118
+ #
119
+ # @param [Integer] redirects
120
+ #
121
+ # @param [Integer] client_errors
122
+ #
123
+ # @param [Integer] server_errors
124
+ #
125
+ # @param [Integer] total_scans
126
+ #
127
+ # @param [Integer] initial_targets
128
+ #
129
+ # @param [Integer] links_extracted
130
+ #
131
+ # @param [Integer] extensions_collected
132
+ #
133
+ # @param [Integer] status_200s
134
+ #
135
+ # @param [Integer] status_301s
136
+ #
137
+ # @param [Integer] status_302s
138
+ #
139
+ # @param [Integer] status_401s
140
+ #
141
+ # @param [Integer] status_403s
142
+ #
143
+ # @param [Integer] status_429s
144
+ #
145
+ # @param [Integer] status_500s
146
+ #
147
+ # @param [Integer] status_503s
148
+ #
149
+ # @param [Integer] status_504s
150
+ #
151
+ # @param [Integer] status_508s
152
+ #
153
+ # @param [Integer] wildcards_filtered
154
+ #
155
+ # @param [Integer] responses_filtered
156
+ #
157
+ # @param [Integer] resources_discovered
158
+ #
159
+ # @param [Integer] url_format_errors
160
+ #
161
+ # @param [Integer] redirection_errors
162
+ #
163
+ # @param [Integer] connection_errors
164
+ #
165
+ # @param [Integer] request_errors
166
+ #
167
+ # @param [Array<Float>] directory_scan_times
168
+ #
169
+ # @param [Array<Float>] total_runtime
170
+ #
171
+ def initialize(timeouts: 0,
172
+ requests: 0,
173
+ expected_per_scan: 6,
174
+ total_expected: 0,
175
+ errors: 0,
176
+ successes: 0,
177
+ redirects: 0,
178
+ client_errors: 0,
179
+ server_errors: 0,
180
+ total_scans: 0,
181
+ initial_targets: 0,
182
+ links_extracted: 0,
183
+ extensions_collected: 0,
184
+ status_200s: 0,
185
+ status_301s: 0,
186
+ status_302s: 0,
187
+ status_401s: 0,
188
+ status_403s: 0,
189
+ status_429s: 0,
190
+ status_500s: 0,
191
+ status_503s: 0,
192
+ status_504s: 0,
193
+ status_508s: 0,
194
+ wildcards_filtered: 0,
195
+ responses_filtered: 0,
196
+ resources_discovered: 0,
197
+ url_format_errors: 0,
198
+ redirection_errors: 0,
199
+ connection_errors: 0,
200
+ request_errors: 0,
201
+ directory_scan_times: [],
202
+ total_runtime: [])
203
+ @timeouts = timeouts
204
+ @requests = requests
205
+ @expected_per_scan = expected_per_scan
206
+ @total_expected = total_expected
207
+ @errors = errors
208
+ @successes = successes
209
+ @redirects = redirects
210
+ @client_errors = client_errors
211
+ @server_errors = server_errors
212
+ @total_scans = total_scans
213
+ @initial_targets = initial_targets
214
+ @links_extracted = links_extracted
215
+ @extensions_collected = extensions_collected
216
+ @status_200s = status_200s
217
+ @status_301s = status_301s
218
+ @status_302s = status_302s
219
+ @status_401s = status_401s
220
+ @status_403s = status_403s
221
+ @status_429s = status_429s
222
+ @status_500s = status_500s
223
+ @status_503s = status_503s
224
+ @status_504s = status_504s
225
+ @status_508s = status_508s
226
+ @wildcards_filtered = wildcards_filtered
227
+ @responses_filtered = responses_filtered
228
+ @resources_discovered = resources_discovered
229
+ @url_format_errors = url_format_errors
230
+ @redirection_errors = redirection_errors
231
+ @connection_errors = connection_errors
232
+ @request_errors = request_errors
233
+ @directory_scan_times = directory_scan_times
234
+ @total_runtime = total_runtime
235
+ end
236
+
237
+ end
238
+ end
@@ -0,0 +1,4 @@
1
+ module Feroxbuster
2
+ # ruby-feroxbuster version
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'feroxbuster/version'
14
+ Feroxbuster::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+ gem.metadata = gemspec['metadata'] if gemspec['metadata']
24
+
25
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
26
+
27
+ gem.files = `git ls-files`.split($/)
28
+ gem.files = glob[gemspec['files']] if gemspec['files']
29
+
30
+ gem.executables = gemspec.fetch('executables') do
31
+ glob['bin/*'].map { |path| File.basename(path) }
32
+ end
33
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
34
+
35
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
36
+ gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
37
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
38
+
39
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
40
+ %w[ext lib].select { |dir| File.directory?(dir) }
41
+ })
42
+
43
+ gem.requirements = Array(gemspec['requirements'])
44
+ gem.required_ruby_version = gemspec['required_ruby_version']
45
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
46
+ gem.post_install_message = gemspec['post_install_message']
47
+
48
+ split = lambda { |string| string.split(/,\s*/) }
49
+
50
+ if gemspec['dependencies']
51
+ gemspec['dependencies'].each do |name,versions|
52
+ gem.add_dependency(name,split[versions])
53
+ end
54
+ end
55
+
56
+ if gemspec['development_dependencies']
57
+ gemspec['development_dependencies'].each do |name,versions|
58
+ gem.add_development_dependency(name,split[versions])
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'feroxbuster/command'
3
+
4
+ describe Feroxbuster::Command do
5
+ describe described_class::StatusCode do
6
+ describe "#initialize" do
7
+ it "must set #range to 100..599 by default" do
8
+ expect(subject.range).to eq(100..599)
9
+ end
10
+ end
11
+ end
12
+
13
+ describe described_class::TimeSpec do
14
+ describe "#validate" do
15
+ context "when given nil" do
16
+ let(:value) { nil }
17
+
18
+ it "must return [false, \"cannot be nil\"]" do
19
+ expect(subject.validate(value)).to eq(
20
+ [false, "cannot be nil"]
21
+ )
22
+ end
23
+ end
24
+
25
+ context "when given a String" do
26
+ context "when given an empty String" do
27
+ let(:value) { "" }
28
+
29
+ it "must return [false, \"does not allow an empty value\"]" do
30
+ expect(subject.validate(value)).to eq(
31
+ [false, "does not allow an empty value"]
32
+ )
33
+ end
34
+ end
35
+
36
+ context "when given a number" do
37
+ let(:value) { "10" }
38
+
39
+ it "must return [false, \"must be a number and end with 'm', 's', 'ms', or 'ns'\"]" do
40
+ expect(subject.validate(value)).to eq(
41
+ [false, "must be a number and end with 'm', 's', 'ms', or 'ns'"]
42
+ )
43
+ end
44
+ end
45
+
46
+ context "when given a number that ends with a unit" do
47
+ let(:value) { "10s" }
48
+
49
+ it "must return true" do
50
+ expect(subject.validate(value)).to be(true)
51
+ end
52
+
53
+ context "but the unit isn't recognized" do
54
+ let(:value) { "10x" }
55
+
56
+ it "must return [false, \"must be a number and end with 'm', 's', 'ms', or 'ns'\"]" do
57
+ expect(subject.validate(value)).to eq(
58
+ [false, "must be a number and end with 'm', 's', 'ms', or 'ns'"]
59
+ )
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,5 @@
1
+ {"type":"response","url":"https://github.com/test","original_url":"https://github.com","path":"/test","wildcard":false,"status":200,"method":"GET","content_length":0,"line_count":2010,"word_count":11253,"headers":{"permissions-policy":"interest-cohort=()","accept-ranges":"bytes","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","x-content-type-options":"nosniff","cache-control":"max-age=0, private, must-revalidate","expect-ct":"max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"","server":"GitHub.com","x-github-request-id":"85A8:0E10:20B03E:27719A:6260B1D5","transfer-encoding":"chunked","date":"Thu, 21 Apr 2022 01:22:16 GMT","strict-transport-security":"max-age=31536000; includeSubdomains; preload","etag":"W/\"7c98cb0440eb94eddcfd360497fae419\"","x-frame-options":"deny","content-security-policy":"default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com online.visualstudio.com/api/v1/locations github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/","x-xss-protection":"0","set-cookie":"logged_in=no; Path=/; Domain=github.com; Expires=Fri, 21 Apr 2023 01:22:29 GMT; HttpOnly; Secure; SameSite=Lax","vary":"X-Requested-With, X-PJAX-Container, Accept-Encoding, Accept, X-Requested-With","content-type":"text/html; charset=utf-8"},"extension":""}
2
+ {"type":"response","url":"https://github.com/dev","original_url":"https://github.com","path":"/dev","wildcard":false,"status":200,"method":"GET","content_length":0,"line_count":2067,"word_count":11308,"headers":{"date":"Thu, 21 Apr 2022 01:22:17 GMT","cache-control":"max-age=0, private, must-revalidate","content-security-policy":"default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com online.visualstudio.com/api/v1/locations github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/","set-cookie":"logged_in=no; Path=/; Domain=github.com; Expires=Fri, 21 Apr 2023 01:22:29 GMT; HttpOnly; Secure; SameSite=Lax","permissions-policy":"interest-cohort=()","x-frame-options":"deny","server":"GitHub.com","accept-ranges":"bytes","etag":"W/\"3ca853f280bb864544f7842cdaa124e5\"","vary":"X-Requested-With, X-PJAX-Container, Accept-Encoding, Accept, X-Requested-With","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","x-github-request-id":"85AC:2947:2319C4:29E4C8:6260B1D5","transfer-encoding":"chunked","expect-ct":"max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"","x-content-type-options":"nosniff","content-type":"text/html; charset=utf-8","x-xss-protection":"0","strict-transport-security":"max-age=31536000; includeSubdomains; preload"},"extension":""}
3
+ {"type":"response","url":"https://github.com/www","original_url":"https://github.com","path":"/www","wildcard":false,"status":200,"method":"GET","content_length":0,"line_count":1813,"word_count":10399,"headers":{"content-type":"text/html; charset=utf-8","accept-ranges":"bytes","date":"Thu, 21 Apr 2022 01:22:16 GMT","transfer-encoding":"chunked","vary":"X-Requested-With, X-PJAX-Container, Accept-Encoding, Accept, X-Requested-With","x-xss-protection":"0","x-github-request-id":"85B4:4678:28A012:2F9830:6260B1D5","content-security-policy":"default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com online.visualstudio.com/api/v1/locations github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","etag":"W/\"e03d055bf6b701eb6ecf1fa7c1a137f0\"","permissions-policy":"interest-cohort=()","strict-transport-security":"max-age=31536000; includeSubdomains; preload","x-frame-options":"deny","set-cookie":"logged_in=no; Path=/; Domain=github.com; Expires=Fri, 21 Apr 2023 01:22:29 GMT; HttpOnly; Secure; SameSite=Lax","expect-ct":"max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"","server":"GitHub.com","cache-control":"max-age=0, private, must-revalidate","x-content-type-options":"nosniff"},"extension":""}
4
+ {"type":"response","url":"https://github.com/","original_url":"https://github.com","path":"/","wildcard":false,"status":200,"method":"GET","content_length":0,"line_count":2596,"word_count":13002,"headers":{"cache-control":"max-age=0, private, must-revalidate","server":"GitHub.com","set-cookie":"logged_in=no; Path=/; Domain=github.com; Expires=Fri, 21 Apr 2023 01:22:29 GMT; HttpOnly; Secure; SameSite=Lax","permissions-policy":"interest-cohort=()","vary":"X-PJAX, X-PJAX-Container, Accept-Language, Accept-Encoding, Accept, X-Requested-With","content-security-policy":"default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com online.visualstudio.com/api/v1/locations github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com github.githubassets.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com customer-stories-feed.github.com spotlights-feed.github.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/ github.githubassets.com; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/","x-xss-protection":"0","expect-ct":"max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"","x-github-request-id":"85B2:4670:5B88C:BE8AA:6260B1D5","date":"Thu, 21 Apr 2022 01:22:22 GMT","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","x-content-type-options":"nosniff","etag":"W/\"b1bb181559a6360603ad7f0ed22c198c\"","accept-ranges":"bytes","x-frame-options":"deny","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","content-type":"text/html; charset=utf-8","content-language":"en-US"},"extension":""}
5
+ {"type":"statistics","timeouts":0,"requests":18,"expected_per_scan":6,"total_expected":12,"errors":0,"successes":12,"redirects":0,"client_errors":6,"server_errors":0,"total_scans":2,"initial_targets":0,"links_extracted":0,"extensions_collected":0,"status_200s":12,"status_301s":0,"status_302s":0,"status_401s":0,"status_403s":0,"status_429s":0,"status_500s":0,"status_503s":0,"status_504s":0,"status_508s":0,"wildcards_filtered":0,"responses_filtered":0,"resources_discovered":4,"url_format_errors":0,"redirection_errors":0,"connection_errors":0,"request_errors":0,"directory_scan_times":[0.434531853,0.434228035],"total_runtime":[1.6527268240000001]}
@@ -0,0 +1,4 @@
1
+ 200 GET 2596l 13002w 0c https://github.com/
2
+ 200 GET 2010l 11253w 0c https://github.com/test
3
+ 200 GET 1813l 10399w 0c https://github.com/www
4
+ 200 GET 2067l 11308w 0c https://github.com/dev
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+ require 'feroxbuster/output_file'
3
+
4
+ describe Feroxbuster::OutputFile do
5
+ let(:fixtures_dir) { File.expand_path(File.join(__dir__,'fixtures')) }
6
+
7
+ describe ".infer_format" do
8
+ subject { described_class.infer_format(path) }
9
+
10
+ context "when the path ends in .json" do
11
+ let(:path) { '/path/to/file.json' }
12
+
13
+ it { expect(subject).to eq(:json) }
14
+ end
15
+
16
+ context "when the path ends in .txt" do
17
+ let(:path) { '/path/to/file.txt' }
18
+
19
+ it { expect(subject).to eq(:txt) }
20
+ end
21
+ end
22
+
23
+ describe "PARSERS" do
24
+ subject { described_class::PARSERS }
25
+
26
+ describe ":txt" do
27
+ it { expect(subject[:txt]).to eq(Feroxbuster::Parsers::TXT) }
28
+ end
29
+
30
+ describe ":json" do
31
+ it { expect(subject[:json]).to eq(Feroxbuster::Parsers::JSON) }
32
+ end
33
+ end
34
+
35
+ describe "FILE_FORMATS" do
36
+ subject { described_class::FILE_FORMATS }
37
+
38
+ describe ".txt" do
39
+ it { expect(subject['.txt']).to be(:txt) }
40
+ end
41
+
42
+ describe ".json" do
43
+ it { expect(subject['.json']).to be(:json) }
44
+ end
45
+ end
46
+
47
+ describe "#initialize" do
48
+ let(:path) { "/path/to/file.json" }
49
+
50
+ subject { described_class.new(path) }
51
+
52
+ it "must set #path" do
53
+ expect(subject.path).to eq(path)
54
+ end
55
+
56
+ it "must infer the format from the file's extname" do
57
+ expect(subject.format).to eq(:json)
58
+ end
59
+
60
+ it "must set #parser based on #format" do
61
+ expect(subject.parser).to eq(described_class::PARSERS[subject.format])
62
+ end
63
+
64
+ context "when given an format: keyword" do
65
+ let(:format) { :txt }
66
+
67
+ subject { described_class.new(path, format: format) }
68
+
69
+ it "must set #format" do
70
+ expect(subject.format).to be(format)
71
+ end
72
+
73
+ it "must set #parser based on the given format:" do
74
+ expect(subject.parser).to eq(described_class::PARSERS[format])
75
+ end
76
+ end
77
+ end
78
+
79
+ let(:path) { File.join(fixtures_dir,'output.json') }
80
+
81
+ subject { described_class.new(path) }
82
+
83
+ describe "#each" do
84
+ context "when a block is given" do
85
+ it "must yield each parsed Response object" do
86
+ yielded_objects = []
87
+
88
+ subject.each do |object|
89
+ yielded_objects << object
90
+ end
91
+
92
+ expect(yielded_objects).to_not be_empty
93
+ expect(yielded_objects[0..-2]).to all(be_kind_of(Feroxbuster::Response))
94
+
95
+ expect(yielded_objects.last).to be_kind_of(Feroxbuster::Statistics)
96
+ end
97
+ end
98
+
99
+ context "when no block is given" do
100
+ it "must return an Enumerator of the parsed Response objects" do
101
+ objects = subject.each.to_a
102
+
103
+ expect(objects).to_not be_empty
104
+ expect(objects[0..-2]).to all(be_kind_of(Feroxbuster::Response))
105
+
106
+ expect(objects.last).to be_kind_of(Feroxbuster::Statistics)
107
+ end
108
+ end
109
+ end
110
+
111
+ describe "#to_s" do
112
+ it "must return #path" do
113
+ expect(subject.to_s).to eq(path)
114
+ end
115
+ end
116
+ end