feat(hashview): download rule files from /v1/rules

Add HashviewAPI.list_rules and download_rules wrapping the Hashview
rule endpoints. The server gzip-compresses plaintext rules on the
fly, so download_rules decompresses before saving, yielding a file
usable directly with hashcat -r. Wired into the interactive Hashview
menu (Download Rule) and the CLI (hashview download-rules
--rules-id/--output). Unknown rule ids surface the real HTTP 404.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-07-21 18:06:19 -04:00
co-authored by Claude Opus 4.8
parent 201cc37e55
commit 144d1a5334
5 changed files with 215 additions and 0 deletions
+24
View File
@@ -19,6 +19,13 @@ class DummyHashviewAPI:
self.calls.append(("upload_wordlist_file", wordlist_path, wordlist_name))
return {"msg": "Wordlist uploaded", "wordlist_id": 123}
def download_rules(self, rules_id, output_file=None):
self.calls.append(("download_rules", rules_id, output_file))
return {
"output_file": output_file or f"rule_{rules_id}.rule",
"size": 77,
}
def download_left_hashes(self, customer_id, hashfile_id, output_file=None):
self.calls.append(
("download_left_hashes", customer_id, hashfile_id, output_file)
@@ -111,6 +118,23 @@ def test_hashview_cli_upload_wordlist(_patch_hashview, monkeypatch, tmp_path, ca
assert "Wordlist uploaded" in captured.out
def test_hashview_cli_download_rules(_patch_hashview, monkeypatch, tmp_path, capsys):
code = _run_main_with_args(
monkeypatch,
[
"hashview",
"download-rules",
"--rules-id",
"4",
"--output",
str(tmp_path / "best64.rule"),
],
)
captured = capsys.readouterr()
assert code == 0
assert "Downloaded 77 bytes" in captured.out
def test_hashview_cli_upload_hashfile_job(
_patch_hashview, monkeypatch, tmp_path, capsys
):