def get_scrape(self, paramslist): fs = {} if 'info_hash' in paramslist: if self.config['scrape_allowed'] not in ['specific', 'full']: return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': 'specific scrape function ' 'is not available with this tracker.'})) for hash in paramslist['info_hash']: if self.allowed is not None: if hash in self.allowed: fs[hash] = self.scrapedata(hash) elif hash in self.downloads: fs[hash] = self.scrapedata(hash) else: if self.config['scrape_allowed'] != 'full': return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': 'full scrape function is ' 'not available with this tracker.'})) if self.allowed is not None: keys = self.allowed.iterkeys() else: keys = self.downloads.iterkeys() for hash in keys: fs[hash] = self.scrapedata(hash) return (200, 'OK', {'Content-Type': 'text/plain'}, bencode({'files': fs}))
def natcheckOK(self, infohash, peerid, ip, port, peer): seed = not peer['left'] bc = self.becache.setdefault(infohash, self.cache_default) cp = compact_peer_info(ip, port) reqc = peer['requirecrypto'] bc[2][seed][peerid] = (cp, chr(reqc)) if peer['supportcrypto']: bc[1][seed][peerid] = cp if not reqc: bc[0][seed][peerid] = cp if not self.config['compact_reqd']: bc[3][seed][peerid] = Bencached( bencode({'ip': ip, 'port': port, 'peer id': peerid})) bc[4][seed][peerid] = Bencached( bencode({'ip': ip, 'port': port}))
def writeTorrentData(self, torrent, data): """Add a torrent data file to cache""" self.torrentDataBuffer[torrent] = data fname = os.path.join(self.dir_datacache, hexlify(torrent).decode()) try: with open(fname, 'wb') as f: f.write(bencode(data)) return True except (IOError, TypeError, KeyError): self.deleteTorrentData(torrent) return False
def writeTorrentData(self, torrent, data): """Add a torrent data file to cache""" self.torrentDataBuffer[torrent] = data fname = os.path.join(self.dir_datacache, hexlify(torrent)) try: with open(fname, 'wb') as f: f.write(bencode(data)) return True except (IOError, TypeError, KeyError): self.deleteTorrentData(torrent) return False
def check_allowed(self, infohash, paramslist): if self.aggregator_key is not None and not ( 'password' in paramslist and paramslist['password'][0] == self.aggregator_key): return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': 'Requested download is not ' 'authorized for use with this tracker.'})) if self.allowed is not None: if infohash not in self.allowed: return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': 'Requested download is not ' 'authorized for use with this tracker.'})) if self.config['allowed_controls']: if 'failure reason' in self.allowed[infohash]: return ( 200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': self.allowed[infohash]['failure reason']})) if 'tracker' in paramslist: # turned off or contacted self if self.config['multitracker_allowed'] == 'none' or \ paramslist['peer_id'][0] == self.trackerid: return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': 'disallowed'})) if self.config['multitracker_allowed'] == 'autodetect' and \ 'announce-list' not in self.allowed[infohash]: return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': 'Requested download is not ' 'authorized for multitracker use.'})) return None
def parse_torrent(path, return_metainfo=False): """Load and derive metadata from torrent file Parameters str - path of file to parse bool - parsed metadata to include full torrent data Returns {str: *} - torrent file metadata str - sha hash of encoded info dict """ fname = os.path.basename(path) data = MetaInfo.read(path) # Validate and hash info dict info = data['info'] check_info(info) infohash = hashlib.sha1(bencode(info)).digest() single = 'length' in info torrentinfo = { 'path': path, 'file': fname, 'name': info.get('name', fname), 'numfiles': 1 if single else len(info['files']), 'length': info['length'] if single else sum(li['length'] for li in info['files'] if 'length' in li) } for key in ('failure reason', 'warning message', 'announce-list'): if key in data: torrentinfo[key] = data[key] if return_metainfo: torrentinfo['metainfo'] = data return torrentinfo, infohash
def writeTorrent(self, data, torrent, version=-1): """Write data to a torrent file If no version is provided, create a new version""" torrent = hexlify(torrent) fname = os.path.join(self.dir_torrentcache, torrent) if version == -1: try: version = max(self.getTorrentVariations(torrent)) + 1 except ValueError: version = 0 if version: fname += '.' + str(version) try: with open(fname, 'wb') as f: f.write(bencode(data)) except (IOError, TypeError, KeyError): return None return version
def parse_torrent(path, return_metainfo=False): """Load and derive metadata from torrent file Parameters str - path of file to parse bool - parsed metadata to include full torrent data Returns {str: *} - torrent file metadata str - sha hash of encoded info dict """ fname = os.path.basename(path) data = MetaInfo.read(path) # Validate and hash info dict info = data['info'] check_info(info) infohash = hashlib.sha1(bencode(info)).digest() single = 'length' in info torrentinfo = { 'path': path, 'file': fname, 'name': info.get('name', fname), 'numfiles': 1 if single else len(info['files']), 'length': info['length'] if single else sum( li['length'] for li in info['files'] if 'length' in li) } for key in ('failure reason', 'warning message', 'announce-list'): if key in data: torrentinfo[key] = data[key] if return_metainfo: torrentinfo['metainfo'] = data return torrentinfo, infohash
def test_bencode(self): """Test encoding of encodable and unencodable data structures""" self.assertEqual(bencode(4), b'i4e') self.assertEqual(bencode(0), b'i0e') self.assertEqual(bencode(-10), b'i-10e') self.assertEqual(bencode(12345678901234567890), b'i12345678901234567890e') self.assertEqual(bencode(''), b'0:') self.assertEqual(bencode('abc'), b'3:abc') self.assertEqual(bencode('1234567890'), b'10:1234567890') self.assertEqual(bencode([]), b'le') self.assertEqual(bencode([1, 2, 3]), b'li1ei2ei3ee') self.assertEqual(bencode([['Alice', 'Bob'], [2, 3]]), b'll5:Alice3:Bobeli2ei3eee') self.assertEqual(bencode({}), b'de') self.assertEqual(bencode({'age': 25, 'eyes': 'blue'}), b'd3:agei25e4:eyes4:bluee') self.assertEqual(bencode({'spam.mp3': {'author': 'Alice', 'length': 100000}}), b'd8:spam.mp3d6:author5:Alice6:lengthi100000eee') self.assertRaises(TypeError, bencode, {1: 'foo'}) self.assertRaises(TypeError, bencode, {'foo': 1.0}) cached = Bencached.cache({'age': 25}) self.assertEqual(bencode(cached), cached.bencoded) self.assertEqual(bencode(''), bencode(b''))
def test_bencode(self): """Test encoding of encodable and unencodable data structures""" self.assertEqual(bencode(4), b'i4e') self.assertEqual(bencode(0), b'i0e') self.assertEqual(bencode(-10), b'i-10e') self.assertEqual(bencode(12345678901234567890), b'i12345678901234567890e') self.assertEqual(bencode(''), b'0:') self.assertEqual(bencode('abc'), b'3:abc') self.assertEqual(bencode('1234567890'), b'10:1234567890') self.assertEqual(bencode([]), b'le') self.assertEqual(bencode([1, 2, 3]), b'li1ei2ei3ee') self.assertEqual(bencode([['Alice', 'Bob'], [2, 3]]), b'll5:Alice3:Bobeli2ei3eee') self.assertEqual(bencode({}), b'de') self.assertEqual(bencode({ 'age': 25, 'eyes': 'blue' }), b'd3:agei25e4:eyes4:bluee') self.assertEqual( bencode({'spam.mp3': { 'author': 'Alice', 'length': 100000 }}), b'd8:spam.mp3d6:author5:Alice6:lengthi100000eee') self.assertRaises(TypeError, bencode, {1: 'foo'}) self.assertRaises(TypeError, bencode, {'foo': 1.0}) cached = Bencached.cache({'age': 25}) self.assertEqual(bencode(cached), cached.bencoded) self.assertEqual(bencode(''), bencode(b''))
def run(scrwin, errlist, params): doneflag = threading.Event() d = CursesDisplayer(scrwin, errlist, doneflag) try: while 1: configdir = ConfigDir('downloadcurses') defaultsToIgnore = ['responsefile', 'url', 'priority'] configdir.setDefaults(defaults, defaultsToIgnore) configdefaults = configdir.loadConfig() defaults.append( ('save_options', 0, 'whether to save the current options as ' 'the new default configuration (only for btdownloadcurses.py)' )) try: config = parse_params(params, configdefaults) except ValueError as e: d.error('error: {}\nrun with no args for parameter ' 'explanations'.format(e)) break if not config: d.error(get_usage(defaults, d.fieldw, configdefaults)) break if config['save_options']: configdir.saveConfig(config) configdir.deleteOldCacheData(config['expire_cache_data']) myid = createPeerID() random.seed(myid) rawserver = RawServer( doneflag, config['timeout_check_interval'], config['timeout'], ipv6_enable=config['ipv6_enabled'], failfunc=d.failed, errorfunc=d.error) upnp_type = UPnP_test(config['upnp_nat_access']) while True: try: listen_port = rawserver.find_and_bind( config['minport'], config['maxport'], config['bind'], ipv6_socket_style=config['ipv6_binds_v4'], upnp=upnp_type, randomizer=config['random_port']) break except socket.error as e: if upnp_type and e == UPnP_ERROR: d.error('WARNING: COULD NOT FORWARD VIA UPnP') upnp_type = 0 continue d.error("Couldn't listen - " + str(e)) d.failed() return response = get_response(config['responsefile'], config['url'], d.error) if not response: break infohash = hashlib.sha1(bencode(response['info'])).digest() dow = BT1Download( d.display, d.finished, d.error, d.error, doneflag, config, response, infohash, myid, rawserver, listen_port, configdir) if not dow.saveAs(d.chooseFile): break if not dow.initFiles(old_style=True): break if not dow.startEngine(): dow.shutdown() break dow.startRerequester() dow.autoStats() if not dow.am_I_finished(): d.display(activity='connecting to peers') rawserver.listen_forever(dow.getPortHandler()) d.display(activity='shutting down') dow.shutdown() break except KeyboardInterrupt: # ^C to exit... pass try: rawserver.shutdown() except Exception: pass if not d.done: d.failed()
def get(self, connection, path, headers): real_ip = connection.get_ip() ip = real_ip if is_ipv4(ip): ipv4 = True else: try: ip = ipv6_to_ipv4(ip) ipv4 = True except ValueError: ipv4 = False if self.allowed_IPs and ip not in self.allowed_IPs or \ self.banned_IPs and ip in self.banned_IPs: return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': 'your IP is not allowed on this tracker'})) nip = get_forwarded_ip(headers) if nip and not self.only_local_override_ip: ip = nip try: ip = to_ipv4(ip) ipv4 = True except ValueError: ipv4 = False paramslist = {} def params(key, default=None, l=paramslist): if key in l: return l[key][0] return default try: (_, _, path, _, query, _) = urlparse(path) if self.uq_broken == 1: path = path.replace('+', ' ') query = query.replace('+', ' ') path = urllib.unquote(path)[1:] for s in query.split('&'): if s: i = s.index('=') kw = urllib.unquote(s[:i]) paramslist.setdefault(kw, []) paramslist[kw] += [urllib.unquote(s[i + 1:])] if path == '' or path == 'index.html': return self.get_infopage() if path == 'file': return self.get_file(params('info_hash')) if path == 'favicon.ico' and self.favicon is not None: return (200, 'OK', {'Content-Type': 'image/x-icon'}, self.favicon) # automated access from here on if path in ('scrape', 'scrape.php', 'tracker.php/scrape'): return self.get_scrape(paramslist) if path not in ('announce', 'announce.php', 'tracker.php/announce'): return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas) # main tracker function #filtered = self.Filter.check(real_ip, paramslist, headers) #if filtered: # return (400, 'Not Authorized', {'Content-Type': 'text/plain', # 'Pragma': 'no-cache'}, # bencode({'failure reason': filtered})) infohash = params('info_hash') if not infohash: raise ValueError('no info hash') notallowed = self.check_allowed(infohash, paramslist) if notallowed: return notallowed event = params('event') rsize = self.add_data(infohash, event, ip, paramslist) except ValueError as e: return (400, 'Bad Request', {'Content-Type': 'text/plain'}, 'you sent me garbage - ' + str(e)) if self.aggregate_forward and 'tracker' not in paramslist: self.aggregate_senddata(query) if self.is_aggregator: # don't return peer data here return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'response': 'OK'})) if params('compact') and ipv4: if params('requirecrypto'): return_type = 1 elif params('supportcrypto'): return_type = 2 else: return_type = 0 elif self.config['compact_reqd'] and ipv4: return (400, 'Bad Request', {'Content-Type': 'text/plain'}, 'your client is outdated, please upgrade') elif params('no_peer_id'): return_type = 4 else: return_type = 3 data = self.peerlist(infohash, event == 'stopped', params('tracker'), not params('left'), return_type, rsize, params('supportcrypto')) if 'scrape' in paramslist: # deprecated data['scrape'] = self.scrapedata(infohash, False) if self.dedicated_seed_id: if params('seed_id') == self.dedicated_seed_id and \ params('left') == 0: self.is_seeded[infohash] = True if params('check_seeded') and self.is_seeded.get(infohash): data['seeded'] = 1 return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode(data))
def save_state(self): self.rawserver.add_task(self.save_state, self.save_dfile_interval) with open(self.dfile, 'wb') as h: h.write(bencode(self.state))
def run(params): h = HeadlessDisplayer() while 1: configdir = ConfigDir('downloadheadless') defaultsToIgnore = ['metafile', 'url', 'priority'] configdir.setDefaults(defaults, defaultsToIgnore) configdefaults = configdir.loadConfig() defaults.append( ('save_options', 0, 'whether to save the current options as the ' 'new default configuration (only for btdownloadheadless.py)')) try: config = parse_params(params, configdefaults) except ValueError as e: print('error: {}\n'.format(e), 'run with no args for parameter explanations') break if not config: print(get_usage(defaults, 80, configdefaults)) break if config['save_options']: configdir.saveConfig(config) configdir.deleteOldCacheData(config['expire_cache_data']) myid = createPeerID() random.seed(myid) doneflag = threading.Event() def disp_exception(text): print(text) rawserver = RawServer( doneflag, config['timeout_check_interval'], config['timeout'], ipv6_enable=config['ipv6_enabled'], failfunc=h.failed, errorfunc=disp_exception) upnp_type = UPnP_test(config['upnp_nat_access']) while True: try: listen_port = rawserver.find_and_bind( config['minport'], config['maxport'], config['bind'], ipv6_socket_style=config['ipv6_binds_v4'], upnp=upnp_type, randomizer=config['random_port']) break except socket.error as e: if upnp_type and e == UPnP_ERROR: print('WARNING: COULD NOT FORWARD VIA UPnP') upnp_type = 0 continue print("error: Couldn't listen - ", e) h.failed() return metainfo = get_metainfo(config['metafile'], config['url'], h.error) if not metainfo: break infohash = hashlib.sha1(bencode(metainfo['info'])).digest() dow = BT1Download( h.display, h.finished, h.error, disp_exception, doneflag, config, metainfo, infohash, myid, rawserver, listen_port, configdir) if not dow.saveAs(h.chooseFile, h.newpath): break if not dow.initFiles(old_style=True): break if not dow.startEngine(): dow.shutdown() break dow.startRerequester() dow.autoStats() if not dow.am_I_finished(): h.display(activity='connecting to peers') rawserver.listen_forever(dow.getPortHandler()) h.display(activity='shutting down') dow.shutdown() break try: rawserver.shutdown() except Exception: pass if not h.done: h.failed()
def run(scrwin, errlist, params): doneflag = threading.Event() d = CursesDisplayer(scrwin, errlist, doneflag) try: while 1: configdir = ConfigDir("downloadcurses") defaultsToIgnore = ["metafile", "url", "priority"] configdir.setDefaults(defaults, defaultsToIgnore) configdefaults = configdir.loadConfig() defaults.append( ( "save_options", 0, "whether to save the current options as " "the new default configuration (only for btdownloadcurses.py)", ) ) try: config = parse_params(params, configdefaults) except ValueError as e: d.error("error: {}\nrun with no args for parameter " "explanations".format(e)) break if not config: d.error(get_usage(defaults, d.fieldw, configdefaults)) break if config["save_options"]: configdir.saveConfig(config) configdir.deleteOldCacheData(config["expire_cache_data"]) myid = createPeerID() random.seed(myid) rawserver = RawServer( doneflag, config["timeout_check_interval"], config["timeout"], ipv6_enable=config["ipv6_enabled"], failfunc=d.failed, errorfunc=d.error, ) upnp_type = UPnP_test(config["upnp_nat_access"]) while True: try: listen_port = rawserver.find_and_bind( config["minport"], config["maxport"], config["bind"], ipv6_socket_style=config["ipv6_binds_v4"], upnp=upnp_type, randomizer=config["random_port"], ) break except socket.error as e: if upnp_type and e == UPnP_ERROR: d.error("WARNING: COULD NOT FORWARD VIA UPnP") upnp_type = 0 continue d.error("Couldn't listen - " + str(e)) d.failed() return metainfo = get_metainfo(config["metafile"], config["url"], d.error) if not metainfo: break infohash = hashlib.sha1(bencode(metainfo["info"])).digest() dow = BT1Download( d.display, d.finished, d.error, d.error, doneflag, config, metainfo, infohash, myid, rawserver, listen_port, configdir, ) if not dow.saveAs(d.chooseFile): break if not dow.initFiles(old_style=True): break if not dow.startEngine(): dow.shutdown() break dow.startRerequester() dow.autoStats() if not dow.am_I_finished(): d.display(activity="connecting to peers") rawserver.listen_forever(dow.getPortHandler()) d.display(activity="shutting down") dow.shutdown() break except KeyboardInterrupt: # ^C to exit... pass try: rawserver.shutdown() except Exception: pass if not d.done: d.failed()
def run(params): h = HeadlessDisplayer() while 1: configdir = ConfigDir('downloadheadless') defaultsToIgnore = ['metafile', 'url', 'priority'] configdir.setDefaults(defaults, defaultsToIgnore) configdefaults = configdir.loadConfig() defaults.append( ('save_options', 0, 'whether to save the current options as the ' 'new default configuration (only for btdownloadheadless.py)')) try: config = parse_params(params, configdefaults) except ValueError as e: print('error: {}\n'.format(e), 'run with no args for parameter explanations') break if not config: print(get_usage(defaults, 80, configdefaults)) break if config['save_options']: configdir.saveConfig(config) configdir.deleteOldCacheData(config['expire_cache_data']) myid = createPeerID() random.seed(myid) doneflag = threading.Event() def disp_exception(text): print(text) rawserver = RawServer(doneflag, config['timeout_check_interval'], config['timeout'], ipv6_enable=config['ipv6_enabled'], failfunc=h.failed, errorfunc=disp_exception) upnp_type = UPnP_test(config['upnp_nat_access']) while True: try: listen_port = rawserver.find_and_bind( config['minport'], config['maxport'], config['bind'], ipv6_socket_style=config['ipv6_binds_v4'], upnp=upnp_type, randomizer=config['random_port']) break except socket.error as e: if upnp_type and e == UPnP_ERROR: print('WARNING: COULD NOT FORWARD VIA UPnP') upnp_type = 0 continue print("error: Couldn't listen - ", e) h.failed() return metainfo = get_metainfo(config['metafile'], config['url'], h.error) if not metainfo: break infohash = hashlib.sha1(bencode(metainfo['info'])).digest() dow = BT1Download(h.display, h.finished, h.error, disp_exception, doneflag, config, metainfo, infohash, myid, rawserver, listen_port, configdir) if not dow.saveAs(h.chooseFile, h.newpath): break if not dow.initFiles(old_style=True): break if not dow.startEngine(): dow.shutdown() break dow.startRerequester() dow.autoStats() if not dow.am_I_finished(): h.display(activity='connecting to peers') rawserver.listen_forever(dow.getPortHandler()) h.display(activity='shutting down') dow.shutdown() break try: rawserver.shutdown() except Exception: pass if not h.done: h.failed()
NAME, EXT = os.path.splitext(os.path.basename(sys.argv[0])) VERSION = '20130326' print('{} {} - decode BitTorrent metainfo files'.format(NAME, VERSION)) print() if len(sys.argv) == 1: print('{} file1.torrent file2.torrent file3.torrent ...'.format( sys.argv[0])) print() sys.exit(2) # common exit code for syntax error for metainfo_name in sys.argv[1:]: metainfo = MetaInfo.read(metainfo_name) info = metainfo['info'] info_hash = hashlib.sha1(bencode(info)) print('metainfo file.:', os.path.basename(metainfo_name)) print('info hash.....:', info_hash.hexdigest()) piece_length = info['piece length'] if 'length' in info: # let's assume we just have a file print('file name.....:', info['name']) file_length = info['length'] name = 'file size.....:' else: # let's assume we have a directory structure print('directory name:', info['name']) print('files.........:') file_length = 0 for file in info['files']: