From 762bfc4e78b5a42e6ffebbd66f5bcc5d7552ce7e Mon Sep 17 00:00:00 2001 From: epi <43392618+epi052@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:00:14 -0400 Subject: [PATCH] 907 dont skip dir listings (#1192) * bumped version to 2.11.0 * updated deps * new cli options * added --request-file, --protocol, --scan-dir-listings * added tests / clippy * removed errant module definition * implemented visible bar limiter * many fixes; feature implemented i believe * added banner test for limit-bars * beginning troubleshooting of recursion panic * put a bandaid on trace-level logging bug * clippy --- Cargo.lock | 1101 +++++++++++++--------------- Cargo.toml | 14 +- Makefile.toml | 2 +- ferox-config.toml.example | 4 + shell_completions/_feroxbuster | 14 +- shell_completions/_feroxbuster.ps1 | 202 ++--- shell_completions/feroxbuster.bash | 25 +- shell_completions/feroxbuster.elv | 14 +- src/banner/container.rs | 37 + src/config/container.rs | 110 ++- src/config/tests.rs | 36 + src/config/utils.rs | 1056 +++++++++++++++++++++++++- src/event_handlers/scans.rs | 9 +- src/extractor/container.rs | 7 +- src/extractor/tests.rs | 6 +- src/main.rs | 4 +- src/parser.rs | 35 +- src/progress.rs | 15 +- src/scan_manager/scan.rs | 201 ++++- src/scan_manager/scan_container.rs | 116 ++- src/scan_manager/tests.rs | 90 ++- src/scan_manager/utils.rs | 83 ++- src/scanner/ferox_scanner.rs | 28 +- src/scanner/requester.rs | 11 +- src/scanner/tests.rs | 2 +- src/utils.rs | 66 +- tests/test_banner.rs | 83 +++ tests/test_policies.rs | 2 +- tests/test_scanner.rs | 2 + 29 files changed, 2551 insertions(+), 824 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e337a4..a2c8b5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -41,9 +47,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -56,33 +62,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -115,13 +121,14 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", "bstr", "doc-comment", + "libc", "predicates", "predicates-core", "predicates-tree", @@ -163,13 +170,13 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-lite 2.3.0", "slab", ] @@ -182,7 +189,7 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "blocking", "futures-lite 2.3.0", @@ -211,9 +218,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ "async-lock 3.4.0", "cfg-if", @@ -221,11 +228,11 @@ dependencies = [ "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.7.2", + "polling 3.7.3", "rustix 0.38.34", "slab", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -276,11 +283,11 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "atomic-waker", "cfg-if", @@ -289,7 +296,7 @@ dependencies = [ "rustix 0.38.34", "signal-hook-registry", "slab", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -328,13 +335,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -359,7 +366,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -416,9 +423,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -444,9 +451,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "regex-automata", @@ -467,15 +474,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.0.99" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -485,24 +495,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.7" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -513,24 +523,24 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.5" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2020fa13af48afc65a9a87335bda648309ab3d154cd03c7ff95b378c7ed39c4" +checksum = "531d7959c5bbb6e266cecdd0f20213639c3a5c3e4d615f97db87661745f781ff" dependencies = [ "clap", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "concurrent-queue" @@ -572,15 +582,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -625,10 +635,10 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "crossterm_winapi", "libc", - "mio", + "mio 0.8.11", "parking_lot", "signal-hook", "signal-hook-mio", @@ -680,31 +690,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] name = "ctrlc" -version = "3.4.4" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" +checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" dependencies = [ "nix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest", "fiat-crypto", - "platforms", "rustc_version", "subtle", "zeroize", @@ -718,7 +727,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -748,7 +757,7 @@ checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -809,17 +818,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "displaydoc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "doc-comment" version = "0.3.3" @@ -868,15 +866,15 @@ dependencies = [ [[package]] name = "ego-tree" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" +checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642" [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "ena" @@ -904,9 +902,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -914,9 +912,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -990,13 +988,13 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "feroxbuster" -version = "2.10.4" +version = "2.11.0" dependencies = [ "anyhow", "assert_cmd", @@ -1040,14 +1038,14 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -1058,12 +1056,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -1190,7 +1188,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-core", "futures-io", "parking", @@ -1205,7 +1203,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -1320,9 +1318,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -1357,16 +1355,16 @@ checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "html5ever" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" dependencies = [ "log", "mac", "markup5ever", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.76", ] [[package]] @@ -1404,9 +1402,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -1421,15 +1419,15 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -1452,7 +1450,7 @@ dependencies = [ "crossbeam-utils", "form_urlencoded", "futures-util", - "hyper 0.14.29", + "hyper 0.14.30", "lazy_static", "levenshtein", "log", @@ -1473,9 +1471,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", @@ -1496,16 +1494,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "itoa", "pin-project-lite", @@ -1514,6 +1512,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1522,7 +1537,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", "native-tls", "tokio", @@ -1532,16 +1547,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body 1.0.1", + "hyper 1.4.1", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -1550,141 +1565,21 @@ dependencies = [ "tracing", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "idna" -version = "1.0.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", - "smallvec", - "utf8_iter", + "unicode-bidi", + "unicode-normalization", ] [[package]] name = "indexmap" -version = "2.2.6" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", @@ -1731,9 +1626,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -1761,9 +1656,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -1810,9 +1705,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leaky-bucket" @@ -1833,9 +1728,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libredox" @@ -1843,8 +1738,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", + "redox_syscall", ] [[package]] @@ -1859,12 +1755,6 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - [[package]] name = "lock_api" version = "0.4.12" @@ -1877,9 +1767,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "value-bag", ] @@ -1892,13 +1782,13 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen", + "phf 0.11.2", + "phf_codegen 0.11.2", "string_cache", "string_cache_codegen", "tendril", @@ -1918,13 +1808,22 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "0.8.11" @@ -1937,6 +1836,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -1962,11 +1873,11 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", @@ -1993,16 +1904,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "number_prefix" version = "0.4.0" @@ -2011,9 +1912,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.36.0" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] @@ -2026,11 +1927,11 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -2047,7 +1948,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -2067,9 +1968,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -2108,9 +2009,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2158,6 +2059,16 @@ dependencies = [ "phf_shared 0.10.0", ] +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", +] + [[package]] name = "phf_generator" version = "0.10.0" @@ -2188,7 +2099,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -2232,7 +2143,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -2249,12 +2160,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-io", ] @@ -2274,12 +2185,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - [[package]] name = "polling" version = "2.8.0" @@ -2298,9 +2203,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.2" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", @@ -2308,14 +2213,14 @@ dependencies = [ "pin-project-lite", "rustix 0.38.34", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "powerfmt" @@ -2325,9 +2230,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "precomputed-hash" @@ -2337,9 +2245,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "predicates" -version = "3.1.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", @@ -2351,15 +2259,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", @@ -2367,9 +2275,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -2385,9 +2293,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -2509,27 +2417,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" -dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", @@ -2538,9 +2437,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -2567,9 +2466,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64 0.22.1", "bytes", @@ -2579,9 +2478,10 @@ dependencies = [ "futures-util", "h2", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -2606,7 +2506,22 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -2653,7 +2568,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys 0.4.14", @@ -2661,10 +2576,23 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.1.2" +name = "rustls" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64 0.22.1", "rustls-pki-types", @@ -2672,9 +2600,20 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] [[package]] name = "rustversion" @@ -2714,9 +2653,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b80b33679ff7a0ea53d37f3b39de77ea0c75b12c5805ac43ec0c33b3051af1b" +checksum = "761fb705fdf625482d2ed91d3f0559dcfeab2798fe2771c69560a774865d0802" dependencies = [ "ahash", "cssparser", @@ -2736,11 +2675,11 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2749,9 +2688,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -2763,14 +2702,14 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cssparser", "derive_more", "fxhash", "log", "new_debug_unreachable", "phf 0.10.1", - "phf_codegen", + "phf_codegen 0.10.0", "precomputed-hash", "servo_arc", "smallvec", @@ -2778,9 +2717,9 @@ dependencies = [ [[package]] name = "self-replace" -version = "1.3.7" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525db198616b2bcd0f245daf7bfd8130222f7ee6af9ff9984c19a61bf1160c55" +checksum = "f7828a58998685d8bf5a3c5e7a3379a5867289c20828c3ee436280b44b598515" dependencies = [ "fastrand 1.9.0", "tempfile", @@ -2795,7 +2734,7 @@ checksum = "4e4997484b55df069a4773d822715695b2cc27b23829eca2a4b41690e948bdeb" dependencies = [ "either", "flate2", - "hyper 1.3.1", + "hyper 1.4.1", "indicatif", "log", "quick-xml", @@ -2819,31 +2758,32 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -2860,9 +2800,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -2916,6 +2856,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72bb884be1ddfbded5873be4672cf5aee71210ce0f8ae99787d158b9b72b5ca0" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -2928,12 +2874,12 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 0.8.11", "signal-hook", ] @@ -2958,9 +2904,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" [[package]] name = "siphasher" @@ -3006,6 +2952,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.7.3" @@ -3056,9 +3008,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -3073,9 +3025,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", @@ -3084,37 +3036,29 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", + "futures-core", ] [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -3133,14 +3077,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", - "fastrand 2.1.0", + "fastrand 2.1.1", + "once_cell", "rustix 0.38.34", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3183,22 +3128,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -3230,43 +3175,47 @@ dependencies = [ ] [[package]] -name = "tinystr" -version = "0.7.6" +name = "tinyvec" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ - "displaydoc", - "zerovec", + "tinyvec_macros", ] [[package]] -name = "tokio" -version = "1.38.0" +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.39.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.2", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.5.7", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -3280,10 +3229,21 @@ dependencies = [ ] [[package]] -name = "tokio-socks" -version = "0.5.1" +name = "tokio-rustls" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" dependencies = [ "either", "futures-util", @@ -3306,9 +3266,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -3318,18 +3278,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", @@ -3355,15 +3315,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -3386,9 +3346,9 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b2cb4fbb9995eeb36ac86fadf24031ccd58f99d6b4b2d7b911db70bddb80d90" +checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369" dependencies = [ "serde", "stable_deref_trait", @@ -3406,12 +3366,27 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.13" @@ -3420,15 +3395,21 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -3448,18 +3429,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -3468,9 +3437,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", ] @@ -3489,9 +3458,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" @@ -3535,34 +3504,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -3572,9 +3542,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3582,28 +3552,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -3627,11 +3597,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3640,6 +3610,36 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3655,7 +3655,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -3675,18 +3684,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -3697,9 +3706,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -3709,9 +3718,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -3721,15 +3730,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -3739,9 +3748,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -3751,9 +3760,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -3763,9 +3772,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -3775,41 +3784,19 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "xattr" version = "1.3.1" @@ -3821,69 +3808,25 @@ dependencies = [ "rustix 0.38.34", ] -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", -] - -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", + "syn 2.0.76", ] [[package]] @@ -3892,28 +3835,6 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -[[package]] -name = "zerovec" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "zip" version = "0.6.6" @@ -3929,11 +3850,11 @@ dependencies = [ [[package]] name = "zipsign-api" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba5aa1827d6b1a35a29b3413ec69ce5f796e4d897e3e5b38f461bef41d225ea" +checksum = "6413a546ada9dbcd0b9a3e0b0880581279e35047bce9797e523b3408e1df607c" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "ed25519-dalek", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 785ee46..ad7b83b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "feroxbuster" -version = "2.10.4" +version = "2.11.0" authors = ["Ben 'epi' Risher (@epi052)"] license = "MIT" edition = "2021" @@ -25,13 +25,13 @@ maintenance = { status = "actively-developed" } clap = { version = "4.5", features = ["wrap_help", "cargo"] } clap_complete = "4.5" regex = "1.10" -lazy_static = "1.4" +lazy_static = "1.5" dirs = "5.0" [dependencies] scraper = "0.19" futures = "0.3" -tokio = { version = "1.38", features = ["full"] } +tokio = { version = "1.39", features = ["full"] } tokio-util = { version = "0.7", features = ["codec"] } log = "0.4" env_logger = "0.11" @@ -40,13 +40,11 @@ reqwest = { version = "0.12", features = ["socks", "native-tls-alpn"] } url = { version = "2.5", features = ["serde"] } serde_regex = "1.1" clap = { version = "4.5", features = ["wrap_help", "cargo"] } -lazy_static = "1.4" +lazy_static = "1.5" toml = "0.8" serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" -uuid = { version = "1.8", features = ["v4"] } -# last known working version of indicatif; 0.17.5 has a bug that causes the -# scan menu to fail spectacularly +uuid = { version = "1.10", features = ["v4"] } indicatif = { version = "0.17.8" } console = "0.15" openssl = { version = "0.10", features = ["vendored"] } @@ -69,7 +67,7 @@ self_update = { version = "0.40", features = [ ] } [dev-dependencies] -tempfile = "3.10" +tempfile = "3.12" httpmock = "0.7" assert_cmd = "2.0" predicates = "3.1" diff --git a/Makefile.toml b/Makefile.toml index f80e430..49b8df0 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -14,7 +14,7 @@ rm ferox-*.state # dependency management [tasks.upgrade-deps] command = "cargo" -args = ["upgrade", "--exclude", "indicatif, self_update"] +args = ["upgrade", "--exclude", "self_update"] [tasks.update] command = "cargo" diff --git a/ferox-config.toml.example b/ferox-config.toml.example index 1c87a2c..bd8a0d9 100644 --- a/ferox-config.toml.example +++ b/ferox-config.toml.example @@ -45,6 +45,7 @@ # dont_filter = true # extract_links = true # depth = 1 +# limit_bars = 3 # force_recursion = true # filter_size = [5174] # filter_regex = ["^ignore me$"] @@ -57,6 +58,9 @@ # server_certs = ["/some/cert.pem", "/some/other/cert.pem"] # client_cert = "/some/client/cert.pem" # client_key = "/some/client/key.pem" +# request_file = "/some/raw/request/file" +# protocol = "http" +# scan_dir_listings = true # headers can be specified on multiple lines or as an inline table # diff --git a/shell_completions/_feroxbuster b/shell_completions/_feroxbuster index 43605a2..d78f388 100644 --- a/shell_completions/_feroxbuster +++ b/shell_completions/_feroxbuster @@ -15,17 +15,18 @@ _feroxbuster() { local context curcontext="$curcontext" state line _arguments "${_arguments_options[@]}" : \ -'-u+[The target URL (required, unless \[--stdin || --resume-from\] used)]:URL:_urls' \ -'--url=[The target URL (required, unless \[--stdin || --resume-from\] used)]:URL:_urls' \ +'-u+[The target URL (required, unless \[--stdin || --resume-from || --request-file\] used)]:URL:_urls' \ +'--url=[The target URL (required, unless \[--stdin || --resume-from || --request-file\] used)]:URL:_urls' \ '(-u --url)--resume-from=[State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)]:STATE_FILE:_files' \ +'(-u --url)--request-file=[Raw HTTP request file to use as a template for all requests]:REQUEST_FILE:_files' \ '-p+[Proxy to use for requests (ex\: http(s)\://host\:port, socks5(h)\://host\:port)]:PROXY:_urls' \ '--proxy=[Proxy to use for requests (ex\: http(s)\://host\:port, socks5(h)\://host\:port)]:PROXY:_urls' \ '-P+[Send only unfiltered requests through a Replay Proxy, instead of all requests]:REPLAY_PROXY:_urls' \ '--replay-proxy=[Send only unfiltered requests through a Replay Proxy, instead of all requests]:REPLAY_PROXY:_urls' \ '*-R+[Status Codes to send through a Replay Proxy when found (default\: --status-codes value)]:REPLAY_CODE: ' \ '*--replay-codes=[Status Codes to send through a Replay Proxy when found (default\: --status-codes value)]:REPLAY_CODE: ' \ -'-a+[Sets the User-Agent (default\: feroxbuster/2.10.4)]:USER_AGENT: ' \ -'--user-agent=[Sets the User-Agent (default\: feroxbuster/2.10.4)]:USER_AGENT: ' \ +'-a+[Sets the User-Agent (default\: feroxbuster/2.11.0)]:USER_AGENT: ' \ +'--user-agent=[Sets the User-Agent (default\: feroxbuster/2.11.0)]:USER_AGENT: ' \ '*-x+[File extension(s) to search for (ex\: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex\: @ext.txt)]:FILE_EXTENSION: ' \ '*--extensions=[File extension(s) to search for (ex\: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex\: @ext.txt)]:FILE_EXTENSION: ' \ '*-m+[Which HTTP request method(s) should be sent (default\: GET)]:HTTP_METHODS: ' \ @@ -37,6 +38,7 @@ _feroxbuster() { '*--cookies=[Specify HTTP cookies to be used in each request (ex\: -b stuff=things)]:COOKIE: ' \ '*-Q+[Request'\''s URL query parameters (ex\: -Q token=stuff -Q secret=key)]:QUERY: ' \ '*--query=[Request'\''s URL query parameters (ex\: -Q token=stuff -Q secret=key)]:QUERY: ' \ +'--protocol=[Specify the protocol to use when targeting via --request-file or --url with domain only (default\: https)]:PROTOCOL: ' \ '*--dont-scan=[URL(s) or Regex Pattern(s) to exclude from recursion/scans]:URL: ' \ '*-S+[Filter out messages of a particular size (ex\: -S 5120 -S 4927,1970)]:SIZE: ' \ '*--filter-size=[Filter out messages of a particular size (ex\: -S 5120 -S 4927,1970)]:SIZE: ' \ @@ -74,11 +76,12 @@ _feroxbuster() { '-o+[Output file to write results to (use w/ --json for JSON entries)]:FILE:_files' \ '--output=[Output file to write results to (use w/ --json for JSON entries)]:FILE:_files' \ '--debug-log=[Output file to write log entries (use w/ --json for JSON entries)]:FILE:_files' \ +'--limit-bars=[Number of directory scan bars to show at any given time (default\: no limit)]:NUM_BARS_TO_SHOW: ' \ '(-u --url)--stdin[Read url(s) from STDIN]' \ '(-p --proxy -k --insecure --burp-replay)--burp[Set --proxy to http\://127.0.0.1\:8080 and set --insecure to true]' \ '(-P --replay-proxy -k --insecure)--burp-replay[Set --replay-proxy to http\://127.0.0.1\:8080 and set --insecure to true]' \ '(--rate-limit --auto-bail)--smart[Set --auto-tune, --collect-words, and --collect-backups to true]' \ -'(--rate-limit --auto-bail)--thorough[Use the same settings as --smart and set --collect-extensions to true]' \ +'(--rate-limit --auto-bail)--thorough[Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true]' \ '-A[Use a random User-Agent]' \ '--random-agent[Use a random User-Agent]' \ '-f[Append / to each request'\''s URL]' \ @@ -101,6 +104,7 @@ _feroxbuster() { '--collect-extensions[Automatically discover extensions and add them to --extensions (unless they'\''re in --dont-collect)]' \ '-g[Automatically discover important words from within responses and add them to the wordlist]' \ '--collect-words[Automatically discover important words from within responses and add them to the wordlist]' \ +'--scan-dir-listings[Force scans to recurse into directory listings]' \ '(--silent)*-v[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \ '(--silent)*--verbosity[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \ '(-q --quiet)--silent[Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)]' \ diff --git a/shell_completions/_feroxbuster.ps1 b/shell_completions/_feroxbuster.ps1 index c7ad2e9..a738be3 100644 --- a/shell_completions/_feroxbuster.ps1 +++ b/shell_completions/_feroxbuster.ps1 @@ -21,105 +21,109 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock { $completions = @(switch ($command) { 'feroxbuster' { - [CompletionResult]::new('-u', 'u', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from] used)') - [CompletionResult]::new('--url', 'url', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from] used)') - [CompletionResult]::new('--resume-from', 'resume-from', [CompletionResultType]::ParameterName, 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)') - [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)') - [CompletionResult]::new('--proxy', 'proxy', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)') - [CompletionResult]::new('-P', 'P ', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests') - [CompletionResult]::new('--replay-proxy', 'replay-proxy', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests') - [CompletionResult]::new('-R', 'R ', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)') - [CompletionResult]::new('--replay-codes', 'replay-codes', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)') - [CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.4)') - [CompletionResult]::new('--user-agent', 'user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.4)') - [CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)') - [CompletionResult]::new('--extensions', 'extensions', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)') - [CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)') - [CompletionResult]::new('--methods', 'methods', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)') - [CompletionResult]::new('--data', 'data', [CompletionResultType]::ParameterName, 'Request''s Body; can read data from a file if input starts with an @ (ex: @post.bin)') - [CompletionResult]::new('-H', 'H ', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')') - [CompletionResult]::new('--headers', 'headers', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')') - [CompletionResult]::new('-b', 'b', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)') - [CompletionResult]::new('--cookies', 'cookies', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)') - [CompletionResult]::new('-Q', 'Q ', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)') - [CompletionResult]::new('--query', 'query', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)') - [CompletionResult]::new('--dont-scan', 'dont-scan', [CompletionResultType]::ParameterName, 'URL(s) or Regex Pattern(s) to exclude from recursion/scans') - [CompletionResult]::new('-S', 'S ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)') - [CompletionResult]::new('--filter-size', 'filter-size', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)') - [CompletionResult]::new('-X', 'X ', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')') - [CompletionResult]::new('--filter-regex', 'filter-regex', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')') - [CompletionResult]::new('-W', 'W ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)') - [CompletionResult]::new('--filter-words', 'filter-words', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)') - [CompletionResult]::new('-N', 'N ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)') - [CompletionResult]::new('--filter-lines', 'filter-lines', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)') - [CompletionResult]::new('-C', 'C ', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)') - [CompletionResult]::new('--filter-status', 'filter-status', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)') - [CompletionResult]::new('--filter-similar-to', 'filter-similar-to', [CompletionResultType]::ParameterName, 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)') - [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)') - [CompletionResult]::new('--status-codes', 'status-codes', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)') - [CompletionResult]::new('-T', 'T ', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)') - [CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)') - [CompletionResult]::new('--server-certs', 'server-certs', [CompletionResultType]::ParameterName, 'Add custom root certificate(s) for servers with unknown certificates') - [CompletionResult]::new('--client-cert', 'client-cert', [CompletionResultType]::ParameterName, 'Add a PEM encoded certificate for mutual authentication (mTLS)') - [CompletionResult]::new('--client-key', 'client-key', [CompletionResultType]::ParameterName, 'Add a PEM encoded private key for mutual authentication (mTLS)') - [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)') - [CompletionResult]::new('--threads', 'threads', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)') - [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)') - [CompletionResult]::new('--depth', 'depth', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)') - [CompletionResult]::new('-L', 'L ', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)') - [CompletionResult]::new('--scan-limit', 'scan-limit', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)') - [CompletionResult]::new('--parallel', 'parallel', [CompletionResultType]::ParameterName, 'Run parallel feroxbuster instances (one child process per url passed via stdin)') - [CompletionResult]::new('--rate-limit', 'rate-limit', [CompletionResultType]::ParameterName, 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)') - [CompletionResult]::new('--time-limit', 'time-limit', [CompletionResultType]::ParameterName, 'Limit total run time of all scans (ex: --time-limit 10m)') - [CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist') - [CompletionResult]::new('--wordlist', 'wordlist', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist') - [CompletionResult]::new('-B', 'B ', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)') - [CompletionResult]::new('--collect-backups', 'collect-backups', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)') - [CompletionResult]::new('-I', 'I ', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)') - [CompletionResult]::new('--dont-collect', 'dont-collect', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)') - [CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)') - [CompletionResult]::new('--output', 'output', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)') - [CompletionResult]::new('--debug-log', 'debug-log', [CompletionResultType]::ParameterName, 'Output file to write log entries (use w/ --json for JSON entries)') - [CompletionResult]::new('--stdin', 'stdin', [CompletionResultType]::ParameterName, 'Read url(s) from STDIN') - [CompletionResult]::new('--burp', 'burp', [CompletionResultType]::ParameterName, 'Set --proxy to http://127.0.0.1:8080 and set --insecure to true') - [CompletionResult]::new('--burp-replay', 'burp-replay', [CompletionResultType]::ParameterName, 'Set --replay-proxy to http://127.0.0.1:8080 and set --insecure to true') - [CompletionResult]::new('--smart', 'smart', [CompletionResultType]::ParameterName, 'Set --auto-tune, --collect-words, and --collect-backups to true') - [CompletionResult]::new('--thorough', 'thorough', [CompletionResultType]::ParameterName, 'Use the same settings as --smart and set --collect-extensions to true') - [CompletionResult]::new('-A', 'A ', [CompletionResultType]::ParameterName, 'Use a random User-Agent') - [CompletionResult]::new('--random-agent', 'random-agent', [CompletionResultType]::ParameterName, 'Use a random User-Agent') - [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Append / to each request''s URL') - [CompletionResult]::new('--add-slash', 'add-slash', [CompletionResultType]::ParameterName, 'Append / to each request''s URL') - [CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Allow client to follow redirects') - [CompletionResult]::new('--redirects', 'redirects', [CompletionResultType]::ParameterName, 'Allow client to follow redirects') - [CompletionResult]::new('-k', 'k', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client') - [CompletionResult]::new('--insecure', 'insecure', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client') - [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Do not scan recursively') - [CompletionResult]::new('--no-recursion', 'no-recursion', [CompletionResultType]::ParameterName, 'Do not scan recursively') - [CompletionResult]::new('--force-recursion', 'force-recursion', [CompletionResultType]::ParameterName, 'Force recursion attempts on all ''found'' endpoints (still respects recursion depth)') - [CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)') - [CompletionResult]::new('--extract-links', 'extract-links', [CompletionResultType]::ParameterName, 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)') - [CompletionResult]::new('--dont-extract-links', 'dont-extract-links', [CompletionResultType]::ParameterName, 'Don''t extract links from response body (html, javascript, etc...)') - [CompletionResult]::new('--auto-tune', 'auto-tune', [CompletionResultType]::ParameterName, 'Automatically lower scan rate when an excessive amount of errors are encountered') - [CompletionResult]::new('--auto-bail', 'auto-bail', [CompletionResultType]::ParameterName, 'Automatically stop scanning when an excessive amount of errors are encountered') - [CompletionResult]::new('-D', 'D ', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses') - [CompletionResult]::new('--dont-filter', 'dont-filter', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses') - [CompletionResult]::new('-E', 'E ', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)') - [CompletionResult]::new('--collect-extensions', 'collect-extensions', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)') - [CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist') - [CompletionResult]::new('--collect-words', 'collect-words', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist') - [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)') - [CompletionResult]::new('--verbosity', 'verbosity', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)') - [CompletionResult]::new('--silent', 'silent', [CompletionResultType]::ParameterName, 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)') - [CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)') - [CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)') - [CompletionResult]::new('--json', 'json', [CompletionResultType]::ParameterName, 'Emit JSON logs to --output and --debug-log instead of normal text') - [CompletionResult]::new('--no-state', 'no-state', [CompletionResultType]::ParameterName, 'Disable state output file (*.state)') - [CompletionResult]::new('-U', 'U ', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version') - [CompletionResult]::new('--update', 'update', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version') - [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') - [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') - [CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version') - [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version') + [CompletionResult]::new('-u', '-u', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)') + [CompletionResult]::new('--url', '--url', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)') + [CompletionResult]::new('--resume-from', '--resume-from', [CompletionResultType]::ParameterName, 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)') + [CompletionResult]::new('--request-file', '--request-file', [CompletionResultType]::ParameterName, 'Raw HTTP request file to use as a template for all requests') + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)') + [CompletionResult]::new('--proxy', '--proxy', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)') + [CompletionResult]::new('-P', '-P ', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests') + [CompletionResult]::new('--replay-proxy', '--replay-proxy', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests') + [CompletionResult]::new('-R', '-R ', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)') + [CompletionResult]::new('--replay-codes', '--replay-codes', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)') + [CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.11.0)') + [CompletionResult]::new('--user-agent', '--user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.11.0)') + [CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)') + [CompletionResult]::new('--extensions', '--extensions', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)') + [CompletionResult]::new('-m', '-m', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)') + [CompletionResult]::new('--methods', '--methods', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)') + [CompletionResult]::new('--data', '--data', [CompletionResultType]::ParameterName, 'Request''s Body; can read data from a file if input starts with an @ (ex: @post.bin)') + [CompletionResult]::new('-H', '-H ', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')') + [CompletionResult]::new('--headers', '--headers', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')') + [CompletionResult]::new('-b', '-b', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)') + [CompletionResult]::new('--cookies', '--cookies', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)') + [CompletionResult]::new('-Q', '-Q ', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)') + [CompletionResult]::new('--query', '--query', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)') + [CompletionResult]::new('--protocol', '--protocol', [CompletionResultType]::ParameterName, 'Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)') + [CompletionResult]::new('--dont-scan', '--dont-scan', [CompletionResultType]::ParameterName, 'URL(s) or Regex Pattern(s) to exclude from recursion/scans') + [CompletionResult]::new('-S', '-S ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)') + [CompletionResult]::new('--filter-size', '--filter-size', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)') + [CompletionResult]::new('-X', '-X ', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')') + [CompletionResult]::new('--filter-regex', '--filter-regex', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')') + [CompletionResult]::new('-W', '-W ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)') + [CompletionResult]::new('--filter-words', '--filter-words', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)') + [CompletionResult]::new('-N', '-N ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)') + [CompletionResult]::new('--filter-lines', '--filter-lines', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)') + [CompletionResult]::new('-C', '-C ', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)') + [CompletionResult]::new('--filter-status', '--filter-status', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)') + [CompletionResult]::new('--filter-similar-to', '--filter-similar-to', [CompletionResultType]::ParameterName, 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)') + [CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)') + [CompletionResult]::new('--status-codes', '--status-codes', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)') + [CompletionResult]::new('-T', '-T ', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)') + [CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)') + [CompletionResult]::new('--server-certs', '--server-certs', [CompletionResultType]::ParameterName, 'Add custom root certificate(s) for servers with unknown certificates') + [CompletionResult]::new('--client-cert', '--client-cert', [CompletionResultType]::ParameterName, 'Add a PEM encoded certificate for mutual authentication (mTLS)') + [CompletionResult]::new('--client-key', '--client-key', [CompletionResultType]::ParameterName, 'Add a PEM encoded private key for mutual authentication (mTLS)') + [CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)') + [CompletionResult]::new('--threads', '--threads', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)') + [CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)') + [CompletionResult]::new('--depth', '--depth', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)') + [CompletionResult]::new('-L', '-L ', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)') + [CompletionResult]::new('--scan-limit', '--scan-limit', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)') + [CompletionResult]::new('--parallel', '--parallel', [CompletionResultType]::ParameterName, 'Run parallel feroxbuster instances (one child process per url passed via stdin)') + [CompletionResult]::new('--rate-limit', '--rate-limit', [CompletionResultType]::ParameterName, 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)') + [CompletionResult]::new('--time-limit', '--time-limit', [CompletionResultType]::ParameterName, 'Limit total run time of all scans (ex: --time-limit 10m)') + [CompletionResult]::new('-w', '-w', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist') + [CompletionResult]::new('--wordlist', '--wordlist', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist') + [CompletionResult]::new('-B', '-B ', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)') + [CompletionResult]::new('--collect-backups', '--collect-backups', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)') + [CompletionResult]::new('-I', '-I ', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)') + [CompletionResult]::new('--dont-collect', '--dont-collect', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)') + [CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)') + [CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)') + [CompletionResult]::new('--debug-log', '--debug-log', [CompletionResultType]::ParameterName, 'Output file to write log entries (use w/ --json for JSON entries)') + [CompletionResult]::new('--limit-bars', '--limit-bars', [CompletionResultType]::ParameterName, 'Number of directory scan bars to show at any given time (default: no limit)') + [CompletionResult]::new('--stdin', '--stdin', [CompletionResultType]::ParameterName, 'Read url(s) from STDIN') + [CompletionResult]::new('--burp', '--burp', [CompletionResultType]::ParameterName, 'Set --proxy to http://127.0.0.1:8080 and set --insecure to true') + [CompletionResult]::new('--burp-replay', '--burp-replay', [CompletionResultType]::ParameterName, 'Set --replay-proxy to http://127.0.0.1:8080 and set --insecure to true') + [CompletionResult]::new('--smart', '--smart', [CompletionResultType]::ParameterName, 'Set --auto-tune, --collect-words, and --collect-backups to true') + [CompletionResult]::new('--thorough', '--thorough', [CompletionResultType]::ParameterName, 'Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true') + [CompletionResult]::new('-A', '-A ', [CompletionResultType]::ParameterName, 'Use a random User-Agent') + [CompletionResult]::new('--random-agent', '--random-agent', [CompletionResultType]::ParameterName, 'Use a random User-Agent') + [CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'Append / to each request''s URL') + [CompletionResult]::new('--add-slash', '--add-slash', [CompletionResultType]::ParameterName, 'Append / to each request''s URL') + [CompletionResult]::new('-r', '-r', [CompletionResultType]::ParameterName, 'Allow client to follow redirects') + [CompletionResult]::new('--redirects', '--redirects', [CompletionResultType]::ParameterName, 'Allow client to follow redirects') + [CompletionResult]::new('-k', '-k', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client') + [CompletionResult]::new('--insecure', '--insecure', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client') + [CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'Do not scan recursively') + [CompletionResult]::new('--no-recursion', '--no-recursion', [CompletionResultType]::ParameterName, 'Do not scan recursively') + [CompletionResult]::new('--force-recursion', '--force-recursion', [CompletionResultType]::ParameterName, 'Force recursion attempts on all ''found'' endpoints (still respects recursion depth)') + [CompletionResult]::new('-e', '-e', [CompletionResultType]::ParameterName, 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)') + [CompletionResult]::new('--extract-links', '--extract-links', [CompletionResultType]::ParameterName, 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)') + [CompletionResult]::new('--dont-extract-links', '--dont-extract-links', [CompletionResultType]::ParameterName, 'Don''t extract links from response body (html, javascript, etc...)') + [CompletionResult]::new('--auto-tune', '--auto-tune', [CompletionResultType]::ParameterName, 'Automatically lower scan rate when an excessive amount of errors are encountered') + [CompletionResult]::new('--auto-bail', '--auto-bail', [CompletionResultType]::ParameterName, 'Automatically stop scanning when an excessive amount of errors are encountered') + [CompletionResult]::new('-D', '-D ', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses') + [CompletionResult]::new('--dont-filter', '--dont-filter', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses') + [CompletionResult]::new('-E', '-E ', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)') + [CompletionResult]::new('--collect-extensions', '--collect-extensions', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)') + [CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist') + [CompletionResult]::new('--collect-words', '--collect-words', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist') + [CompletionResult]::new('--scan-dir-listings', '--scan-dir-listings', [CompletionResultType]::ParameterName, 'Force scans to recurse into directory listings') + [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)') + [CompletionResult]::new('--verbosity', '--verbosity', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)') + [CompletionResult]::new('--silent', '--silent', [CompletionResultType]::ParameterName, 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)') + [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)') + [CompletionResult]::new('--quiet', '--quiet', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)') + [CompletionResult]::new('--json', '--json', [CompletionResultType]::ParameterName, 'Emit JSON logs to --output and --debug-log instead of normal text') + [CompletionResult]::new('--no-state', '--no-state', [CompletionResultType]::ParameterName, 'Disable state output file (*.state)') + [CompletionResult]::new('-U', '-U ', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version') + [CompletionResult]::new('--update', '--update', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version') + [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') + [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') + [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version') + [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version') break } }) diff --git a/shell_completions/feroxbuster.bash b/shell_completions/feroxbuster.bash index 45f7b48..04c9506 100644 --- a/shell_completions/feroxbuster.bash +++ b/shell_completions/feroxbuster.bash @@ -19,7 +19,7 @@ _feroxbuster() { case "${cmd}" in feroxbuster) - opts="-u -p -P -R -a -A -x -m -H -b -Q -f -S -X -W -N -C -s -T -r -k -t -n -d -e -L -w -D -E -B -g -I -v -q -o -U -h -V --url --stdin --resume-from --burp --burp-replay --smart --thorough --proxy --replay-proxy --replay-codes --user-agent --random-agent --extensions --methods --data --headers --cookies --query --add-slash --dont-scan --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --status-codes --timeout --redirects --insecure --server-certs --client-cert --client-key --threads --no-recursion --depth --force-recursion --extract-links --dont-extract-links --scan-limit --parallel --rate-limit --time-limit --wordlist --auto-tune --auto-bail --dont-filter --collect-extensions --collect-backups --collect-words --dont-collect --verbosity --silent --quiet --json --output --debug-log --no-state --update --help --version" + opts="-u -p -P -R -a -A -x -m -H -b -Q -f -S -X -W -N -C -s -T -r -k -t -n -d -e -L -w -D -E -B -g -I -v -q -o -U -h -V --url --stdin --resume-from --request-file --burp --burp-replay --smart --thorough --proxy --replay-proxy --replay-codes --user-agent --random-agent --extensions --methods --data --headers --cookies --query --add-slash --protocol --dont-scan --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --status-codes --timeout --redirects --insecure --server-certs --client-cert --client-key --threads --no-recursion --depth --force-recursion --extract-links --dont-extract-links --scan-limit --parallel --rate-limit --time-limit --wordlist --auto-tune --auto-bail --dont-filter --collect-extensions --collect-backups --collect-words --dont-collect --scan-dir-listings --verbosity --silent --quiet --json --output --debug-log --no-state --limit-bars --update --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -48,6 +48,21 @@ _feroxbuster() { fi return 0 ;; + --request-file) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; --proxy) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -124,6 +139,10 @@ _feroxbuster() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --protocol) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --dont-scan) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -360,6 +379,10 @@ _feroxbuster() { fi return 0 ;; + --limit-bars) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; diff --git a/shell_completions/feroxbuster.elv b/shell_completions/feroxbuster.elv index c3daeaf..7c452c7 100644 --- a/shell_completions/feroxbuster.elv +++ b/shell_completions/feroxbuster.elv @@ -18,17 +18,18 @@ set edit:completion:arg-completer[feroxbuster] = {|@words| } var completions = [ &'feroxbuster'= { - cand -u 'The target URL (required, unless [--stdin || --resume-from] used)' - cand --url 'The target URL (required, unless [--stdin || --resume-from] used)' + cand -u 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)' + cand --url 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)' cand --resume-from 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)' + cand --request-file 'Raw HTTP request file to use as a template for all requests' cand -p 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)' cand --proxy 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)' cand -P 'Send only unfiltered requests through a Replay Proxy, instead of all requests' cand --replay-proxy 'Send only unfiltered requests through a Replay Proxy, instead of all requests' cand -R 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)' cand --replay-codes 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)' - cand -a 'Sets the User-Agent (default: feroxbuster/2.10.4)' - cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.10.4)' + cand -a 'Sets the User-Agent (default: feroxbuster/2.11.0)' + cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.11.0)' cand -x 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)' cand --extensions 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)' cand -m 'Which HTTP request method(s) should be sent (default: GET)' @@ -40,6 +41,7 @@ set edit:completion:arg-completer[feroxbuster] = {|@words| cand --cookies 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)' cand -Q 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)' cand --query 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)' + cand --protocol 'Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)' cand --dont-scan 'URL(s) or Regex Pattern(s) to exclude from recursion/scans' cand -S 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)' cand --filter-size 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)' @@ -77,11 +79,12 @@ set edit:completion:arg-completer[feroxbuster] = {|@words| cand -o 'Output file to write results to (use w/ --json for JSON entries)' cand --output 'Output file to write results to (use w/ --json for JSON entries)' cand --debug-log 'Output file to write log entries (use w/ --json for JSON entries)' + cand --limit-bars 'Number of directory scan bars to show at any given time (default: no limit)' cand --stdin 'Read url(s) from STDIN' cand --burp 'Set --proxy to http://127.0.0.1:8080 and set --insecure to true' cand --burp-replay 'Set --replay-proxy to http://127.0.0.1:8080 and set --insecure to true' cand --smart 'Set --auto-tune, --collect-words, and --collect-backups to true' - cand --thorough 'Use the same settings as --smart and set --collect-extensions to true' + cand --thorough 'Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true' cand -A 'Use a random User-Agent' cand --random-agent 'Use a random User-Agent' cand -f 'Append / to each request''s URL' @@ -104,6 +107,7 @@ set edit:completion:arg-completer[feroxbuster] = {|@words| cand --collect-extensions 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)' cand -g 'Automatically discover important words from within responses and add them to the wordlist' cand --collect-words 'Automatically discover important words from within responses and add them to the wordlist' + cand --scan-dir-listings 'Force scans to recurse into directory listings' cand -v 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)' cand --verbosity 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)' cand --silent 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)' diff --git a/src/banner/container.rs b/src/banner/container.rs index a7fe564..a427f3d 100644 --- a/src/banner/container.rs +++ b/src/banner/container.rs @@ -176,6 +176,15 @@ pub struct Banner { /// represents Configuration.collect_words force_recursion: BannerEntry, + + /// represents Configuration.protocol + protocol: BannerEntry, + + /// represents Configuration.scan_dir_listings + scan_dir_listings: BannerEntry, + + /// represents Configuration.limit_bars + limit_bars: BannerEntry, } /// implementation of Banner @@ -320,6 +329,12 @@ impl Banner { BannerEntry::new("🚫", "Do Not Recurse", &config.no_recursion.to_string()) }; + let protocol = if config.protocol.to_lowercase() == "http" { + BannerEntry::new("🔓", "Default Protocol", &config.protocol) + } else { + BannerEntry::new("🔒", "Default Protocol", &config.protocol) + }; + let scan_limit = BannerEntry::new( "🦥", "Concurrent Scan Limit", @@ -331,6 +346,11 @@ impl Banner { let replay_proxy = BannerEntry::new("🎥", "Replay Proxy", &config.replay_proxy); let auto_tune = BannerEntry::new("🎶", "Auto Tune", &config.auto_tune.to_string()); let auto_bail = BannerEntry::new("🙅", "Auto Bail", &config.auto_bail.to_string()); + let scan_dir_listings = BannerEntry::new( + "📂", + "Scan Dir Listings", + &config.scan_dir_listings.to_string(), + ); let cfg = BannerEntry::new("💉", "Config File", &config.config); let proxy = BannerEntry::new("💎", "Proxy", &config.proxy); let server_certs = BannerEntry::new( @@ -341,6 +361,8 @@ impl Banner { let client_cert = BannerEntry::new("🏅", "Client Certificate", &config.client_cert); let client_key = BannerEntry::new("🔑", "Client Key", &config.client_key); let threads = BannerEntry::new("🚀", "Threads", &config.threads.to_string()); + let limit_bars = + BannerEntry::new("📊", "Limit Dir Scan Bars", &config.limit_bars.to_string()); let wordlist = BannerEntry::new("📖", "Wordlist", &config.wordlist); let timeout = BannerEntry::new("💥", "Timeout (secs)", &config.timeout.to_string()); let user_agent = BannerEntry::new("🦡", "User-Agent", &config.user_agent); @@ -455,6 +477,9 @@ impl Banner { collect_words, dont_collect, config: cfg, + scan_dir_listings, + protocol, + limit_bars, version: VERSION.to_string(), update_status: UpdateStatus::Unknown, } @@ -595,6 +620,14 @@ by Ben "epi" Risher {} ver: {}"#, } // followed by the maybe printed or variably displayed values + if !config.request_file.is_empty() || !config.target_url.starts_with("http") { + writeln!(&mut writer, "{}", self.protocol)?; + } + + if config.limit_bars > 0 { + writeln!(&mut writer, "{}", self.limit_bars)?; + } + if !config.config.is_empty() { writeln!(&mut writer, "{}", self.config)?; } @@ -662,6 +695,10 @@ by Ben "epi" Risher {} ver: {}"#, writeln!(&mut writer, "{}", self.output)?; } + if config.scan_dir_listings { + writeln!(&mut writer, "{}", self.scan_dir_listings)?; + } + if !config.debug_log.is_empty() { writeln!(&mut writer, "{}", self.debug_log)?; } diff --git a/src/config/container.rs b/src/config/container.rs index 7bf8eb7..20ab4bd 100644 --- a/src/config/container.rs +++ b/src/config/container.rs @@ -1,15 +1,16 @@ use super::utils::{ - backup_extensions, depth, extract_links, ignored_extensions, methods, report_and_exit, - save_state, serialized_type, status_codes, threads, timeout, user_agent, wordlist, OutputLevel, + backup_extensions, depth, determine_requester_policy, extract_links, ignored_extensions, + methods, parse_request_file, report_and_exit, request_protocol, save_state, serialized_type, + split_header, split_query, status_codes, threads, timeout, user_agent, wordlist, OutputLevel, RequesterPolicy, }; + use crate::config::determine_output_level; -use crate::config::utils::determine_requester_policy; use crate::{ client, parser, scan_manager::resume_scan, traits::FeroxSerialize, - utils::{fmt_err, parse_url_with_raw_path}, + utils::{fmt_err, module_colorizer, parse_url_with_raw_path, status_colorizer}, DEFAULT_CONFIG_NAME, }; use anyhow::{anyhow, Context, Result}; @@ -332,6 +333,22 @@ pub struct Configuration { /// Auto update app feature #[serde(skip)] pub update_app: bool, + + /// whether to recurse into directory listings or not + #[serde(default)] + pub scan_dir_listings: bool, + + /// path to a raw request file generated by burp or similar + #[serde(skip)] + pub request_file: String, + + /// default request protocol + #[serde(default = "request_protocol")] + pub protocol: String, + + /// number of directory scan bars to show at any given time, 0 is no limit + #[serde(default)] + pub limit_bars: usize, } impl Default for Configuration { @@ -378,10 +395,12 @@ impl Default for Configuration { resumed: false, stdin: false, json: false, + scan_dir_listings: false, verbosity: 0, scan_limit: 0, parallel: 0, rate_limit: 0, + limit_bars: 0, add_slash: false, insecure: false, redirects: false, @@ -403,6 +422,8 @@ impl Default for Configuration { time_limit: String::new(), resume_from: String::new(), replay_proxy: String::new(), + request_file: String::new(), + protocol: request_protocol(), server_certs: Vec::new(), queries: Vec::new(), extensions: Vec::new(), @@ -476,12 +497,16 @@ impl Configuration { /// - **depth**: `4` (maximum recursion depth) /// - **force_recursion**: `false` (still respects recursion depth) /// - **scan_limit**: `0` (no limit on concurrent scans imposed) + /// - **limit_bars**: `0` (no limit on number of directory scan bars shown) /// - **parallel**: `0` (no limit on parallel scans imposed) /// - **rate_limit**: `0` (no limit on requests per second imposed) /// - **time_limit**: `None` (no limit on length of scan imposed) /// - **replay_proxy**: `None` (no limit on concurrent scans imposed) /// - **replay_codes**: [`DEFAULT_RESPONSE_CODES`](constant.DEFAULT_RESPONSE_CODES.html) /// - **update_app**: `false` + /// - **scan_dir_listings**: `false` + /// - **request_file**: `None` + /// - **protocol**: `https` /// /// After which, any values defined in a /// [ferox-config.toml](constant.DEFAULT_CONFIG_NAME.html) config file will override the @@ -555,6 +580,18 @@ impl Configuration { // merge the cli options into the config file options and return the result Self::merge_config(&mut config, cli_config); + // if the user provided a raw request file as the target, we'll need to parse out + // the provided info and update the config with those values. This call needs to + // come after the cli/config merge so we can allow the cli options to override + // the raw request values (i.e. --headers "stuff: things" should override a "stuff" + // header from the raw request). + // + // Additionally, this call needs to come before client rebuild so that the things + // like user-agent can be set at the client level instead of the header level. + if !config.request_file.is_empty() { + parse_request_file(&mut config)?; + } + // rebuild clients is the last step in either code branch Self::try_rebuild_clients(&mut config); @@ -614,10 +651,13 @@ impl Configuration { update_config_with_num_type_if_present!(&mut config.depth, args, "depth", usize); update_config_with_num_type_if_present!(&mut config.scan_limit, args, "scan_limit", usize); update_config_with_num_type_if_present!(&mut config.rate_limit, args, "rate_limit", usize); + update_config_with_num_type_if_present!(&mut config.limit_bars, args, "limit_bars", usize); update_config_if_present!(&mut config.wordlist, args, "wordlist", String); update_config_if_present!(&mut config.output, args, "output", String); update_config_if_present!(&mut config.debug_log, args, "debug_log", String); update_config_if_present!(&mut config.resume_from, args, "resume_from", String); + update_config_if_present!(&mut config.request_file, args, "request_file", String); + update_config_if_present!(&mut config.protocol, args, "protocol", String); if let Ok(Some(inner)) = args.try_get_one::("time_limit") { inner.clone_into(&mut config.time_limit); @@ -831,6 +871,10 @@ impl Configuration { config.save_state = false; } + if came_from_cli!(args, "scan_dir_listings") || came_from_cli!(args, "thorough") { + config.scan_dir_listings = true; + } + if came_from_cli!(args, "dont_filter") { config.dont_filter = true; } @@ -871,6 +915,25 @@ impl Configuration { // occurrences_of returns 0 if none are found; this is protected in // an if block for the same reason as the quiet option config.verbosity = args.get_count("verbosity"); + + // todo: starting on 2.11.0 (907-dont-skip-dir-listings), trace-level + // logging started causing the following error: + // + // thread 'tokio-runtime-worker' has overflowed its stack + // fatal runtime error: stack overflow + // Aborted (core dumped) + // + // as a temporary fix, we'll disable trace logging to prevent the stack + // overflow until I get time to investigate the root cause + if config.verbosity > 3 { + eprintln!( + "{} {}: Trace level logging is disabled; setting log level to debug", + status_colorizer("WRN"), + module_colorizer("Configuration::parse_cli_args"), + ); + + config.verbosity = 3; + } } if came_from_cli!(args, "no_recursion") { @@ -932,23 +995,11 @@ impl Configuration { if let Some(headers) = args.get_many::("headers") { for val in headers { - let mut split_val = val.split(':'); - - // explicitly take first split value as header's name - let name = split_val.next().unwrap().trim(); - - // all other items in the iterator returned by split, when combined with the - // original split deliminator (:), make up the header's final value - let value = split_val.collect::>().join(":"); - - if value.starts_with(' ') && !value.starts_with(" ") { - // first character is a space and the second character isn't - // we can trim the leading space - let trimmed = value.trim_start(); - config.headers.insert(name.to_string(), trimmed.to_string()); - } else { - config.headers.insert(name.to_string(), value.to_string()); - } + let Ok((name, value)) = split_header(val) else { + log::warn!("Invalid header: {}", val); + continue; + }; + config.headers.insert(name, value); } } @@ -982,14 +1033,11 @@ impl Configuration { if let Some(queries) = args.get_many::("queries") { for val in queries { - // same basic logic used as reading in the headers HashMap above - let mut split_val = val.split('='); - - let name = split_val.next().unwrap().trim(); - - let value = split_val.collect::>().join("="); - - config.queries.push((name.to_string(), value.to_string())); + let Ok((name, value)) = split_query(val) else { + log::warn!("Invalid query string: {}", val); + continue; + }; + config.queries.push((name, value)); } } @@ -1111,6 +1159,7 @@ impl Configuration { update_if_not_default!(&mut conf.client_cert, new.client_cert, ""); update_if_not_default!(&mut conf.client_key, new.client_key, ""); update_if_not_default!(&mut conf.verbosity, new.verbosity, 0); + update_if_not_default!(&mut conf.limit_bars, new.limit_bars, 0); update_if_not_default!(&mut conf.silent, new.silent, false); update_if_not_default!(&mut conf.quiet, new.quiet, false); update_if_not_default!(&mut conf.auto_bail, new.auto_bail, false); @@ -1171,12 +1220,15 @@ impl Configuration { Vec::::new() ); update_if_not_default!(&mut conf.dont_filter, new.dont_filter, false); + update_if_not_default!(&mut conf.scan_dir_listings, new.scan_dir_listings, false); update_if_not_default!(&mut conf.scan_limit, new.scan_limit, 0); update_if_not_default!(&mut conf.parallel, new.parallel, 0); update_if_not_default!(&mut conf.rate_limit, new.rate_limit, 0); update_if_not_default!(&mut conf.replay_proxy, new.replay_proxy, ""); update_if_not_default!(&mut conf.debug_log, new.debug_log, ""); update_if_not_default!(&mut conf.resume_from, new.resume_from, ""); + update_if_not_default!(&mut conf.request_file, new.request_file, ""); + update_if_not_default!(&mut conf.protocol, new.protocol, request_protocol()); update_if_not_default!(&mut conf.timeout, new.timeout, timeout()); update_if_not_default!(&mut conf.user_agent, new.user_agent, user_agent()); diff --git a/src/config/tests.rs b/src/config/tests.rs index cbde931..06affb3 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -49,6 +49,10 @@ fn setup_config_test() -> Configuration { json = true save_state = false depth = 1 + limit_bars = 3 + protocol = "http" + request_file = "/some/request/file" + scan_dir_listings = true force_recursion = true filter_size = [4120] filter_regex = ["^ignore me$"] @@ -87,6 +91,7 @@ fn default_configuration() { assert_eq!(config.timeout, timeout()); assert_eq!(config.verbosity, 0); assert_eq!(config.scan_limit, 0); + assert_eq!(config.limit_bars, 0); assert!(!config.silent); assert!(!config.quiet); assert_eq!(config.output_level, OutputLevel::Default); @@ -107,6 +112,7 @@ fn default_configuration() { assert!(!config.collect_extensions); assert!(!config.collect_backups); assert!(!config.collect_words); + assert!(!config.scan_dir_listings); assert!(config.regex_denylist.is_empty()); assert_eq!(config.queries, Vec::new()); assert_eq!(config.filter_size, Vec::::new()); @@ -125,6 +131,8 @@ fn default_configuration() { assert_eq!(config.client_cert, String::new()); assert_eq!(config.client_key, String::new()); assert_eq!(config.backup_extensions, backup_extensions()); + assert_eq!(config.protocol, request_protocol()); + assert_eq!(config.request_file, String::new()); } #[test] @@ -260,6 +268,13 @@ fn config_reads_verbosity() { assert_eq!(config.verbosity, 1); } +#[test] +/// parse the test config and see that the value parsed is correct +fn config_reads_limit_bars() { + let config = setup_config_test(); + assert_eq!(config.limit_bars, 3); +} + #[test] /// parse the test config and see that the value parsed is correct fn config_reads_output() { @@ -444,6 +459,27 @@ fn config_reads_time_limit() { assert_eq!(config.time_limit, "10m"); } +#[test] +/// parse the test config and see that the value parsed is correct +fn config_reads_scan_dir_listings() { + let config = setup_config_test(); + assert!(config.scan_dir_listings); +} + +#[test] +/// parse the test config and see that the value parsed is correct +fn config_reads_protocol() { + let config = setup_config_test(); + assert_eq!(config.protocol, "http"); +} + +#[test] +/// parse the test config and see that the value parsed is correct +fn config_reads_request_file() { + let config = setup_config_test(); + assert_eq!(config.request_file, String::new()); +} + #[test] /// parse the test config and see that the value parsed is correct fn config_reads_resume_from() { diff --git a/src/config/utils.rs b/src/config/utils.rs index f3bd3b4..128249d 100644 --- a/src/config/utils.rs +++ b/src/config/utils.rs @@ -1,8 +1,12 @@ +use super::Configuration; use crate::{ - utils::{module_colorizer, status_colorizer}, + utils::{module_colorizer, parse_url_with_raw_path, status_colorizer}, DEFAULT_BACKUP_EXTENSIONS, DEFAULT_IGNORED_EXTENSIONS, DEFAULT_METHOD, DEFAULT_STATUS_CODES, DEFAULT_WORDLIST, VERSION, }; +use anyhow::{bail, Result}; +use std::collections::HashMap; + #[cfg(not(test))] use std::process::exit; @@ -45,6 +49,11 @@ pub(super) fn threads() -> usize { 50 } +/// default protocol value +pub(super) fn request_protocol() -> String { + String::from("https") +} + /// default status codes pub(super) fn status_codes() -> Vec { DEFAULT_STATUS_CODES @@ -179,9 +188,449 @@ pub fn determine_requester_policy(auto_tune: bool, auto_bail: bool) -> Requester } } +/// Splits a query string into a key-value pair. +/// +/// This function takes a query string in the format of `"key=value"` and splits it into +/// a tuple containing the key and value as separate strings. If the query string is +/// malformed (e.g., empty or without a key), it returns an error. +/// +/// # Arguments +/// +/// * `query` - A string slice that holds the query string to be split. +/// +/// # Returns +/// +/// * `Result<(String, String)>` - A tuple containing the key and value as `String`s, +/// or an error if the input is invalid. +/// +/// # Errors +/// +/// This function will return an error if: +/// * The input string is empty or equal to `"="`. +/// * The key part of the query string is empty (i.e., if the string starts with `"="`). +/// +/// # Examples +/// +/// ``` +/// let result = split_query("name=John"); +/// assert_eq!(result.unwrap(), ("name".to_string(), "John".to_string())); +/// +/// let result = split_query("name="); +/// assert_eq!(result.unwrap(), ("name".to_string(), "".to_string())); +/// +/// let result = split_query("name=John=Doe"); +/// assert_eq!(result.unwrap(), ("name".to_string(), "John=Doe".to_string())); +/// +/// let result = split_query("=John"); +/// assert!(result.is_err()); +/// +/// let result = split_query(""); +/// assert!(result.is_err()); +/// ``` +pub fn split_query(query: &str) -> Result<(String, String)> { + if query.is_empty() || query == "=" { + bail!("Empty query string provided"); + } + + let mut split_val = query.split('='); + + let name = split_val.next().unwrap().trim(); + + if name.is_empty() { + bail!("Empty key in query string"); + } + + let value = split_val.collect::>().join("="); + + Ok((name.to_string(), value.to_string())) +} + +/// Splits an HTTP header string into a key-value pair. +/// +/// This function takes a header string in the format of `"Key: Value"` and splits it into +/// a tuple containing the key and value as separate strings. If the header string is +/// malformed (e.g., empty or missing a key), it returns an error. +/// +/// # Arguments +/// +/// * `header` - A string slice that holds the header string to be split. +/// +/// # Returns +/// +/// * `Result<(String, String)>` - A tuple containing the key and value as `String`s, +/// or an error if the input is invalid. +/// +/// # Errors +/// +/// This function will return an error if: +/// * The input string is empty. +/// * The key part of the header string is empty (i.e., if the string starts with `":"`). +/// +/// # Examples +/// +/// ``` +/// let result = split_header("Content-Type: application/json"); +/// assert_eq!(result.unwrap(), ("Content-Type".to_string(), "application/json".to_string())); +/// +/// let result = split_header("Content-Length: 1234"); +/// assert_eq!(result.unwrap(), ("Content-Length".to_string(), "1234".to_string())); +/// +/// let result = split_header("Authorization: Bearer token"); +/// assert_eq!(result.unwrap(), ("Authorization".to_string(), "Bearer token".to_string())); +/// +/// let result = split_header("InvalidHeader"); +/// assert!(result.is_err()); +/// +/// let result = split_header(""); +/// assert!(result.is_err()); +/// ``` +pub fn split_header(header: &str) -> Result<(String, String)> { + if header.is_empty() { + bail!("Empty header provided"); + } + + let mut split_val = header.split(':'); + + // explicitly take first split value as header's name + let name = split_val.next().unwrap().trim().to_string(); + + if name.is_empty() { + bail!("Empty header name provided"); + } + + // all other items in the iterator returned by split, when combined with the + // original split deliminator (:), make up the header's final value + let value = split_val.collect::>().join(":"); + + if value.starts_with(' ') && !value.starts_with(" ") { + // first character is a space and the second character isn't + // we can trim the leading space + let trimmed = value.trim_start(); + Ok((name, trimmed.to_string())) + } else { + Ok((name, value)) + } +} + +/// Combines two `Cookie` header strings into a single, unified `Cookie` header string. +/// +/// The function parses both input strings into individual key-value pairs, ensuring that each +/// key is unique. If a key appears in both input strings, the value from the second string +/// will override the value from the first string. The resulting combined `Cookie` header string +/// is returned with all key-value pairs separated by `;`. +/// +/// # Arguments +/// +/// * `cookie1` - A string slice representing the first `Cookie` header. +/// * `cookie2` - A string slice representing the second `Cookie` header. +/// +/// # Returns +/// +/// * A `String` containing the combined `Cookie` header with unique keys. +/// +/// # Example +/// +/// ``` +/// let cookie1 = "super=duper; stuff=things"; +/// let cookie2 = "stuff=mothings; derp=tronic"; +/// let combined_cookie = combine_cookies(cookie1, cookie2); +/// assert_eq!(combined_cookie, "super=duper; stuff=mothings; derp=tronic"); +/// ``` +/// +/// The output string will contain all unique keys from both input strings, with the value +/// from the second string taking precedence in the case of key collisions. +fn combine_cookies(cookie1: &str, cookie2: &str) -> String { + let mut cookie_map = HashMap::new(); + + // Helper function to parse a cookie string and insert it into the map + let parse_cookie = |cookie_str: &str, map: &mut HashMap| { + for pair in cookie_str.split(';') { + let mut key_value = pair.trim().splitn(2, '='); + if let (Some(key), Some(value)) = (key_value.next(), key_value.next()) { + map.insert(key.to_string(), value.to_string()); + } + } + }; + + // Parse both cookie strings into the map + parse_cookie(cookie1, &mut cookie_map); + parse_cookie(cookie2, &mut cookie_map); + + // Build the final cookie header string + cookie_map + .into_iter() + .map(|(key, value)| format!("{}={}", key, value)) + .collect::>() + .join("; ") +} + +/// Parses a raw HTTP request from a file and updates the provided configuration. +/// +/// This function reads an HTTP request from the file specified by `config.request_file`, +/// parses the request line, headers, and body, and updates the `config` object +/// with the parsed values. If certain elements (e.g., headers or body) are +/// already provided via the CLI, they take precedence over the parsed values. +/// +/// # Arguments +/// +/// * `config` - A mutable reference to a `Configuration` object that will be +/// updated with the parsed request data. +/// +/// # Returns +/// +/// * `Result<()>` - Returns `Ok(())` if parsing and configuration updates +/// were successful, or an error if the raw file or request is invalid. +/// +/// # Errors +/// +/// This function will return an error if: +/// * The file specified in `config.request_file` is empty. +/// * The request is malformed (e.g., missing the request line, method, or URI). +/// * Required headers are missing (e.g., `Host` when the request line URI is not a full URL). +/// +/// # Details +/// +/// * The request body is only set if it hasn't been overridden by the CLI options. +/// * The request line method is added to `config.methods` if it's not already present. +/// * Headers from the raw request are added to `config.headers`, unless overridden +/// by CLI options. Special handling is applied to `User-Agent`, `Content-Length`, +/// and `Cookie` headers. +/// * The request URI is validated and parsed. If it's not a full URL, it will be +/// combined with the `Host` header to form a full target URL. +/// * Query parameters are extracted from the URI and added to `config.queries`, +/// unless overridden by CLI options. +/// +/// # Examples +/// +/// ```rust +/// let mut config = Configuration::default(); +/// config.request_file = "path/to/raw/request.txt".to_string(); +/// +/// let result = parse_request_file(&mut config); +/// assert!(result.is_ok()); +/// assert_eq!(config.methods, vec!["GET".to_string()]); +/// assert_eq!(config.target_url, "http://example.com/path".to_string()); +/// assert_eq!(config.headers.get("User-Agent").unwrap(), "MyCustomAgent"); +/// assert_eq!(config.data, b"key=value".to_vec()); +/// ``` +pub fn parse_request_file(config: &mut Configuration) -> Result<()> { + // read in the file located at config.request_file + // parse the file into a Request struct + let contents = std::fs::read_to_string(&config.request_file)?; + + if contents.is_empty() { + bail!("Empty --request-file file provided"); + } + + // this should split the body from the request line and headers + let lines = contents.split("\r\n\r\n").collect::>(); + + if lines.len() < 2 { + bail!("Invalid request: Missing head/body CRLF separator"); + } + + let head = lines[0]; + let body = lines[1].as_bytes().to_vec(); + + // we only want to use the request's body if the user hasn't + // overridden it on the cli + if config.data.is_empty() { + config.data = body; + } + + // begin parsing the request line and headers + let mut head_parts = head.split("\r\n"); + + let Some(request_line) = head_parts.next() else { + bail!("Invalid request: Missing request line"); + }; + + if request_line.is_empty() { + bail!("Invalid request: Empty request line"); + } + + let mut request_parts = request_line.split_whitespace(); + + let Some(method) = request_parts.next() else { + bail!("Invalid request: Missing method"); + }; + + if method.is_empty() { + bail!("Invalid request: Empty method"); + } + + let method = method.to_string(); + + if !config.methods.contains(&method) { + config.methods.push(method); + } + + let Some(uri) = request_parts.next() else { + bail!("Invalid request: Missing request line URI"); + }; + + if uri.is_empty() { + bail!("Invalid request: Empty request line URI"); + } + + for mut line in head_parts { + line = line.trim(); + + if line.is_empty() { + break; // Empty line signals the end of headers + } + + let Ok((name, value)) = split_header(line) else { + log::warn!("Invalid header: {}", line); + continue; + }; + + if name.is_empty() { + log::warn!("Invalid header name: {}", line); + continue; + } + + if name.to_lowercase() == "user-agent" { + if config.user_agent == user_agent() { + config.user_agent = value; + } + continue; + } + + if name.to_lowercase() == "content-length" { + log::debug!("Skipping content-length header, a new one will be created"); + continue; + } + + if config.headers.contains_key(&name) { + if name.to_lowercase() == "cookie" { + // the cookie header already exists, so we need to extend it with + // our values and ensure cli-provided cookie values override those + // from the request + let existing = config.headers.get_mut(&name).unwrap(); + // second param takes precedence over first + let combined = combine_cookies(&value, existing); + *existing = combined; + continue; + } + log::debug!("Found header from cli, overriding raw request with cli entry: {name}"); + continue; + } + + config.headers.insert(name, value); + } + + let url = parse_url_with_raw_path(uri); + + if url.is_err() { + // uri in request line is not a valid URL, so it's most likely a path/relative url + // we need to combine it with the host header + for (key, value) in &config.headers { + if key.to_lowercase() == "host" { + config.target_url = format!("{}{}", value, uri); + break; + } + } + + if config.target_url.is_empty() { + bail!("Invalid request: Missing Host header and request line URI isn't a full URL"); + } + + // need to parse queries from the uri, if any are present + let mut uri_parts = uri.splitn(2, '?'); + + // skip the path + uri_parts.next(); + + if let Some(queries) = uri_parts.next() { + let query_parts = queries.split("&"); + + query_parts.into_iter().for_each(|query| { + let Ok((name, value)) = split_query(query) else { + return; + }; + for (k, _) in &config.queries { + if k.to_lowercase() == name.to_lowercase() { + // allow cli options to take precedent when query names match + return; + } + } + + config.queries.push((name, value)); + }); + } + } else { + let mut url = url.unwrap(); + + if let Some(host) = config.headers.get("Host") { + url.set_host(Some(host)).unwrap(); + } + + url.query_pairs().for_each(|(key, value)| { + for (k, _) in &config.queries { + if k.to_lowercase() == key.to_lowercase() { + // allow cli options to take precedent when query names match + return; + } + } + + config.queries.push((key.to_string(), value.to_string())); + }); + + url.set_query(None); + url.set_fragment(None); + + config.target_url = url.to_string(); + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; + use std::env; + use std::fs::{self, File}; + use std::io::{self, Write}; + use std::path::PathBuf; + use std::time::{SystemTime, UNIX_EPOCH}; + + struct TempSetup { + pub path: PathBuf, + pub config: Configuration, + pub file: File, + } + + impl TempSetup { + pub fn new() -> Self { + let mut temp_dir: PathBuf = env::temp_dir(); + + temp_dir.push(format!( + "temp_request_file_{}.txt", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + + let config: Configuration = Configuration { + request_file: temp_dir.to_str().unwrap().to_string(), + ..Default::default() + }; + + let file = File::create(&temp_dir).unwrap(); + + Self { + path: temp_dir, + config, + file, + } + } + + pub fn cleanup(self) { + fs::remove_file(self.path).unwrap(); + } + } #[test] /// test determine_output_level returns higher of the two levels if both given values are true @@ -233,4 +682,609 @@ mod tests { fn report_and_exit_panics_under_test() { report_and_exit("test"); } + + #[test] + fn test_split_query_simple() { + let query = "name=value"; + let result = split_query(query).unwrap(); + assert_eq!(result, ("name".to_string(), "value".to_string())); + } + + #[test] + fn test_split_query_with_spaces() { + let query = " name = value "; + let result = split_query(query).unwrap(); + assert_eq!(result, ("name".to_string(), " value ".to_string())); + } + + #[test] + fn test_split_query_empty_value() { + let query = "name="; + let result = split_query(query).unwrap(); + assert_eq!(result, ("name".to_string(), "".to_string())); + } + + #[test] + fn test_split_query_no_value() { + let query = "name"; + let result = split_query(query).unwrap(); + assert_eq!(result, ("name".to_string(), "".to_string())); + } + + #[test] + fn test_split_query_multiple_equals() { + let query = "name=value=another"; + let result = split_query(query).unwrap(); + assert_eq!(result, ("name".to_string(), "value=another".to_string())); + } + + #[test] + fn test_split_query_empty_key_and_value() { + let query = "="; + let result = split_query(query); + assert!(result.is_err()); + } + + #[test] + fn test_split_query_empty_key() { + let query = "=value"; + let result = split_query(query); + assert!(result.is_err()); + } + + #[test] + fn test_split_query_trailing_equals_in_value() { + let query = "name=value="; + let result = split_query(query).unwrap(); + assert_eq!(result, ("name".to_string(), "value=".to_string())); + } + + #[test] + fn test_split_query_no_equals() { + let query = "just_a_key"; + let result = split_query(query).unwrap(); + assert_eq!(result, ("just_a_key".to_string(), "".to_string())); + } + + #[test] + fn test_split_query_empty_input() { + let query = ""; + assert!(split_query(query).is_err()); + } + + #[test] + fn test_split_header_simple() -> Result<()> { + let header = "Content-Type: text/html"; + let result = split_header(header)?; + assert_eq!( + result, + ("Content-Type".to_string(), "text/html".to_string()) + ); + Ok(()) + } + + #[test] + fn test_split_header_with_leading_space_in_value() -> Result<()> { + let header = "Content-Type: text/html"; + let result = split_header(header)?; + assert_eq!( + result, + ("Content-Type".to_string(), " text/html".to_string()) + ); + Ok(()) + } + + #[test] + fn test_split_header_with_trimmed_leading_space() -> Result<()> { + let header = "Content-Type: text/html"; + let result = split_header(header)?; + assert_eq!( + result, + ("Content-Type".to_string(), "text/html".to_string()) + ); + Ok(()) + } + + #[test] + fn test_split_header_with_multiple_colons() -> Result<()> { + let header = "Date: Mon, 27 Jul 2009 12:28:53 GMT"; + let result = split_header(header)?; + assert_eq!( + result, + ( + "Date".to_string(), + "Mon, 27 Jul 2009 12:28:53 GMT".to_string() + ) + ); + Ok(()) + } + + #[test] + fn test_split_header_empty_value() -> Result<()> { + let header = "X-Custom-Header: "; + let result = split_header(header)?; + assert_eq!(result, ("X-Custom-Header".to_string(), "".to_string())); + Ok(()) + } + + #[test] + fn test_split_header_no_value() -> Result<()> { + let header = "X-Custom-Header:"; + let result = split_header(header)?; + assert_eq!(result, ("X-Custom-Header".to_string(), "".to_string())); + Ok(()) + } + + #[test] + fn test_split_header_no_colon() -> Result<()> { + let header = "InvalidHeader"; + let result = split_header(header)?; + assert_eq!(result, ("InvalidHeader".to_string(), "".to_string())); + Ok(()) + } + + #[test] + fn test_split_header_empty_key() { + let header = ": value"; + let result = split_header(header); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Empty header name provided" + ); + } + + #[test] + fn test_split_header_empty_key_and_value() { + let header = ": "; + let result = split_header(header); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Empty header name provided" + ); + } + + #[test] + fn test_split_header_empty_input() { + let header = ""; + let result = split_header(header); + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), "Empty header provided"); + } + + #[test] + fn test_split_header_value_with_leading_single_space() -> Result<()> { + let header = "Authorization: Bearer token"; + let result = split_header(header)?; + assert_eq!( + result, + ("Authorization".to_string(), "Bearer token".to_string()) + ); + Ok(()) + } + + #[test] + fn test_split_header_value_with_leading_multiple_spaces() -> Result<()> { + let header = "Authorization: Bearer token"; + let result = split_header(header)?; + assert_eq!( + result, + ("Authorization".to_string(), " Bearer token".to_string()) + ); + Ok(()) + } + + #[test] + fn test_parse_raw_with_empty_request() { + let mut config = Configuration::new().unwrap(); + let result = parse_request_file(&mut config); + assert!(result.is_err()); + } + #[test] + fn test_parse_raw_with_empty_file() -> io::Result<()> { + let mut tmp = TempSetup::new(); + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Empty --request-file file provided" + ); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_without_head_body_crlf() -> io::Result<()> { + let mut tmp = TempSetup::new(); + + write!(tmp.file, "GET / HTTP/1.1\r\n")?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid request: Missing head/body CRLF separator" + ); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_only_head_body_crlf() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + writeln!(tmp.file, "\r\n\r\n")?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid request: Empty request line" + ); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_body_is_overridden_by_cli() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "GET http://localhost/srv HTTP/1.0\r\n\r\nrequest-body" + )?; + + parse_request_file(&mut tmp.config).unwrap(); + assert_eq!(tmp.config.data, b"request-body".to_vec()); + + tmp.config.data = b"cli-data".to_vec(); + + parse_request_file(&mut tmp.config).unwrap(); + assert_eq!(tmp.config.data, b"cli-data".to_vec()); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_empty_request_line() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!(tmp.file, "\r\nHost: example.com\r\n\r\n")?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid request: Empty request line" + ); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_missing_uri() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!(tmp.file, "GET\r\nHost: example.com\r\n\r\n")?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid request: Missing request line URI" + ); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_missing_method() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!(tmp.file, " \r\nHost: example.com\r\n\r\n")?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid request: Missing method" + ); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_methods_are_appended_if_unique() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "POST / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test-agent\r\n\r\n" + )?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert_eq!(tmp.config.methods, vec!["GET", "POST"]); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_methods_are_ignored_if_already_present_from_cli() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test-agent\r\n\r\n" + )?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert_eq!(tmp.config.methods, vec!["GET"]); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_headers_added_to_config_if_missing_else_overridden_from_cli() -> io::Result<()> + { + let mut tmp: TempSetup = TempSetup::new(); + + // header from cli + tmp.config + .headers + .insert(String::from("stuff"), String::from("things")); + + // stuff header will be overridden by the one in the cli config (i.e. the raw request's + // stuff header will be ignored because of the cli config) + write!( + tmp.file, + "GET / HTTP/1.1\r\nHost: example.com\r\nstuff: mothings\r\n\r\n" + )?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert!(tmp.config.headers.contains_key("Host")); + assert_eq!(tmp.config.headers.get("stuff").unwrap(), "things"); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_user_agent_in_request() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test-agent\r\n\r\n" + )?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert_eq!(tmp.config.user_agent, "test-agent"); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_user_agent_in_request_and_cli() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test-agent\r\n\r\n" + )?; + + tmp.config.user_agent = "cli-agent".to_string(); + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert_eq!(tmp.config.user_agent, "cli-agent"); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_content_length_is_always_skipped() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "GET / HTTP/1.1\r\nHost: example.com\r\nContent-length: 21\r\n\r\n" + )?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert!(!tmp.config.headers.contains_key("Content-length")); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_cookie_header_appended_or_overridden() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: derp=tronic2; super=duper2\r\n\r\n" + )?; + + tmp.config.headers.insert( + "Cookie".to_string(), + "derp=tronic; stuff=things".to_string(), + ); + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + + let cookies = tmp.config.headers.get("Cookie").unwrap(); + + assert!(cookies.contains("derp=tronic")); + assert!(cookies.contains("stuff=things")); + assert!(cookies.contains("super=duper2")); + + // got overridden + assert!(!cookies.contains("derp=tronic2")); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_relative_path_and_partial_host_header() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!(tmp.file, "GET /srv HTTP/1.1\r\nHost: example.com\r\n\r\n")?; + + let result = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert_eq!(tmp.config.target_url, "example.com/srv"); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_relative_path_and_no_host_header() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!(tmp.file, "GET /srv HTTP/1.1\r\n\r\n")?; + + let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid request: Missing Host header and request line URI isn't a full URL" + ); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_full_url_and_no_host_header() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!(tmp.file, "GET http://localhost/srv HTTP/1.1\r\n\r\n")?; + + let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert_eq!(tmp.config.target_url, "http://localhost/srv"); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_full_url_and_host_header() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "GET http://localhost/srv HTTP/1.1\r\nHost: example.com\r\n\r\n" + )?; + + let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert_eq!(tmp.config.target_url, "http://example.com/srv"); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_partial_url_and_queries() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "GET /srv?mostuff=mothings&derp=tronic2 HTTP/1.1\r\nHost: example.com\r\n\r\n" + )?; + + tmp.config + .queries + .push(("derp".to_string(), "tronic".to_string())); + tmp.config + .queries + .push(("stuff".to_string(), "things".to_string())); + + let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert_eq!( + tmp.config.queries, + vec![ + (String::from("derp"), String::from("tronic")), + (String::from("stuff"), String::from("things")), + (String::from("mostuff"), String::from("mothings")) + ] + ); + + tmp.cleanup(); + Ok(()) + } + + #[test] + fn test_parse_raw_with_full_url_and_queries() -> io::Result<()> { + let mut tmp: TempSetup = TempSetup::new(); + + write!( + tmp.file, + "GET http://localhost/srv?mostuff=mothings&derp=tronic2 HTTP/1.1\r\nHost: example.com\r\n\r\n" + )?; + + tmp.config + .queries + .push(("derp".to_string(), "tronic".to_string())); + tmp.config + .queries + .push(("stuff".to_string(), "things".to_string())); + + let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config); + + assert!(result.is_ok()); + assert_eq!( + tmp.config.queries, + vec![ + (String::from("derp"), String::from("tronic")), + (String::from("stuff"), String::from("things")), + (String::from("mostuff"), String::from("mothings")) + ] + ); + + tmp.cleanup(); + Ok(()) + } } diff --git a/src/event_handlers/scans.rs b/src/event_handlers/scans.rs index 5dc8ab9..b3640ff 100644 --- a/src/event_handlers/scans.rs +++ b/src/event_handlers/scans.rs @@ -120,7 +120,10 @@ impl ScanHandler { pub fn initialize(handles: Arc) -> (Joiner, ScanHandle) { log::trace!("enter: initialize"); - let data = Arc::new(FeroxScans::new(handles.config.output_level)); + let data = Arc::new(FeroxScans::new( + handles.config.output_level, + handles.config.limit_bars, + )); let (tx, rx): FeroxChannel = mpsc::unbounded_channel(); let max_depth = handles.config.depth; @@ -322,7 +325,9 @@ impl ScanHandler { let scan = if let Some(ferox_scan) = self.data.get_scan_by_url(&target) { ferox_scan // scan already known } else { - self.data.add_directory_scan(&target, order).1 // add the new target; return FeroxScan + self.data + .add_directory_scan(&target, order, self.handles.clone()) + .1 // add the new target; return FeroxScan }; if should_test_deny diff --git a/src/extractor/container.rs b/src/extractor/container.rs index ad9f4f1..70871c8 100644 --- a/src/extractor/container.rs +++ b/src/extractor/container.rs @@ -228,8 +228,11 @@ impl<'a> Extractor<'a> { if resp.is_file() || !resp.is_directory() { log::debug!("Extracted File: {}", resp); - c_scanned_urls - .add_file_scan(resp.url().as_str(), ScanOrder::Latest); + c_scanned_urls.add_file_scan( + resp.url().as_str(), + ScanOrder::Latest, + c_handles.clone(), + ); if c_handles.config.collect_extensions { // no real reason this should fail diff --git a/src/extractor/tests.rs b/src/extractor/tests.rs index 68221f5..5b8b19a 100644 --- a/src/extractor/tests.rs +++ b/src/extractor/tests.rs @@ -386,7 +386,11 @@ async fn request_link_bails_on_seen_url() -> Result<()> { }); let scans = Arc::new(FeroxScans::default()); - scans.add_file_scan(&served, ScanOrder::Latest); + scans.add_file_scan( + &served, + ScanOrder::Latest, + Arc::new(Handles::for_testing(None, None).0), + ); let robots = setup_extractor(ExtractionTarget::RobotsTxt, scans.clone()); let body = setup_extractor(ExtractionTarget::ResponseBody, scans); diff --git a/src/main.rs b/src/main.rs index 0e779e1..ec2cddc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -197,9 +197,9 @@ async fn get_targets(handles: Arc) -> Result> { } } - if !target.starts_with("http") && !target.starts_with("https") { + if !target.starts_with("http") { // --url hackerone.com - *target = format!("https://{target}"); + *target = format!("{}://{target}", handles.config.protocol); } } diff --git a/src/parser.rs b/src/parser.rs index 8b4a252..31cf64b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -40,12 +40,12 @@ pub fn initialize() -> Command { Arg::new("url") .short('u') .long("url") - .required_unless_present_any(["stdin", "resume_from", "update_app"]) + .required_unless_present_any(["stdin", "resume_from", "update_app", "request_file"]) .help_heading("Target selection") .value_name("URL") .use_value_delimiter(true) .value_hint(ValueHint::Url) - .help("The target URL (required, unless [--stdin || --resume-from] used)"), + .help("The target URL (required, unless [--stdin || --resume-from || --request-file] used)"), ) .arg( Arg::new("stdin") @@ -64,6 +64,15 @@ pub fn initialize() -> Command { .help("State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)") .conflicts_with("url") .num_args(1), + ).arg( + Arg::new("request_file") + .long("request-file") + .help_heading("Target selection") + .value_hint(ValueHint::FilePath) + .conflicts_with("url") + .num_args(1) + .value_name("REQUEST_FILE") + .help("Raw HTTP request file to use as a template for all requests"), ); ///////////////////////////////////////////////////////////////////// @@ -100,7 +109,7 @@ pub fn initialize() -> Command { .num_args(0) .help_heading("Composite settings") .conflicts_with_all(["rate_limit", "auto_bail"]) - .help("Use the same settings as --smart and set --collect-extensions to true"), + .help("Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true"), ); ///////////////////////////////////////////////////////////////////// @@ -248,6 +257,13 @@ pub fn initialize() -> Command { .help_heading("Request settings") .num_args(0) .help("Append / to each request's URL") + ).arg( + Arg::new("protocol") + .long("protocol") + .value_name("PROTOCOL") + .num_args(1) + .help_heading("Request settings") + .help("Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)"), ); ///////////////////////////////////////////////////////////////////// @@ -574,6 +590,12 @@ pub fn initialize() -> Command { .help( "File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)", ), + ).arg( + Arg::new("scan_dir_listings") + .long("scan-dir-listings") + .num_args(0) + .help_heading("Scan settings") + .help("Force scans to recurse into directory listings") ); ///////////////////////////////////////////////////////////////////// @@ -638,6 +660,13 @@ pub fn initialize() -> Command { .num_args(0) .help_heading("Output settings") .help("Disable state output file (*.state)") + ).arg( + Arg::new("limit_bars") + .long("limit-bars") + .value_name("NUM_BARS_TO_SHOW") + .num_args(1) + .help_heading("Output settings") + .help("Number of directory scan bars to show at any given time (default: no limit)"), ); ///////////////////////////////////////////////////////////////////// diff --git a/src/progress.rs b/src/progress.rs index 42e235a..372caf2 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -31,6 +31,15 @@ pub enum BarType { /// Add an [indicatif::ProgressBar](https://docs.rs/indicatif/latest/indicatif/struct.ProgressBar.html) /// to the global [PROGRESS_BAR](../config/struct.PROGRESS_BAR.html) pub fn add_bar(prefix: &str, length: u64, bar_type: BarType) -> ProgressBar { + let pb = ProgressBar::new(length).with_prefix(prefix.to_string()); + + update_style(&pb, bar_type); + + PROGRESS_BAR.add(pb) +} + +/// Update the style of a progress bar based on the `BarType` +pub fn update_style(bar: &ProgressBar, bar_type: BarType) { let mut style = ProgressStyle::default_bar().progress_chars("#>-").with_key( "smoothed_per_sec", |state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| match ( @@ -66,11 +75,7 @@ pub fn add_bar(prefix: &str, length: u64, bar_type: BarType) -> ProgressBar { BarType::Quiet => style.template("Scanning: {prefix}").unwrap(), }; - PROGRESS_BAR.add( - ProgressBar::new(length) - .with_style(style) - .with_prefix(prefix.to_string()), - ) + bar.set_style(style); } #[cfg(test)] diff --git a/src/scan_manager/scan.rs b/src/scan_manager/scan.rs index f279816..20e7629 100644 --- a/src/scan_manager/scan.rs +++ b/src/scan_manager/scan.rs @@ -1,7 +1,10 @@ use super::*; use crate::{ config::OutputLevel, + event_handlers::Handles, + progress::update_style, progress::{add_bar, BarType}, + scan_manager::utils::determine_bar_type, scanner::PolicyTrigger, }; use anyhow::Result; @@ -16,10 +19,20 @@ use std::{ time::Instant, }; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use tokio::{sync, task::JoinHandle}; use uuid::Uuid; +#[derive(Debug, Default, Copy, Clone)] +pub enum Visibility { + /// whether a FeroxScan's progress bar is currently shown + #[default] + Visible, + + /// whether a FeroxScan's progress bar is currently hidden + Hidden, +} + /// Struct to hold scan-related state /// /// The purpose of this container is to open up the pathway to aborting currently running tasks and @@ -58,7 +71,7 @@ pub struct FeroxScan { pub(super) task: sync::Mutex>>, /// The progress bar associated with this scan - pub(super) progress_bar: Mutex>, + pub progress_bar: Mutex>, /// whether or not the user passed --silent|--quiet on the command line pub(super) output_level: OutputLevel, @@ -74,6 +87,12 @@ pub struct FeroxScan { /// tracker for the time at which this scan was started pub(super) start_time: Instant, + + /// whether the progress bar is currently visible or hidden + pub(super) visible: AtomicBool, + + /// handles object pointer + pub(super) handles: Option>, } /// Default implementation for FeroxScan @@ -86,6 +105,7 @@ impl Default for FeroxScan { id: new_id, task: sync::Mutex::new(None), // tokio mutex status: Mutex::new(ScanStatus::default()), + handles: None, num_requests: 0, requests_made_so_far: 0, scan_order: ScanOrder::Latest, @@ -98,14 +118,54 @@ impl Default for FeroxScan { status_429s: Default::default(), status_403s: Default::default(), start_time: Instant::now(), + visible: AtomicBool::new(true), } } } /// Implementation of FeroxScan impl FeroxScan { + /// return the visibility of the scan as a boolean + pub fn visible(&self) -> bool { + self.visible.load(Ordering::Relaxed) + } + + pub fn swap_visibility(&self) { + // fetch_xor toggles the boolean to its opposite and returns the previous value + let visible = self.visible.fetch_xor(true, Ordering::Relaxed); + + let Ok(bar) = self.progress_bar.lock() else { + log::warn!("couldn't unlock progress bar for {}", self.url); + return; + }; + + if bar.is_none() { + log::warn!("there is no progress bar for {}", self.url); + return; + } + + let Some(handles) = self.handles.as_ref() else { + log::warn!("couldn't access handles pointer for {}", self.url); + return; + }; + + let bar_type = if !visible { + // visibility was false before we xor'd the value + match handles.config.output_level { + OutputLevel::Default => BarType::Default, + OutputLevel::Quiet => BarType::Quiet, + OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden, + } + } else { + // visibility was true before we xor'd the value + BarType::Hidden + }; + + update_style(bar.as_ref().unwrap(), bar_type); + } + /// Stop a currently running scan - pub async fn abort(&self) -> Result<()> { + pub async fn abort(&self, active_bars: usize) -> Result<()> { log::trace!("enter: abort"); match self.task.try_lock() { @@ -114,7 +174,7 @@ impl FeroxScan { log::trace!("aborting {:?}", self); task.abort(); self.set_status(ScanStatus::Cancelled)?; - self.stop_progress_bar(); + self.stop_progress_bar(active_bars); } } Err(e) => { @@ -151,15 +211,26 @@ impl FeroxScan { } /// Simple helper to call .finish on the scan's progress bar - pub(super) fn stop_progress_bar(&self) { + pub(super) fn stop_progress_bar(&self, active_bars: usize) { if let Ok(guard) = self.progress_bar.lock() { if guard.is_some() { let pb = (*guard).as_ref().unwrap(); - if pb.position() > self.num_requests { - pb.finish() + let bar_limit = if let Some(handles) = self.handles.as_ref() { + handles.config.limit_bars } else { - pb.abandon() + 0 + }; + + if bar_limit > 0 && bar_limit < active_bars { + pb.finish_and_clear(); + return; + } + + if pb.position() > self.num_requests { + pb.finish(); + } else { + pb.abandon(); } } } @@ -172,12 +243,18 @@ impl FeroxScan { if guard.is_some() { (*guard).as_ref().unwrap().clone() } else { - let bar_type = match self.output_level { - OutputLevel::Default => BarType::Default, - OutputLevel::Quiet => BarType::Quiet, - OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden, + let (active_bars, bar_limit) = if let Some(handles) = self.handles.as_ref() { + if let Ok(scans) = handles.ferox_scans() { + (scans.number_of_bars(), handles.config.limit_bars) + } else { + (0, handles.config.limit_bars) + } + } else { + (0, 0) }; + let bar_type = determine_bar_type(bar_limit, active_bars, self.output_level); + let pb = add_bar(&self.url, self.num_requests, bar_type); pb.reset_elapsed(); @@ -191,12 +268,18 @@ impl FeroxScan { Err(_) => { log::warn!("Could not unlock progress bar on {:?}", self); - let bar_type = match self.output_level { - OutputLevel::Default => BarType::Default, - OutputLevel::Quiet => BarType::Quiet, - OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden, + let (active_bars, bar_limit) = if let Some(handles) = self.handles.as_ref() { + if let Ok(scans) = handles.ferox_scans() { + (scans.number_of_bars(), handles.config.limit_bars) + } else { + (0, handles.config.limit_bars) + } + } else { + (0, 0) }; + let bar_type = determine_bar_type(bar_limit, active_bars, self.output_level); + let pb = add_bar(&self.url, self.num_requests, bar_type); pb.reset_elapsed(); @@ -206,6 +289,7 @@ impl FeroxScan { } /// Given a URL and ProgressBar, create a new FeroxScan, wrap it in an Arc and return it + #[allow(clippy::too_many_arguments)] pub fn new( url: &str, scan_type: ScanType, @@ -213,6 +297,8 @@ impl FeroxScan { num_requests: u64, output_level: OutputLevel, pb: Option, + visibility: bool, + handles: Arc, ) -> Arc { Arc::new(Self { url: url.to_string(), @@ -222,14 +308,16 @@ impl FeroxScan { num_requests, output_level, progress_bar: Mutex::new(pb), + visible: AtomicBool::new(visibility), + handles: Some(handles), ..Default::default() }) } /// Mark the scan as complete and stop the scan's progress bar - pub fn finish(&self) -> Result<()> { + pub fn finish(&self, active_bars: usize) -> Result<()> { self.set_status(ScanStatus::Complete)?; - self.stop_progress_bar(); + self.stop_progress_bar(active_bars); Ok(()) } @@ -262,6 +350,22 @@ impl FeroxScan { false } + /// small wrapper to inspect ScanStatus and see if it's Running + pub fn is_running(&self) -> bool { + if let Ok(guard) = self.status.lock() { + return matches!(*guard, ScanStatus::Running); + } + false + } + + /// small wrapper to inspect ScanStatus and see if it's NotStarted + pub fn is_not_started(&self) -> bool { + if let Ok(guard) = self.status.lock() { + return matches!(*guard, ScanStatus::NotStarted); + } + false + } + /// await a task's completion, similar to a thread's join; perform necessary bookkeeping pub async fn join(&self) { log::trace!("enter join({:?})", self); @@ -507,6 +611,8 @@ mod tests { 1000, OutputLevel::Default, None, + true, + Arc::new(Handles::for_testing(None, None).0), ); scan.add_error(); @@ -532,6 +638,7 @@ mod tests { scan_order: ScanOrder::Initial, num_requests: 0, requests_made_so_far: 0, + visible: AtomicBool::new(true), status: Mutex::new(ScanStatus::Running), task: Default::default(), progress_bar: Mutex::new(None), @@ -540,6 +647,7 @@ mod tests { status_429s: Default::default(), errors: Default::default(), start_time: Instant::now(), + handles: None, }; let pb = scan.progress_bar(); @@ -551,7 +659,62 @@ mod tests { assert_eq!(req_sec, 100); - scan.finish().unwrap(); + scan.finish(0).unwrap(); assert_eq!(scan.requests_per_second(), 0); } + + #[test] + fn test_swap_visibility() { + let scan = FeroxScan::new( + "http://localhost", + ScanType::Directory, + ScanOrder::Latest, + 1000, + OutputLevel::Default, + None, + true, + Arc::new(Handles::for_testing(None, None).0), + ); + + assert!(scan.visible()); + + scan.swap_visibility(); + assert!(!scan.visible()); + + scan.swap_visibility(); + assert!(scan.visible()); + + scan.swap_visibility(); + assert!(!scan.visible()); + + scan.swap_visibility(); + assert!(scan.visible()); + } + + #[test] + /// test for is_running method + fn test_is_running() { + let scan = FeroxScan::new( + "http://localhost", + ScanType::Directory, + ScanOrder::Latest, + 1000, + OutputLevel::Default, + None, + true, + Arc::new(Handles::for_testing(None, None).0), + ); + + assert!(scan.is_not_started()); + assert!(!scan.is_running()); + assert!(!scan.is_complete()); + assert!(!scan.is_cancelled()); + + *scan.status.lock().unwrap() = ScanStatus::Running; + + assert!(!scan.is_not_started()); + assert!(scan.is_running()); + assert!(!scan.is_complete()); + assert!(!scan.is_cancelled()); + } } diff --git a/src/scan_manager/scan_container.rs b/src/scan_manager/scan_container.rs index 34107cc..d2a078e 100644 --- a/src/scan_manager/scan_container.rs +++ b/src/scan_manager/scan_container.rs @@ -12,6 +12,7 @@ use crate::{ config::OutputLevel, progress::PROGRESS_PRINTER, progress::{add_bar, BarType}, + scan_manager::utils::determine_bar_type, scan_manager::{MenuCmd, MenuCmdResult}, scanner::RESPONSES, traits::FeroxSerialize, @@ -61,6 +62,9 @@ pub struct FeroxScans { /// vector of extensions discovered and collected during scans pub(crate) collected_extensions: RwLock>, + + /// stored value for Configuration.limit_bars + bar_limit: usize, } /// Serialize implementation for FeroxScans @@ -93,9 +97,10 @@ impl Serialize for FeroxScans { /// Implementation of `FeroxScans` impl FeroxScans { /// given an OutputLevel, create a new FeroxScans object - pub fn new(output_level: OutputLevel) -> Self { + pub fn new(output_level: OutputLevel, bar_limit: usize) -> Self { Self { output_level, + bar_limit, ..Default::default() } } @@ -388,8 +393,9 @@ impl FeroxScans { if input == 'y' || input == '\n' { self.menu.println(&format!("Stopping {}...", selected.url)); + let active_bars = self.number_of_bars(); selected - .abort() + .abort(active_bars) .await .unwrap_or_else(|e| log::warn!("Could not cancel task: {}", e)); @@ -521,14 +527,22 @@ impl FeroxScans { /// if a resumed scan is already complete, display a completed progress bar to the user pub fn print_completed_bars(&self, bar_length: usize) -> Result<()> { - let bar_type = match self.output_level { - OutputLevel::Default => BarType::Message, - OutputLevel::Quiet => BarType::Quiet, - OutputLevel::Silent | OutputLevel::SilentJSON => return Ok(()), // fast exit when --silent was used - }; + if self.output_level == OutputLevel::SilentJSON || self.output_level == OutputLevel::Silent + { + // fast exit when --silent was used + return Ok(()); + } + + let bar_type: BarType = + determine_bar_type(self.bar_limit, self.number_of_bars(), self.output_level); if let Ok(scans) = self.scans.read() { for scan in scans.iter() { + if matches!(bar_type, BarType::Hidden) { + // no need to show hidden bars + continue; + } + if scan.is_complete() { // these scans are complete, and just need to be shown to the user let pb = add_bar( @@ -605,6 +619,7 @@ impl FeroxScans { url: &str, scan_type: ScanType, scan_order: ScanOrder, + handles: Arc, ) -> (bool, Arc) { let bar_length = if let Ok(guard) = self.bar_length.lock() { *guard @@ -612,14 +627,11 @@ impl FeroxScans { 0 }; + let active_bars = self.number_of_bars(); + let bar_type = determine_bar_type(self.bar_limit, active_bars, self.output_level); + let bar = match scan_type { ScanType::Directory => { - let bar_type = match self.output_level { - OutputLevel::Default => BarType::Default, - OutputLevel::Quiet => BarType::Quiet, - OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden, - }; - let progress_bar = add_bar(url, bar_length, bar_type); progress_bar.reset_elapsed(); @@ -629,6 +641,8 @@ impl FeroxScans { ScanType::File => None, }; + let is_visible = !matches!(bar_type, BarType::Hidden); + let ferox_scan = FeroxScan::new( url, scan_type, @@ -636,6 +650,8 @@ impl FeroxScans { bar_length, self.output_level, bar, + is_visible, + handles, ); // If the set did not contain the scan, true is returned. @@ -650,9 +666,14 @@ impl FeroxScans { /// If `FeroxScans` did not already contain the scan, return true; otherwise return false /// /// Also return a reference to the new `FeroxScan` - pub fn add_directory_scan(&self, url: &str, scan_order: ScanOrder) -> (bool, Arc) { + pub fn add_directory_scan( + &self, + url: &str, + scan_order: ScanOrder, + handles: Arc, + ) -> (bool, Arc) { let normalized = format!("{}/", url.trim_end_matches('/')); - self.add_scan(&normalized, ScanType::Directory, scan_order) + self.add_scan(&normalized, ScanType::Directory, scan_order, handles) } /// Given a url, create a new `FeroxScan` and add it to `FeroxScans` as a File Scan @@ -660,8 +681,65 @@ impl FeroxScans { /// If `FeroxScans` did not already contain the scan, return true; otherwise return false /// /// Also return a reference to the new `FeroxScan` - pub fn add_file_scan(&self, url: &str, scan_order: ScanOrder) -> (bool, Arc) { - self.add_scan(url, ScanType::File, scan_order) + pub fn add_file_scan( + &self, + url: &str, + scan_order: ScanOrder, + handles: Arc, + ) -> (bool, Arc) { + self.add_scan(url, ScanType::File, scan_order, handles) + } + + /// returns the number of active AND visible scans; supports --limit-bars functionality + pub fn number_of_bars(&self) -> usize { + let Ok(scans) = self.scans.read() else { + return 0; + }; + + // starting at one ensures we don't have an extra bar + // due to counting up from 0 when there's actually 1 bar + let mut count = 1; + + for scan in &*scans { + if scan.is_active() && scan.visible() { + count += 1; + } + } + + count + } + + /// make one hidden bar visible; supports --limit-bars functionality + pub fn make_visible(&self) { + if let Ok(guard) = self.scans.read() { + // when swapping visibility, we'll prefer an actively running scan + // if none are found, we'll + let mut queued = None; + + for scan in &*guard { + if !matches!(scan.scan_type, ScanType::Directory) { + // visibility only makes sense for directory scans + continue; + } + + if scan.visible() { + continue; + } + + if scan.is_running() { + scan.swap_visibility(); + return; + } + + if queued.is_none() && scan.is_not_started() { + queued = Some(scan.clone()); + } + } + + if let Some(scan) = queued { + scan.swap_visibility(); + } + } } /// small helper to determine whether any scans are active or not @@ -726,7 +804,7 @@ mod tests { #[test] /// unknown extension should be added to collected_extensions fn unknown_extension_is_added_to_collected_extensions() { - let scans = FeroxScans::new(OutputLevel::Default); + let scans = FeroxScans::new(OutputLevel::Default, 0); assert_eq!(0, scans.collected_extensions.read().unwrap().len()); @@ -739,7 +817,7 @@ mod tests { #[test] /// known extension should not be added to collected_extensions fn known_extension_is_added_to_collected_extensions() { - let scans = FeroxScans::new(OutputLevel::Default); + let scans = FeroxScans::new(OutputLevel::Default, 0); scans .collected_extensions .write() diff --git a/src/scan_manager/tests.rs b/src/scan_manager/tests.rs index a3bb460..25f37ce 100644 --- a/src/scan_manager/tests.rs +++ b/src/scan_manager/tests.rs @@ -15,6 +15,7 @@ use crate::{ use indicatif::ProgressBar; use predicates::prelude::*; use regex::Regex; +use std::sync::atomic::AtomicBool; use std::sync::{atomic::Ordering, Arc}; use std::thread::sleep; use std::time::Instant; @@ -57,7 +58,12 @@ async fn scanner_pause_scan_with_finished_spinner() { fn add_url_to_list_of_scanned_urls_with_unknown_url() { let urls = FeroxScans::default(); let url = "http://unknown_url"; - let (result, _scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest); + let (result, _scan) = urls.add_scan( + url, + ScanType::Directory, + ScanOrder::Latest, + Arc::new(Handles::for_testing(None, None).0), + ); assert!(result); } @@ -75,11 +81,18 @@ fn add_url_to_list_of_scanned_urls_with_known_url() { pb.length().unwrap(), OutputLevel::Default, Some(pb), + true, + Arc::new(Handles::for_testing(None, None).0), ); assert!(urls.insert(scan)); - let (result, _scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest); + let (result, _scan) = urls.add_scan( + url, + ScanType::Directory, + ScanOrder::Latest, + Arc::new(Handles::for_testing(None, None).0), + ); assert!(!result); } @@ -97,6 +110,8 @@ fn stop_progress_bar_stops_bar() { pb.length().unwrap(), OutputLevel::Default, Some(pb), + true, + Arc::new(Handles::for_testing(None, None).0), ); assert!(!scan @@ -107,7 +122,7 @@ fn stop_progress_bar_stops_bar() { .unwrap() .is_finished()); - scan.stop_progress_bar(); + scan.stop_progress_bar(0); assert!(scan .progress_bar @@ -124,18 +139,25 @@ fn add_url_to_list_of_scanned_urls_with_known_url_without_slash() { let urls = FeroxScans::default(); let url = "http://unknown_url"; - let scan = FeroxScan::new( + let scan: Arc = FeroxScan::new( url, ScanType::File, ScanOrder::Latest, 0, OutputLevel::Default, None, + true, + Arc::new(Handles::for_testing(None, None).0), ); assert!(urls.insert(scan)); - let (result, _scan) = urls.add_scan(url, ScanType::File, ScanOrder::Latest); + let (result, _scan) = urls.add_scan( + url, + ScanType::File, + ScanOrder::Latest, + Arc::new(Handles::for_testing(None, None).0), + ); assert!(!result); } @@ -155,6 +177,8 @@ async fn call_display_scans() { pb.length().unwrap(), OutputLevel::Default, Some(pb), + true, + Arc::new(Handles::for_testing(None, None).0), ); let scan_two = FeroxScan::new( url_two, @@ -163,9 +187,11 @@ async fn call_display_scans() { pb_two.length().unwrap(), OutputLevel::Default, Some(pb_two), + true, + Arc::new(Handles::for_testing(None, None).0), ); - scan_two.finish().unwrap(); // one complete, one incomplete + scan_two.finish(0).unwrap(); // one complete, one incomplete scan_two .set_task(tokio::spawn(async move { sleep(Duration::from_millis(SLEEP_DURATION)); @@ -190,6 +216,8 @@ fn partial_eq_compares_the_id_field() { 0, OutputLevel::Default, None, + true, + Arc::new(Handles::for_testing(None, None).0), ); let scan_two = FeroxScan::new( url, @@ -198,6 +226,8 @@ fn partial_eq_compares_the_id_field() { 0, OutputLevel::Default, None, + true, + Arc::new(Handles::for_testing(None, None).0), ); assert!(!scan.eq(&scan_two)); @@ -280,6 +310,8 @@ fn ferox_scan_serialize() { 0, OutputLevel::Default, None, + true, + Arc::new(Handles::for_testing(None, None).0), ); let fs_json = format!( r#"{{"id":"{}","url":"https://spiritanimal.com","normalized_url":"https://spiritanimal.com/","scan_type":"Directory","status":"NotStarted","num_requests":0,"requests_made_so_far":0}}"#, @@ -298,6 +330,8 @@ fn ferox_scans_serialize() { 0, OutputLevel::Default, None, + true, + Arc::new(Handles::for_testing(None, None).0), ); let ferox_scans = FeroxScans::default(); let ferox_scans_json = format!( @@ -360,6 +394,8 @@ fn feroxstates_feroxserialize_implementation() { 0, OutputLevel::Default, None, + true, + Arc::new(Handles::for_testing(None, None).0), ); let ferox_scans = FeroxScans::default(); let saved_id = ferox_scan.id.clone(); @@ -501,12 +537,15 @@ fn feroxstates_feroxserialize_implementation() { r#""method":"GET""#, r#""content_length":173"#, r#""line_count":10"#, + r#""limit_bars":0"#, r#""word_count":16"#, r#""headers""#, r#""server":"nginx/1.16.1"#, r#""collect_extensions":true"#, r#""collect_backups":false"#, r#""collect_words":false"#, + r#""scan_dir_listings":false"#, + r#""protocol":"https""#, r#""filters":[{"filter_code":100},{"word_count":200},{"content_length":300},{"line_count":400},{"compiled":".*","raw_string":".*"},{"hash":1,"original_url":"http://localhost:12345/"}]"#, r#""collected_extensions":["php"]"#, r#""dont_collect":["tif","tiff","ico","cur","bmp","webp","svg","png","jpg","jpeg","jfif","gif","avif","apng","pjpeg","pjp","mov","wav","mpg","mpeg","mp3","mp4","m4a","m4p","m4v","ogg","webm","ogv","oga","flac","aac","3gp","css","zip","xls","xml","gz","tgz"]"#, @@ -567,8 +606,10 @@ fn feroxscan_display() { normalized_url: String::from("http://localhost/"), scan_order: ScanOrder::Latest, scan_type: Default::default(), + handles: Some(Arc::new(Handles::for_testing(None, None).0)), num_requests: 0, requests_made_so_far: 0, + visible: AtomicBool::new(true), start_time: Instant::now(), output_level: OutputLevel::Default, status_403s: Default::default(), @@ -617,6 +658,7 @@ async fn ferox_scan_abort() { requests_made_so_far: 0, start_time: Instant::now(), output_level: OutputLevel::Default, + visible: AtomicBool::new(true), status_403s: Default::default(), status_429s: Default::default(), status: std::sync::Mutex::new(ScanStatus::Running), @@ -625,9 +667,10 @@ async fn ferox_scan_abort() { }))), progress_bar: std::sync::Mutex::new(None), errors: Default::default(), + handles: Some(Arc::new(Handles::for_testing(None, None).0)), }; - scan.abort().await.unwrap(); + scan.abort(0).await.unwrap(); assert!(matches!( *scan.status.lock().unwrap(), @@ -718,15 +761,26 @@ fn split_to_nums_is_correct() { #[test] /// given a deep url, find the correct scan fn get_base_scan_by_url_finds_correct_scan() { + let handles = Arc::new(Handles::for_testing(None, None).0); let urls = FeroxScans::default(); let url = "http://localhost"; let url1 = "http://localhost/stuff"; let url2 = "http://shlocalhost/stuff/things"; let url3 = "http://shlocalhost/stuff/things/mostuff"; - let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest); - let (_, scan1) = urls.add_scan(url1, ScanType::Directory, ScanOrder::Latest); - let (_, scan2) = urls.add_scan(url2, ScanType::Directory, ScanOrder::Latest); - let (_, scan3) = urls.add_scan(url3, ScanType::Directory, ScanOrder::Latest); + let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest, handles.clone()); + let (_, scan1) = urls.add_scan( + url1, + ScanType::Directory, + ScanOrder::Latest, + handles.clone(), + ); + let (_, scan2) = urls.add_scan( + url2, + ScanType::Directory, + ScanOrder::Latest, + handles.clone(), + ); + let (_, scan3) = urls.add_scan(url3, ScanType::Directory, ScanOrder::Latest, handles); assert_eq!( urls.get_base_scan_by_url("http://localhost/things.php") @@ -759,7 +813,12 @@ fn get_base_scan_by_url_finds_correct_scan() { fn get_base_scan_by_url_finds_correct_scan_without_trailing_slash() { let urls = FeroxScans::default(); let url = "http://localhost"; - let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest); + let (_, scan) = urls.add_scan( + url, + ScanType::Directory, + ScanOrder::Latest, + Arc::new(Handles::for_testing(None, None).0), + ); assert_eq!( urls.get_base_scan_by_url("http://localhost/BKPMiherrortBPKcw") .unwrap() @@ -773,7 +832,12 @@ fn get_base_scan_by_url_finds_correct_scan_without_trailing_slash() { fn get_base_scan_by_url_finds_correct_scan_with_trailing_slash() { let urls = FeroxScans::default(); let url = "http://127.0.0.1:41971/"; - let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest); + let (_, scan) = urls.add_scan( + url, + ScanType::Directory, + ScanOrder::Latest, + Arc::new(Handles::for_testing(None, None).0), + ); assert_eq!( urls.get_base_scan_by_url("http://127.0.0.1:41971/BKPMiherrortBPKcw") .unwrap() diff --git a/src/scan_manager/utils.rs b/src/scan_manager/utils.rs index 1844957..6f06e08 100644 --- a/src/scan_manager/utils.rs +++ b/src/scan_manager/utils.rs @@ -1,7 +1,12 @@ #[cfg(not(test))] use crate::event_handlers::TermInputHandler; use crate::{ - config::Configuration, event_handlers::Handles, parser::TIMESPEC_REGEX, scanner::RESPONSES, + config::{Configuration, OutputLevel}, + event_handlers::Handles, + parser::TIMESPEC_REGEX, + progress::BarType, + scan_manager::scan::Visibility, + scanner::RESPONSES, }; use std::{fs::File, io::BufReader, sync::Arc}; @@ -90,3 +95,79 @@ pub fn resume_scan(filename: &str) -> Configuration { log::trace!("exit: resume_scan -> {:?}", config); config } + +/// determine the type of progress bar to display +/// takes both --limit-bars and output-level (--quiet|--silent|etc) +/// into account to arrive at a `BarType` +pub fn determine_bar_type( + bar_limit: usize, + number_of_bars: usize, + output_level: OutputLevel, +) -> BarType { + let visibility = if bar_limit == 0 { + // no limit from cli, just set the value to visible + // this protects us from a mutex unlock in number_of_bars + // in the normal case + Visibility::Visible + } else if bar_limit < number_of_bars { + // active bars exceed limit; hidden + Visibility::Hidden + } else { + Visibility::Visible + }; + + match (output_level, visibility) { + (OutputLevel::Default, Visibility::Visible) => BarType::Default, + (OutputLevel::Quiet, Visibility::Visible) => BarType::Quiet, + (OutputLevel::Default, Visibility::Hidden) => BarType::Hidden, + (OutputLevel::Quiet, Visibility::Hidden) => BarType::Hidden, + (OutputLevel::Silent | OutputLevel::SilentJSON, _) => BarType::Hidden, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_no_limit_visible() { + let bar_type = determine_bar_type(0, 1, OutputLevel::Default); + assert!(matches!(bar_type, BarType::Default)); + } + + #[test] + fn test_limit_exceeded_hidden() { + let bar_type = determine_bar_type(1, 2, OutputLevel::Default); + assert!(matches!(bar_type, BarType::Hidden)); + } + + #[test] + fn test_limit_not_exceeded_visible() { + let bar_type = determine_bar_type(2, 1, OutputLevel::Default); + assert!(matches!(bar_type, BarType::Default)); + } + + #[test] + fn test_quiet_visible() { + let bar_type = determine_bar_type(0, 1, OutputLevel::Quiet); + assert!(matches!(bar_type, BarType::Quiet)); + } + + #[test] + fn test_quiet_hidden() { + let bar_type = determine_bar_type(1, 2, OutputLevel::Quiet); + assert!(matches!(bar_type, BarType::Hidden)); + } + + #[test] + fn test_silent_hidden() { + let bar_type = determine_bar_type(0, 1, OutputLevel::Silent); + assert!(matches!(bar_type, BarType::Hidden)); + } + + #[test] + fn test_silent_json_hidden() { + let bar_type = determine_bar_type(0, 1, OutputLevel::SilentJSON); + assert!(matches!(bar_type, BarType::Hidden)); + } +} diff --git a/src/scanner/ferox_scanner.rs b/src/scanner/ferox_scanner.rs index 03b3a86..7959f60 100644 --- a/src/scanner/ferox_scanner.rs +++ b/src/scanner/ferox_scanner.rs @@ -283,6 +283,14 @@ impl FeroxScanner { let mut message = format!("=> {}", style("Directory listing").blue().bright()); + if !self.handles.config.scan_dir_listings { + write!( + message, + " (add {} to scan)", + style("--scan-dir-listings").bright().yellow() + )?; + } + if !self.handles.config.extract_links { write!( message, @@ -291,7 +299,7 @@ impl FeroxScanner { )?; } - if !self.handles.config.force_recursion { + if !self.handles.config.force_recursion && !self.handles.config.scan_dir_listings { for handle in extraction_tasks.into_iter().flatten() { _ = handle.await; } @@ -299,7 +307,14 @@ impl FeroxScanner { progress_bar.reset_eta(); progress_bar.finish_with_message(message); - ferox_scan.finish()?; + if self.handles.config.limit_bars > 0 { + let scans = self.handles.ferox_scans()?; + let num_bars = scans.number_of_bars(); + ferox_scan.finish(num_bars)?; + scans.make_visible(); + } else { + ferox_scan.finish(0)?; + } return Ok(()); // nothing left to do if we found a dir listing } @@ -382,7 +397,14 @@ impl FeroxScanner { _ = handle.await; } - ferox_scan.finish()?; + if self.handles.config.limit_bars > 0 { + let scans = self.handles.ferox_scans()?; + let num_bars = scans.number_of_bars(); + ferox_scan.finish(num_bars)?; + scans.make_visible(); + } else { + ferox_scan.finish(0)?; + } log::trace!("exit: scan_url"); diff --git a/src/scanner/requester.rs b/src/scanner/requester.rs index 0517eca..2fe0f30 100644 --- a/src/scanner/requester.rs +++ b/src/scanner/requester.rs @@ -313,9 +313,12 @@ impl Requester { .set_status(ScanStatus::Cancelled) .unwrap_or_else(|e| log::warn!("Could not set scan status: {}", e)); + let scans = self.handles.ferox_scans()?; + let active_bars = scans.number_of_bars(); + // kill the scan self.ferox_scan - .abort() + .abort(active_bars) .await .unwrap_or_else(|e| log::warn!("Could not bail on scan: {}", e)); @@ -646,6 +649,8 @@ mod tests { 1000, OutputLevel::Default, None, + true, + handles.clone(), ); scan.set_status(ScanStatus::Running).unwrap(); @@ -1144,6 +1149,8 @@ mod tests { 1000, OutputLevel::Default, None, + true, + Arc::new(Handles::for_testing(None, None).0), ); scan.set_status(ScanStatus::Running).unwrap(); scan.add_429(); @@ -1177,7 +1184,7 @@ mod tests { 200 ); - scan.finish().unwrap(); + scan.finish(0).unwrap(); assert!(start.elapsed().as_millis() >= 2000); } } diff --git a/src/scanner/tests.rs b/src/scanner/tests.rs index d7e0323..698f164 100644 --- a/src/scanner/tests.rs +++ b/src/scanner/tests.rs @@ -15,7 +15,7 @@ use super::*; /// try to hit struct field coverage of FileOutHandler async fn get_scan_by_url_bails_on_unfound_url() { let sem = Semaphore::new(10); - let urls = FeroxScans::new(OutputLevel::Default); + let urls = FeroxScans::new(OutputLevel::Default, 0); let scanner = FeroxScanner::new( "http://localhost", diff --git a/src/utils.rs b/src/utils.rs index b9ae6fa..b0cc7fc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -975,7 +975,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.url_denylist = vec![Url::parse(deny_url).unwrap()]; @@ -994,7 +998,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.url_denylist = vec![Url::parse(deny_url).unwrap()]; @@ -1013,7 +1021,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.url_denylist = vec![Url::parse(deny_url).unwrap()]; @@ -1034,7 +1046,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.url_denylist = vec![Url::parse(deny_url).unwrap()]; @@ -1062,7 +1078,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.url_denylist = vec![Url::parse(deny_url).unwrap()]; @@ -1080,7 +1100,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/api/denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.url_denylist = vec![Url::parse(deny_url).unwrap()]; @@ -1099,7 +1123,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/not-denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.url_denylist = vec![Url::parse(deny_url).unwrap()]; @@ -1118,7 +1146,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/stuff/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.url_denylist = vec![Url::parse(deny_url).unwrap()]; @@ -1137,7 +1169,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/api/not-denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.url_denylist = vec![Url::parse(deny_url).unwrap()]; @@ -1157,7 +1193,11 @@ mod tests { let tested_url = Url::parse("https://testdomain.com/denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.regex_denylist = vec![Regex::new(deny_pattern).unwrap()]; @@ -1178,7 +1218,11 @@ mod tests { let tested_https_url = Url::parse("https://testdomain.com/denied/").unwrap(); let scans = Arc::new(FeroxScans::default()); - scans.add_directory_scan(scan_url, ScanOrder::Initial); + scans.add_directory_scan( + scan_url, + ScanOrder::Initial, + Arc::new(Handles::for_testing(None, None).0), + ); let mut config = Configuration::new().unwrap(); config.regex_denylist = vec![Regex::new(deny_pattern).unwrap()]; diff --git a/tests/test_banner.rs b/tests/test_banner.rs index 96d8a4f..19623eb 100644 --- a/tests/test_banner.rs +++ b/tests/test_banner.rs @@ -1487,6 +1487,89 @@ fn banner_prints_force_recursion() { ); } +#[test] +/// test allows non-existent wordlist to trigger the banner printing to stderr +/// expect to see all mandatory prints + scan-dir-listings +fn banner_prints_scan_dir_listings() { + Command::cargo_bin("feroxbuster") + .unwrap() + .arg("--url") + .arg("http://localhost") + .arg("--scan-dir-listings") + .arg("--wordlist") + .arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676") + .assert() + .success() + .stderr( + predicate::str::contains("─┬─") + .and(predicate::str::contains("Target Url")) + .and(predicate::str::contains("http://localhost")) + .and(predicate::str::contains("Threads")) + .and(predicate::str::contains("Wordlist")) + .and(predicate::str::contains("Status Codes")) + .and(predicate::str::contains("Timeout (secs)")) + .and(predicate::str::contains("User-Agent")) + .and(predicate::str::contains("Scan Dir Listings")) + .and(predicate::str::contains("─┴─")), + ); +} + +#[test] +/// test allows non-existent wordlist to trigger the banner printing to stderr +/// expect to see all mandatory prints + protocol +fn banner_prints_protocol() { + Command::cargo_bin("feroxbuster") + .unwrap() + .arg("--url") + .arg("localhost") + .arg("--protocol") + .arg("http") + .arg("--wordlist") + .arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676") + .assert() + .success() + .stderr( + predicate::str::contains("─┬─") + .and(predicate::str::contains("Target Url")) + .and(predicate::str::contains("http://localhost")) + .and(predicate::str::contains("Threads")) + .and(predicate::str::contains("Wordlist")) + .and(predicate::str::contains("Status Codes")) + .and(predicate::str::contains("Timeout (secs)")) + .and(predicate::str::contains("User-Agent")) + .and(predicate::str::contains("Default Protocol")) + .and(predicate::str::contains("─┴─")), + ); +} + +#[test] +/// test allows non-existent wordlist to trigger the banner printing to stderr +/// expect to see all mandatory prints + protocol +fn banner_prints_limit_dirs() { + Command::cargo_bin("feroxbuster") + .unwrap() + .arg("--url") + .arg("localhost") + .arg("--limit-bars") + .arg("3") + .arg("--wordlist") + .arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676") + .assert() + .success() + .stderr( + predicate::str::contains("─┬─") + .and(predicate::str::contains("Target Url")) + .and(predicate::str::contains("http://localhost")) + .and(predicate::str::contains("Threads")) + .and(predicate::str::contains("Wordlist")) + .and(predicate::str::contains("Status Codes")) + .and(predicate::str::contains("Timeout (secs)")) + .and(predicate::str::contains("User-Agent")) + .and(predicate::str::contains("Limit Dir Scan Bars")) + .and(predicate::str::contains("─┴─")), + ); +} + #[test] /// test allows non-existent wordlist to trigger the banner printing to stderr /// expect to see all mandatory prints + force recursion diff --git a/tests/test_policies.rs b/tests/test_policies.rs index 1de65dd..9849d7d 100644 --- a/tests/test_policies.rs +++ b/tests/test_policies.rs @@ -58,7 +58,7 @@ fn auto_bail_cancels_scan_with_timeouts() { Command::cargo_bin("feroxbuster") .unwrap() .arg("--url") - .arg(&srv.url("/")) + .arg(srv.url("/")) .arg("--wordlist") .arg(file.as_os_str()) .arg("--auto-bail") diff --git a/tests/test_scanner.rs b/tests/test_scanner.rs index b72fd45..058029d 100644 --- a/tests/test_scanner.rs +++ b/tests/test_scanner.rs @@ -430,6 +430,7 @@ fn scanner_single_request_scan_with_filtered_result() -> Result<(), Box