├── LICENSE
├── README.md
├── ffuf-sample.json
├── ffufsee
└── main.go
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 fyoorer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ffufsee
2 |
3 | Browse Ffuf's JSON output as a neatly formatted HTML report in the comfort of your browser.
4 |
5 | But... but... Ffuf already has HTML output flag available!? I know, I am using the same code 😁
6 |
7 | ffufsee was created for specific use cases:
8 | 1. You have the JSON file generated by Ffuf and you want to generate HTML report. As far as I know, Ffuf does not have this option.
9 | 2. You are like me and run distributed Ffuf using [ShadowClone](https://github.com/fyoorer/ShadowClone) so generating hundreds of HTML reports does not make sense. JSON files can be combined easily!
10 |
11 | ## Installation & Usage
12 | ```bash
13 | git clone https://github.com/fyoorer/ffufsee.git
14 | cd ffufsee
15 | go run main.go ~/path/to/your/ffuf-output.json
16 | ```
17 |
18 | Or you can build a binary and execute from anywhere
19 | ```bash
20 | go build .
21 | cp ffufsee /usr/local/bin/
22 | ffufsee ~/path/to/your/ffuf-output.json
23 | ```
24 |
25 | Open browser and visit `http://localhost:5505`
26 |
27 | ## Note!
28 | The Ffuf JSON file needs to be created by Ffuf v2.0.0 (current version). JSON files generated by older Ffuf versions are not supported, sorry!
29 |
30 |
--------------------------------------------------------------------------------
/ffuf-sample.json:
--------------------------------------------------------------------------------
1 | {"commandline":"ffuf -w /home/aditya/tools/wordlists/SecLists/Discovery/Web-Content/raft-small-files.txt -of json -o ff2.json -u http://testphp.vulnweb.com/FUZZ","time":"2023-02-24T00:33:56-05:00","results":[{"input":{"FFUFHASH":"c6d5a2","FUZZ":"search.php"},"position":2,"status":200,"length":4732,"words":482,"lines":104,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":77043277,"resultfile":"","url":"http://testphp.vulnweb.com/search.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a1","FUZZ":"index.php"},"position":1,"status":200,"length":4958,"words":514,"lines":110,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":75398081,"resultfile":"","url":"http://testphp.vulnweb.com/index.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a4","FUZZ":"login.php"},"position":4,"status":200,"length":5523,"words":557,"lines":120,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":75488200,"resultfile":"","url":"http://testphp.vulnweb.com/login.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a38","FUZZ":"cart.php"},"position":56,"status":200,"length":4903,"words":502,"lines":109,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":75531541,"resultfile":"","url":"http://testphp.vulnweb.com/cart.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a66","FUZZ":"favicon.ico"},"position":102,"status":200,"length":894,"words":2,"lines":4,"content-type":"image/x-icon","redirectlocation":"","scraper":{},"duration":70633835,"resultfile":"","url":"http://testphp.vulnweb.com/favicon.ico","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a90","FUZZ":"product.php"},"position":144,"status":200,"length":5056,"words":490,"lines":111,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":71284736,"resultfile":"","url":"http://testphp.vulnweb.com/product.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a98","FUZZ":"comment.php"},"position":152,"status":302,"length":1246,"words":125,"lines":39,"content-type":"text/html; charset=UTF-8","redirectlocation":"./index.php","scraper":{},"duration":71786197,"resultfile":"","url":"http://testphp.vulnweb.com/comment.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a94","FUZZ":"logout.php"},"position":148,"status":200,"length":4830,"words":492,"lines":107,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":72236201,"resultfile":"","url":"http://testphp.vulnweb.com/logout.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a9a","FUZZ":"404.php"},"position":154,"status":200,"length":5267,"words":529,"lines":112,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":71715574,"resultfile":"","url":"http://testphp.vulnweb.com/404.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5aa3","FUZZ":"style.css"},"position":163,"status":200,"length":5482,"words":510,"lines":324,"content-type":"text/css","redirectlocation":"","scraper":{},"duration":120052086,"resultfile":"","url":"http://testphp.vulnweb.com/style.css","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a15e","FUZZ":"signup.php"},"position":350,"status":200,"length":6033,"words":547,"lines":122,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":70400848,"resultfile":"","url":"http://testphp.vulnweb.com/signup.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a173","FUZZ":"."},"position":371,"status":200,"length":4958,"words":514,"lines":110,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":99916849,"resultfile":"","url":"http://testphp.vulnweb.com/.","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a1af","FUZZ":"disclaimer.php"},"position":431,"status":200,"length":5524,"words":574,"lines":115,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":73466708,"resultfile":"","url":"http://testphp.vulnweb.com/disclaimer.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a1bd","FUZZ":"crossdomain.xml"},"position":445,"status":200,"length":224,"words":8,"lines":5,"content-type":"text/xml","redirectlocation":"","scraper":{},"duration":75492999,"resultfile":"","url":"http://testphp.vulnweb.com/crossdomain.xml","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a220","FUZZ":"categories.php"},"position":544,"status":200,"length":6115,"words":656,"lines":117,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":82575163,"resultfile":"","url":"http://testphp.vulnweb.com/categories.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a2de","FUZZ":"guestbook.php"},"position":734,"status":200,"length":5390,"words":515,"lines":113,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":112576485,"resultfile":"","url":"http://testphp.vulnweb.com/guestbook.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a41a","FUZZ":"userinfo.php"},"position":1050,"status":302,"length":14,"words":3,"lines":1,"content-type":"text/html; charset=UTF-8","redirectlocation":"login.php","scraper":{},"duration":71979439,"resultfile":"","url":"http://testphp.vulnweb.com/userinfo.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a7c8","FUZZ":"showimage.php"},"position":1992,"status":200,"length":0,"words":1,"lines":1,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":70626110,"resultfile":"","url":"http://testphp.vulnweb.com/showimage.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a1d04","FUZZ":"artists.php"},"position":7428,"status":200,"length":5328,"words":503,"lines":105,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":72460111,"resultfile":"","url":"http://testphp.vulnweb.com/artists.php","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a2cb2","FUZZ":"vendor/"},"position":11442,"status":200,"length":268,"words":60,"lines":8,"content-type":"text/html","redirectlocation":"","scraper":{},"duration":71721475,"resultfile":"","url":"http://testphp.vulnweb.com/vendor/","host":"testphp.vulnweb.com"},{"input":{"FFUFHASH":"c6d5a2cea","FUZZ":"?q=admin"},"position":11498,"status":200,"length":4958,"words":514,"lines":110,"content-type":"text/html; charset=UTF-8","redirectlocation":"","scraper":{},"duration":98206572,"resultfile":"","url":"http://testphp.vulnweb.com/?q=admin","host":"testphp.vulnweb.com"}],"config":{"autocalibration":false,"autocalibration_keyword":"FUZZ","autocalibration_perhost":false,"autocalibration_strategy":"basic","autocalibration_strings":[],"colors":false,"cmdline":"ffuf -w /home/aditya/tools/wordlists/SecLists/Discovery/Web-Content/raft-small-files.txt -of json -o ff2.json -u http://testphp.vulnweb.com/FUZZ","configfile":"","postdata":"","debuglog":"","delay":{"value":"0.00"},"dirsearch_compatibility":false,"extensions":[],"fmode":"or","follow_redirects":false,"headers":{},"ignorebody":false,"ignore_wordlist_comments":false,"inputmode":"clusterbomb","cmd_inputnum":100,"inputproviders":[{"name":"wordlist","keyword":"FUZZ","value":"/home/aditya/tools/wordlists/SecLists/Discovery/Web-Content/raft-small-files.txt","template":""}],"inputshell":"","json":false,"matchers":{"IsCalibrated":false,"Mutex":{},"Matchers":{"status":{"value":"200,204,301,302,307,401,403,405,500"}},"Filters":{},"PerDomainFilters":{}},"mmode":"or","maxtime":0,"maxtime_job":0,"method":"GET","noninteractive":false,"outputdirectory":"","outputfile":"ff2.json","outputformat":"json","OutputSkipEmptyFile":false,"proxyurl":"","quiet":false,"rate":0,"recursion":false,"recursion_depth":0,"recursion_strategy":"default","replayproxyurl":"","requestfile":"","requestproto":"https","scraperfile":"","scrapers":"all","sni":"","stop_403":false,"stop_all":false,"stop_errors":false,"threads":40,"timeout":10,"url":"http://testphp.vulnweb.com/FUZZ","verbose":false,"wordlists":["/home/aditya/tools/wordlists/SecLists/Discovery/Web-Content/raft-small-files.txt"],"http2":false}}
--------------------------------------------------------------------------------
/ffufsee:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fyoorer/ffufsee/c9c2344650eba35ec1623ed4f75cb058e702d16e/ffufsee
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "html"
8 | "html/template"
9 | "io/ioutil"
10 | "net/http"
11 | "os"
12 | "time"
13 |
14 | "github.com/ffuf/ffuf/pkg/ffuf"
15 | )
16 |
17 | type PerDomainFilter struct {
18 | IsCalibrated bool
19 | Filters map[string]ffuf.FilterProvider
20 | }
21 |
22 | type optRange struct {
23 | Min float64
24 | Max float64
25 | IsRange bool
26 | HasDelay bool
27 | }
28 |
29 | type Config struct {
30 | AutoCalibration bool `json:"autocalibration"`
31 | AutoCalibrationKeyword string `json:"autocalibration_keyword"`
32 | AutoCalibrationPerHost bool `json:"autocalibration_perhost"`
33 | AutoCalibrationStrategy string `json:"autocalibration_strategy"`
34 | AutoCalibrationStrings []string `json:"autocalibration_strings"`
35 | Cancel context.CancelFunc `json:"-"`
36 | Colors bool `json:"colors"`
37 | CommandKeywords []string `json:"-"`
38 | CommandLine string `json:"cmdline"`
39 | ConfigFile string `json:"configfile"`
40 | Context context.Context `json:"-"`
41 | Data string `json:"postdata"`
42 | Debuglog string `json:"debuglog"`
43 | Delay optRange `json:"delay"`
44 | DirSearchCompat bool `json:"dirsearch_compatibility"`
45 | Extensions []string `json:"extensions"`
46 | FilterMode string `json:"fmode"`
47 | FollowRedirects bool `json:"follow_redirects"`
48 | Headers map[string]string `json:"headers"`
49 | IgnoreBody bool `json:"ignorebody"`
50 | IgnoreWordlistComments bool `json:"ignore_wordlist_comments"`
51 | InputMode string `json:"inputmode"`
52 | InputNum int `json:"cmd_inputnum"`
53 | InputProviders []InputProviderConfig `json:"inputproviders"`
54 | InputShell string `json:"inputshell"`
55 | Json bool `json:"json"`
56 | Matchers struct {
57 | IsCalibrated bool `json:"IsCalibrated"`
58 | Mutex struct {
59 | } `json:"Mutex"`
60 | Matchers struct {
61 | Status struct {
62 | Value string `json:"value"`
63 | } `json:"status"`
64 | } `json:"Matchers"`
65 | Filters struct {
66 | } `json:"Filters"`
67 | PerDomainFilters struct {
68 | } `json:"PerDomainFilters"`
69 | } `json:"matchers"`
70 | MatcherMode string `json:"mmode"`
71 | MaxTime int `json:"maxtime"`
72 | MaxTimeJob int `json:"maxtime_job"`
73 | Method string `json:"method"`
74 | Noninteractive bool `json:"noninteractive"`
75 | OutputDirectory string `json:"outputdirectory"`
76 | OutputFile string `json:"outputfile"`
77 | OutputFormat string `json:"outputformat"`
78 | OutputSkipEmptyFile bool `json:"OutputSkipEmptyFile"`
79 | ProgressFrequency int `json:"-"`
80 | ProxyURL string `json:"proxyurl"`
81 | Quiet bool `json:"quiet"`
82 | Rate int64 `json:"rate"`
83 | Recursion bool `json:"recursion"`
84 | RecursionDepth int `json:"recursion_depth"`
85 | RecursionStrategy string `json:"recursion_strategy"`
86 | ReplayProxyURL string `json:"replayproxyurl"`
87 | RequestFile string `json:"requestfile"`
88 | RequestProto string `json:"requestproto"`
89 | ScraperFile string `json:"scraperfile"`
90 | Scrapers string `json:"scrapers"`
91 | SNI string `json:"sni"`
92 | StopOn403 bool `json:"stop_403"`
93 | StopOnAll bool `json:"stop_all"`
94 | StopOnErrors bool `json:"stop_errors"`
95 | Threads int `json:"threads"`
96 | Timeout int `json:"timeout"`
97 | Url string `json:"url"`
98 | Verbose bool `json:"verbose"`
99 | Wordlists []string `json:"wordlists"`
100 | Http2 bool `json:"http2"`
101 | }
102 |
103 | type InputProviderConfig struct {
104 | Name string `json:"name"`
105 | Keyword string `json:"keyword"`
106 | Value string `json:"value"`
107 | Template string `json:"template"` // the templating string used for sniper mode (usually "§")
108 | }
109 |
110 | type JsonResult struct {
111 | Input map[string]string `json:"input"`
112 | Position int `json:"position"`
113 | StatusCode int64 `json:"status"`
114 | ContentLength int64 `json:"length"`
115 | ContentWords int64 `json:"words"`
116 | ContentLines int64 `json:"lines"`
117 | ContentType string `json:"content-type"`
118 | RedirectLocation string `json:"redirectlocation"`
119 | ScraperData map[string][]string `json:"scraper"`
120 | Duration time.Duration `json:"duration"`
121 | ResultFile string `json:"resultfile"`
122 | Url string `json:"url"`
123 | Host string `json:"host"`
124 | HTMLColor string `json:"-"`
125 | }
126 |
127 | type htmlResult struct {
128 | Input map[string]string
129 | Position int
130 | StatusCode int64
131 | ContentLength int64
132 | ContentWords int64
133 | ContentLines int64
134 | ContentType string
135 | RedirectLocation string
136 | ScraperData string
137 | Duration time.Duration
138 | ResultFile string
139 | Url string
140 | Host string
141 | HTMLColor string
142 | FfufHash string
143 | }
144 |
145 | type htmlFileOutput struct {
146 | CommandLine string
147 | Time string
148 | Keys []string
149 | Results []htmlResult
150 | }
151 |
152 | const (
153 | htmlTemplate = `
154 |
155 |
156 |
157 |
158 |
162 | FFUF Report -
163 |
164 |
165 |
169 |
173 |
178 |
179 |
180 |
181 |
182 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
{{ .CommandLine }}
197 |
{{ .Time }}
198 |
199 |
200 |
201 |
202 | |result_raw|StatusCode{{ range $keyword := .Keys }}|{{ $keyword | printf "%s" }}{{ end }}|Url|RedirectLocation|Position|ContentLength|ContentWords|ContentLines|ContentType|Duration|Resultfile|ScraperData|FfufHash|
203 |
204 |
205 | Status |
206 | {{ range .Keys }} {{ . }} | {{ end }}
207 | URL |
208 | Redirect location |
209 | Position |
210 | Length |
211 | Words |
212 | Lines |
213 | Type |
214 | Duration |
215 | Resultfile |
216 | Scraper data |
217 | Ffuf Hash |
218 |
219 |
220 |
221 |
222 | {{range $result := .Results}}
223 |
224 | |result_raw|{{ $result.StatusCode }}{{ range $keyword, $value := $result.Input }}|{{ $value | printf "%s" }}{{ end }}|{{ $result.Url }}|{{ $result.RedirectLocation }}|{{ $result.Position }}|{{ $result.ContentLength }}|{{ $result.ContentWords }}|{{ $result.ContentLines }}|{{ $result.ContentType }}|{{ $result.Duration }}|{{ $result.ResultFile }}|{{ $result.ScraperData }}|{{ $result.FfufHash }}|
225 |
226 |
227 | {{ $result.StatusCode }} |
228 | {{ range $keyword, $value := $result.Input }}
229 | {{ $value | printf "%s" }} |
230 | {{ end }}
231 | {{ $result.Url }} |
232 | {{ $result.RedirectLocation }} |
233 | {{ $result.Position }} |
234 | {{ $result.ContentLength }} |
235 | {{ $result.ContentWords }} |
236 | {{ $result.ContentLines }} |
237 | {{ $result.ContentType }} |
238 | {{ $result.Duration }} |
239 | {{ $result.ResultFile }} |
240 | {{ $result.ScraperData }} |
241 | {{ $result.FfufHash }} |
242 |
243 | {{ end }}
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
269 |
280 |
281 |
282 |
283 | `
284 | )
285 |
286 | // colorizeResults returns a new slice with HTMLColor attribute
287 | func colorizeResults(results []JsonResult) []JsonResult {
288 | newResults := make([]JsonResult, 0)
289 |
290 | for _, r := range results {
291 | result := r
292 | result.HTMLColor = "black"
293 |
294 | s := result.StatusCode
295 |
296 | if s >= 200 && s <= 299 {
297 | result.HTMLColor = "#adea9e"
298 | }
299 |
300 | if s >= 300 && s <= 399 {
301 | result.HTMLColor = "#bbbbe6"
302 | }
303 |
304 | if s >= 400 && s <= 499 {
305 | result.HTMLColor = "#d2cb7e"
306 | }
307 |
308 | if s >= 500 && s <= 599 {
309 | result.HTMLColor = "#de8dc1"
310 | }
311 |
312 | newResults = append(newResults, result)
313 | }
314 |
315 | return newResults
316 | }
317 |
318 | func writeHTML(config Config, results []JsonResult) error {
319 | results = colorizeResults(results)
320 |
321 | ti := time.Now()
322 |
323 | keywords := make([]string, 0)
324 | for _, inputprovider := range config.InputProviders {
325 | keywords = append(keywords, inputprovider.Keyword)
326 | }
327 | htmlResults := make([]htmlResult, 0)
328 |
329 | for _, r := range results {
330 | ffufhash := ""
331 | strinput := make(map[string]string)
332 | for k, v := range r.Input {
333 | if k == "FFUFHASH" {
334 | ffufhash = string(v)
335 | } else {
336 | strinput[k] = string(v)
337 | }
338 | }
339 | strscraper := ""
340 | for k, v := range r.ScraperData {
341 | if len(v) > 0 {
342 | strscraper = strscraper + "" + html.EscapeString(k) + ":
"
343 | firstval := true
344 | for _, val := range v {
345 | if !firstval {
346 | strscraper += "
"
347 | }
348 | strscraper += html.EscapeString(val)
349 | firstval = false
350 | }
351 | strscraper += "
"
352 | }
353 | }
354 | hres := htmlResult{
355 | Input: strinput,
356 | Position: r.Position,
357 | StatusCode: r.StatusCode,
358 | ContentLength: r.ContentLength,
359 | ContentWords: r.ContentWords,
360 | ContentLines: r.ContentLines,
361 | ContentType: r.ContentType,
362 | RedirectLocation: r.RedirectLocation,
363 | ScraperData: strscraper,
364 | Duration: r.Duration,
365 | ResultFile: r.ResultFile,
366 | Url: r.Url,
367 | Host: r.Host,
368 | HTMLColor: r.HTMLColor,
369 | FfufHash: ffufhash,
370 | }
371 | htmlResults = append(htmlResults, hres)
372 | }
373 | outHTML := htmlFileOutput{
374 | Time: ti.Format(time.RFC3339),
375 | Results: htmlResults,
376 | CommandLine: config.CommandLine,
377 | Keys: keywords,
378 | }
379 |
380 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
381 | templateName := "index"
382 | t := template.New(templateName).Delims("{{", "}}")
383 | _, err := t.Parse(htmlTemplate)
384 | if err != nil {
385 | fmt.Println("Failed to parse HTML template:", err)
386 | return
387 | }
388 | err = t.ExecuteTemplate(w, "index", outHTML)
389 |
390 | })
391 |
392 | // Start the server
393 | fmt.Println("Starting server...")
394 | fmt.Println("View the report in the browser at http://localhost:5505")
395 | err := http.ListenAndServe(":5505", nil)
396 | return err
397 | }
398 |
399 | func main() {
400 | // Check if path to the JSON file is provided
401 | if len(os.Args) < 2 {
402 | fmt.Println("Please provide the path to the JSON file as a command line argument")
403 | return
404 | }
405 | path := os.Args[1]
406 |
407 | // Load the JSON data from file
408 | file, err := ioutil.ReadFile(path)
409 | if err != nil {
410 | fmt.Println("Failed to load JSON file:", err)
411 | return
412 | }
413 |
414 | // Unmarshal the JSON into a map[string]json.RawMessage
415 | var data map[string]json.RawMessage
416 | err = json.Unmarshal(file, &data)
417 | if err != nil {
418 | fmt.Println("Error unmarshalling JSON:", err)
419 | return
420 | }
421 |
422 | // Parse the JSON data
423 | var ffufResult []JsonResult
424 | err = json.Unmarshal(data["results"], &ffufResult)
425 | if err != nil {
426 | fmt.Println("Failed to parse JSON into ffuf result structure:", err)
427 | return
428 | }
429 |
430 | var ffufConfig Config
431 | err = json.Unmarshal(data["config"], &ffufConfig)
432 | if err != nil {
433 | fmt.Println("Failed to parse JSON into ffuf config structure:", err)
434 | return
435 | }
436 |
437 | err = writeHTML(ffufConfig, ffufResult)
438 | if err != nil {
439 | fmt.Println("Something went wrong:", err)
440 | return
441 | }
442 |
443 | }
444 |
--------------------------------------------------------------------------------