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.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.github/workflows/ruby.yml +27 -0
- data/.gitignore +12 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +10 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.md +87 -0
- data/Rakefile +23 -0
- data/gemspec.yml +28 -0
- data/lib/feroxbuster/command.rb +134 -0
- data/lib/feroxbuster/output_file.rb +124 -0
- data/lib/feroxbuster/parsers/json.rb +121 -0
- data/lib/feroxbuster/parsers/txt.rb +48 -0
- data/lib/feroxbuster/response.rb +117 -0
- data/lib/feroxbuster/statistics.rb +238 -0
- data/lib/feroxbuster/version.rb +4 -0
- data/ruby-feroxbuster.gemspec +61 -0
- data/spec/command_spec.rb +66 -0
- data/spec/fixtures/output.json +5 -0
- data/spec/fixtures/output.txt +4 -0
- data/spec/output_file_spec.rb +116 -0
- data/spec/parsers/json_spec.rb +92 -0
- data/spec/parsers/txt_spec.rb +88 -0
- data/spec/spec_helper.rb +3 -0
- metadata +112 -0
@@ -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,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,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
|