Compare commits

...

74 Commits

Author SHA1 Message Date
epi
2af87971d5 fixed build script when on cd pipeline 2021-06-15 11:46:20 -05:00
epi
e6753d9474 fixed build script when on cd pipeline 2021-06-15 11:45:35 -05:00
epi
d23717dc6c troubleshooting build script 2021-06-15 11:32:00 -05:00
epi
4debe68ed6 updated pipeline build 2021-06-15 11:28:46 -05:00
epi
e6b78e3986 Merge pull request #292 from epi052/287-always-parse-help-parameter
added small check for help param to always print and exit
2021-06-15 09:22:34 -07:00
epi
7b268cf197 bumped version to 2.2.5 2021-06-15 11:22:14 -05:00
epi
34ff884d52 added tests for new help param catcher 2021-06-15 11:02:43 -05:00
epi
7fef23f888 added small check for help param to always print and exit 2021-06-15 10:28:20 -05:00
epi
7a8d6d0d52 fixed build when config copied on cd 2021-06-15 10:14:59 -05:00
epi
6d4f2a7ed9 Update build.yml 2021-06-15 10:14:16 -05:00
epi
329d04252f config file is dropped to disk when installing via cargo 2021-06-15 08:00:21 -05:00
epi
9b4092ea8c added plusdirs to bash completion script and regenerated it 2021-06-15 06:37:03 -05:00
epi
d942a7705a bumped various lib versions 2021-06-14 20:09:22 -05:00
epi
e3365b42a2 Merge branch 'main' of github.com:epi052/feroxbuster 2021-06-14 20:08:11 -05:00
epi
41689bd742 added verify command 2021-06-14 20:07:03 -05:00
epi
bc487475f0 Update pull_request_template.md 2021-06-14 20:00:19 -05:00
epi
393e775285 satisfied clippy 2021-05-08 16:10:01 -05:00
epi
cf6c02307c bumped version to 2.2.4 2021-05-08 16:01:23 -05:00
epi
88b9bc3a01 Merge pull request #270 from epi052/268-cancel-scan-by-range
updated scan cancel input to support comma and range delimited values
2021-05-08 15:57:28 -05:00
epi
d1f90efb09 bumped lib versions 2021-05-05 06:12:50 -05:00
epi
df4fad07a9 Merge branch 'main' into 268-cancel-scan-by-range 2021-05-05 06:06:19 -05:00
epi
56d533117e updated docs with new cancel scan info 2021-05-05 05:56:54 -05:00
epi
9549e27f19 updated cancel menu footer with description about -f 2021-04-20 11:57:56 -05:00
epi
1677b51c2d reverted workflow file 2021-04-20 11:43:05 -05:00
epi
d4f9442d38 examining codecov env 2021-04-20 11:05:17 -05:00
epi
8191fa1a5e updated scan cancel input to support comma and range delimd values 2021-04-20 07:39:11 -05:00
epi
4811b37aa4 Merge pull request #267 from epi052/restyled/pull-266
check for unzip before continuing
2021-04-15 05:15:59 -05:00
Restyled.io
941cad5844 Restyled by shfmt 2021-04-14 17:37:28 +00:00
Restyled.io
d59af94f62 Restyled by shellharden 2021-04-14 17:37:26 +00:00
Craig
cf403c4d4a check for unzip before continuing 2021-04-14 19:35:24 +02:00
epi
57a2b1cbab bumped ctrlc, tokio, reqwest, tokio-util, and futures 2021-04-14 05:59:35 -05:00
epi
ef195bd653 Merge branch 'main' of github.com:epi052/feroxbuster into main 2021-04-01 06:01:45 -05:00
epi
9b1a24bca3 updated libs; fixed new clippy errors 2021-04-01 06:00:44 -05:00
epi
c6aefbfa97 Merge pull request #249 from noraj/patch-1
add blackarch install description
2021-03-20 14:06:35 -05:00
Alexandre ZANNI
42bad85208 add blackarch install 2021-03-19 16:29:27 +01:00
epi
f5709739fa Merge pull request #247 from epi052/dependabot/cargo/regex-1.4.5
Bump regex from 1.4.4 to 1.4.5
2021-03-17 05:59:12 -05:00
epi
248f56ed7a Merge pull request #246 from epi052/dependabot/cargo/console-0.14.1
Bump console from 0.14.0 to 0.14.1
2021-03-17 05:58:55 -05:00
dependabot[bot]
3de6ed9696 Bump regex from 1.4.4 to 1.4.5
Bumps [regex](https://github.com/rust-lang/regex) from 1.4.4 to 1.4.5.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.4.4...1.4.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-15 07:59:58 +00:00
dependabot[bot]
4bad39f4b9 Bump console from 0.14.0 to 0.14.1
Bumps [console](https://github.com/mitsuhiko/console) from 0.14.0 to 0.14.1.
- [Release notes](https://github.com/mitsuhiko/console/releases)
- [Changelog](https://github.com/mitsuhiko/console/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/console/compare/0.14.0...0.14.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-15 07:59:47 +00:00
epi
9b303d8b5a update dependencies 2021-03-14 07:29:47 -05:00
epi
7e0b003216 Merge branch 'main' of github.com:epi052/feroxbuster into main 2021-03-14 07:22:30 -05:00
epi
dc36a7bf4d updated deendencies 2021-03-14 07:22:16 -05:00
epi
d33632c421 update ko-fi 2021-03-13 07:05:01 -06:00
epi
7dc6a867a5 update ko-fi 2021-03-13 07:04:31 -06:00
epi
b937a0191e Create FUNDING.yml 2021-03-13 06:24:03 -06:00
epi
d57a83956c Merge pull request #237 from epi052/235-add-arm-build
added support for arm builds
2021-03-05 11:10:09 -06:00
epi
71efd78f03 fixed docstrings for Stats 2021-03-05 11:04:00 -06:00
epi
139006d0a7 added aarch64 to the build matrix 2021-03-05 11:03:36 -06:00
epi
b5abb8b6e8 ci build for aarch64 works, restricting to main again 2021-03-05 10:49:13 -06:00
epi
a076a333df updated README for arm installs 2021-03-05 10:31:56 -06:00
epi
461ed0a9ff added aarch64 build to ci 2021-03-05 10:24:57 -06:00
epi
4381569a0f ci build works, restricting to main again 2021-03-05 09:59:48 -06:00
epi
a52bd10340 added arm specific strip command 2021-03-05 09:34:23 -06:00
epi
56a1144865 testing ci build of armv7 2021-03-05 09:21:00 -06:00
epi
23ab009c08 added linker flag for arm builds 2021-03-05 09:14:43 -06:00
epi
fa4e3d5d88 added linker flag for arm builds 2021-03-05 09:13:10 -06:00
epi
ad7a1ffe44 added custom Stats::serialize to accomodated arm builds 2021-03-05 09:05:51 -06:00
epi
0e4f8893f8 added custom Stats::deserialize to accomodated arm builds 2021-03-05 08:25:33 -06:00
epi
8e0b801ec5 bumped version to 2.3.3 2021-03-05 06:55:00 -06:00
epi
97889f917d Merge pull request #234 from epi052/233-ordered-wordlist
changed wordlist read so that ordering is maintained
2021-03-01 16:48:54 -06:00
epi
cedb3ccc8d wordlist order is now maintained 2021-03-01 16:38:59 -06:00
epi
d7cfd8ff60 bumped version to 2.2.2 2021-03-01 16:17:05 -06:00
epi
223e75923d updated 3 libs, added command completion to .deb build 2021-03-01 06:18:47 -06:00
epi
dd9f2f72c0 added Kali install to readme 2021-02-27 07:01:46 -06:00
epi
8ffea2500d Merge pull request #228 from epi052/fix-makefile-for-kali-repos
Fix makefile for kali repos
2021-02-23 07:44:58 -06:00
epi
5ed890e3fd bumped futures to 0.3.13 2021-02-23 07:34:44 -06:00
epi
8fe458263d fixed bash completion location 2021-02-23 07:30:04 -06:00
epi
6de36585a9 Merge pull request #224 from bsysop/patch-1
Update README.md
2021-02-22 17:09:08 -06:00
epi
30538c366c makefile works for kali 2021-02-22 16:48:42 -06:00
bsysop
89a0ac8aa4 Update README.md
FFUF supports SOCKS, it's just not documented yet =]

`ffuf -x socks5://127.0.0.1:1234`
2021-02-22 13:34:41 -03:00
epi
c9a93f2843 changed filter status emoji 2021-02-21 14:46:47 -06:00
epi
bfdb4abdce changed filter status emoji 2021-02-21 14:37:04 -06:00
epi
eb17eeecd3 bumped reqwest version to 0.11.1 2021-02-21 11:49:34 -06:00
epi
c2819ef2e7 Update README.md 2021-02-18 13:24:40 -06:00
28 changed files with 991 additions and 603 deletions

5
.cargo/config Normal file
View File

@@ -0,0 +1,5 @@
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

4
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
# These are supported funding model platforms
github: [epi052]
ko_fi: epi052

View File

@@ -11,7 +11,7 @@ Long form explanations of most of the items below can be found in the [CONTRIBUT
## Static analysis checks
- [ ] All rust files are formatted using `cargo fmt`
- [ ] All `clippy` checks pass when running `cargo clippy --all-targets --all-features -- -D warnings -A clippy::deref_addrof -A clippy::mutex-atomic`
- [ ] All `clippy` checks pass when running `cargo clippy --all-targets --all-features -- -D warnings -A clippy::mutex-atomic`
- [ ] All existing tests pass
## Documentation

View File

@@ -4,11 +4,13 @@ on: [push]
jobs:
build-nix:
env:
IN_PIPELINE: true
runs-on: ${{ matrix.os }}
if: github.ref == 'refs/heads/main'
strategy:
matrix:
type: [ubuntu-x64, ubuntu-x86]
type: [ubuntu-x64, ubuntu-x86, armv7, aarch64]
include:
- type: ubuntu-x64
os: ubuntu-latest
@@ -22,12 +24,25 @@ jobs:
name: x86-linux-feroxbuster
path: target/i686-unknown-linux-musl/release/feroxbuster
pkg_config_path: /usr/lib/i686-linux-gnu/pkgconfig
- type: armv7
os: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
name: armv7-feroxbuster
path: target/armv7-unknown-linux-gnueabihf/release/feroxbuster
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
- type: aarch64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
name: aarch64-feroxbuster
path: target/aarch64-unknown-linux-gnu/release/feroxbuster
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
steps:
- uses: actions/checkout@v2
- name: Install System Dependencies
run: |
env
sudo apt-get update
sudo apt-get install -y --no-install-recommends libssl-dev pkg-config
sudo apt-get install -y --no-install-recommends libssl-dev pkg-config gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
@@ -43,7 +58,7 @@ jobs:
args: --release --target=${{ matrix.target }}
- name: Strip symbols from binary
run: |
strip -s ${{ matrix.path }}
strip -s ${{ matrix.path }} || arm-linux-gnueabihf-strip -s ${{ matrix.path }} || aarch64-linux-gnu-strip -s ${{ matrix.path }}
- name: Build tar.gz for homebrew installs
if: matrix.type == 'ubuntu-x64'
run: |
@@ -72,6 +87,8 @@ jobs:
path: ./target/x86_64-unknown-linux-musl/debian/*
build-macos:
env:
IN_PIPELINE: true
runs-on: macos-latest
if: github.ref == 'refs/heads/main'
steps:
@@ -102,6 +119,8 @@ jobs:
path: x86_64-macos-feroxbuster.tar.gz
build-windows:
env:
IN_PIPELINE: true
runs-on: ${{ matrix.os }}
if: github.ref == 'refs/heads/main'
strategy:
@@ -134,4 +153,3 @@ jobs:
with:
name: ${{ matrix.name }}
path: ${{ matrix.path }}

762
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "feroxbuster"
version = "2.2.0"
version = "2.2.5"
authors = ["Ben 'epi' Risher <epibar052@gmail.com>"]
license = "MIT"
edition = "2018"
@@ -19,37 +19,38 @@ maintenance = { status = "actively-developed" }
clap = "2.33"
regex = "1"
lazy_static = "1.4"
dirs = "3.0"
[dependencies]
futures = { version = "0.3"}
tokio = { version = "1.2.0", features = ["full"] }
tokio-util = {version = "0.6.3", features = ["codec"]}
futures = { version = "0.3.14"}
tokio = { version = "1.6", features = ["full"] }
tokio-util = {version = "0.6.6", features = ["codec"]}
log = "0.4"
env_logger = "0.8.3"
env_logger = "0.8"
reqwest = { version = "0.11", features = ["socks"] }
clap = "2.33"
lazy_static = "1.4"
toml = "0.5"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0.62"
serde_json = "1.0.64"
uuid = { version = "0.8", features = ["v4"] }
indicatif = "0.15"
console = "0.14"
openssl = { version = "0.10", features = ["vendored"] }
dirs = "3.0"
regex = "1"
crossterm = "0.19"
rlimit = "0.5"
ctrlc = "3.1"
crossterm = "0.20"
rlimit = "0.5.4"
ctrlc = "3.1.9"
fuzzyhash = "0.2.1"
anyhow = "1.0"
leaky-bucket = "0.10.0"
[dev-dependencies]
tempfile = "3.1"
httpmock = "0.5.2"
assert_cmd = "1.0.3"
predicates = "1.0.7"
httpmock = "0.5.8"
assert_cmd = "1.0"
predicates = "1.0.8"
[profile.release]
lto = true
@@ -63,4 +64,7 @@ conf-files = ["/etc/feroxbuster/ferox-config.toml"]
assets = [
["target/release/feroxbuster", "/usr/bin/", "755"],
["ferox-config.toml.example", "/etc/feroxbuster/ferox-config.toml", "644"],
["shell_completions/feroxbuster.bash", "/usr/share/bash-completion/completions/feroxbuster.bash", "644"],
["shell_completions/feroxbuster.fish", "/usr/share/fish/completions/feroxbuster.fish", "644"],
["shell_completions/_feroxbuster", "/usr/share/zsh/vendor-completions/_feroxbuster", "644"],
]

View File

@@ -6,12 +6,16 @@ datarootdir = $(prefix)/share
datadir = $(datarootdir)
example_config = ferox-config.toml.example
config_file = ferox-config.toml
completion_dir = shell_completions
completion_prefix = $(completion_dir)/$(BIN)
BIN=feroxbuster
SHR_SOURCES = $(shell find src -type f -wholename '*src/*.rs') Cargo.toml Cargo.lock
RELEASE = debug
DEBUG ?= 0
ifeq (0,$(DEBUG))
ifeq (0, $(DEBUG))
ARGS = --release
RELEASE = release
endif
@@ -23,54 +27,52 @@ endif
TARGET = target/$(RELEASE)
.PHONY: all clean distclean install uninstall update
BIN=feroxbuster
DESKTOP=$(APPID).desktop
.PHONY: all clean install uninstall test update
all: cli
cli: $(TARGET)/$(BIN) $(TARGET)/$(BIN).1.gz $(SHR_SOURCES)
install: all install-cli
verify:
cargo fmt
cargo clippy --all-targets --all-features -- -D warnings -A clippy::mutex-atomic
cargo test
clean:
cargo clean
distclean: clean
rm -rf .cargo vendor Cargo.lock vendor.tar
vendor: vendor.tar
vendor.tar:
mkdir -p .cargo
cargo vendor | head -n -1 > .cargo/config
echo 'directory = "vendor"' >> .cargo/config
cargo vendor
tar pcf vendor.tar vendor
rm -rf vendor
install-cli: cli
install -Dm 0755 "$(TARGET)/$(BIN)" "$(DESTDIR)$(bindir)/$(BIN)"
install -Dm 0644 "$(completion_prefix).bash" "$(DESTDIR)/usr/share/bash-completion/completions/$(BIN).bash"
install -Dm 0644 "$(completion_prefix).fish" "$(DESTDIR)/usr/share/fish/completions/$(BIN).fish"
install -Dm 0644 "$(completion_dir)/_$(BIN)" "$(DESTDIR)/usr/share/zsh/vendor-completions/_$(BIN)"
install -sDm 0755 "$(TARGET)/$(BIN)" "$(DESTDIR)$(bindir)/$(BIN)"
install -Dm 0644 "$(TARGET)/$(BIN).1.gz" "$(DESTDIR)$(datadir)/man/man1/$(BIN).1.gz"
install -Dm 0644 "$(example_config)" "/etc/$(BIN)/$(config_File)"
install -Dm 0644 "$(example_config)" "$(DESTDIR)/etc/$(BIN)/$(config_file)"
install: all install-cli
uninstall-cli:
uninstall:
rm -f "$(DESTDIR)$(bindir)/$(BIN)"
rm -f "$(DESTDIR)$(datadir)/man/man1/$(BIN).1.gz"
rm -rf "/etc/$(BIN)/"
uninstall: uninstall-cli
update:
cargo update
rm -rf "$(DESTDIR)/etc/$(BIN)/"
rm -f "$(DESTDIR)/usr/share/bash-completion/completions/$(BIN).bash"
rm -f "$(DESTDIR)/usr/share/zsh/vendor-completions/_$(BIN)"
rm -f "$(DESTDIR)/usr/share/fish/completions/$(BIN).fish"
extract:
ifeq ($(VENDORED),1)
ifeq (1, $(VENDORED))
tar pxf vendor.tar
endif
$(TARGET)/$(BIN): extract
cargo build --manifest-path Cargo.toml $(ARGS)
mkdir -p .cargo
cp debian/cargo.config .cargo/config.toml
cargo build $(ARGS)
$(TARGET)/$(BIN).1.gz: $(TARGET)/$(BIN)
help2man --no-info $< | gzip -c > $@.partial

View File

@@ -70,6 +70,7 @@ Enumeration.
- [Snap Install](#snap-install)
- [Homebrew on MacOS and Linux](#homebrew-on-macos-and-linux)
- [Cargo Install](#cargo-install)
- [Kali Install](#kali-install)
- [apt Install](#apt-install)
- [AUR Install](#aur-install)
- [Docker Install](#docker-install)
@@ -118,8 +119,9 @@ Enumeration.
### Download a Release
Releases for multiple architectures can be found in the [Releases](https://github.com/epi052/feroxbuster/releases)
section. The latest release for each of the following systems can be downloaded and executed as shown below.
Releases for `armv7`, `aarch64`, and an `x86_64 .deb` can be found in the [Releases](https://github.com/epi052/feroxbuster/releases) section.
All other OS/architecture combinations can be installed dynamically using one of the methods shown below.
#### Linux (32 and 64-bit) & MacOS
@@ -194,6 +196,16 @@ brew install feroxbuster
cargo install feroxbuster
```
### Kali Install
🥳 `feroxbuster` was recently added to the official Kali Linux repos 🥳
If you're using kali, this is the preferred install method. Installing from the repos adds a [**ferox-config.toml**](#ferox-config.toml) in `/etc/feroxbuster/`, adds command completion for bash, fish, and zsh, includes a man page entry, and installs `feroxbuster` itself.
```
sudo apt update && sudo apt install -y feroxbuster
```
### apt Install
Download `feroxbuster_amd64.deb` from the [Releases](https://github.com/epi052/feroxbuster/releases) section. After
@@ -213,6 +225,14 @@ Install `feroxbuster-git` on Arch Linux with your AUR helper of choice:
yay -S feroxbuster-git
```
### BlackArch install
Install `feroxbuster` on BlackArch Linux:
```
pacman -S feroxbuster
```
### Docker Install
> The following steps assume you have docker installed / setup
@@ -816,10 +836,11 @@ Below is an example of the Scan Cancel Menu™.
Using the menu is pretty simple:
- Press `ENTER` to view the menu
- Choose a scan to cancel by entering its scan index (`1`)
- more than one scan can be selected by using a comma-separated list (`1,2,3` ... etc)
- more than one scan can be selected by using a comma-separated list of indexes and/or ranges (`1-4,8,9-13` ... etc)
- Confirm selections, after which all non-cancelled scans will resume
- To skip confirmation, simply add a `-f` somewhere in your input (`3-5 -f`)
Here is a short demonstration of cancelling two in-progress scans found via recursion.
Here is a short demonstration of force cancelling a range of scans followed by a single scan with interactive prompt.
![cancel-scan](img/cancel-scan.gif)
@@ -918,12 +939,12 @@ cat large-target-list | ./feroxbuster --stdin --parallel 10 --extract-links --au
Resuling Process List (illustrative):
```
\_ target/debug/feroxbuster --stdin --parallel 10
\_ target/debug/feroxbuster --silent --extract-links --auto-bail -u https://target-one
\_ target/debug/feroxbuster --silent --extract-links --auto-bail -u https://target-two
\_ target/debug/feroxbuster --silent --extract-links --auto-bail -u https://target-three
\_ ...
\_ target/debug/feroxbuster --silent --extract-links --auto-bail -u https://target-ten
feroxbuster --stdin --parallel 10
\_ feroxbuster --silent --extract-links --auto-bail -u https://target-one
\_ feroxbuster --silent --extract-links --auto-bail -u https://target-two
\_ feroxbuster --silent --extract-links --auto-bail -u https://target-three
\_ ...
\_ feroxbuster --silent --extract-links --auto-bail -u https://target-ten
```
## 🧐 Comparison w/ Similar Tools
@@ -949,7 +970,7 @@ few of the use-cases in which feroxbuster may be a better fit:
| fast | ✔ | ✔ | ✔ |
| allows recursion | ✔ | | ✔ |
| can specify query parameters | ✔ | | ✔ |
| SOCKS proxy support | ✔ | | |
| SOCKS proxy support | ✔ | | |
| multiple target scan (via stdin or multiple -u) | ✔ | | ✔ |
| configuration file for default value override | ✔ | | ✔ |
| can accept urls via STDIN as part of a pipeline | ✔ | | ✔ |

View File

@@ -1,4 +1,7 @@
use std::fs::{copy, create_dir_all, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
extern crate clap;
extern crate dirs;
use clap::Shell;
@@ -20,4 +23,64 @@ fn main() {
for shell in &shells {
app.gen_completions("feroxbuster", *shell, outdir);
}
// 0xdf pointed out an oddity when tab-completing options that expect file paths, the fix we
// landed on was to add -o plusdirs to the bash completion script. The following code aims to
// automate that fix and have it present in all future builds
let mut contents = String::new();
let mut bash_file = OpenOptions::new()
.read(true)
.write(true)
.open(format!("{}/feroxbuster.bash", outdir))
.expect("Couldn't open bash completion script");
bash_file
.read_to_string(&mut contents)
.expect("Couldn't read bash completion script");
contents = contents.replace("default feroxbuster", "default -o plusdirs feroxbuster");
bash_file
.seek(SeekFrom::Start(0))
.expect("Couldn't seek to position 0 in bash completion script");
bash_file
.write_all(contents.as_bytes())
.expect("Couldn't write updated bash completion script to disk");
// hunter0x8 let me know that when installing via cargo, it would be nice if we dropped a
// config file during the build process. The following code will place an example config in
// the user's configuration directory
// - linux: $XDG_CONFIG_HOME or $HOME/.config
// - macOS: $HOME/Library/Application Support
// - windows: {FOLDERID_RoamingAppData}
let mut config_dir = dirs::config_dir().expect("Couldn't resolve user's config directory");
config_dir = config_dir.join("feroxbuster"); // $HOME/.config/feroxbuster
if !config_dir.exists() {
// recursively create the feroxbuster directory and all of its parent components if
// they are missing
if !config_dir.exists() {
// recursively create the feroxbuster directory and all of its parent components if
// they are missing
if create_dir_all(&config_dir).is_err() {
// only copy the config file when we're not running in the CI/CD pipeline
// which fails with permission denied
eprintln!("Couldn't create one or more directories needed to copy the config file");
return;
}
}
}
// hard-coding config name here to not rely on the crate we're building, if DEFAULT_CONFIG_NAME
// ever changes, this will need to be updated
let config_file = config_dir.join("ferox-config.toml");
if !config_file.exists() {
// config file doesn't exist, add it to the config directory
if copy("ferox-config.toml.example", config_file).is_err() {
eprintln!("Couldn't copy example config into config directory");
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 670 KiB

View File

@@ -3,59 +3,63 @@
BASE_URL=https://github.com/epi052/feroxbuster/releases/latest/download
MAC_ZIP=x86_64-macos-feroxbuster.zip
MAC_URL="${BASE_URL}/${MAC_ZIP}"
MAC_URL="$BASE_URL/$MAC_ZIP"
LIN32_ZIP=x86-linux-feroxbuster.zip
LIN32_URL="${BASE_URL}/${LIN32_ZIP}"
LIN32_URL="$BASE_URL/$LIN32_ZIP"
LIN64_ZIP=x86_64-linux-feroxbuster.zip
LIN64_URL="${BASE_URL}/${LIN64_ZIP}"
LIN64_URL="$BASE_URL/$LIN64_ZIP"
EMOJI_URL=https://gist.github.com/epi052/8196b550ea51d0907ad4b93751b1b57d/raw/6112c9f32ae07922983fdc549c54fd3fb9a38e4c/NotoColorEmoji.ttf
echo "[+] Installing feroxbuster!"
if [[ "$(uname)" == "Darwin" ]]; then
echo "[=] Found MacOS, downloading from ${MAC_URL}"
curl -sLO "${MAC_URL}"
unzip -o "${MAC_ZIP}" > /dev/null
rm "${MAC_ZIP}"
elif [[ "$(expr substr $(uname -s) 1 5)" == "Linux" ]]; then
if [[ $(getconf LONG_BIT) == 32 ]]; then
echo "[=] Found 32-bit Linux, downloading from ${LIN32_URL}"
curl -sLO "${LIN32_URL}"
unzip -o "${LIN32_ZIP}" > /dev/null
rm "${LIN32_ZIP}"
else
echo "[=] Found 64-bit Linux, downloading from ${LIN64_URL}"
curl -sLO "${LIN64_URL}"
unzip -o "${LIN64_ZIP}" > /dev/null
rm "${LIN64_ZIP}"
fi
if [[ -e ~/.fonts/NotoColorEmoji.ttf ]]; then
echo "[=] Found Noto Emoji Font, skipping install"
else
echo "[=] Installing Noto Emoji Font"
mkdir -p ~/.fonts
pushd ~/.fonts 2>&1 >/dev/null
curl -sLO "${EMOJI_URL}"
fc-cache -f -v >/dev/null
popd 2>&1 >/dev/null
echo "[+] Noto Emoji Font installed"
fi
which unzip &>/dev/null
if [ "$?" = "0" ]; then
echo "[+] unzip found"
else
echo "[ ] unzip not found, exiting. "
exit -1
fi
if [[ "$(uname)" == "Darwin" ]]; then
echo "[=] Found MacOS, downloading from $MAC_URL"
curl -sLO "$MAC_URL"
unzip -o "$MAC_ZIP" >/dev/null
rm "$MAC_ZIP"
elif [[ "$(expr substr $(uname -s) 1 5)" == "Linux" ]]; then
if [[ $(getconf LONG_BIT) == 32 ]]; then
echo "[=] Found 32-bit Linux, downloading from $LIN32_URL"
curl -sLO "$LIN32_URL"
unzip -o "$LIN32_ZIP" >/dev/null
rm "$LIN32_ZIP"
else
echo "[=] Found 64-bit Linux, downloading from $LIN64_URL"
curl -sLO "$LIN64_URL"
unzip -o "$LIN64_ZIP" >/dev/null
rm "$LIN64_ZIP"
fi
if [[ -e ~/.fonts/NotoColorEmoji.ttf ]]; then
echo "[=] Found Noto Emoji Font, skipping install"
else
echo "[=] Installing Noto Emoji Font"
mkdir -p ~/.fonts
pushd ~/.fonts 2>&1 >/dev/null
curl -sLO "$EMOJI_URL"
fc-cache -f -v >/dev/null
popd 2>&1 >/dev/null
echo "[+] Noto Emoji Font installed"
fi
fi
chmod +x ./feroxbuster
echo "[+] Installed feroxbuster version $(./feroxbuster -V)"

View File

@@ -222,4 +222,4 @@ _feroxbuster() {
esac
}
complete -F _feroxbuster -o bashdefault -o default feroxbuster
complete -F _feroxbuster -o bashdefault -o default -o plusdirs feroxbuster

View File

@@ -171,7 +171,7 @@ impl Banner {
code_filters.push(status_colorizer(&code.to_string()))
}
let filter_status = BannerEntry::new(
"🗑",
"💢",
"Status Code Filters",
&format!("[{}]", code_filters.join(", ")),
);

View File

@@ -364,9 +364,10 @@ fn config_reads_headers() {
/// parse the test config and see that the values parsed are correct
fn config_reads_queries() {
let config = setup_config_test();
let mut queries = vec![];
queries.push(("name".to_string(), "value".to_string()));
queries.push(("rick".to_string(), "astley".to_string()));
let queries = vec![
("name".to_string(), "value".to_string()),
("rick".to_string(), "astley".to_string()),
];
assert_eq!(config.queries, queries);
}

View File

@@ -1,4 +1,3 @@
use std::collections::HashSet;
use std::sync::Arc;
use reqwest::StatusCode;
@@ -53,7 +52,7 @@ pub enum Command {
TryRecursion(Box<FeroxResponse>),
/// Send a pointer to the wordlist to the recursion handler
UpdateWordlist(Arc<HashSet<String>>),
UpdateWordlist(Arc<Vec<String>>),
/// Instruct the ScanHandler to join on all known scans, use sender to notify main when done
JoinTasks(Sender<bool>),

View File

@@ -139,8 +139,8 @@ impl TermOutHandler {
Self {
receiver,
tx_file,
config,
file_task,
config,
}
}

View File

@@ -1,4 +1,3 @@
use std::collections::HashSet;
use std::sync::Arc;
use anyhow::{bail, Result};
@@ -54,7 +53,7 @@ pub struct ScanHandler {
receiver: CommandReceiver,
/// wordlist (re)used for each scan
wordlist: std::sync::Mutex<Option<Arc<HashSet<String>>>>,
wordlist: std::sync::Mutex<Option<Arc<Vec<String>>>>,
/// group of scans that need to be joined
tasks: Vec<Arc<FeroxScan>>,
@@ -105,7 +104,7 @@ impl ScanHandler {
}
/// Set the wordlist
fn wordlist(&self, wordlist: Arc<HashSet<String>>) {
fn wordlist(&self, wordlist: Arc<Vec<String>>) {
if let Ok(mut guard) = self.wordlist.lock() {
if guard.is_none() {
let _ = std::mem::replace(&mut *guard, Some(wordlist));
@@ -175,7 +174,7 @@ impl ScanHandler {
}
/// Helper to easily get the (locked) underlying wordlist
pub fn get_wordlist(&self) -> Result<Arc<HashSet<String>>> {
pub fn get_wordlist(&self) -> Result<Arc<Vec<String>>> {
if let Ok(guard) = self.wordlist.lock().as_ref() {
if let Some(list) = guard.as_ref() {
return Ok(list.clone());

View File

@@ -1,5 +1,4 @@
use std::{
collections::HashSet,
env::args,
fs::File,
io::{stderr, BufRead, BufReader},
@@ -41,14 +40,14 @@ lazy_static! {
}
/// Create a HashSet of Strings from the given wordlist then stores it inside an Arc
fn get_unique_words_from_wordlist(path: &str) -> Result<Arc<HashSet<String>>> {
fn get_unique_words_from_wordlist(path: &str) -> Result<Arc<Vec<String>>> {
log::trace!("enter: get_unique_words_from_wordlist({})", path);
let file = File::open(&path).with_context(|| format!("Could not open {}", path))?;
let reader = BufReader::new(file);
let mut words = HashSet::new();
let mut words = Vec::new();
for line in reader.lines() {
let result = match line {
@@ -60,7 +59,7 @@ fn get_unique_words_from_wordlist(path: &str) -> Result<Arc<HashSet<String>>> {
continue;
}
words.insert(result);
words.push(result);
}
log::trace!(
@@ -78,11 +77,7 @@ async fn scan(targets: Vec<String>, handles: Arc<Handles>) -> Result<()> {
// so that will allow for cheap/safe sharing of a single wordlist across multi-target scans
// as well as additional directories found as part of recursion
let words = {
let words_handles = handles.clone();
tokio::spawn(async move { get_unique_words_from_wordlist(&words_handles.config.wordlist) })
.await??
};
let words = get_unique_words_from_wordlist(&handles.config.wordlist)?;
if words.len() == 0 {
bail!("Did not find any words in {}", handles.config.wordlist);

View File

@@ -1,6 +1,8 @@
use clap::{App, Arg, ArgGroup};
use lazy_static::lazy_static;
use regex::Regex;
use std::env;
use std::process;
lazy_static! {
/// Regex used to validate values passed to --time-limit
@@ -16,7 +18,7 @@ lazy_static! {
/// Create and return an instance of [clap::App](https://docs.rs/clap/latest/clap/struct.App.html), i.e. the Command Line Interface's configuration
pub fn initialize() -> App<'static, 'static> {
App::new("feroxbuster")
let mut app = App::new("feroxbuster")
.version(env!("CARGO_PKG_VERSION"))
.author("Ben 'epi' Risher (@epi052)")
.about("A fast, simple, recursive content discovery tool written in Rust")
@@ -410,7 +412,20 @@ EXAMPLES:
Ludicrous speed... go!
./feroxbuster -u http://127.1 -t 200
"#)
"#);
for arg in env::args() {
// secure-77 noticed that when an incorrect flag/option is used, the short help message is printed
// which is fine, but if you add -h|--help, it still errors out on the bad flag/option,
// never showing the full help message. This code addresses that behavior
if arg == "--help" || arg == "-h" {
app.print_long_help().unwrap();
println!(); // just a newline to mirror original --help output
process::exit(1);
}
}
app
}
/// Validate that a string is formatted as a number followed by s, m, h, or d (10d, 30s, etc...)

View File

@@ -31,9 +31,10 @@ impl Menu {
let separator = "".to_string();
let instructions = format!(
"Enter a {} list of indexes to {} (ex: 2,3)",
"Enter a {} list of indexes/ranges to {} ({}: 1-4,8,9-13)",
style("comma-separated").yellow(),
style("cancel").red(),
style("ex").cyan(),
);
let name = format!(
@@ -43,14 +44,22 @@ impl Menu {
"💀"
);
let force_msg = format!(
"Add {} to {} confirmation ({}: 3-5 -f)",
style("-f").yellow(),
style("skip").yellow(),
style("ex").cyan(),
);
let longest = measure_text_width(&instructions).max(measure_text_width(&name));
let border = separator.repeat(longest);
let padded_name = pad_str(&name, longest, Alignment::Center, None);
let padded_force = pad_str(&force_msg, longest, Alignment::Center, None);
let header = format!("{}\n{}\n{}", border, padded_name, border);
let footer = format!("{}\n{}\n{}", border, instructions, border);
let footer = format!("{}\n{}\n{}\n{}", border, instructions, padded_force, border);
Self {
separator,
@@ -93,23 +102,71 @@ impl Menu {
self.term.write_line(msg).unwrap_or_default();
}
/// split a string into vec of usizes
pub(super) fn split_to_nums(&self, line: &str) -> Vec<usize> {
line.split(',')
.map(|s| {
s.trim().to_string().parse::<usize>().unwrap_or_else(|e| {
self.println(&format!("Found non-numeric input: {}", e));
0
})
/// Helper for parsing a usize from a str
fn str_to_usize(&self, value: &str) -> usize {
if value.is_empty() {
return 0;
}
value
.trim()
.to_string()
.parse::<usize>()
.unwrap_or_else(|e| {
self.println(&format!("Found non-numeric input: {}: {:?}", e, value));
0
})
.filter(|m| *m != 0)
.collect()
}
/// split a comma delimited string into vec of usizes
pub(super) fn split_to_nums(&self, line: &str) -> Vec<usize> {
let mut nums = Vec::new();
let values = line.split(',');
for mut value in values {
value = value.trim();
if value.contains('-') {
// range of two values, needs further processing
let range: Vec<usize> = value
.split('-')
.map(|s| self.str_to_usize(s))
.filter(|m| *m != 0)
.collect();
if range.len() != 2 {
// expecting [1, 4] or similar, if a 0 was used, we'd be left with a vec of size 1
self.println(&format!("Found invalid range of scans: {}", value));
continue;
}
(range[0]..=range[1]).for_each(|n| {
// iterate from lower to upper bound and add all interim values, skipping
// any already known
if !nums.contains(&n) {
nums.push(n)
}
});
} else {
let value = self.str_to_usize(value);
if value != 0 && !nums.contains(&value) {
// the zeroth scan is always skipped, skip already known values
nums.push(value);
}
}
}
nums
}
/// get comma-separated list of scan indexes from the user
pub(super) fn get_scans_from_user(&self) -> Option<Vec<usize>> {
pub(super) fn get_scans_from_user(&self) -> Option<(Vec<usize>, bool)> {
if let Ok(line) = self.term.read_line() {
Some(self.split_to_nums(&line))
let force = line.contains("-f");
let line = line.replace("-f", "");
Some((self.split_to_nums(&line), force))
} else {
None
}

View File

@@ -252,7 +252,7 @@ impl FeroxScans {
}
/// Given a list of indexes, cancel their associated FeroxScans
async fn cancel_scans(&self, indexes: Vec<usize>) -> usize {
async fn cancel_scans(&self, indexes: Vec<usize>, force: bool) -> usize {
let menu_pause_duration = Duration::from_millis(SLEEP_DURATION);
let mut num_cancelled = 0_usize;
@@ -273,7 +273,11 @@ impl FeroxScans {
Err(..) => continue,
};
let input = self.menu.confirm_cancellation(&selected.url);
let input = if force {
'y'
} else {
self.menu.confirm_cancellation(&selected.url)
};
if input == 'y' || input == '\n' {
self.menu.println(&format!("Stopping {}...", selected.url));
@@ -305,8 +309,8 @@ impl FeroxScans {
let mut num_cancelled = 0_usize;
if let Some(input) = self.menu.get_scans_from_user() {
num_cancelled += self.cancel_scans(input).await;
if let Some((input, force)) = self.menu.get_scans_from_user() {
num_cancelled += self.cancel_scans(input, force).await;
};
self.menu.clear_screen();

View File

@@ -47,7 +47,7 @@ impl FeroxSerialize for FeroxState {
/// Simple call to produce a JSON string using the given FeroxState
fn as_json(&self) -> Result<String> {
Ok(serde_json::to_string(&self)
.with_context(|| fmt_err("Could not convert scan's running state to JSON"))?)
serde_json::to_string(&self)
.with_context(|| fmt_err("Could not convert scan's running state to JSON"))
}
}

View File

@@ -521,9 +521,13 @@ fn menu_print_header_and_footer() {
fn split_to_nums_is_correct() {
let menu = Menu::new();
let nums = menu.split_to_nums("1, 3, 4");
let nums = menu.split_to_nums("1, 3, 4, 7 - 12, 10-10, 10-11, 9-12, 12-6, -1, 4-");
assert_eq!(nums, vec![1, 3, 4]);
assert_eq!(nums, vec![1, 3, 4, 7, 8, 9, 10, 11, 12]);
assert_eq!(menu.split_to_nums("9-12"), vec![9, 10, 11, 12]);
assert!(menu.split_to_nums("-12").is_empty());
assert!(menu.split_to_nums("12-").is_empty());
assert!(menu.split_to_nums("\n").is_empty());
}
#[test]

View File

@@ -41,7 +41,7 @@ pub async fn start_max_time_thread(handles: Arc<Handles>) {
log::trace!("exit: start_max_time_thread");
#[cfg(test)]
panic!(handles);
panic!("{:?}", handles);
#[cfg(not(test))]
let _ = TermInputHandler::sigint_handler(handles.clone());
}

View File

@@ -1,4 +1,4 @@
use std::{collections::HashSet, ops::Deref, sync::atomic::Ordering, sync::Arc, time::Instant};
use std::{ops::Deref, sync::atomic::Ordering, sync::Arc, time::Instant};
use anyhow::{bail, Result};
use futures::{stream, StreamExt};
@@ -40,7 +40,7 @@ pub struct FeroxScanner {
order: ScanOrder,
/// wordlist that's already been read from disk
wordlist: Arc<HashSet<String>>,
wordlist: Arc<Vec<String>>,
/// limiter that restricts the number of active FeroxScanners
scan_limiter: Arc<Semaphore>,
@@ -52,7 +52,7 @@ impl FeroxScanner {
pub fn new(
target_url: &str,
order: ScanOrder,
wordlist: Arc<HashSet<String>>,
wordlist: Arc<Vec<String>>,
scan_limiter: Arc<Semaphore>,
handles: Arc<Handles>,
) -> Self {

View File

@@ -1,4 +1,6 @@
use std::{
collections::HashMap,
convert::TryFrom,
fs::File,
io::BufReader,
sync::{
@@ -9,7 +11,8 @@ use std::{
use anyhow::{Context, Result};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use crate::{
traits::FeroxSerialize,
@@ -19,9 +22,8 @@ use crate::{
use super::{error::StatError, field::StatField};
/// Data collection of statistics related to a scan
#[derive(Default, Deserialize, Debug, Serialize)]
#[derive(Default, Debug)]
pub struct Stats {
#[serde(rename = "type")]
/// Name of this type of struct, used for serialization, i.e. `{"type":"statistics"}`
kind: String,
@@ -125,11 +127,9 @@ pub struct Stats {
total_runtime: Mutex<Vec<f64>>,
/// tracker for the number of extensions the user specified
#[serde(skip)]
num_extensions: usize,
/// tracker for whether to use json during serialization or not
#[serde(skip)]
json: bool,
}
@@ -147,6 +147,301 @@ impl FeroxSerialize for Stats {
}
}
/// Serialize implementation for Stats
impl Serialize for Stats {
/// Function that handles serialization of Stats
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Stats", 32)?;
state.serialize_field("type", &self.kind)?;
state.serialize_field("timeouts", &atomic_load!(self.timeouts))?;
state.serialize_field("requests", &atomic_load!(self.requests))?;
state.serialize_field("expected_per_scan", &atomic_load!(self.expected_per_scan))?;
state.serialize_field("total_expected", &atomic_load!(self.total_expected))?;
state.serialize_field("errors", &atomic_load!(self.errors))?;
state.serialize_field("successes", &atomic_load!(self.successes))?;
state.serialize_field("redirects", &atomic_load!(self.redirects))?;
state.serialize_field("client_errors", &atomic_load!(self.client_errors))?;
state.serialize_field("server_errors", &atomic_load!(self.server_errors))?;
state.serialize_field("total_scans", &atomic_load!(self.total_scans))?;
state.serialize_field("initial_targets", &atomic_load!(self.initial_targets))?;
state.serialize_field("links_extracted", &atomic_load!(self.links_extracted))?;
state.serialize_field("status_200s", &atomic_load!(self.status_200s))?;
state.serialize_field("status_301s", &atomic_load!(self.status_301s))?;
state.serialize_field("status_302s", &atomic_load!(self.status_302s))?;
state.serialize_field("status_401s", &atomic_load!(self.status_401s))?;
state.serialize_field("status_403s", &atomic_load!(self.status_403s))?;
state.serialize_field("status_429s", &atomic_load!(self.status_429s))?;
state.serialize_field("status_500s", &atomic_load!(self.status_500s))?;
state.serialize_field("status_503s", &atomic_load!(self.status_503s))?;
state.serialize_field("status_504s", &atomic_load!(self.status_504s))?;
state.serialize_field("status_508s", &atomic_load!(self.status_508s))?;
state.serialize_field("wildcards_filtered", &atomic_load!(self.wildcards_filtered))?;
state.serialize_field("responses_filtered", &atomic_load!(self.responses_filtered))?;
state.serialize_field(
"resources_discovered",
&atomic_load!(self.resources_discovered),
)?;
state.serialize_field("url_format_errors", &atomic_load!(self.url_format_errors))?;
state.serialize_field("redirection_errors", &atomic_load!(self.redirection_errors))?;
state.serialize_field("connection_errors", &atomic_load!(self.connection_errors))?;
state.serialize_field("request_errors", &atomic_load!(self.request_errors))?;
state.serialize_field("directory_scan_times", &self.directory_scan_times)?;
state.serialize_field("total_runtime", &self.total_runtime)?;
state.end()
}
}
/// Deserialize implementation for Stats
impl<'a> Deserialize<'a> for Stats {
/// Deserialize a Stats object from a serde_json::Value
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
let stats = Self::new(0, false);
let map: HashMap<String, Value> = HashMap::deserialize(deserializer)?;
for (key, value) in &map {
match key.as_str() {
"timeouts" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.timeouts, parsed);
}
}
}
"requests" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.requests, parsed);
}
}
}
"expected_per_scan" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.expected_per_scan, parsed);
}
}
}
"total_expected" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.total_expected, parsed);
}
}
}
"errors" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.errors, parsed);
}
}
}
"successes" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.successes, parsed);
}
}
}
"redirects" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.redirects, parsed);
}
}
}
"client_errors" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.client_errors, parsed);
}
}
}
"server_errors" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.server_errors, parsed);
}
}
}
"total_scans" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.total_scans, parsed);
}
}
}
"initial_targets" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.initial_targets, parsed);
}
}
}
"links_extracted" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.links_extracted, parsed);
}
}
}
"status_200s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_200s, parsed);
}
}
}
"status_301s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_301s, parsed);
}
}
}
"status_302s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_302s, parsed);
}
}
}
"status_401s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_401s, parsed);
}
}
}
"status_403s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_403s, parsed);
}
}
}
"status_429s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_429s, parsed);
}
}
}
"status_500s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_500s, parsed);
}
}
}
"status_503s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_503s, parsed);
}
}
}
"status_504s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_504s, parsed);
}
}
}
"status_508s" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.status_508s, parsed);
}
}
}
"wildcards_filtered" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.wildcards_filtered, parsed);
}
}
}
"responses_filtered" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.responses_filtered, parsed);
}
}
}
"resources_discovered" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.resources_discovered, parsed);
}
}
}
"url_format_errors" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.url_format_errors, parsed);
}
}
}
"redirection_errors" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.redirection_errors, parsed);
}
}
}
"connection_errors" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.connection_errors, parsed);
}
}
}
"request_errors" => {
if let Some(num) = value.as_u64() {
if let Ok(parsed) = usize::try_from(num) {
atomic_increment!(stats.request_errors, parsed);
}
}
}
"directory_scan_times" => {
if let Some(arr) = value.as_array() {
for val in arr {
if let Some(parsed) = val.as_f64() {
if let Ok(mut guard) = stats.directory_scan_times.lock() {
guard.push(parsed)
}
}
}
}
}
"total_runtime" => {
if let Some(arr) = value.as_array() {
for val in arr {
if let Some(parsed) = val.as_f64() {
if let Ok(mut guard) = stats.total_runtime.lock() {
guard.push(parsed)
}
}
}
}
}
_ => {}
}
}
Ok(stats)
}
}
/// implementation of statistics data collection struct
impl Stats {
/// Small wrapper for default to set `kind` to "statistics" and `total_runtime` to have at least

46
tests/test_parser.rs Normal file
View File

@@ -0,0 +1,46 @@
use assert_cmd::Command;
use predicates::prelude::*;
#[test]
/// specify an incorrect param (-fc) with --help after it on the command line
/// old behavior printed
/// error: Found argument '-c' which wasn't expected, or isn't valid in this context
///
/// USAGE:
/// feroxbuster --add-slash --url <URL>...
///
/// For more information try --help
///
/// the new behavior we expect to see is to print the long form help message, of which
/// Ludicrous speed... go! is near the bottom of that output, so we can test for that
fn parser_incorrect_param_with_tack_tack_help() {
Command::cargo_bin("feroxbuster")
.unwrap()
.arg("-fc")
.arg("--help")
.assert()
.failure()
.stdout(predicate::str::contains("Ludicrous speed... go!"));
}
#[test]
/// specify an incorrect param (-fc) with --help after it on the command line
/// old behavior printed
/// error: Found argument '-c' which wasn't expected, or isn't valid in this context
///
/// USAGE:
/// feroxbuster --add-slash --url <URL>...
///
/// For more information try --help
///
/// the new behavior we expect to see is to print the long form help message, of which
/// Ludicrous speed... go! is near the bottom of that output, so we can test for that
fn parser_incorrect_param_with_tack_h() {
Command::cargo_bin("feroxbuster")
.unwrap()
.arg("-fc")
.arg("-h")
.assert()
.failure()
.stdout(predicate::str::contains("Ludicrous speed... go!"));
}