hbkit 0.4.7__tar.gz → 0.4.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hbkit
3
- Version: 0.4.7
3
+ Version: 0.4.8
4
4
  Summary: Recover files from Synology Hyper Backup (.hbk) archives without Synology software
5
5
  Project-URL: Homepage, https://github.com/YordiLorenzo/hbkit
6
6
  Project-URL: Source, https://github.com/YordiLorenzo/hbkit
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hbkit"
7
- version = "0.4.7"
7
+ version = "0.4.8"
8
8
  description = "Recover files from Synology Hyper Backup (.hbk) archives without Synology software"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  """hbkit - recover files from Synology Hyper Backup (.hbk) archives without Synology software."""
2
2
 
3
- __version__ = "0.4.7"
3
+ __version__ = "0.4.8"
4
4
 
5
5
  from .archive import (Archive, NeedPassword, UnsupportedArchive, # noqa: F401
6
6
  find_archive_root, is_archive)
@@ -92,6 +92,23 @@ def resolve_password(password):
92
92
  return password or os.environ.get("HBK_PASSWORD")
93
93
 
94
94
 
95
+ def stdin_is_interactive():
96
+ """True when a password prompt can be answered: stdin is a real terminal/console."""
97
+ if not sys.stdin or not sys.stdin.isatty():
98
+ return False
99
+ if os.name != "nt":
100
+ return True
101
+ # `< NUL` is a character device, so isatty() is True; only a real console has a mode
102
+ import ctypes
103
+ import msvcrt
104
+ try:
105
+ h = msvcrt.get_osfhandle(sys.stdin.fileno())
106
+ except OSError:
107
+ return False
108
+ mode = ctypes.c_uint32()
109
+ return bool(ctypes.windll.kernel32.GetConsoleMode(ctypes.c_void_p(h), ctypes.byref(mode)))
110
+
111
+
95
112
  def open_archive(archive, password=None):
96
113
  """Open, prompting for a password only if the archive turns out to be encrypted."""
97
114
  try:
@@ -99,7 +116,7 @@ def open_archive(archive, password=None):
99
116
  except hbk.NeedPassword:
100
117
  pw = resolve_password(password)
101
118
  if not pw:
102
- if not sys.stdin.isatty():
119
+ if not stdin_is_interactive():
103
120
  sys.exit("archive is encrypted - pass --password or set HBK_PASSWORD")
104
121
  pw = getpass.getpass("archive password: ")
105
122
  try:
@@ -303,6 +303,26 @@ def test_verify_rejects_options_it_does_not_take():
303
303
  assert "unknown option --version" in r.stderr
304
304
 
305
305
 
306
+ def test_encrypted_archive_with_null_stdin_fails_instead_of_prompting(tmp_path):
307
+ """On Windows `< NUL` is a character device, so isatty() says True and getpass used
308
+ to wait on the console forever. Only enough of an archive to reach NeedPassword."""
309
+ import struct
310
+ header = struct.pack(">16I", hbk.MAGIC, 0, 0, 0, 56, *([0] * 11))
311
+ root = tmp_path / "enc.hbk"
312
+ for d in ("Config/virtual_file.index", "Pool/chunk_index",
313
+ "Config/file_chunk1.index", "Pool/0"):
314
+ (root / d).mkdir(parents=True)
315
+ (root / "Config/virtual_file.index/0.idx").write_bytes(header)
316
+ (root / "Pool/chunk_index/0.idx").write_bytes(header)
317
+ (root / "_Syno_TaskConfig").write_text('enable_data_encrypt="true"\n')
318
+ env = {k: v for k, v in os.environ.items() if k != "HBK_PASSWORD"}
319
+ r = subprocess.run([sys.executable, "-m", "hbkit.cli", str(root), "list"],
320
+ stdin=subprocess.DEVNULL, capture_output=True, text=True,
321
+ env=env, timeout=30, check=False)
322
+ assert r.returncode != 0, r.stderr
323
+ assert "pass --password or set HBK_PASSWORD" in r.stderr
324
+
325
+
306
326
  # ------------------------------------------------- shard maths (no archive needed)
307
327
 
308
328
  def _make_shards(tmp_path, sizes, shard_size, monkeypatch):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes