├── 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 |

FFUF Report

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 | 206 | {{ range .Keys }} {{ end }} 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 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 | 228 | {{ range $keyword, $value := $result.Input }} 229 | 230 | {{ end }} 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | {{ end }} 244 | 245 |
Status{{ . }}URLRedirect locationPositionLengthWordsLinesTypeDurationResultfileScraper dataFfuf Hash
{{ $result.StatusCode }}{{ $value | printf "%s" }}{{ $result.Url }}{{ $result.RedirectLocation }}{{ $result.Position }}{{ $result.ContentLength }}{{ $result.ContentWords }}{{ $result.ContentLines }}{{ $result.ContentType }}{{ $result.Duration }}{{ $result.ResultFile }}{{ $result.ScraperData }}{{ $result.FfufHash }}
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 | --------------------------------------------------------------------------------