# Copyright 2007 by Petru Paler # Copyright 2011 by Julien (jvoisin) Voisin # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # """ A quick (and also nice) lib to bencode/bdecode torrent files """ class BTFailure(Exception): """Custom Exception""" pass class Bencached(object): """Custom type : cached string""" __slots__ = ['bencoded'] def __init__(self, string): self.bencoded = string def decode_int(x, f): """decode an int""" f += 1 newf = x.index('e', f) if x[f:f + 1] == '-0': raise ValueError elif x[f] == '0' and newf != f + 1: raise ValueError return int(x[f:newf]), newf + 1 def decode_string(x, f): """decode a string""" colon = x.index(':', f) if x[f] == '0' and colon != f + 1: raise ValueError n = int(x[f:colon]) colon += 1 return x[colon:colon + n], colon + n def decode_list(x, f): """decode a list""" result = [] f += 1 while x[f] != 'e': v, f = DECODE_FUNC[x[f]](x, f) result.append(v) return result, f + 1 def decode_dict(x, f): """decode a dict""" result = {} f += 1 while x[f] != 'e': k, f = decode_string(x, f) result[k], f = DECODE_FUNC[x[f]](x, f) return result, f + 1 def encode_bool(x, r): """bencode a boolean""" encode_int(1 if r else 0, r) def encode_int(x, r): """bencode an integer/float""" r.extend(('i', str(x), 'e')) def encode_list(x, r): """bencode a list/tuple""" r.append('l') [ENCODE_FUNC[type(item)](item, r) for item in x] r.append('e') def encode_dict(x, result): """bencode a dict""" result.append('d') ilist = list(x.items()) ilist.sort() for k, v in ilist: result.extend((str(len(k)), ':', k)) ENCODE_FUNC[type(v)](v, result) result.append('e') DECODE_FUNC = {str(x): decode_string for x in range(9)} DECODE_FUNC['l'] = decode_list DECODE_FUNC['d'] = decode_dict DECODE_FUNC['i'] = decode_int ENCODE_FUNC = {} ENCODE_FUNC[Bencached] = lambda x, r: r.append(x.bencoded) ENCODE_FUNC[int] = encode_int ENCODE_FUNC[int] = encode_int ENCODE_FUNC[bytes] = lambda x, r: r.extend((str(len(x)), ':', x)) ENCODE_FUNC[list] = encode_list ENCODE_FUNC[tuple] = encode_list ENCODE_FUNC[dict] = encode_dict ENCODE_FUNC[bool] = encode_bool def bencode(string): """bencode $string""" table = [] ENCODE_FUNC[type(string)](string, table) return ''.join(table) def bdecode(string): """decode $string""" try: result, lenght = DECODE_FUNC[string[0]](string, 0) except (IndexError, KeyError, ValueError): raise BTFailure('Not a valid bencoded string') if lenght != len(string): raise BTFailure('Invalid bencoded value (data after valid prefix)') return result