Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
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)