Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
at main 160 lines 4.4 kB view raw
1def loads(data): 2 if not isinstance(data, bytes): 3 raise TypeError("Expected 'bytes' object, got {}".format(type(data))) 4 5 offset = 0 6 7 8 def parseInteger(): 9 nonlocal offset 10 11 offset += 1 12 had_digit = False 13 abs_value = 0 14 15 sign = 1 16 if data[offset] == ord("-"): 17 sign = -1 18 offset += 1 19 while offset < len(data): 20 if data[offset] == ord("e"): 21 # End of string 22 offset += 1 23 if not had_digit: 24 raise ValueError("Integer without value") 25 break 26 if ord("0") <= data[offset] <= ord("9"): 27 abs_value = abs_value * 10 + int(chr(data[offset])) 28 had_digit = True 29 offset += 1 30 else: 31 raise ValueError("Invalid integer") 32 else: 33 raise ValueError("Unexpected EOF, expected integer") 34 35 if not had_digit: 36 raise ValueError("Empty integer") 37 38 return sign * abs_value 39 40 41 def parseString(): 42 nonlocal offset 43 44 length = int(chr(data[offset])) 45 offset += 1 46 47 while offset < len(data): 48 if data[offset] == ord(":"): 49 offset += 1 50 break 51 if ord("0") <= data[offset] <= ord("9"): 52 length = length * 10 + int(chr(data[offset])) 53 offset += 1 54 else: 55 raise ValueError("Invalid string length") 56 else: 57 raise ValueError("Unexpected EOF, expected string contents") 58 59 if offset + length > len(data): 60 raise ValueError("Unexpected EOF, expected string contents") 61 offset += length 62 63 return data[offset - length:offset] 64 65 66 def parseList(): 67 nonlocal offset 68 69 offset += 1 70 values = [] 71 72 while offset < len(data): 73 if data[offset] == ord("e"): 74 # End of list 75 offset += 1 76 return values 77 else: 78 values.append(parse()) 79 80 raise ValueError("Unexpected EOF, expected list contents") 81 82 83 def parseDict(): 84 nonlocal offset 85 86 offset += 1 87 items = {} 88 89 while offset < len(data): 90 if data[offset] == ord("e"): 91 # End of list 92 offset += 1 93 return items 94 else: 95 key, value = parse(), parse() 96 if not isinstance(key, bytes): 97 raise ValueError("A dict key must be a byte string") 98 if key in items: 99 raise ValueError("Duplicate dict key: {}".format(key)) 100 items[key] = value 101 102 raise ValueError("Unexpected EOF, expected dict contents") 103 104 105 def parse(): 106 nonlocal offset 107 108 if data[offset] == ord("i"): 109 return parseInteger() 110 elif data[offset] == ord("l"): 111 return parseList() 112 elif data[offset] == ord("d"): 113 return parseDict() 114 elif ord("0") <= data[offset] <= ord("9"): 115 return parseString() 116 117 raise ValueError("Unknown type specifier: '{}'".format(chr(data[offset]))) 118 119 result = parse() 120 121 if offset != len(data): 122 raise ValueError("Expected EOF, got {} bytes left".format(len(data) - offset)) 123 124 return result 125 126 127def dumps(data): 128 result = bytearray() 129 130 131 def convert(data): 132 nonlocal result 133 134 if isinstance(data, str): 135 raise ValueError("bencode only supports bytes, not str. Use encode") 136 137 if isinstance(data, bytes): 138 result += str(len(data)).encode() + b":" + data 139 elif isinstance(data, int): 140 result += b"i" + str(data).encode() + b"e" 141 elif isinstance(data, list): 142 result += b"l" 143 for val in data: 144 convert(val) 145 result += b"e" 146 elif isinstance(data, dict): 147 result += b"d" 148 for key in sorted(data.keys()): 149 if not isinstance(key, bytes): 150 raise ValueError("Dict key can only be bytes, not {}".format(type(key))) 151 convert(key) 152 convert(data[key]) 153 result += b"e" 154 else: 155 raise ValueError("bencode only supports bytes, int, list and dict") 156 157 158 convert(data) 159 160 return bytes(result)