mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
feat(wordlist-optimize): accept directories as input, inline tab-completion display, add Wordlist/Rule Tools to main menu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
60ae6656ad
commit
9ec56d4ead
+19
-7
@@ -1195,17 +1195,29 @@ def wordlist_shard(ctx: Any) -> None:
|
||||
def wordlist_optimize(ctx: Any) -> None:
|
||||
"""Prompt for input wordlists and output directory, then optimize."""
|
||||
raw = ctx.select_file_with_autocomplete(
|
||||
"\n[*] Enter input wordlist paths (comma-separated)",
|
||||
"\n[*] Enter input wordlist paths (comma-separated files or directories)",
|
||||
base_dir=ctx.hcatWordlists,
|
||||
).strip()
|
||||
inputs = [p.strip() for p in raw.split(",") if p.strip()]
|
||||
if not inputs:
|
||||
raw_entries = [p.strip() for p in raw.split(",") if p.strip()]
|
||||
if not raw_entries:
|
||||
print("[!] No input wordlists provided.")
|
||||
return
|
||||
missing = [p for p in inputs if not os.path.isfile(p)]
|
||||
if missing:
|
||||
print("[!] Files not found:")
|
||||
for p in missing:
|
||||
inputs: list[str] = []
|
||||
not_found: list[str] = []
|
||||
for entry in raw_entries:
|
||||
if os.path.isfile(entry):
|
||||
inputs.append(entry)
|
||||
elif os.path.isdir(entry):
|
||||
files = [os.path.join(entry, f) for f in ctx.list_wordlist_files(entry)]
|
||||
if not files:
|
||||
print(f"[!] No wordlist files found in: {entry}")
|
||||
return
|
||||
inputs.extend(files)
|
||||
else:
|
||||
not_found.append(entry)
|
||||
if not_found:
|
||||
print("[!] Not found (not a file or directory):")
|
||||
for p in not_found:
|
||||
print(f" {p}")
|
||||
return
|
||||
outdir = ctx.select_file_with_autocomplete("[*] Enter output directory path").strip()
|
||||
|
||||
+15
-4
@@ -1079,12 +1079,17 @@ def select_file_with_autocomplete(
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def display_matches(substitution, matches, longest_match_length):
|
||||
print()
|
||||
for match in matches:
|
||||
print(f" {match}")
|
||||
readline.redisplay()
|
||||
|
||||
# Configure readline for tab completion
|
||||
readline.set_completer_delims(" \t\n;")
|
||||
# Disable the "Display all X possibilities?" prompt
|
||||
try:
|
||||
readline.parse_and_bind("set completion-query-items -1")
|
||||
except Exception:
|
||||
readline.set_completion_display_matches_hook(display_matches)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
readline.parse_and_bind("tab: complete")
|
||||
@@ -4892,7 +4897,9 @@ def main():
|
||||
("2", "Download wordlists from Weakpass"),
|
||||
("3", "Download wordlists from Hashmob.net"),
|
||||
("4", "Download rules from Hashmob.net"),
|
||||
("5", "Exit"),
|
||||
("5", "Wordlist Tools"),
|
||||
("6", "Rule File Tools"),
|
||||
("7", "Exit"),
|
||||
]
|
||||
menu_loop = True
|
||||
while menu_loop:
|
||||
@@ -4930,6 +4937,10 @@ def main():
|
||||
sys.exit(0)
|
||||
# Otherwise continue the menu loop
|
||||
elif choice == "5":
|
||||
wordlist_tools_submenu()
|
||||
elif choice == "6":
|
||||
rule_tools_submenu()
|
||||
elif choice == "7":
|
||||
sys.exit(0)
|
||||
else:
|
||||
if (
|
||||
|
||||
@@ -330,7 +330,10 @@ class TestWordlistOptimize:
|
||||
f"{wl_a},{wl_b}",
|
||||
outdir,
|
||||
]
|
||||
with patch("hate_crack.attacks.os.path.isfile", return_value=True):
|
||||
with (
|
||||
patch("hate_crack.attacks.os.path.isfile", return_value=True),
|
||||
patch("hate_crack.attacks.os.path.isdir", return_value=False),
|
||||
):
|
||||
wordlist_optimize(ctx)
|
||||
ctx.wordlist_optimize.assert_called_once_with(
|
||||
[str(wl_a), str(wl_b)], outdir
|
||||
@@ -338,6 +341,35 @@ class TestWordlistOptimize:
|
||||
out = capsys.readouterr().out
|
||||
assert outdir in out
|
||||
|
||||
def test_directory_expansion(self, tmp_path, capsys):
|
||||
ctx = _make_ctx()
|
||||
wl_dir = str(tmp_path / "wls")
|
||||
outdir = str(tmp_path / "out")
|
||||
ctx.select_file_with_autocomplete.side_effect = [wl_dir, outdir]
|
||||
ctx.list_wordlist_files.return_value = ["a.txt", "b.txt"]
|
||||
with (
|
||||
patch("hate_crack.attacks.os.path.isfile", return_value=False),
|
||||
patch("hate_crack.attacks.os.path.isdir", return_value=True),
|
||||
):
|
||||
wordlist_optimize(ctx)
|
||||
ctx.wordlist_optimize.assert_called_once_with(
|
||||
[os.path.join(wl_dir, "a.txt"), os.path.join(wl_dir, "b.txt")], outdir
|
||||
)
|
||||
|
||||
def test_empty_directory_rejection(self, tmp_path, capsys):
|
||||
ctx = _make_ctx()
|
||||
wl_dir = str(tmp_path / "wls")
|
||||
ctx.select_file_with_autocomplete.return_value = wl_dir
|
||||
ctx.list_wordlist_files.return_value = []
|
||||
with (
|
||||
patch("hate_crack.attacks.os.path.isfile", return_value=False),
|
||||
patch("hate_crack.attacks.os.path.isdir", return_value=True),
|
||||
):
|
||||
wordlist_optimize(ctx)
|
||||
out = capsys.readouterr().out
|
||||
assert "No wordlist files found" in out
|
||||
ctx.wordlist_optimize.assert_not_called()
|
||||
|
||||
def test_empty_input_rejection(self, capsys):
|
||||
ctx = _make_ctx()
|
||||
ctx.select_file_with_autocomplete.return_value = ","
|
||||
@@ -359,10 +391,13 @@ class TestWordlistOptimize:
|
||||
existing = tmp_path / "a.txt"
|
||||
existing.write_text("word\n")
|
||||
ctx.select_file_with_autocomplete.return_value = f"{existing},/nonexistent/missing.txt"
|
||||
with patch("hate_crack.attacks.os.path.isfile", side_effect=lambda p: p == str(existing)):
|
||||
with (
|
||||
patch("hate_crack.attacks.os.path.isfile", side_effect=lambda p: p == str(existing)),
|
||||
patch("hate_crack.attacks.os.path.isdir", return_value=False),
|
||||
):
|
||||
wordlist_optimize(ctx)
|
||||
out = capsys.readouterr().out
|
||||
assert "Files not found" in out
|
||||
assert "Not found" in out
|
||||
ctx.wordlist_optimize.assert_not_called()
|
||||
|
||||
def test_empty_outdir_rejection(self, tmp_path, capsys):
|
||||
@@ -370,7 +405,10 @@ class TestWordlistOptimize:
|
||||
wl = tmp_path / "a.txt"
|
||||
wl.write_text("word\n")
|
||||
ctx.select_file_with_autocomplete.side_effect = [str(wl), ""]
|
||||
with patch("hate_crack.attacks.os.path.isfile", return_value=True):
|
||||
with (
|
||||
patch("hate_crack.attacks.os.path.isfile", return_value=True),
|
||||
patch("hate_crack.attacks.os.path.isdir", return_value=False),
|
||||
):
|
||||
wordlist_optimize(ctx)
|
||||
out = capsys.readouterr().out
|
||||
assert "Output directory cannot be empty" in out
|
||||
@@ -383,7 +421,10 @@ class TestWordlistOptimize:
|
||||
wl.write_text("word\n")
|
||||
outdir = str(tmp_path / "out")
|
||||
ctx.select_file_with_autocomplete.side_effect = [str(wl), outdir]
|
||||
with patch("hate_crack.attacks.os.path.isfile", return_value=True):
|
||||
with (
|
||||
patch("hate_crack.attacks.os.path.isfile", return_value=True),
|
||||
patch("hate_crack.attacks.os.path.isdir", return_value=False),
|
||||
):
|
||||
wordlist_optimize(ctx)
|
||||
out = capsys.readouterr().out
|
||||
assert "Optimization failed" in out
|
||||
|
||||
Reference in New Issue
Block a user