From 651535581f69c29a5b8aa153754e1a133ffe4098 Mon Sep 17 00:00:00 2001 From: evilhero Date: Tue, 21 Feb 2017 12:23:32 -0500 Subject: [PATCH] FIX: Fix for utorrent not setting labels after adding torrent, IMP: Moved bencode to lib folder and set rtorrent/utorrent to use instead of different versions --- lib/bencode.py | 281 ++++++++++++++++++++++++++++++++++++++++++++++ mylar/bencode.py | 131 --------------------- mylar/test.py | 5 +- mylar/utorrent.py | 16 +-- 4 files changed, 291 insertions(+), 142 deletions(-) create mode 100644 lib/bencode.py delete mode 100644 mylar/bencode.py diff --git a/lib/bencode.py b/lib/bencode.py new file mode 100644 index 00000000..97bd2f0e --- /dev/null +++ b/lib/bencode.py @@ -0,0 +1,281 @@ +# Copyright (C) 2011 by clueless +# +# 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. +# +# Version: 20111107 +# +# Changelog +# --------- +# 2011-11-07 - Added support for Python2 (tested on 2.6) +# 2011-10-03 - Fixed: moved check for end of list at the top of the while loop +# in _decode_list (in case the list is empty) (Chris Lucas) +# - Converted dictionary keys to str +# 2011-04-24 - Changed date format to YYYY-MM-DD for versioning, bigger +# integer denotes a newer version +# - Fixed a bug that would treat False as an integral type but +# encode it using the 'False' string, attempting to encode a +# boolean now results in an error +# - Fixed a bug where an integer value of 0 in a list or +# dictionary resulted in a parse error while decoding +# +# 2011-04-03 - Original release + +import sys + +_py3 = sys.version_info[0] == 3 + +if _py3: + _VALID_STRING_TYPES = (str,) +else: + _VALID_STRING_TYPES = (str, unicode) # @UndefinedVariable + +_TYPE_INT = 1 +_TYPE_STRING = 2 +_TYPE_LIST = 3 +_TYPE_DICTIONARY = 4 +_TYPE_END = 5 +_TYPE_INVALID = 6 + +# Function to determine the type of he next value/item +# Arguments: +# char First character of the string that is to be decoded +# Return value: +# Returns an integer that describes what type the next value/item is + + +def _gettype(char): + if not isinstance(char, int): + char = ord(char) + if char == 0x6C: # 'l' + return _TYPE_LIST + elif char == 0x64: # 'd' + return _TYPE_DICTIONARY + elif char == 0x69: # 'i' + return _TYPE_INT + elif char == 0x65: # 'e' + return _TYPE_END + elif char >= 0x30 and char <= 0x39: # '0' '9' + return _TYPE_STRING + else: + return _TYPE_INVALID + +# Function to parse a string from the bendcoded data +# Arguments: +# data bencoded data, must be guaranteed to be a string +# Return Value: +# Returns a tuple, the first member of the tuple is the parsed string +# The second member is whatever remains of the bencoded data so it can +# be used to parse the next part of the data + + +def _decode_string(data): + end = 1 + # if py3, data[end] is going to be an int + # if py2, data[end] will be a string + if _py3: + char = 0x3A + else: + char = chr(0x3A) + + while data[end] != char: # ':' + end = end + 1 + strlen = int(data[:end]) + return (data[end + 1:strlen + end + 1], data[strlen + end + 1:]) + +# Function to parse an integer from the bencoded data +# Arguments: +# data bencoded data, must be guaranteed to be an integer +# Return Value: +# Returns a tuple, the first member of the tuple is the parsed string +# The second member is whatever remains of the bencoded data so it can +# be used to parse the next part of the data + + +def _decode_int(data): + end = 1 + # if py3, data[end] is going to be an int + # if py2, data[end] will be a string + if _py3: + char = 0x65 + else: + char = chr(0x65) + + while data[end] != char: # 'e' + end = end + 1 + return (int(data[1:end]), data[end + 1:]) + +# Function to parse a bencoded list +# Arguments: +# data bencoded data, must be guaranted to be the start of a list +# Return Value: +# Returns a tuple, the first member of the tuple is the parsed list +# The second member is whatever remains of the bencoded data so it can +# be used to parse the next part of the data + + +def _decode_list(data): + x = [] + overflow = data[1:] + while True: # Loop over the data + if _gettype(overflow[0]) == _TYPE_END: # - Break if we reach the end of the list + return (x, overflow[1:]) # and return the list and overflow + + value, overflow = _decode(overflow) # + if isinstance(value, bool) or overflow == '': # - if we have a parse error + return (False, False) # Die with error + else: # - Otherwise + x.append(value) # add the value to the list + + +# Function to parse a bencoded list +# Arguments: +# data bencoded data, must be guaranted to be the start of a list +# Return Value: +# Returns a tuple, the first member of the tuple is the parsed dictionary +# The second member is whatever remains of the bencoded data so it can +# be used to parse the next part of the data +def _decode_dict(data): + x = {} + overflow = data[1:] + while True: # Loop over the data + if _gettype(overflow[0]) != _TYPE_STRING: # - If the key is not a string + return (False, False) # Die with error + key, overflow = _decode(overflow) # + if key == False or overflow == '': # - If parse error + return (False, False) # Die with error + value, overflow = _decode(overflow) # + if isinstance(value, bool) or overflow == '': # - If parse error + print("Error parsing value") + print(value) + print(overflow) + return (False, False) # Die with error + else: + # don't use bytes for the key + key = key.decode() + x[key] = value + if _gettype(overflow[0]) == _TYPE_END: + return (x, overflow[1:]) + +# Arguments: +# data bencoded data in bytes format +# Return Values: +# Returns a tuple, the first member is the parsed data, could be a string, +# an integer, a list or a dictionary, or a combination of those +# The second member is the leftover of parsing, if everything parses correctly this +# should be an empty byte string + + +def _decode(data): + btype = _gettype(data[0]) + if btype == _TYPE_INT: + return _decode_int(data) + elif btype == _TYPE_STRING: + return _decode_string(data) + elif btype == _TYPE_LIST: + return _decode_list(data) + elif btype == _TYPE_DICTIONARY: + return _decode_dict(data) + else: + return (False, False) + +# Function to decode bencoded data +# Arguments: +# data bencoded data, can be str or bytes +# Return Values: +# Returns the decoded data on success, this coud be bytes, int, dict or list +# or a combinatin of those +# If an error occurs the return value is False + + +def decode(data): + # if isinstance(data, str): + # data = data.encode() + decoded, overflow = _decode(data) + return decoded + +# Args: data as integer +# return: encoded byte string + + +def _encode_int(data): + return b'i' + str(data).encode() + b'e' + +# Args: data as string or bytes +# Return: encoded byte string + + +def _encode_string(data): + return str(len(data)).encode() + b':' + data + +# Args: data as list +# Return: Encoded byte string, false on error + + +def _encode_list(data): + elist = b'l' + for item in data: + eitem = encode(item) + if eitem == False: + return False + elist += eitem + return elist + b'e' + +# Args: data as dict +# Return: encoded byte string, false on error + + +def _encode_dict(data): + edict = b'd' + keys = [] + for key in data: + if not isinstance(key, _VALID_STRING_TYPES) and not isinstance(key, bytes): + return False + keys.append(key) + keys.sort() + for key in keys: + ekey = encode(key) + eitem = encode(data[key]) + if ekey == False or eitem == False: + return False + edict += ekey + eitem + return edict + b'e' + +# Function to encode a variable in bencoding +# Arguments: +# data Variable to be encoded, can be a list, dict, str, bytes, int or a combination of those +# Return Values: +# Returns the encoded data as a byte string when successful +# If an error occurs the return value is False + + +def encode(data): + if isinstance(data, bool): + return False + elif isinstance(data, int): + return _encode_int(data) + elif isinstance(data, bytes): + return _encode_string(data) + elif isinstance(data, _VALID_STRING_TYPES): + return _encode_string(data.encode()) + elif isinstance(data, list): + return _encode_list(data) + elif isinstance(data, dict): + return _encode_dict(data) + else: + return False diff --git a/mylar/bencode.py b/mylar/bencode.py deleted file mode 100644 index f91ae02a..00000000 --- a/mylar/bencode.py +++ /dev/null @@ -1,131 +0,0 @@ -# Got this from here: https://gist.github.com/1126793 - -# The contents of this file are subject to the Python Software Foundation -# License Version 2.3 (the License). You may not copy or use this file, in -# either source code or executable form, except in compliance with the License. -# You may obtain a copy of the License at http://www.python.org/license. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Petru Paler - -# Minor modifications made by Andrew Resch to replace the BTFailure errors with Exceptions - -def decode_int(x, f): - f += 1 - newf = x.index('e', f) - n = int(x[f:newf]) - if x[f] == '-': - if x[f + 1] == '0': - raise ValueError - elif x[f] == '0' and newf != f+1: - raise ValueError - return (n, newf+1) - -def decode_string(x, f): - colon = x.index(':', f) - n = int(x[f:colon]) - if x[f] == '0' and colon != f+1: - raise ValueError - colon += 1 - return (x[colon:colon+n], colon+n) - -def decode_list(x, f): - r, f = [], f+1 - while x[f] != 'e': - v, f = decode_func[x[f]](x, f) - r.append(v) - return (r, f + 1) - -def decode_dict(x, f): - r, f = {}, f+1 - while x[f] != 'e': - k, f = decode_string(x, f) - r[k], f = decode_func[x[f]](x, f) - return (r, f + 1) - -decode_func = {} -decode_func['l'] = decode_list -decode_func['d'] = decode_dict -decode_func['i'] = decode_int -decode_func['0'] = decode_string -decode_func['1'] = decode_string -decode_func['2'] = decode_string -decode_func['3'] = decode_string -decode_func['4'] = decode_string -decode_func['5'] = decode_string -decode_func['6'] = decode_string -decode_func['7'] = decode_string -decode_func['8'] = decode_string -decode_func['9'] = decode_string - -def bdecode(x): - try: - r, l = decode_func[x[0]](x, 0) - except (IndexError, KeyError, ValueError): - raise Exception("not a valid bencoded string") - - return r - -from types import StringType, IntType, LongType, DictType, ListType, TupleType - - -class Bencached(object): - - __slots__ = ['bencoded'] - - def __init__(self, s): - self.bencoded = s - -def encode_bencached(x,r): - r.append(x.bencoded) - -def encode_int(x, r): - r.extend(('i', str(x), 'e')) - -def encode_bool(x, r): - if x: - encode_int(1, r) - else: - encode_int(0, r) - -def encode_string(x, r): - r.extend((str(len(x)), ':', x)) - -def encode_list(x, r): - r.append('l') - for i in x: - encode_func[type(i)](i, r) - r.append('e') - -def encode_dict(x,r): - r.append('d') - ilist = x.items() - ilist.sort() - for k, v in ilist: - r.extend((str(len(k)), ':', k)) - encode_func[type(v)](v, r) - r.append('e') - -encode_func = {} -encode_func[Bencached] = encode_bencached -encode_func[IntType] = encode_int -encode_func[LongType] = encode_int -encode_func[StringType] = encode_string -encode_func[ListType] = encode_list -encode_func[TupleType] = encode_list -encode_func[DictType] = encode_dict - -try: - from types import BooleanType - encode_func[BooleanType] = encode_bool -except ImportError: - pass - -def bencode(x): - r = [] - encode_func[type(x)](x, r) - return ''.join(r) \ No newline at end of file diff --git a/mylar/test.py b/mylar/test.py index e45ae9fc..76d21ded 100644 --- a/mylar/test.py +++ b/mylar/test.py @@ -21,6 +21,8 @@ import shutil import traceback from base64 import b16encode, b32decode +import hashlib, StringIO +import bencode from torrent.helpers.variable import link, symlink, is_rarfile import requests @@ -89,9 +91,6 @@ class RTorrent(object): return torrent_info def get_the_hash(self, filepath): - import hashlib, StringIO - import rtorrent.lib.bencode as bencode - # Open torrent file torrent_file = open(filepath, "rb") metainfo = bencode.decode(torrent_file.read()) diff --git a/mylar/utorrent.py b/mylar/utorrent.py index efe81d1c..a642c799 100644 --- a/mylar/utorrent.py +++ b/mylar/utorrent.py @@ -16,9 +16,9 @@ import re import os import requests -from bencode import bencode, bdecode -from hashlib import sha1 -from cStringIO import StringIO +import bencode +import hashlib +import StringIO import mylar from mylar import logger @@ -101,16 +101,16 @@ class utorrentclient(object): logger.info('Unable to label torrent') return - def calculate_torrent_hash(link=None, filepath=None, data=None): + def calculate_torrent_hash(self, link=None, filepath=None, data=None): thehash = None - if not link: + if link is None: if filepath: torrent_file = open(filepath, "rb") - metainfo = bdecode(torrent_file.read()) + metainfo = bencode.decode(torrent_file.read()) else: - metainfo = bdecode(data) + metainfo = bencode.decode(data) info = metainfo['info'] - thehash = hashlib.sha1(bencode(info)).hexdigest().upper() + thehash = hashlib.sha1(bencode.encode(info)).hexdigest().upper() logger.info('Hash: ' + thehash) else: if link.startswith("magnet:"):