New upstream release.
Kali Janitor
3 years ago
9 | 9 | gcflags: |
10 | 10 | - all=-trimpath={{.Env.GOPATH}} |
11 | 11 | ldflags: | |
12 | -s -w -X github.com/ffuf/ffuf/pkg/ffuf.VERSION_APPENDIX= -extldflags '-static' | |
12 | -s -w -X 'github.com/ffuf/ffuf/pkg/ffuf.VERSION_APPENDIX= exclusive <3' -extldflags '-static' | |
13 | 13 | goos: |
14 | 14 | - linux |
15 | 15 | - windows |
1 | 1 | - master |
2 | 2 | - New |
3 | 3 | - Changed |
4 | ||
5 | - v1.3.1 | |
6 | - New | |
7 | - Added a CLI flag to disable the interactive mode | |
8 | - Changed | |
9 | - Do not read the last newline in the end of the raw request file when using -request | |
10 | - Fixed an issue with storing the matches for recursion jobs | |
11 | - Fixed the way the "size" is calculated, it should match content-length now | |
12 | - Fixed an issue with header canonicalization when a keyword was just a part of the header name | |
13 | - Fixed output writing so it doesn't silently fail if it needs to create directories recursively | |
4 | 14 | |
5 | 15 | - v1.3.0 |
6 | 16 | - New |
21 | 21 | * [jvesiluoma](https://github.com/jvesiluoma) |
22 | 22 | * [Kiblyn11](https://github.com/Kiblyn11) |
23 | 23 | * [lc](https://github.com/lc) |
24 | * [mprencipe](https://github.com/mprencipe) | |
24 | 25 | * [nnwakelam](https://twitter.com/nnwakelam) |
25 | 26 | * [noraj](https://pwn.by/noraj) |
26 | 27 | * [oh6hay](https://github.com/oh6hay) |
157 | 157 | To define the test case for ffuf, use the keyword `FUZZ` anywhere in the URL (`-u`), headers (`-H`), or POST data (`-d`). |
158 | 158 | |
159 | 159 | ``` |
160 | Fuzz Faster U Fool - v1.2.0-git | |
160 | Fuzz Faster U Fool - v1.3.0-dev | |
161 | 161 | |
162 | 162 | HTTP OPTIONS: |
163 | 163 | -H Header `"Name: Value"`, separated by colon. Multiple -H flags are accepted. |
175 | 175 | -x Proxy URL (SOCKS5 or HTTP). For example: http://127.0.0.1:8080 or socks5://127.0.0.1:8080 |
176 | 176 | |
177 | 177 | GENERAL OPTIONS: |
178 | -V Show version information. (default: false) | |
179 | -ac Automatically calibrate filtering options (default: false) | |
180 | -acc Custom auto-calibration string. Can be used multiple times. Implies -ac | |
181 | -c Colorize output. (default: false) | |
182 | -config Load configuration from a file | |
183 | -maxtime Maximum running time in seconds for entire process. (default: 0) | |
184 | -maxtime-job Maximum running time in seconds per job. (default: 0) | |
185 | -p Seconds of `delay` between requests, or a range of random delay. For example "0.1" or "0.1-2.0" | |
186 | -rate Rate of requests per second (default: 0) | |
187 | -s Do not print additional information (silent mode) (default: false) | |
188 | -sa Stop on all error cases. Implies -sf and -se. (default: false) | |
189 | -se Stop on spurious errors (default: false) | |
190 | -sf Stop when > 95% of responses return 403 Forbidden (default: false) | |
191 | -t Number of concurrent threads. (default: 40) | |
192 | -v Verbose output, printing full URL and redirect location (if any) with the results. (default: false) | |
178 | -V Show version information. (default: false) | |
179 | -ac Automatically calibrate filtering options (default: false) | |
180 | -acc Custom auto-calibration string. Can be used multiple times. Implies -ac | |
181 | -c Colorize output. (default: false) | |
182 | -config Load configuration from a file | |
183 | -maxtime Maximum running time in seconds for entire process. (default: 0) | |
184 | -maxtime-job Maximum running time in seconds per job. (default: 0) | |
185 | -noninteractive Disable the interactive console functionality (default: false) | |
186 | -p Seconds of `delay` between requests, or a range of random delay. For example "0.1" or "0.1-2.0" | |
187 | -rate Rate of requests per second (default: 0) | |
188 | -s Do not print additional information (silent mode) (default: false) | |
189 | -sa Stop on all error cases. Implies -sf and -se. (default: false) | |
190 | -se Stop on spurious errors (default: false) | |
191 | -sf Stop when > 95% of responses return 403 Forbidden (default: false) | |
192 | -t Number of concurrent threads. (default: 40) | |
193 | -v Verbose output, printing full URL and redirect location (if any) with the results. (default: false) | |
193 | 194 | |
194 | 195 | MATCHER OPTIONS: |
195 | -mc Match HTTP status codes, or "all" for everything. (default: 200,204,301,302,307,401,403,405) | |
196 | -ml Match amount of lines in response | |
197 | -mr Match regexp | |
198 | -ms Match HTTP response size | |
199 | -mw Match amount of words in response | |
196 | -mc Match HTTP status codes, or "all" for everything. (default: 200,204,301,302,307,401,403,405) | |
197 | -ml Match amount of lines in response | |
198 | -mr Match regexp | |
199 | -ms Match HTTP response size | |
200 | -mw Match amount of words in response | |
200 | 201 | |
201 | 202 | FILTER OPTIONS: |
202 | -fc Filter HTTP status codes from response. Comma separated list of codes and ranges | |
203 | -fl Filter by amount of lines in response. Comma separated list of line counts and ranges | |
204 | -fr Filter regexp | |
205 | -fs Filter HTTP response size. Comma separated list of sizes and ranges | |
206 | -fw Filter by amount of words in response. Comma separated list of word counts and ranges | |
203 | -fc Filter HTTP status codes from response. Comma separated list of codes and ranges | |
204 | -fl Filter by amount of lines in response. Comma separated list of line counts and ranges | |
205 | -fr Filter regexp | |
206 | -fs Filter HTTP response size. Comma separated list of sizes and ranges | |
207 | -fw Filter by amount of words in response. Comma separated list of word counts and ranges | |
207 | 208 | |
208 | 209 | INPUT OPTIONS: |
209 | -D DirSearch wordlist compatibility mode. Used in conjunction with -e flag. (default: false) | |
210 | -e Comma separated list of extensions. Extends FUZZ keyword. | |
211 | -ic Ignore wordlist comments (default: false) | |
212 | -input-cmd Command producing the input. --input-num is required when using this input method. Overrides -w. | |
213 | -input-num Number of inputs to test. Used in conjunction with --input-cmd. (default: 100) | |
214 | -mode Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork (default: clusterbomb) | |
215 | -request File containing the raw http request | |
216 | -request-proto Protocol to use along with raw request (default: https) | |
217 | -w Wordlist file path and (optional) keyword separated by colon. eg. '/path/to/wordlist:KEYWORD' | |
210 | -D DirSearch wordlist compatibility mode. Used in conjunction with -e flag. (default: false) | |
211 | -e Comma separated list of extensions. Extends FUZZ keyword. | |
212 | -ic Ignore wordlist comments (default: false) | |
213 | -input-cmd Command producing the input. --input-num is required when using this input method. Overrides -w. | |
214 | -input-num Number of inputs to test. Used in conjunction with --input-cmd. (default: 100) | |
215 | -input-shell Shell to be used for running command | |
216 | -mode Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork (default: clusterbomb) | |
217 | -request File containing the raw http request | |
218 | -request-proto Protocol to use along with raw request (default: https) | |
219 | -w Wordlist file path and (optional) keyword separated by colon. eg. '/path/to/wordlist:KEYWORD' | |
218 | 220 | |
219 | 221 | OUTPUT OPTIONS: |
220 | -debug-log Write all of the internal logging to the specified file. | |
221 | -o Write output to file | |
222 | -od Directory path to store matched results to. | |
223 | -of Output file format. Available formats: json, ejson, html, md, csv, ecsv (or, 'all' for all formats) (default: json) | |
224 | -or Don't create the output file if we don't have results | |
222 | -debug-log Write all of the internal logging to the specified file. | |
223 | -o Write output to file | |
224 | -od Directory path to store matched results to. | |
225 | -of Output file format. Available formats: json, ejson, html, md, csv, ecsv (or, 'all' for all formats) (default: json) | |
226 | -or Don't create the output file if we don't have results (default: false) | |
225 | 227 | |
226 | 228 | EXAMPLE USAGE: |
227 | 229 | Fuzz file paths from wordlist.txt, match all responses but filter out those with content-size 42. |
260 | 262 | queueskip - advance to the next queued recursion job |
261 | 263 | restart - restart and resume the current ffuf job |
262 | 264 | resume - resume current ffuf job (or: ENTER) |
263 | show - show results | |
265 | show - show results for the current job | |
264 | 266 | savejson [filename] - save current matches to a file |
265 | 267 | help - you are looking at it |
266 | 268 | > |
0 | ffuf (1.3.1-0kali1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Kali Janitor <[email protected]> Thu, 29 Apr 2021 19:58:57 -0000 | |
5 | ||
0 | 6 | ffuf (1.3.0-0kali1) kali-dev; urgency=medium |
1 | 7 | |
2 | 8 | [ Kali Janitor ] |
30 | 30 | delay = "" |
31 | 31 | maxtime = 0 |
32 | 32 | maxtimejob = 0 |
33 | noninteractive = false | |
33 | 34 | quiet = false |
34 | 35 | rate = 0 |
35 | 36 | stopon403 = false |
60 | 60 | Description: "", |
61 | 61 | Flags: make([]UsageFlag, 0), |
62 | 62 | Hidden: false, |
63 | ExpectedFlags: []string{"ac", "acc", "c", "config", "maxtime", "maxtime-job", "p", "rate", "s", "sa", "se", "sf", "t", "v", "V"}, | |
63 | ExpectedFlags: []string{"ac", "acc", "c", "config", "maxtime", "maxtime-job", "noninteractive", "p", "rate", "s", "sa", "se", "sf", "t", "v", "V"}, | |
64 | 64 | } |
65 | 65 | u_compat := UsageSection{ |
66 | 66 | Name: "COMPATIBILITY OPTIONS", |
60 | 60 | flag.BoolVar(&opts.Output.OutputCreateEmptyFile, "or", opts.Output.OutputCreateEmptyFile, "Don't create the output file if we don't have results") |
61 | 61 | flag.BoolVar(&opts.General.AutoCalibration, "ac", opts.General.AutoCalibration, "Automatically calibrate filtering options") |
62 | 62 | flag.BoolVar(&opts.General.Colors, "c", opts.General.Colors, "Colorize output.") |
63 | flag.BoolVar(&opts.General.Noninteractive, "noninteractive", opts.General.Noninteractive, "Disable the interactive console functionality") | |
63 | 64 | flag.BoolVar(&opts.General.Quiet, "s", opts.General.Quiet, "Do not print additional information (silent mode)") |
64 | 65 | flag.BoolVar(&opts.General.ShowVersion, "V", opts.General.ShowVersion, "Show version information.") |
65 | 66 | flag.BoolVar(&opts.General.StopOn403, "sf", opts.General.StopOn403, "Stop when > 95% of responses return 403 Forbidden") |
197 | 198 | fmt.Fprintf(os.Stderr, "Error in autocalibration, exiting: %s\n", err) |
198 | 199 | os.Exit(1) |
199 | 200 | } |
200 | go func() { | |
201 | err := interactive.Handle(job) | |
202 | if err != nil { | |
203 | log.Printf("Error while trying to initialize interactive session: %s", err) | |
204 | } | |
205 | }() | |
201 | if !conf.Noninteractive { | |
202 | go func() { | |
203 | err := interactive.Handle(job) | |
204 | if err != nil { | |
205 | log.Printf("Error while trying to initialize interactive session: %s", err) | |
206 | } | |
207 | }() | |
208 | } | |
206 | 209 | |
207 | 210 | // Job handles waiting for goroutines to complete itself |
208 | 211 | job.Start() |
29 | 29 | MaxTime int `json:"maxtime"` |
30 | 30 | MaxTimeJob int `json:"maxtime_job"` |
31 | 31 | Method string `json:"method"` |
32 | Noninteractive bool `json:"noninteractive"` | |
32 | 33 | OutputDirectory string `json:"outputdirectory"` |
33 | 34 | OutputFile string `json:"outputfile"` |
34 | 35 | OutputFormat string `json:"outputformat"` |
78 | 79 | conf.MaxTime = 0 |
79 | 80 | conf.MaxTimeJob = 0 |
80 | 81 | conf.Method = "GET" |
82 | conf.Noninteractive = false | |
81 | 83 | conf.ProgressFrequency = 125 |
82 | 84 | conf.ProxyURL = "" |
83 | 85 | conf.Quiet = false |
45 | 45 | Result(resp Response) |
46 | 46 | PrintResult(res Result) |
47 | 47 | SaveFile(filename, format string) error |
48 | GetResults() []Result | |
49 | SetResults(results []Result) | |
48 | GetCurrentResults() []Result | |
49 | SetCurrentResults(results []Result) | |
50 | 50 | Reset() |
51 | Cycle() | |
51 | 52 | } |
52 | 53 | |
53 | 54 | type Result struct { |
122 | 122 | j.interruptMonitor() |
123 | 123 | for j.jobsInQueue() { |
124 | 124 | j.prepareQueueJob() |
125 | j.Reset() | |
125 | j.Reset(true) | |
126 | 126 | j.RunningJob = true |
127 | 127 | j.startExecution() |
128 | 128 | } |
134 | 134 | } |
135 | 135 | |
136 | 136 | // Reset resets the counters and wordlist position for a job |
137 | func (j *Job) Reset() { | |
137 | func (j *Job) Reset(cycle bool) { | |
138 | 138 | j.Input.Reset() |
139 | 139 | j.Counter = 0 |
140 | 140 | j.skipQueue = false |
141 | 141 | j.startTimeJob = time.Now() |
142 | j.Output.Reset() | |
142 | if cycle { | |
143 | j.Output.Cycle() | |
144 | } else { | |
145 | j.Output.Reset() | |
146 | } | |
143 | 147 | } |
144 | 148 | |
145 | 149 | func (j *Job) jobsInQueue() bool { |
48 | 48 | Delay string |
49 | 49 | MaxTime int |
50 | 50 | MaxTimeJob int |
51 | Noninteractive bool | |
51 | 52 | Quiet bool |
52 | 53 | Rate int |
53 | 54 | ShowVersion bool `toml:"-"` |
108 | 109 | c.General.Delay = "" |
109 | 110 | c.General.MaxTime = 0 |
110 | 111 | c.General.MaxTimeJob = 0 |
112 | c.General.Noninteractive = false | |
111 | 113 | c.General.Quiet = false |
112 | 114 | c.General.Rate = 0 |
113 | 115 | c.General.ShowVersion = false |
254 | 256 | // except if used in custom defined header |
255 | 257 | var CanonicalNeeded = true |
256 | 258 | for _, a := range conf.CommandKeywords { |
257 | if a == hs[0] { | |
259 | if strings.Contains(hs[0], a) { | |
258 | 260 | CanonicalNeeded = false |
259 | 261 | } |
260 | 262 | } |
261 | 263 | // check if part of InputProviders |
262 | 264 | if CanonicalNeeded { |
263 | 265 | for _, b := range conf.InputProviders { |
264 | if b.Keyword == hs[0] { | |
266 | if strings.Contains(hs[0], b.Keyword) { | |
265 | 267 | CanonicalNeeded = false |
266 | 268 | } |
267 | 269 | } |
394 | 396 | conf.Timeout = parseOpts.HTTP.Timeout |
395 | 397 | conf.MaxTime = parseOpts.General.MaxTime |
396 | 398 | conf.MaxTimeJob = parseOpts.General.MaxTimeJob |
399 | conf.Noninteractive = parseOpts.General.Noninteractive | |
397 | 400 | conf.Verbose = parseOpts.General.Verbose |
398 | 401 | |
399 | 402 | // Handle copy as curl situation where POST method is implied by --data flag. If method is set to anything but GET, NOOP |
485 | 488 | } |
486 | 489 | conf.Data = string(b) |
487 | 490 | |
491 | // Remove newline (typically added by the editor) at the end of the file | |
492 | if strings.HasSuffix(conf.Data, "\r\n") { | |
493 | conf.Data = conf.Data[:len(conf.Data)-2] | |
494 | } else if strings.HasSuffix(conf.Data, "\n") { | |
495 | conf.Data = conf.Data[:len(conf.Data)-1] | |
496 | } | |
488 | 497 | return nil |
489 | 498 | } |
490 | 499 |
1 | 1 | |
2 | 2 | var ( |
3 | 3 | //VERSION holds the current version number |
4 | VERSION = "1.3.0" | |
4 | VERSION = "1.3.1" | |
5 | 5 | //VERSION_APPENDIX holds additional version definition |
6 | 6 | VERSION_APPENDIX = "-exclusive-dev" |
7 | 7 | ) |
52 | 52 | i.paused = false |
53 | 53 | i.Job.Resume() |
54 | 54 | case "restart": |
55 | i.Job.Reset() | |
55 | i.Job.Reset(false) | |
56 | 56 | i.paused = false |
57 | 57 | i.Job.Output.Info("Restarting the current ffuf job!") |
58 | 58 | i.Job.Resume() |
59 | 59 | case "show": |
60 | for _, r := range i.Job.Output.GetResults() { | |
60 | for _, r := range i.Job.Output.GetCurrentResults() { | |
61 | 61 | i.Job.Output.PrintResult(r) |
62 | 62 | } |
63 | 63 | case "savejson": |
149 | 149 | } |
150 | 150 | |
151 | 151 | results := make([]ffuf.Result, 0) |
152 | for _, res := range i.Job.Output.GetResults() { | |
152 | for _, res := range i.Job.Output.GetCurrentResults() { | |
153 | 153 | fakeResp := &ffuf.Response{ |
154 | 154 | StatusCode: res.StatusCode, |
155 | 155 | ContentLines: res.ContentLength, |
161 | 161 | results = append(results, res) |
162 | 162 | } |
163 | 163 | } |
164 | i.Job.Output.SetResults(results) | |
164 | i.Job.Output.SetCurrentResults(results) | |
165 | 165 | } |
166 | 166 | } |
167 | 167 | |
228 | 228 | queueskip - advance to the next queued recursion job |
229 | 229 | restart - restart and resume the current ffuf job |
230 | 230 | resume - resume current ffuf job (or: ENTER) |
231 | show - show results | |
231 | show - show results for the current job | |
232 | 232 | savejson [filename] - save current matches to a file |
233 | 233 | help - you are looking at it |
234 | 234 | ` |
25 | 25 | ) |
26 | 26 | |
27 | 27 | type Stdoutput struct { |
28 | config *ffuf.Config | |
29 | Results []ffuf.Result | |
28 | config *ffuf.Config | |
29 | Results []ffuf.Result | |
30 | CurrentResults []ffuf.Result | |
30 | 31 | } |
31 | 32 | |
32 | 33 | func NewStdoutput(conf *ffuf.Config) *Stdoutput { |
33 | 34 | var outp Stdoutput |
34 | 35 | outp.config = conf |
35 | outp.Results = []ffuf.Result{} | |
36 | outp.Results = make([]ffuf.Result, 0) | |
37 | outp.CurrentResults = make([]ffuf.Result, 0) | |
36 | 38 | return &outp |
37 | 39 | } |
38 | 40 | |
132 | 134 | |
133 | 135 | // Reset resets the result slice |
134 | 136 | func (s *Stdoutput) Reset() { |
135 | s.Results = make([]ffuf.Result, 0) | |
137 | s.CurrentResults = make([]ffuf.Result, 0) | |
138 | } | |
139 | ||
140 | // Cycle moves the CurrentResults to Results and resets the results slice | |
141 | func (s *Stdoutput) Cycle() { | |
142 | s.Results = append(s.Results, s.CurrentResults...) | |
143 | s.Reset() | |
136 | 144 | } |
137 | 145 | |
138 | 146 | // GetResults returns the result slice |
139 | func (s *Stdoutput) GetResults() []ffuf.Result { | |
140 | return s.Results | |
147 | func (s *Stdoutput) GetCurrentResults() []ffuf.Result { | |
148 | return s.CurrentResults | |
141 | 149 | } |
142 | 150 | |
143 | 151 | // SetResults sets the result slice |
144 | func (s *Stdoutput) SetResults(results []ffuf.Result) { | |
145 | s.Results = results | |
152 | func (s *Stdoutput) SetCurrentResults(results []ffuf.Result) { | |
153 | s.CurrentResults = results | |
146 | 154 | } |
147 | 155 | |
148 | 156 | func (s *Stdoutput) Progress(status ffuf.Progress) { |
221 | 229 | } |
222 | 230 | |
223 | 231 | s.config.OutputFile = BaseFilename + ".json" |
224 | err = writeJSON(filename, s.config, s.Results) | |
232 | err = writeJSON(filename, s.config, res) | |
225 | 233 | if err != nil { |
226 | 234 | s.Error(err.Error()) |
227 | 235 | } |
228 | 236 | |
229 | 237 | s.config.OutputFile = BaseFilename + ".ejson" |
230 | err = writeEJSON(filename, s.config, s.Results) | |
238 | err = writeEJSON(filename, s.config, res) | |
231 | 239 | if err != nil { |
232 | 240 | s.Error(err.Error()) |
233 | 241 | } |
234 | 242 | |
235 | 243 | s.config.OutputFile = BaseFilename + ".html" |
236 | err = writeHTML(filename, s.config, s.Results) | |
244 | err = writeHTML(filename, s.config, res) | |
237 | 245 | if err != nil { |
238 | 246 | s.Error(err.Error()) |
239 | 247 | } |
240 | 248 | |
241 | 249 | s.config.OutputFile = BaseFilename + ".md" |
242 | err = writeMarkdown(filename, s.config, s.Results) | |
250 | err = writeMarkdown(filename, s.config, res) | |
243 | 251 | if err != nil { |
244 | 252 | s.Error(err.Error()) |
245 | 253 | } |
246 | 254 | |
247 | 255 | s.config.OutputFile = BaseFilename + ".csv" |
248 | err = writeCSV(filename, s.config, s.Results, false) | |
256 | err = writeCSV(filename, s.config, res, false) | |
249 | 257 | if err != nil { |
250 | 258 | s.Error(err.Error()) |
251 | 259 | } |
252 | 260 | |
253 | 261 | s.config.OutputFile = BaseFilename + ".ecsv" |
254 | err = writeCSV(filename, s.config, s.Results, true) | |
262 | err = writeCSV(filename, s.config, res, true) | |
255 | 263 | if err != nil { |
256 | 264 | s.Error(err.Error()) |
257 | 265 | } |
265 | 273 | var err error |
266 | 274 | switch format { |
267 | 275 | case "all": |
268 | err = s.writeToAll(filename, s.config, s.Results) | |
276 | err = s.writeToAll(filename, s.config, append(s.Results, s.CurrentResults...)) | |
269 | 277 | case "json": |
270 | err = writeJSON(filename, s.config, s.Results) | |
278 | err = writeJSON(filename, s.config, append(s.Results, s.CurrentResults...)) | |
271 | 279 | case "ejson": |
272 | err = writeEJSON(filename, s.config, s.Results) | |
280 | err = writeEJSON(filename, s.config, append(s.Results, s.CurrentResults...)) | |
273 | 281 | case "html": |
274 | err = writeHTML(filename, s.config, s.Results) | |
282 | err = writeHTML(filename, s.config, append(s.Results, s.CurrentResults...)) | |
275 | 283 | case "md": |
276 | err = writeMarkdown(filename, s.config, s.Results) | |
284 | err = writeMarkdown(filename, s.config, append(s.Results, s.CurrentResults...)) | |
277 | 285 | case "csv": |
278 | err = writeCSV(filename, s.config, s.Results, false) | |
286 | err = writeCSV(filename, s.config, append(s.Results, s.CurrentResults...), false) | |
279 | 287 | case "ecsv": |
280 | err = writeCSV(filename, s.config, s.Results, true) | |
288 | err = writeCSV(filename, s.config, append(s.Results, s.CurrentResults...), true) | |
281 | 289 | } |
282 | 290 | return err |
283 | 291 | } |
318 | 326 | ResultFile: resp.ResultFile, |
319 | 327 | Host: resp.Request.Host, |
320 | 328 | } |
321 | s.Results = append(s.Results, sResult) | |
329 | s.CurrentResults = append(s.CurrentResults, sResult) | |
322 | 330 | // Output the result |
323 | 331 | s.PrintResult(sResult) |
324 | 332 | } |
327 | 335 | var fileContent, fileName, filePath string |
328 | 336 | // Create directory if needed |
329 | 337 | if s.config.OutputDirectory != "" { |
330 | err := os.Mkdir(s.config.OutputDirectory, 0750) | |
338 | err := os.MkdirAll(s.config.OutputDirectory, 0750) | |
331 | 339 | if err != nil { |
332 | 340 | if !os.IsExist(err) { |
333 | 341 | s.Error(err.Error()) |
12 | 12 | "strconv" |
13 | 13 | "strings" |
14 | 14 | "time" |
15 | "unicode/utf8" | |
16 | 15 | |
17 | 16 | "github.com/ffuf/ffuf/pkg/ffuf" |
18 | 17 | ) |
146 | 145 | } |
147 | 146 | |
148 | 147 | if respbody, err := ioutil.ReadAll(httpresp.Body); err == nil { |
149 | resp.ContentLength = int64(utf8.RuneCountInString(string(respbody))) | |
148 | resp.ContentLength = int64(len(string(respbody))) | |
150 | 149 | resp.Data = respbody |
151 | 150 | } |
152 | 151 |