Skip to content

Commit 2547603

Browse files
committed
Implemented BencodeDecoder and BencodeEncoder classes
1 parent 7813643 commit 2547603

File tree

7 files changed

+343
-262
lines changed

7 files changed

+343
-262
lines changed

bencode/BTL.py

-9
This file was deleted.

bencode/__init__.py

+31-247
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,22 @@
1212

1313
"""bencode.py - bencode encoder + decoder."""
1414

15-
from bencode.BTL import BTFailure
15+
from bencode.common import Bencached
16+
from bencode.decoder import BencodeDecoder
17+
from bencode.encoder import BencodeEncoder
1618
from bencode.exceptions import BencodeDecodeError
1719

18-
from collections import deque
19-
import sys
20-
21-
try:
22-
from typing import Dict, List, Tuple, Deque, Union, TextIO, BinaryIO, Any
23-
except ImportError:
24-
Dict = List = Tuple = Deque = Union = TextIO = BinaryIO = Any = None
25-
26-
try:
27-
from collections import OrderedDict
28-
except ImportError:
29-
OrderedDict = None
30-
3120
try:
3221
import pathlib
3322
except ImportError:
3423
pathlib = None
3524

36-
PY2 = sys.version_info[0] == 2
37-
PY3 = sys.version_info[0] == 3
38-
3925
__all__ = (
40-
'BTFailure',
26+
'Bencached',
27+
'Bencode',
28+
'BencodeDecoder',
4129
'BencodeDecodeError',
30+
'BencodeEncoder',
4231
'bencode',
4332
'bdecode',
4433
'bread',
@@ -48,90 +37,33 @@
4837
)
4938

5039

51-
def decode_int(x, f):
52-
# type: (bytes, int) -> Tuple[int, int]
53-
f += 1
54-
newf = x.index(b'e', f)
55-
n = int(x[f:newf])
56-
57-
if x[f:f + 1] == b'-':
58-
if x[f + 1:f + 2] == b'0':
59-
raise ValueError
60-
elif x[f:f + 1] == b'0' and newf != f + 1:
61-
raise ValueError
62-
63-
return n, newf + 1
64-
65-
66-
def decode_string(x, f):
67-
# type: (bytes, int) -> Tuple[bytes, int]
68-
"""Decode torrent bencoded 'string' in x starting at f."""
69-
colon = x.index(b':', f)
70-
n = int(x[f:colon])
40+
class Bencode(object):
41+
def __init__(self):
42+
self.decoder = BencodeDecoder()
43+
self.encoder = BencodeEncoder()
7144

72-
if x[f:f + 1] == b'0' and colon != f + 1:
73-
raise ValueError
45+
def decode(self, value):
46+
return self.decoder.decode(value)
7447

75-
colon += 1
76-
s = x[colon:colon + n]
48+
def encode(self, value):
49+
return self.encoder.encode(value)
7750

78-
return bytes(s), colon + n
7951

52+
DEFAULT = Bencode()
8053

81-
def decode_list(x, f):
82-
# type: (bytes, int) -> Tuple[List, int]
83-
r, f = [], f + 1
8454

85-
while x[f:f + 1] != b'e':
86-
v, f = decode_func[x[f:f + 1]](x, f)
87-
r.append(v)
88-
89-
return r, f + 1
90-
91-
92-
def decode_dict(x, f, force_sort=True):
93-
# type: (bytes, int, bool) -> Tuple[OrderedDict[str, Any], int]
94-
"""Decode bencoded data to an OrderedDict.
95-
96-
The BitTorrent standard states that:
97-
Keys must be strings and appear in sorted order (sorted as raw
98-
strings, not alphanumerics)
99-
- http://www.bittorrent.org/beps/bep_0003.html
100-
101-
Therefore, this function will force the keys to be strings (decoded
102-
from utf-8), and by default the keys are (re)sorted after reading.
103-
Set force_sort to False to keep the order of the dictionary as
104-
represented in x, as many other encoders and decoders do not force this
105-
property.
55+
def bencode(value):
56+
# type: (Union[Tuple, List, OrderedDict, Dict, bool, int, str, bytes]) -> bytes
10657
"""
58+
Encode ``value`` into the bencode format.
10759
108-
r, f = OrderedDict(), f + 1
109-
110-
while x[f:f + 1] != b'e':
111-
k, f = decode_string(x, f)
112-
r[k], f = decode_func[x[f:f + 1]](x, f)
113-
114-
if force_sort:
115-
r = OrderedDict(sorted(r.items()))
116-
117-
return r, f + 1
118-
60+
:param value: Value
61+
:type value: object
11962
120-
# noinspection PyDictCreation
121-
decode_func = {}
122-
decode_func[b'l'] = decode_list
123-
decode_func[b'i'] = decode_int
124-
decode_func[b'0'] = decode_string
125-
decode_func[b'1'] = decode_string
126-
decode_func[b'2'] = decode_string
127-
decode_func[b'3'] = decode_string
128-
decode_func[b'4'] = decode_string
129-
decode_func[b'5'] = decode_string
130-
decode_func[b'6'] = decode_string
131-
decode_func[b'7'] = decode_string
132-
decode_func[b'8'] = decode_string
133-
decode_func[b'9'] = decode_string
134-
decode_func[b'd'] = decode_dict
63+
:return: Bencode formatted string
64+
:rtype: str
65+
"""
66+
return DEFAULT.encode(value)
13567

13668

13769
def bdecode(value):
@@ -145,160 +77,7 @@ def bdecode(value):
14577
:return: Decoded value
14678
:rtype: object
14779
"""
148-
try:
149-
value = to_binary(value)
150-
data, length = decode_func[value[0:1]](value, 0)
151-
except (IndexError, KeyError, TypeError, ValueError):
152-
raise BencodeDecodeError("not a valid bencoded string")
153-
154-
if length != len(value):
155-
raise BencodeDecodeError("invalid bencoded value (data after valid prefix)")
156-
157-
return data
158-
159-
160-
class Bencached(object):
161-
__slots__ = ['bencoded']
162-
163-
def __init__(self, s):
164-
self.bencoded = s
165-
166-
167-
def encode_bencached(x, r):
168-
# type: (Bencached, Deque[bytes]) -> None
169-
r.append(x.bencoded)
170-
171-
172-
def encode_int(x, r):
173-
# type: (int, Deque[bytes]) -> None
174-
r.extend((b'i', str(x).encode('utf-8'), b'e'))
175-
176-
177-
def encode_bool(x, r):
178-
# type: (bool, Deque[bytes]) -> None
179-
if x:
180-
encode_int(1, r)
181-
else:
182-
encode_int(0, r)
183-
184-
185-
def encode_bytes(x, r):
186-
# type: (bytes, Deque[bytes]) -> None
187-
r.extend((str(len(x)).encode('utf-8'), b':', x))
188-
189-
190-
def encode_string(x, r):
191-
# type: (str, Deque[bytes]) -> None
192-
return encode_bytes(x.encode("UTF-8"), r)
193-
194-
195-
def encode_list(x, r):
196-
# type: (List, Deque[bytes]) -> None
197-
r.append(b'l')
198-
199-
for i in x:
200-
encode_func[type(i)](i, r)
201-
202-
r.append(b'e')
203-
204-
205-
def encode_dict(x, r):
206-
# type: (Dict, Deque[bytes]) -> None
207-
r.append(b'd')
208-
209-
# force all keys to bytes, because str and bytes are incomparable
210-
ilist = [(to_binary(k), v) for k, v in x.items()]
211-
ilist.sort(key=lambda kv: kv[0])
212-
213-
for k, v in ilist:
214-
encode_func[type(k)](k, r)
215-
encode_func[type(v)](v, r)
216-
217-
r.append(b'e')
218-
219-
220-
def is_binary(s):
221-
if PY3:
222-
return isinstance(s, bytes)
223-
224-
return isinstance(s, str)
225-
226-
227-
def is_text(s):
228-
if PY3:
229-
return isinstance(s, str)
230-
231-
return isinstance(s, unicode) # noqa: F821
232-
233-
234-
def to_binary(s):
235-
if is_binary(s):
236-
return s
237-
238-
if is_text(s):
239-
return s.encode('utf-8', 'strict')
240-
241-
raise TypeError("expected binary or text (found %s)" % type(s))
242-
243-
244-
# noinspection PyDictCreation
245-
encode_func = {}
246-
encode_func[Bencached] = encode_bencached
247-
248-
if PY2:
249-
from types import DictType, IntType, ListType, LongType, StringType, TupleType, UnicodeType
250-
251-
encode_func[DictType] = encode_dict
252-
encode_func[IntType] = encode_int
253-
encode_func[ListType] = encode_list
254-
encode_func[LongType] = encode_int
255-
encode_func[StringType] = encode_bytes
256-
encode_func[TupleType] = encode_list
257-
encode_func[UnicodeType] = encode_string
258-
259-
if OrderedDict is not None:
260-
encode_func[OrderedDict] = encode_dict
261-
262-
try:
263-
from types import BooleanType
264-
265-
encode_func[BooleanType] = encode_bool
266-
except ImportError:
267-
pass
268-
else:
269-
encode_func[OrderedDict] = encode_dict
270-
encode_func[bool] = encode_bool
271-
encode_func[dict] = encode_dict
272-
encode_func[int] = encode_int
273-
encode_func[list] = encode_list
274-
encode_func[str] = encode_string
275-
encode_func[tuple] = encode_list
276-
encode_func[bytes] = encode_bytes
277-
278-
279-
def bencode(value):
280-
# type: (Union[Tuple, List, OrderedDict, Dict, bool, int, str, bytes]) -> bytes
281-
"""
282-
Encode ``value`` into the bencode format.
283-
284-
:param value: Value
285-
:type value: object
286-
287-
:return: Bencode formatted string
288-
:rtype: str
289-
"""
290-
r = deque() # makes more sense for something with lots of appends
291-
292-
# Encode provided value
293-
encode_func[type(value)](value, r)
294-
295-
# Join parts
296-
return b''.join(r)
297-
298-
299-
# Method proxies (for compatibility with other libraries)
300-
decode = bdecode
301-
encode = bencode
80+
return DEFAULT.decode(value)
30281

30382

30483
def bread(fd):
@@ -337,3 +116,8 @@ def bwrite(data, # type: Union[Tuple, List, OrderedDict, Dict, bool, int, str,
337116
fd.write(bencode(data))
338117
else:
339118
fd.write(bencode(data))
119+
120+
121+
# Compatibility Proxies
122+
encode = bencode
123+
decode = bdecode

bencode/common.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Bencached(object):
2+
__slots__ = ['bencoded']
3+
4+
def __init__(self, s):
5+
self.bencoded = s

bencode/compat.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import sys
2+
3+
PY2 = sys.version_info[0] == 2
4+
PY3 = sys.version_info[0] == 3
5+
6+
7+
def is_binary(s):
8+
if PY3:
9+
return isinstance(s, bytes)
10+
11+
return isinstance(s, str)
12+
13+
14+
def is_text(s):
15+
if PY3:
16+
return isinstance(s, str)
17+
18+
return isinstance(s, unicode) # noqa: F821
19+
20+
21+
def to_binary(s):
22+
if is_binary(s):
23+
return s
24+
25+
if is_text(s):
26+
return s.encode('utf-8', 'strict')
27+
28+
raise TypeError("expected binary or text (found %s)" % type(s))

0 commit comments

Comments
 (0)