From 8d267c7f46299d851ffa77d745d62796e8fbaacb Mon Sep 17 00:00:00 2001 From: JustAnotherArchivist Date: Sat, 1 Oct 2022 18:53:10 +0000 Subject: [PATCH] Add bencode2json --- bencode2json | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100755 bencode2json diff --git a/bencode2json b/bencode2json new file mode 100755 index 0000000..d91d486 --- /dev/null +++ b/bencode2json @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +try: + from contextlib import nullcontext +except ImportError: + # Python 3.6 and below + # One-liner because I can. + nullcontext = type("nc", (object,), {"__init__": (lambda self, v: setattr(self, "v", v)), "__enter__": (lambda self: self.v), "__exit__": (lambda self, *args, **kwargs: None)}) +import json +import sys + + +def parse_dict(f): + d = {} + while True: + c = f.read(1) + if c == b'e': + return d + elif b'0' <= c <= b'9': + key = parse_str(f, c) + value = parse_any(f) + d[key] = value + else: + raise ValueError(f'invalid bencode value: {c} instead of expected e or digit') + + +def parse_list(f): + l = [] + while True: + c = f.read(1) + if c == b'e': + return l + l.append(parse_any(f, c)) + + +def _parse_int_bytes(f, stopChar): + # Read until encountering a non-decimal character; if it isn't stopChar, raise a ValueError + v = b'' + # Python 3.8+ (walrus) + #while b'0' <= (c := f.read(1)) <= b'9': + # v += c + while True: + c = f.read(1) + if b'0' <= c <= b'9': + v += c + else: + break + if c == stopChar: + return v + raise ValueError(f'invalid bencode value: {c} instead of expected {stopChar}') + + +def parse_int(f): + return int(_parse_int_bytes(f, b'e')) + + +def parse_str(f, c): + v = _parse_int_bytes(f, b':') + length = int(c + v) + read = 0 + buf = [] + while read < length: + d = f.read(length - read) + buf.append(d) + read += len(d) + buf = b''.join(buf) + try: + return buf.decode('utf-8', 'strict') + except UnicodeDecodeError: + return buf.decode('iso-8859-1') + + +def parse_any(f, c = None): + if c is None: + c = f.read(1) + if c == b'd': + return parse_dict(f) + elif c == b'l': + return parse_list(f) + elif c == b'i': + return parse_int(f) + elif b'0' <= c <= b'9': + return parse_str(f, c) + else: + raise ValueError(f'invalid bencode value: {c} instead of expected d, l, i, or digit') + + +if sys.argv[1:]: + c = open(sys.argv[1], 'rb') +else: + c = nullcontext(sys.stdin.buffer) +with c as fp: + print(json.dumps(parse_any(fp)))