Compare commits

..

No commits in common. "1cd61646ffdc4e682c0827c6684f332d6233ffc5" and "e3e4e6250d1f541168a400bdaa48e111e435c3ec" have entirely different histories.

16 changed files with 38 additions and 364 deletions

View File

@ -1,26 +1,16 @@
## Changelog ## Changelog
- master - master
- New
- Changed
- Fix a bug in autocalibration strategy merging, when two files have the same strategy key
- Fix panic when setting rate to 0 in the interactive console
- v2.1.0
- New - New
- autocalibration-strategy refactored to support extensible strategy configuration - autocalibration-strategy refactored to support extensible strategy configuration
- New cli flag `-raw` to omit urlencoding for URIs - New cli flag `-raw` to omit urlencoding for URIs
- New cli flags `-ck` and `-cc` to enable the use of client side certificate authentication
- Integration with `github.com/ffuf/pencode` library, added `-enc` cli flag to do various in-fly encodings for input data - Integration with `github.com/ffuf/pencode` library, added `-enc` cli flag to do various in-fly encodings for input data
- Changed - Changed
- Fix multiline output
- Explicitly allow TLS1.0 - Explicitly allow TLS1.0
- Fix markdown output file format - Fix markdown output file format
- Fix csv output file format - Fix csv output file format
- Fixed divide by 0 error when setting rate limit to 0 manually. - Fixed divide by 0 error when setting rate limit to 0 manually.
- Automatic brotli and deflate decompression - Automatic brotli and deflate decompression
- Report if request times out when a time based matcher or filter is active - Report if request times out when a time based matcher or filter is active
- All 2XX status codes are now matched
- Allow adding "unused" wordlists in config file
- v2.0.0 - v2.0.0
- New - New

View File

@ -150,19 +150,16 @@ parameter.
To define the test case for ffuf, use the keyword `FUZZ` anywhere in the URL (`-u`), headers (`-H`), or POST data (`-d`). To define the test case for ffuf, use the keyword `FUZZ` anywhere in the URL (`-u`), headers (`-H`), or POST data (`-d`).
``` ```
Fuzz Faster U Fool - v2.1.0 Fuzz Faster U Fool - v2.0.0
HTTP OPTIONS: HTTP OPTIONS:
-H Header `"Name: Value"`, separated by colon. Multiple -H flags are accepted. -H Header `"Name: Value"`, separated by colon. Multiple -H flags are accepted.
-X HTTP method to use -X HTTP method to use
-b Cookie data `"NAME1=VALUE1; NAME2=VALUE2"` for copy as curl functionality. -b Cookie data `"NAME1=VALUE1; NAME2=VALUE2"` for copy as curl functionality.
-cc Client cert for authentication. Client key needs to be defined as well for this to work
-ck Client key for authentication. Client certificate needs to be defined as well for this to work
-d POST data -d POST data
-http2 Use HTTP2 protocol (default: false) -http2 Use HTTP2 protocol (default: false)
-ignore-body Do not fetch the response content. (default: false) -ignore-body Do not fetch the response content. (default: false)
-r Follow redirects (default: false) -r Follow redirects (default: false)
-raw Do not encode URI (default: false)
-recursion Scan recursively. Only FUZZ keyword is supported, and URL (-u) has to end in it. (default: false) -recursion Scan recursively. Only FUZZ keyword is supported, and URL (-u) has to end in it. (default: false)
-recursion-depth Maximum recursion depth. (default: 0) -recursion-depth Maximum recursion depth. (default: 0)
-recursion-strategy Recursion strategy: "default" for a redirect based, and "greedy" to recurse on all matches (default: default) -recursion-strategy Recursion strategy: "default" for a redirect based, and "greedy" to recurse on all matches (default: default)
@ -178,7 +175,7 @@ GENERAL OPTIONS:
-acc Custom auto-calibration string. Can be used multiple times. Implies -ac -acc Custom auto-calibration string. Can be used multiple times. Implies -ac
-ach Per host autocalibration (default: false) -ach Per host autocalibration (default: false)
-ack Autocalibration keyword (default: FUZZ) -ack Autocalibration keyword (default: FUZZ)
-acs Custom auto-calibration strategies. Can be used multiple times. Implies -ac -acs Autocalibration strategy: "basic" or "advanced" (default: basic)
-c Colorize output. (default: false) -c Colorize output. (default: false)
-config Load configuration from a file -config Load configuration from a file
-json JSON output, printing newline-delimited JSON records (default: false) -json JSON output, printing newline-delimited JSON records (default: false)
@ -198,7 +195,7 @@ GENERAL OPTIONS:
-v Verbose output, printing full URL and redirect location (if any) with the results. (default: false) -v Verbose output, printing full URL and redirect location (if any) with the results. (default: false)
MATCHER OPTIONS: MATCHER OPTIONS:
-mc Match HTTP status codes, or "all" for everything. (default: 200-299,301,302,307,401,403,405,500) -mc Match HTTP status codes, or "all" for everything. (default: 200,204,301,302,307,401,403,405,500)
-ml Match amount of lines in response -ml Match amount of lines in response
-mmode Matcher set operator. Either of: and, or (default: or) -mmode Matcher set operator. Either of: and, or (default: or)
-mr Match regexp -mr Match regexp
@ -218,7 +215,6 @@ FILTER OPTIONS:
INPUT OPTIONS: INPUT OPTIONS:
-D DirSearch wordlist compatibility mode. Used in conjunction with -e flag. (default: false) -D DirSearch wordlist compatibility mode. Used in conjunction with -e flag. (default: false)
-e Comma separated list of extensions. Extends FUZZ keyword. -e Comma separated list of extensions. Extends FUZZ keyword.
-enc Encoders for keywords, eg. 'FUZZ:urlencode b64encode'
-ic Ignore wordlist comments (default: false) -ic Ignore wordlist comments (default: false)
-input-cmd Command producing the input. --input-num is required when using this input method. Overrides -w. -input-cmd Command producing the input. --input-num is required when using this input method. Overrides -w.
-input-num Number of inputs to test. Used in conjunction with --input-cmd. (default: 100) -input-num Number of inputs to test. Used in conjunction with --input-cmd. (default: 100)

View File

@ -1,22 +0,0 @@
{ pkgs ? (
let
inherit (builtins) fetchTree fromJSON readFile;
inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix;
in
import (fetchTree nixpkgs.locked) {
overlays = [
(import "${fetchTree gomod2nix.locked}/overlay.nix")
];
}
)
, buildGoApplication ? pkgs.buildGoApplication
}:
buildGoApplication {
# TODO: Change name to ffuff to avoid conflicts
pname = "ffuf";
version = "0.1";
pwd = ./.;
src = ./.;
modules = ./gomod2nix.toml;
}

View File

@ -1,85 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gomod2nix": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1733668782,
"narHash": "sha256-tPsqU00FhgdFr0JiQUiBMgPVbl1jbPCY5gbFiJycL3I=",
"owner": "nix-community",
"repo": "gomod2nix",
"rev": "514283ec89c39ad0079ff2f3b1437404e4cba608",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "gomod2nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1738546358,
"narHash": "sha256-nLivjIygCiqLp5QcL7l56Tca/elVqM9FG1hGd9ZSsrg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c6e957d81b96751a3d5967a0fd73694f303cc914",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,29 +0,0 @@
{
description = "A basic gomod2nix flake";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.gomod2nix.url = "github:nix-community/gomod2nix";
inputs.gomod2nix.inputs.nixpkgs.follows = "nixpkgs";
inputs.gomod2nix.inputs.flake-utils.follows = "flake-utils";
outputs = { self, nixpkgs, flake-utils, gomod2nix }:
(flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = nixpkgs.legacyPackages.${system};
# The current default sdk for macOS fails to compile go projects, so we use a newer one for now.
# This has no effect on other platforms.
callPackage = pkgs.darwin.apple_sdk_11_0.callPackage or pkgs.callPackage;
in
{
packages.default = callPackage ./. {
inherit (gomod2nix.legacyPackages.${system}) buildGoApplication;
};
devShells.default = callPackage ./shell.nix {
inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix;
};
})
);
}

View File

@ -1,30 +0,0 @@
schema = 3
[mod]
[mod."github.com/PuerkitoBio/goquery"]
version = "v1.8.0"
hash = "sha256-I3QaPWATvBOL/F26fIiYWKS13yBUYo+9o3tcsGIu8tY="
[mod."github.com/adrg/xdg"]
version = "v0.4.0"
hash = "sha256-zGjkdUQmrVqD6rMO9oDY+TeJCpuqnHyvkPCaXDlac/U="
[mod."github.com/andybalholm/brotli"]
version = "v1.0.5"
hash = "sha256-/qS8wU8yZQJ+uTOg66rEl9s7spxq9VIXF5L1BcaEClc="
[mod."github.com/andybalholm/cascadia"]
version = "v1.3.1"
hash = "sha256-M0u22DXSeXUaYtl1KoW1qWL46niFpycFkraCEQ/luYA="
[mod."github.com/davecgh/go-spew"]
version = "v1.1.1"
hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI="
[mod."github.com/ffuf/pencode"]
version = "v0.0.0-20230421231718-2cea7e60a693"
hash = "sha256-/ysCCAHXmBBQC8MojiRbmbZSWo5gUE3u9AuCV24ul18="
[mod."github.com/pelletier/go-toml"]
version = "v1.9.5"
hash = "sha256-RJ9K1BTId0Mled7S66iGgxHkZ5JKEIsrrNaEfM8aImc="
[mod."golang.org/x/net"]
version = "v0.7.0"
hash = "sha256-LgZYZRwtMqm+soNh+esxDSeRuIDxRGb9OEfYaFJHCDI="
[mod."golang.org/x/sys"]
version = "v0.5.0"
hash = "sha256-0LTr3KeJ1OMQAwYUQo1513dXJtQAJn5Dq8sFkc8ps1U="

View File

@ -51,7 +51,7 @@ func (m *wordlistFlag) Set(value string) error {
func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions { func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions {
var ignored bool var ignored bool
var cookies, autocalibrationstrings, autocalibrationstrategies, headers, inputcommands multiStringFlag var cookies, autocalibrationstrings, autocalibrationstrategies, headers, inputcommands multiStringFlag
var wordlists, encoders wordlistFlag var wordlists, encoders wordlistFlag
cookies = opts.HTTP.Cookies cookies = opts.HTTP.Cookies
@ -144,7 +144,7 @@ func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions {
opts.General.AutoCalibrationStrings = autocalibrationstrings opts.General.AutoCalibrationStrings = autocalibrationstrings
if len(autocalibrationstrategies) > 0 { if len(autocalibrationstrategies) > 0 {
opts.General.AutoCalibrationStrategies = []string{} opts.General.AutoCalibrationStrategies = []string {}
for _, strategy := range autocalibrationstrategies { for _, strategy := range autocalibrationstrategies {
opts.General.AutoCalibrationStrategies = append(opts.General.AutoCalibrationStrategies, strings.Split(strategy, ",")...) opts.General.AutoCalibrationStrategies = append(opts.General.AutoCalibrationStrategies, strings.Split(strategy, ",")...)
} }

View File

@ -1,14 +1,14 @@
package ffuf package ffuf
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
"os"
"path/filepath"
"strconv" "strconv"
"time" "time"
"encoding/json"
"path/filepath"
"os"
) )
type AutocalibrationStrategy map[string][]string type AutocalibrationStrategy map[string][]string
@ -20,9 +20,9 @@ func (j *Job) autoCalibrationStrings() map[string][]string {
if len(j.Config.AutoCalibrationStrings) > 0 { if len(j.Config.AutoCalibrationStrings) > 0 {
cInputs["custom"] = append(cInputs["custom"], j.Config.AutoCalibrationStrings...) cInputs["custom"] = append(cInputs["custom"], j.Config.AutoCalibrationStrings...)
return cInputs return cInputs
} }
for _, strategy := range j.Config.AutoCalibrationStrategies { for _, strategy := range j.Config.AutoCalibrationStrategies {
jsonStrategy, err := os.ReadFile(filepath.Join(AUTOCALIBDIR, strategy+".json")) jsonStrategy, err := os.ReadFile(filepath.Join(AUTOCALIBDIR, strategy+".json"))
if err != nil { if err != nil {
@ -36,36 +36,36 @@ func (j *Job) autoCalibrationStrings() map[string][]string {
j.Output.Warning(fmt.Sprintf("Skipping strategy \"%s\" because of error: %s\n", strategy, err)) j.Output.Warning(fmt.Sprintf("Skipping strategy \"%s\" because of error: %s\n", strategy, err))
continue continue
} }
cInputs = mergeMaps(cInputs, tmpStrategy) cInputs = mergeMaps(cInputs, tmpStrategy)
} }
return cInputs return cInputs
} }
func setupDefaultAutocalibrationStrategies() error { func setupDefaultAutocalibrationStrategies() error {
basic_strategy := AutocalibrationStrategy{ basic_strategy := AutocalibrationStrategy {
"basic_admin": []string{"admin" + RandomString(16), "admin" + RandomString(8)}, "basic_admin": []string{"admin"+RandomString(16), "admin"+RandomString(8)},
"htaccess": []string{".htaccess" + RandomString(16), ".htaccess" + RandomString(8)}, "htaccess": []string{".htaccess"+RandomString(16), ".htaccess"+RandomString(8)},
"basic_random": []string{RandomString(16), RandomString(8)}, "basic_random": []string{RandomString(16), RandomString(8)},
} }
basic_strategy_json, err := json.Marshal(basic_strategy) basic_strategy_json, err := json.Marshal(basic_strategy)
if err != nil { if err != nil {
return err return err
} }
advanced_strategy := AutocalibrationStrategy{ advanced_strategy := AutocalibrationStrategy {
"basic_admin": []string{"admin" + RandomString(16), "admin" + RandomString(8)}, "basic_admin": []string{"admin"+RandomString(16), "admin"+RandomString(8)},
"htaccess": []string{".htaccess" + RandomString(16), ".htaccess" + RandomString(8)}, "htaccess": []string{".htaccess"+RandomString(16), ".htaccess"+RandomString(8)},
"basic_random": []string{RandomString(16), RandomString(8)}, "basic_random": []string{RandomString(16), RandomString(8)},
"admin_dir": []string{"admin" + RandomString(16) + "/", "admin" + RandomString(8) + "/"}, "admin_dir": []string{"admin"+RandomString(16)+"/", "admin"+RandomString(8)+"/"},
"random_dir": []string{RandomString(16) + "/", RandomString(8) + "/"}, "random_dir": []string{RandomString(16)+"/", RandomString(8)+"/"},
} }
advanced_strategy_json, err := json.Marshal(advanced_strategy) advanced_strategy_json, err := json.Marshal(advanced_strategy)
if err != nil { if err != nil {
return err return err
} }
basic_strategy_file := filepath.Join(AUTOCALIBDIR, "basic.json") basic_strategy_file := filepath.Join(AUTOCALIBDIR, "basic.json")
if !FileExists(basic_strategy_file) { if !FileExists(basic_strategy_file) {
err = os.WriteFile(filepath.Join(AUTOCALIBDIR, "basic.json"), basic_strategy_json, 0640) err = os.WriteFile(filepath.Join(AUTOCALIBDIR, "basic.json"), basic_strategy_json, 0640)
@ -76,7 +76,7 @@ func setupDefaultAutocalibrationStrategies() error {
err = os.WriteFile(filepath.Join(AUTOCALIBDIR, "advanced.json"), advanced_strategy_json, 0640) err = os.WriteFile(filepath.Join(AUTOCALIBDIR, "advanced.json"), advanced_strategy_json, 0640)
return err return err
} }
return nil return nil
} }

View File

@ -1,108 +0,0 @@
package ffuf
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
// NullOutput is a dummy output provider that does nothing
type NullOutput struct {
Results []Result
}
func NewNullOutput() *NullOutput { return &NullOutput{} }
func (o *NullOutput) Banner() {}
func (o *NullOutput) Finalize() error { return nil }
func (o *NullOutput) Progress(status Progress) {}
func (o *NullOutput) Info(infostring string) {}
func (o *NullOutput) Error(errstring string) {}
func (o *NullOutput) Raw(output string) {}
func (o *NullOutput) Warning(warnstring string) {}
func (o *NullOutput) Result(resp Response) {}
func (o *NullOutput) PrintResult(res Result) {}
func (o *NullOutput) SaveFile(filename, format string) error { return nil }
func (o *NullOutput) GetCurrentResults() []Result { return o.Results }
func (o *NullOutput) SetCurrentResults(results []Result) { o.Results = results }
func (o *NullOutput) Reset() {}
func (o *NullOutput) Cycle() {}
func TestAutoCalibrationStrings(t *testing.T) {
// Create a temporary directory for the test
tmpDir, err := os.MkdirTemp("", "ffuf-test")
AUTOCALIBDIR = tmpDir
if err != nil {
t.Fatalf("Failed to create temporary directory: %v", err)
}
defer os.RemoveAll(tmpDir)
// Create a test strategy file
strategy := AutocalibrationStrategy{
"test": {"foo", "bar"},
}
strategyJSON, err := json.Marshal(strategy)
if err != nil {
t.Fatalf("Failed to marshal strategy to JSON: %v", err)
}
strategyFile := filepath.Join(tmpDir, "test.json")
err = os.WriteFile(strategyFile, strategyJSON, 0644)
if err != nil {
t.Fatalf("Failed to write strategy file: %v", err)
}
// Create a test job with the strategy
job := &Job{
Config: &Config{
AutoCalibrationStrategies: []string{"test"},
},
Output: NewNullOutput(),
}
cInputs := job.autoCalibrationStrings()
// Verify that the custom strategy was added
if len(cInputs["custom"]) != 0 {
t.Errorf("Expected custom strategy to be empty, but got %v", cInputs["custom"])
}
// Verify that the test strategy was added
expected := []string{"foo", "bar"}
if len(cInputs["test"]) != len(expected) {
t.Errorf("Expected test strategy to have %d inputs, but got %d", len(expected), len(cInputs["test"]))
}
for i, input := range cInputs["test"] {
if input != expected[i] {
t.Errorf("Expected test strategy input %d to be %q, but got %q", i, expected[i], input)
}
}
// Verify that a missing strategy is skipped
job = &Job{
Config: &Config{
AutoCalibrationStrategies: []string{"missing"},
},
Output: NewNullOutput(),
}
cInputs = job.autoCalibrationStrings()
if len(cInputs) != 0 {
t.Errorf("Expected missing strategy to be skipped, but got %v", cInputs)
}
// Verify that a malformed strategy is skipped
malformedStrategy := []byte(`{"test": "foo"}`)
malformedFile := filepath.Join(tmpDir, "malformed.json")
err = os.WriteFile(malformedFile, malformedStrategy, 0644)
if err != nil {
t.Fatalf("Failed to write malformed strategy file: %v", err)
}
job = &Job{
Config: &Config{
AutoCalibrationStrategies: []string{"malformed"},
},
Output: NewNullOutput(),
}
cInputs = job.autoCalibrationStrings()
if len(cInputs) != 0 {
t.Errorf("Expected malformed strategy to be skipped, but got %v", cInputs)
}
}

View File

@ -7,7 +7,7 @@ import (
var ( var (
//VERSION holds the current version number //VERSION holds the current version number
VERSION = "2.1.0" VERSION = "2.0.0"
//VERSION_APPENDIX holds additional version definition //VERSION_APPENDIX holds additional version definition
VERSION_APPENDIX = "-dev" VERSION_APPENDIX = "-dev"
CONFIGDIR = filepath.Join(xdg.ConfigHome, "ffuf") CONFIGDIR = filepath.Join(xdg.ConfigHome, "ffuf")

View File

@ -347,7 +347,6 @@ func (j *Job) isMatch(resp Response) bool {
} }
if match { if match {
matched = true matched = true
fmt.Printf("%s\n", resp.Data)
} else if j.Config.MatcherMode == "and" { } else if j.Config.MatcherMode == "and" {
// we already know this isn't "and" match // we already know this isn't "and" match
return false return false

View File

@ -170,7 +170,7 @@ func NewConfigOptions() *ConfigOptions {
c.Matcher.Lines = "" c.Matcher.Lines = ""
c.Matcher.Regexp = "" c.Matcher.Regexp = ""
c.Matcher.Size = "" c.Matcher.Size = ""
c.Matcher.Status = "200-299,301,302,307,401,403,405,500" c.Matcher.Status = "200,204,301,302,307,401,403,405,500"
c.Matcher.Time = "" c.Matcher.Time = ""
c.Matcher.Words = "" c.Matcher.Words = ""
c.Output.DebugLog = "" c.Output.DebugLog = ""
@ -373,6 +373,7 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con
conf.ClientKey = parseOpts.HTTP.ClientKey conf.ClientKey = parseOpts.HTTP.ClientKey
} }
//Prepare headers and make canonical //Prepare headers and make canonical
for _, v := range parseOpts.HTTP.Headers { for _, v := range parseOpts.HTTP.Headers {
hs := strings.SplitN(v, ":", 2) hs := strings.SplitN(v, ":", 2)

View File

@ -19,13 +19,15 @@ func NewRateThrottle(conf *Config) *RateThrottle {
Config: conf, Config: conf,
lastAdjustment: time.Now(), lastAdjustment: time.Now(),
} }
if conf.Rate > 0 { if conf.Rate > 0 {
r.rateCounter = ring.New(int(conf.Rate * 5)) r.rateCounter = ring.New(int(conf.Rate * 5))
} else {
r.rateCounter = ring.New(conf.Threads * 5)
}
if conf.Rate > 0 {
ratemicros := 1000000 / conf.Rate ratemicros := 1000000 / conf.Rate
r.RateLimiter = time.NewTicker(time.Microsecond * time.Duration(ratemicros)) r.RateLimiter = time.NewTicker(time.Microsecond * time.Duration(ratemicros))
} else { } else {
r.rateCounter = ring.New(conf.Threads * 5)
//Million rps is probably a decent hardcoded upper speedlimit //Million rps is probably a decent hardcoded upper speedlimit
r.RateLimiter = time.NewTicker(time.Microsecond * 1) r.RateLimiter = time.NewTicker(time.Microsecond * 1)
} }
@ -70,17 +72,10 @@ func (r *RateThrottle) ChangeRate(rate int) {
} }
r.RateLimiter.Stop() r.RateLimiter.Stop()
if rate > 0 { r.RateLimiter = time.NewTicker(time.Microsecond * time.Duration(ratemicros))
r.RateLimiter = time.NewTicker(time.Microsecond * time.Duration(ratemicros))
// reset the rate counter
r.rateCounter = ring.New(rate * 5)
} else {
r.RateLimiter = time.NewTicker(time.Microsecond * 1)
// reset the rate counter
r.rateCounter = ring.New(r.Config.Threads * 5)
}
r.Config.Rate = int64(rate) r.Config.Rate = int64(rate)
// reset the rate counter
r.rateCounter = ring.New(rate * 5)
} }
// rateTick adds a new duration measurement tick to rate counter // rateTick adds a new duration measurement tick to rate counter

View File

@ -131,16 +131,7 @@ func mergeMaps(m1 map[string][]string, m2 map[string][]string) map[string][]stri
merged[k] = v merged[k] = v
} }
for key, value := range m2 { for key, value := range m2 {
if _, ok := merged[key]; !ok { merged[key] = value
// Key not found, add it
merged[key] = value
continue
}
for _, entry := range value {
if !StrInSlice(entry, merged[key]) {
merged[key] = append(merged[key], entry)
}
}
} }
return merged return merged
} }

View File

@ -10,7 +10,7 @@ import (
func TestToCSV(t *testing.T) { func TestToCSV(t *testing.T) {
result := ffuf.Result{ result := ffuf.Result{
Input: map[string][]byte{"x": {66}, "FFUFHASH": {65}}, Input: map[string][]byte{"x": {66}},
Position: 1, Position: 1,
StatusCode: 200, StatusCode: 200,
ContentLength: 3, ContentLength: 3,
@ -37,8 +37,8 @@ func TestToCSV(t *testing.T) {
"5", "5",
"application/json", "application/json",
"123ns", "123ns",
"resultfile", "resultfile"}) {
"A"}) {
t.Errorf("CSV was not generated in expected format") t.Errorf("CSV was not generated in expected format")
} }
} }

View File

@ -1,24 +0,0 @@
{ pkgs ? (
let
inherit (builtins) fetchTree fromJSON readFile;
inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix;
in
import (fetchTree nixpkgs.locked) {
overlays = [
(import "${fetchTree gomod2nix.locked}/overlay.nix")
];
}
)
, mkGoEnv ? pkgs.mkGoEnv
, gomod2nix ? pkgs.gomod2nix
}:
let
goEnv = mkGoEnv { pwd = ./.; };
in
pkgs.mkShell {
packages = [
goEnv
gomod2nix
];
}