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
51 changes: 33 additions & 18 deletions deluge/bencode.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ class BTFailure(Exception):
LIST_DELIM = b'l'
BYTE_SEP = b':'

# The max depth permitted for recursive functions. Depths above this will throw a BTFailure.
MAX_DEPTH: int = 100

def decode_int(x, f):

def decode_int(x, f, depth=0):
f += 1
newf = x.index(END_DELIM, f)
n = int(x[f:newf])
Expand All @@ -34,28 +37,36 @@ def decode_int(x, f):
return (n, newf + 1)


def decode_string(x, f):
def decode_string(x, f, depth=0):
colon = x.index(BYTE_SEP, f)
n = int(x[f:colon])
# The length must be numeric digits only
if not x[f:colon].isdigit():
raise ValueError
n = int(x[f:colon])
if x[f : f + 1] == b'0' and colon != f + 1:
raise ValueError
colon += 1
return (x[colon : colon + n], colon + n)


def decode_list(x, f):
def decode_list(x, f, depth):
if depth > MAX_DEPTH:
raise BTFailure('Too much nesting in list')
r, f = [], f + 1
while x[f : f + 1] != END_DELIM:
v, f = decode_func[x[f : f + 1]](x, f)
v, f = decode_func[x[f : f + 1]](x, f, depth + 1)
r.append(v)
return (r, f + 1)


def decode_dict(x, f):
def decode_dict(x, f, depth):
if depth > MAX_DEPTH:
raise BTFailure('Too much nesting in dict')
r, f = {}, f + 1
while x[f : f + 1] != END_DELIM:
k, f = decode_string(x, f)
r[k], f = decode_func[x[f : f + 1]](x, f)
k, f = decode_string(x, f, depth + 1)
r[k], f = decode_func[x[f : f + 1]](x, f, depth + 1)
return (r, f + 1)


Expand All @@ -77,7 +88,7 @@ def decode_dict(x, f):

def bdecode(x):
try:
r, __ = decode_func[x[0:1]](x, 0)
r, __ = decode_func[x[0:1]](x, 0, 0)
except (LookupError, TypeError, ValueError):
raise BTFailure('Not a valid bencoded string')
else:
Expand Down Expand Up @@ -111,37 +122,41 @@ def encode_bytes(x, r):
r.extend((str(len(x)).encode('utf8'), BYTE_SEP, x))


def encode_list(x, r):
def encode_list(x, r, depth):
if depth > MAX_DEPTH:
raise BTFailure('Too much nesting in list')
r.append(LIST_DELIM)
for i in x:
encode_func[type(i)](i, r)
encode_func[type(i)](i, r, depth + 1)
r.append(END_DELIM)


def encode_dict(x, r):
def encode_dict(x, r, depth):
if depth > MAX_DEPTH:
raise BTFailure('Too much nesting in dict')
r.append(DICT_DELIM)
for k, v in sorted(x.items()):
try:
k = k.encode('utf8')
except AttributeError:
pass
r.extend((str(len(k)).encode('utf8'), BYTE_SEP, k))
encode_func[type(v)](v, r)
encode_func[type(v)](v, r, depth + 1)
r.append(END_DELIM)


encode_func = {}
encode_func[Bencached] = encode_bencached
encode_func[int] = encode_int
encode_func[Bencached] = lambda x, r, d: encode_bencached(x, r)
encode_func[int] = lambda x, r, d: encode_int(x, r)
encode_func[list] = encode_list
encode_func[tuple] = encode_list
encode_func[dict] = encode_dict
encode_func[bool] = encode_bool
encode_func[str] = encode_string
encode_func[bytes] = encode_bytes
encode_func[bool] = lambda x, r, d: encode_bool(x, r)
encode_func[str] = lambda x, r, d: encode_string(x, r)
encode_func[bytes] = lambda x, r, d: encode_bytes(x, r)


def bencode(x):
r = []
encode_func[type(x)](x, r)
encode_func[type(x)](x, r, 0)
return b''.join(r)
25 changes: 25 additions & 0 deletions deluge/tests/test_bencode.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,28 @@ def test_bdecode(self):
bencode.bdecode(b'dEf')
with pytest.raises(bencode.BTFailure):
bencode.bdecode({'dEf': 123})

def test_bdecode_negative(self):
with pytest.raises(bencode.BTFailure):
bencode.bdecode(b'd-4:e')
with pytest.raises(bencode.BTFailure):
bencode.bdecode(b'd-0:i1ee')
with pytest.raises(bencode.BTFailure):
bencode.bdecode(b'd-1:i1ee')
with pytest.raises(bencode.BTFailure):
bencode.bdecode(b' 3:abc')
with pytest.raises(bencode.BTFailure):
bencode.bdecode(b' +3:abc')

def test_bencode_recursion_decode(self):
nesting_level = 200
data = b'l' * nesting_level + b'i1e' + b'e' * nesting_level
with pytest.raises(bencode.BTFailure):
bencode.bdecode(data)

def test_bencode_recursion_encode(self):
obj = 'a'
for _ in range(200):
obj = [obj]
with pytest.raises(bencode.BTFailure):
bencode.bencode(obj)
Loading