Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added __pycache__/test_speed.cpython-312.pyc
Binary file not shown.
Binary file added __pycache__/test_speed2.cpython-312.pyc
Binary file not shown.
Binary file added __pycache__/test_split.cpython-312.pyc
Binary file not shown.
Binary file added __pycache__/test_yenc.cpython-312.pyc
Binary file not shown.
Binary file added __pycache__/test_yenc2.cpython-312.pyc
Binary file not shown.
Binary file added __pycache__/verify_nzb.cpython-312.pyc
Binary file not shown.
Binary file added tests/__pycache__/__init__.cpython-312.pyc
Binary file not shown.
Binary file added tests/__pycache__/test_verify_nzb.cpython-312.pyc
Binary file not shown.
36 changes: 25 additions & 11 deletions verify_nzb.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,19 +115,33 @@ def _parse_yenc_attrs(line: bytes) -> dict[str, str]:
return attrs


_YENC_TABLE = bytes((i - 42) % 256 for i in range(256))

def _decode_yenc_lines(lines: Iterable[bytes]) -> bytes:
joined = b"".join(lines)
if not joined:
return b""
parts = joined.split(b"=")
Comment on lines +121 to +124
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject escapes that cross yEnc line boundaries

Parse escapes per source line instead of on b"".join(lines): the new split-based logic allows a line ending with = to consume the first byte of the next line, so malformed input no longer triggers dangling yEnc escape. This changes validation semantics and can produce false positives in validate_yenc_body when CRC is absent (e.g., =ybegin size=1, data lines b"=" + b"k", =yend size=1 now returns ok=True instead of rejecting the malformed body).

Useful? React with πŸ‘Β / πŸ‘Ž.

if len(parts) == 1:
return joined.translate(_YENC_TABLE)

decoded = bytearray()
for line in lines:
index = 0
while index < len(line):
byte = line[index]
if byte == 61:
index += 1
if index >= len(line):
raise ValueError("dangling yEnc escape")
byte = (line[index] - 64) % 256
decoded.append((byte - 42) % 256)
index += 1
decoded.extend(parts[0].translate(_YENC_TABLE))

iterator = iter(parts[1:])
for part in iterator:
if not part:
decoded.append(211) # (61 - 106) % 256
try:
next_part = next(iterator)
decoded.extend(next_part.translate(_YENC_TABLE))
except StopIteration:
raise ValueError("dangling yEnc escape")
else:
decoded.append((part[0] - 106) % 256)
if len(part) > 1:
decoded.extend(part[1:].translate(_YENC_TABLE))

Comment on lines +131 to +144
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Use explicit exception chaining with from None.

When converting StopIteration to ValueError, use from None to indicate this is an intentional transformation rather than an error during exception handling. This improves debugging clarity and satisfies B904.

Proposed fix
             except StopIteration:
-                raise ValueError("dangling yEnc escape")
+                raise ValueError("dangling yEnc escape") from None
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
iterator = iter(parts[1:])
for part in iterator:
if not part:
decoded.append(211) # (61 - 106) % 256
try:
next_part = next(iterator)
decoded.extend(next_part.translate(_YENC_TABLE))
except StopIteration:
raise ValueError("dangling yEnc escape")
else:
decoded.append((part[0] - 106) % 256)
if len(part) > 1:
decoded.extend(part[1:].translate(_YENC_TABLE))
iterator = iter(parts[1:])
for part in iterator:
if not part:
decoded.append(211) # (61 - 106) % 256
try:
next_part = next(iterator)
decoded.extend(next_part.translate(_YENC_TABLE))
except StopIteration:
raise ValueError("dangling yEnc escape") from None
else:
decoded.append((part[0] - 106) % 256)
if len(part) > 1:
decoded.extend(part[1:].translate(_YENC_TABLE))
🧰 Tools
πŸͺ› Ruff (0.15.13)

[warning] 139-139: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@verify_nzb.py` around lines 131 - 144, In the yEnc decoding loop where you
catch StopIteration (inside the for loop iterating over iterator =
iter(parts[1:])), re-raise the ValueError("dangling yEnc escape") using explicit
exception chaining suppression by writing "raise ValueError('dangling yEnc
escape') from None" so the StopIteration is intentionally masked; update the
except StopIteration block accordingly around the next_part = next(iterator)
handling.

return bytes(decoded)


Expand Down