diff options
author | mkaay <mkaay@mkaay.de> | 2010-12-19 14:12:03 +0100 |
---|---|---|
committer | mkaay <mkaay@mkaay.de> | 2010-12-19 14:12:03 +0100 |
commit | 5120d1a26cac79ab283f38db8d7f18c9c010e786 (patch) | |
tree | 219f8874869ac6f9c1656fea9df8db17b55ce034 | |
parent | acc manager fix (diff) | |
download | pyload-5120d1a26cac79ab283f38db8d7f18c9c010e786.tar.xz |
new experimental backend, new gui status (unfinished)
-rw-r--r-- | module/gui/MainWindow.py | 46 | ||||
-rw-r--r-- | module/gui/Queue.py | 16 | ||||
-rw-r--r-- | module/network/Browser.py | 80 | ||||
-rw-r--r-- | module/network/Bucket.py | 53 | ||||
-rw-r--r-- | module/network/FTPBase.py | 200 | ||||
-rw-r--r-- | module/network/HTTPBase.py | 382 | ||||
-rw-r--r-- | module/network/HTTPChunk.py | 201 | ||||
-rw-r--r-- | module/network/HTTPDownload.py | 305 | ||||
-rw-r--r-- | module/network/MultipartPostHandler.py | 2 | ||||
-rw-r--r-- | module/network/helper.py | 111 | ||||
-rw-r--r-- | module/network/socks.py | 442 | ||||
-rwxr-xr-x | pyLoadGui.py | 37 |
12 files changed, 1847 insertions, 28 deletions
diff --git a/module/gui/MainWindow.py b/module/gui/MainWindow.py index c0fe6e804..a02ea7989 100644 --- a/module/gui/MainWindow.py +++ b/module/gui/MainWindow.py @@ -62,16 +62,40 @@ class MainWindow(QMainWindow): self.setCentralWidget(lw) #status - #@TODO: build a fancy box - - #self.statusw = QFrame() - #self.statusw.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) - #self.statusw.setLineWidth(2) - #self.statusw.setLayout(QGridLayout()) - #l = self.statusw.layout() - #l.addWidget(QLabel("Status:"), 0, 0) - #l.addWidget(QLabel("Speed:"), 0, 3) - #l.addWidget(QLabel("Space:"), 0, 5) + self.statusw = QFrame() + self.statusw.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) + self.statusw.setLineWidth(2) + self.statusw.setLayout(QGridLayout()) + l = self.statusw.layout() + l.addWidget(QLabel(_("packages:")), 0, 0) + l.addWidget(QLabel(_("files:")), 1, 0) + self.packageCount = QLabel("0") + self.fileCount = QLabel("0") + l.addWidget(self.packageCount, 0, 1) + l.addWidget(self.fileCount, 1, 1) + + l.addWidget(QLabel(_("status:")), 0, 3) + l.addWidget(QLabel(_("ip:")), 1, 3) + self.status = QLabel("running") + self.ip = QLabel("") + l.addWidget(self.status, 0, 4) + l.addWidget(self.ip, 1, 4) + + l.addWidget(QLabel(_("speed:")), 0, 5) + l.addWidget(QLabel(_("space:")), 1, 5) + self.speed = QLabel("") + self.space = QLabel("") + l.addWidget(self.speed, 0, 6) + l.addWidget(self.space, 1, 6) + + l.addWidget(QLabel(_("max. downloads:")), 0, 7) + l.addWidget(QLabel(_("max. chunks:")), 1, 7) + self.maxDownloads = QSpinBox() + self.maxDownloads.setEnabled(False) + self.maxChunks = QSpinBox() + self.maxChunks.setEnabled(False) + l.addWidget(self.maxDownloads, 0, 8) + l.addWidget(self.maxChunks, 1, 8) #set menubar and statusbar self.menubar = self.menuBar() @@ -126,7 +150,7 @@ class MainWindow(QMainWindow): #layout self.masterlayout.addWidget(self.tabw) - #self.masterlayout.addWidget(self.statusw) + self.masterlayout.addWidget(self.statusw) #signals.. self.connect(self.mactions["manager"], SIGNAL("triggered()"), self.slotShowConnector) diff --git a/module/gui/Queue.py b/module/gui/Queue.py index d60858e34..7c5c59f15 100644 --- a/module/gui/Queue.py +++ b/module/gui/Queue.py @@ -61,6 +61,22 @@ class QueueModel(CollectorModel): self._data.append(package) self._data = sorted(self._data, key=lambda p: p.data["order"]) self.endInsertRows() + self.updateCount() + + def insertEvent(self, event): + CollectorModel.insertEvent(self, event) + self.updateCount() + + def removeEvent(self, event): + CollectorModel.removeEvent(self, event) + self.updateCount() + + def updateCount(self): + packageCount = len(self._data) + fileCount = 0 + for p in self._data: + fileCount += len(p.children) + self.emit(SIGNAL("updateCount"), packageCount, fileCount) def update(self): locker = QMutexLocker(self.mutex) diff --git a/module/network/Browser.py b/module/network/Browser.py new file mode 100644 index 000000000..65aefbae8 --- /dev/null +++ b/module/network/Browser.py @@ -0,0 +1,80 @@ +from HTTPBase import HTTPBase +from HTTPDownload import HTTPDownload + +from os.path import exists + +import zlib +from cookielib import CookieJar +from FTPBase import FTPDownload + +class Browser(): + def __init__(self, interface=None, cookieJar=CookieJar(), bucket=None, proxies={}): + self.lastURL = None + self.interface = interface + self.cookieJar = cookieJar + self.bucket = bucket + + self.http = HTTPBase(interface=interface, proxies=proxies) + self.http.cookieJar = cookieJar + self.proxies = proxies + + def clearReferer(self): + self.lastURL = None + + def getPage(self, url, get={}, post={}, referer=None, cookies=True, customHeaders={}): + if not referer: + referer = self.lastURL + self.http.followRedirect = True + resp = self.http.getResponse(url, get=get, post=post, referer=referer, cookies=cookies, customHeaders=customHeaders) + data = resp.read() + try: + if resp.info()["Content-Encoding"] == "gzip": + data = zlib.decompress(data, 16+zlib.MAX_WBITS) + elif resp.info()["Content-Encoding"] == "deflate": + data = zlib.decompress(data, -zlib.MAX_WBITS) + except: + pass + self.lastURL = resp.geturl() + return data + + def getRedirectLocation(self, url, get={}, post={}, referer=None, cookies=True, customHeaders={}): + if not referer: + referer = self.lastURL + self.http.followRedirect = False + resp = self.http.getResponse(url, get=get, post=post, referer=referer, cookies=cookies, customHeaders=customHeaders) + resp.close() + self.lastURL = resp.geturl() + location = None + try: + location = resp.info()["Location"] + except: + pass + return location + + def httpDownload(self, url, filename, get={}, post={}, referer=None, cookies=True, customHeaders={}, chunks=1, resume=False): + if not referer: + referer = self.lastURL + + dwnld = HTTPDownload(url, filename, get=get, post=post, referer=referer, cookies=cookies, customHeaders=customHeaders, bucket=self.bucket, interface=self.interface, proxies=self.proxies) + dwnld.cookieJar = self.cookieJar + + d = dwnld.download(chunks=chunks, resume=resume) + return d + + def ftpDownload(self, url, filename, resume=False): + dwnld = FTPDownload(url, filename, bucket=self.bucket, interface=self.interface, proxies=self.proxies) + + d = dwnld.download(resume=resume) + return d + +if __name__ == "__main__": + browser = Browser(proxies={"socks5": "localhost:5000"}) + ip = "http://www.whatismyip.com/automation/n09230945.asp" + #browser.getPage("http://google.com/search?q=bar") + #browser.getPage("https://encrypted.google.com/") + print browser.getPage(ip) + #print browser.getRedirectLocation("http://google.com/") + #browser.getPage("https://encrypted.google.com/") + #browser.getPage("http://google.com/search?q=bar") + + #browser.downloadFile("https://bitbucket.org/spoob/pyload/downloads/Logo_neu.png", "logo.png") diff --git a/module/network/Bucket.py b/module/network/Bucket.py new file mode 100644 index 000000000..35e27bcd4 --- /dev/null +++ b/module/network/Bucket.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: mkaay +""" + +from time import time, sleep +from threading import Lock + +class Bucket: + def __init__(self): + self.content = 0 + self.rate = 0 + self.lastDrip = time() + self.lock = Lock() + + def setRate(self, rate): + self.lock.acquire() + self.rate = rate + self.lock.release() + + def add(self, amount): + self.lock.acquire() + self.drip() + allowable = min(amount, self.rate - self.content) + if allowable > 0: + sleep(0.005) #@XXX: high sysload without?! + + self.content += allowable + self.lock.release() + return allowable + + def drip(self): + if self.rate == 0: + self.content = 0 + else: + now = time() + deltaT = now - self.lastDrip + self.content = long(max(0, self.content - deltaT * self.rate)) + self.lastDrip = now diff --git a/module/network/FTPBase.py b/module/network/FTPBase.py new file mode 100644 index 000000000..9bcb9b45e --- /dev/null +++ b/module/network/FTPBase.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: mkaay +""" + +from ftplib import FTP + +import socket +import socks + +from os.path import getsize +from urlparse import urlparse +from urllib2 import _parse_proxy + +from helper import * + +class FTPBase(FTP): + sourceAddress = ('', 0) + + def setSourceAddress(self, host): + self.sourceAddress = (host, 0) + + def connect(self, host='', port=0, timeout=30, proxies={}): + if host != '': + self.host = host + if port > 0: + self.port = port + self.timeout = timeout + + proxytype = None + proxy = None + if proxies.has_key("socks5"): + proxytype = socks.PROXY_TYPE_SOCKS5 + proxy = proxies["socks5"] + elif proxies.has_key("socks4"): + proxytype = socks.PROXY_TYPE_SOCKS4 + proxy = proxies["socks4"] + if proxytype: + self.sock = socks.socksocket() + t = _parse_proxy(proxy) + self.sock.setproxy(proxytype, addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2]) + else: + self.sock = socket.socket() + self.sock.settimeout(self.timeout) + self.sock.bind(self.sourceAddress) + self.sock.connect((self.host, self.port)) + self.af = self.sock.family + self.file = self.sock.makefile('rb') + self.welcome = self.getresp() + return self.welcome + +class FTPDownload(): + def __init__(self, url, filename, interface=None, bucket=None, proxies={}): + self.url = url + self.filename = filename + + self.bucket = bucket + self.interface = interface + self.proxies = proxies + + self.deferred = Deferred() + + self.finished = False + self.size = None + + self.speed = 0 + + self.abort = False + + self.arrived = 0 + + self.startTime = None + self.endTime = None + + self.speed = 0 #byte/sec + self.speedCalcTime = None + self.speedCalcLen = 0 + + self.bufferSize = 16*1024 #tune if performance is poor + + self.ftp = FTPBase() + self.fh = None + + @threaded + def _download(self, offset): + remotename = self.url.split("/")[-1] + cmd = "RETR %s" % remotename + + self.startTime = inttime() + self.arrived = offset + conn, size = self.ftp.ntransfercmd(cmd, None if offset == 0 else offset) #explicit None + if size: + self.size = size + offset + while True: + if self.abort: + self.ftp.abort() + break + count = self.bufferSize + if self.bucket: + count = self.bucket.add(count) + if count == 0: + sleep(0.01) + continue + + try: + data = conn.recv(count) + except: + self.deferred.error("timeout") + + if self.speedCalcTime < inttime(): + self.speed = self.speedCalcLen + self.speedCalcTime = inttime() + self.speedCalcLen = 0 + size = len(data) + self.speedCalcLen += size + self.arrived += size + + if not data: + break + + self.fh.write(data) + conn.close() + self.endTime = inttime() + if not self.abort: + print self.ftp.voidresp() #debug + + self.ftp.quit() + if self.abort: + self.deferred.error("abort") + elif self.size is None or self.size == self.arrived: + self.deferred.callback(resp) + else: + self.deferred.error("wrong content lenght") + + def download(self, resume=False): + self.fh = open("%s.part" % self.filename, "ab" if resume else "wb") + offset = 0 + if resume: + offset = getsize("%s.part" % self.filename) + + up = urlparse(self.url) + + self.ftp.connect(up.hostname, up.port if up.port else 21, proxies=self.proxies) + self.ftp.login(up.username, up.password) + self.ftp.cwd("/".join(up.path.split("/")[:-1])) + self.ftp.voidcmd('TYPE I') + self.size = self.ftp.size(self.url.split("/")[-1]) + + self._download(offset) + return self.deferred + +if __name__ == "__main__": + import sys + from Bucket import Bucket + bucket = Bucket() + bucket.setRate(200*1000) + #bucket = None + + url = "ftp://mirror.sov.uk.goscomb.net/ubuntu-releases/maverick/ubuntu-10.10-desktop-i386.iso" + + finished = False + def err(*a, **b): + print a, b + def callb(*a, **b): + global finished + finished = True + print a, b + + print "starting" + + dwnld = FTPDownload(url, "ubuntu_ftp.iso") + d = dwnld.download(resume=True) + d.addCallback(callb) + d.addErrback(err) + + try: + while True: + if not dwnld.finished: + print dwnld.speed/1024, "kb/s", "size", dwnld.arrived, "/", dwnld.size#, int(float(dwnld.arrived)/dwnld.size*100), "%" + if finished: + print "- finished" + break + sleep(1) + except KeyboardInterrupt: + dwnld.abort = True + sys.exit() diff --git a/module/network/HTTPBase.py b/module/network/HTTPBase.py new file mode 100644 index 000000000..e8c7d07ad --- /dev/null +++ b/module/network/HTTPBase.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: mkaay +""" + +from urllib import urlencode +from urlparse import urlparse + +from urllib2 import Request +from urllib2 import OpenerDirector + +from urllib2 import BaseHandler +from urllib2 import HTTPHandler +from urllib2 import HTTPRedirectHandler +from urllib2 import HTTPCookieProcessor +from urllib2 import HTTPSHandler +from urllib2 import HTTPDefaultErrorHandler +from urllib2 import HTTPErrorProcessor +from urllib2 import ProxyHandler + +from urllib2 import URLError + +from urllib2 import addinfourl +from urllib2 import _parse_proxy + +from httplib import HTTPConnection +from httplib import HTTPResponse +from httplib import responses as HTTPStatusCodes +from httplib import ResponseNotReady + +from cookielib import CookieJar + +import socket +import socks + +from MultipartPostHandler import MultipartPostHandler + +DEBUG = 1 +HANDLE_ERRORS = 1 + +class PyLoadHTTPResponse(HTTPResponse): + def __init__(self, sock, debuglevel=0, strict=0, method=None): + if method: # the httplib in python 2.3 uses the method arg + HTTPResponse.__init__(self, sock, debuglevel, method) + else: # 2.2 doesn't + HTTPResponse.__init__(self, sock, debuglevel) + self.fileno = sock.fileno + self._rbuf = '' + self._rbufsize = 8096 + self._handler = None # inserted by the handler later + self._host = None # (same) + self._url = None # (same) + + _raw_read = HTTPResponse.read + + def close_connection(self): + self.close() + self._handler._remove_connection(self._host, close=1) + + def info(self): + return self.msg + + def geturl(self): + return self._url + + def read(self, amt=None): + # the _rbuf test is only in this first if for speed. It's not + # logically necessary + if self._rbuf and not amt is None: + L = len(self._rbuf) + if amt > L: + amt -= L + else: + s = self._rbuf[:amt] + self._rbuf = self._rbuf[amt:] + return s + + s = self._rbuf + self._raw_read(amt) + self._rbuf = '' + return s + + def readline(self, limit=-1): + data = "" + i = self._rbuf.find('\n') + while i < 0 and not (0 < limit <= len(self._rbuf)): + new = self._raw_read(self._rbufsize) + if not new: break + i = new.find('\n') + if i >= 0: i = i + len(self._rbuf) + self._rbuf = self._rbuf + new + if i < 0: i = len(self._rbuf) + else: i = i+1 + if 0 <= limit < len(self._rbuf): i = limit + data, self._rbuf = self._rbuf[:i], self._rbuf[i:] + return data + + def readlines(self, sizehint = 0): + total = 0 + list = [] + while 1: + line = self.readline() + if not line: break + list.append(line) + total += len(line) + if sizehint and total >= sizehint: + break + return list + + @property + def code(self): + return self.status + + def getcode(self): + return self.status + +class PyLoadHTTPConnection(HTTPConnection): + sourceAddress = ('', 0) + socksProxy = None + response_class = PyLoadHTTPResponse + + def connect(self): + if self.socksProxy: + self.sock = socks.socksocket() + t = _parse_proxy(self.socksProxy[1]) + self.sock.setproxy(self.socksProxy[0], addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2]) + else: + self.sock = socket.socket() + self.sock.settimeout(30) + self.sock.bind(self.sourceAddress) + self.sock.connect((self.host, self.port)) + + try: + if self._tunnel_host: + self._tunnel() + except: #python2.5 + pass + +class PyLoadHTTPHandler(HTTPHandler): + sourceAddress = ('', 0) + socksProxy = None + + def __init__(self): + self._connections = {} + + def setInterface(self, interface): + if interface is None: + interface = "" + self.sourceAddress = (interface, 0) + + def setSocksProxy(self, *t): + self.socksProxy = t + + def close_connection(self, host): + """close connection to <host> + host is the host:port spec, as in 'www.cnn.com:8080' as passed in. + no error occurs if there is no connection to that host.""" + self._remove_connection(host, close=1) + + def open_connections(self): + """return a list of connected hosts""" + return self._connections.keys() + + def close_all(self): + """close all open connections""" + for host, conn in self._connections.items(): + conn.close() + self._connections = {} + + def _remove_connection(self, host, close=0): + if self._connections.has_key(host): + if close: self._connections[host].close() + del self._connections[host] + + def _start_connection(self, h, req): + try: + if req.has_data(): + data = req.get_data() + h.putrequest('POST', req.get_selector()) + if not req.headers.has_key('Content-type'): + h.putheader('Content-type', + 'application/x-www-form-urlencoded') + if not req.headers.has_key('Content-length'): + h.putheader('Content-length', '%d' % len(data)) + else: + h.putrequest('GET', req.get_selector(), skip_accept_encoding=1) + except socket.error, err: + raise urllib2.URLError(err) + + for args in self.parent.addheaders: + h.putheader(*args) + for k, v in req.headers.items(): + h.putheader(k, v) + h.endheaders() + if req.has_data(): + h.send(data) + + def do_open(self, http_class, req): + host = req.get_host() + if not host: + raise URLError('no host given') + + try: + need_new_connection = 1 + h = self._connections.get(host) + if not h is None: + try: + self._start_connection(h, req) + except socket.error, e: + r = None + else: + try: r = h.getresponse() + except ResponseNotReady, e: r = None + + if r is None or r.version == 9: + # httplib falls back to assuming HTTP 0.9 if it gets a + # bad header back. This is most likely to happen if + # the socket has been closed by the server since we + # last used the connection. + if DEBUG: print "failed to re-use connection to %s" % host + h.close() + else: + if DEBUG: print "re-using connection to %s" % host + need_new_connection = 0 + if need_new_connection: + if DEBUG: print "creating new connection to %s" % host + h = http_class(host) + h.sourceAddress = self.sourceAddress + h.socksProxy = self.socksProxy + self._connections[host] = h + self._start_connection(h, req) + r = h.getresponse() + except socket.error, err: + raise URLError(err) + + # if not a persistent connection, don't try to reuse it + if r.will_close: self._remove_connection(host) + + if DEBUG: + print "STATUS: %s, %s" % (r.status, r.reason) + r._handler = self + r._host = host + r._url = req.get_full_url() + + if r.status in (200, 206) or not HANDLE_ERRORS: + return r + else: + return self.parent.error('http', req, r, r.status, r.reason, r.msg) + + def http_open(self, req): + return self.do_open(PyLoadHTTPConnection, req) + +class NoRedirectHandler(BaseHandler): #supress error + def http_error_302(self, req, fp, code, msg, headers): + resp = addinfourl(fp, headers, req.get_full_url()) + resp.code = code + resp.msg = msg + return resp + + http_error_301 = http_error_303 = http_error_307 = http_error_302 + +class HTTPBase(): + def __init__(self, interface=None, proxies={}): + self.followRedirect = True + self.interface = interface + self.proxies = proxies + + self.size = None + + self.referer = None + + self.cookieJar = None + + self.userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.10" + + self.handler = PyLoadHTTPHandler() + self.handler.setInterface(interface) + if proxies.has_key("socks5"): + self.handler.setSocksProxy(socks.PROXY_TYPE_SOCKS5, proxies["socks5"]) + elif proxies.has_key("socks4"): + self.handler.setSocksProxy(socks.PROXY_TYPE_SOCKS4, proxies["socks4"]) + + self.cookieJar = CookieJar() + + self.debug = True + + def createOpener(self, cookies=True): + opener = OpenerDirector() + opener.add_handler(self.handler) + opener.add_handler(MultipartPostHandler()) + opener.add_handler(HTTPSHandler()) + opener.add_handler(HTTPDefaultErrorHandler()) + opener.add_handler(HTTPErrorProcessor()) + if self.proxies.has_key("http") or self.proxies.has_key("https"): + opener.add_handler(ProxyHandler(self.proxies)) + opener.add_handler(HTTPRedirectHandler() if self.followRedirect else NoRedirectHandler()) + if cookies: + opener.add_handler(HTTPCookieProcessor(self.cookieJar)) + opener.version = self.userAgent + opener.addheaders[0] = ("User-Agent", self.userAgent) + return opener + + def createRequest(self, url, get={}, post={}, referer=None, cookies=True, customHeaders={}): + if get: + if isinstance(get, dict): + get = urlencode(get) + url = "%s?%s" % (url, get) + + req = Request(url) + + if post: + if isinstance(post, dict): + post = urlencode(post) + req.add_data(post) + + req.add_header("Accept", "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5") + + if referer: + req.add_header("Referer", referer) + + req.add_header("Accept-Encoding", "gzip, deflate") + for key, val in customHeaders.iteritems(): + req.add_header(key, val) + + return req + + def getResponse(self, url, get={}, post={}, referer=None, cookies=True, customHeaders={}): + req = self.createRequest(url, get, post, referer, cookies, customHeaders) + opener = self.createOpener(cookies) + + if self.debug: + print "[HTTP] ----" + print "[HTTP] creating request" + print "[HTTP] URL:", url + print "[HTTP] GET" + for key, value in get.iteritems(): + print "[HTTP] \t", key, ":", value + if post: + print "[HTTP] POST" + for key, value in post.iteritems(): + print "[HTTP] \t", key, ":", value + print "[HTTP] headers" + for key, value in opener.addheaders: + print "[HTTP] \t", key, ":", value + for key, value in req.headers.iteritems(): + print "[HTTP] \t", key, ":", value + print "[HTTP] ----" + + resp = opener.open(req) + resp.getcode = lambda: resp.code + + if self.debug: + print "[HTTP] ----" + print "[HTTP] got response" + print "[HTTP] status:", resp.getcode() + print "[HTTP] headers" + for key, value in resp.info().dict.iteritems(): + print "[HTTP] \t", key, ":", value + print "[HTTP] ----" + try: + self.size = int(resp.info()["Content-Length"]) + except: #chunked transfer + pass + return resp + +if __name__ == "__main__": + base = HTTPBase() + resp = base.getResponse("http://python.org/") + print resp.read() diff --git a/module/network/HTTPChunk.py b/module/network/HTTPChunk.py new file mode 100644 index 000000000..37c28f685 --- /dev/null +++ b/module/network/HTTPChunk.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: mkaay +""" + +from HTTPBase import HTTPBase +from urllib2 import HTTPError +from threading import Lock +from helper import * +from time import sleep +from traceback import print_exc + +class HTTPChunk(HTTPBase): + def __init__(self, url, fh, get={}, post={}, referer=None, cookies=True, customHeaders={}, range=None, bucket=None, interface=None, proxies={}): + HTTPBase.__init__(self, interface=interface, proxies=proxies) + + self.url = url + self.bucket = bucket + self.range = range + self.noRangeHeader = False + self.fh = fh + + self.get = get + self.post = post + self.referer = referer + self.cookies = cookies + self.customHeaders = customHeaders + + self.deferred = Deferred() + + self.lock = Lock() + self.abort = False + self.finished = False + + self.arrived = 0 + + self.startTime = None + self.endTime = None + + self.speed = 0 #byte/sec + self.speedCalcTime = None + self.speedCalcLen = 0 + + self.bufferSize = 16*1024 #tune if performance is poor + self.resp = None + + def getSpeed(self): + self.lock.acquire() + speed = self.speed + self.lock.release() + return speed + + @threaded + def _download(self, resp): + self.arrived = 0 + self.lastSpeed = self.startTime = inttime() + + if self.noRangeHeader and not self.range[0] == 0: + self.deferred.error("range starts not at 0") + + running = True + while running: + if self.abort: + break + count = self.bufferSize + if self.noRangeHeader: + if self.range[1] <= self.arrived+count: + count = min(count, self.arrived+count - self.range[1]) + running = False + if self.bucket: + count = self.bucket.add(count) + if count == 0: + sleep(0.01) + continue + + try: + data = resp.read(count) + except: + self.deferred.error("timeout") + break + + if self.speedCalcTime < inttime(): + self.lock.acquire() + self.speed = self.speedCalcLen + self.lock.release() + self.speedCalcTime = inttime() + self.speedCalcLen = 0 + size = len(data) + self.speedCalcLen += size + self.arrived += size + if self.noRangeHeader: + if self.range[1] <= self.arrived: + self.fh.write(data[:-(self.arrived-self.range[1])]) + break + + if data: + self.fh.write(data) + else: + break + + self.speed = 0 + self.endTime = inttime() + self.finished = True + self.fh.close() + + if self.abort: + self.deferred.error("abort") + elif self.size == self.arrived: + self.deferred.callback(resp) + else: + self.deferred.error("wrong content lenght") + + def getEncoding(self): + try: + if self.resp.info()["Content-Encoding"] in ("gzip", "deflate"): + return self.resp.info()["Content-Encoding"] + except: + pass + return "plain" + + def download(self): + if self.range: + self.customHeaders["Range"] = "bytes=%i-%i" % self.range + try: + print "req" + resp = self.getResponse(self.url, self.get, self.post, self.referer, self.cookies, self.customHeaders) + self.resp = resp + except HTTPError, e: + print_exc() + self.deferred.error(e) + return self.deferred + + if (self.range and resp.getcode() == 206) or (not self.range and resp.getcode() == 200): + self._download(resp) + else: + self.deferred.error(resp.getcode(), resp) + return self.deferred + +if __name__ == "__main__": + import sys + from Bucket import Bucket + bucket = Bucket() + #bucket.setRate(200*1000) + bucket = None + + url = "http://download.fedoraproject.org/pub/fedora/linux/releases/13/Live/x86_64/Fedora-13-x86_64-Live.iso" + + finished = 0 + def err(*a, **b): + print a, b + def callb(*a, **b): + global finished + finished += 1 + print a, b + + print "starting" + + conns = 4 + + chunks = [] + for a in range(conns): + fh = open("file.part%d" % a, "wb") + chunk = HTTPChunk(url, fh, bucket=bucket, range=(a*5*1024*1024, (a+1)*5*1024*1024)) + print "fireing chunk #%d" % a + d = chunk.download() + d.addCallback(callb) + d.addErrback(err) + chunks.append(chunk) + + try: + while True: + aspeed = 0 + for a, chunk in enumerate(chunks): + if not chunk.finished: + print "#%d" % a, chunk.getSpeed()/1024, "kb/s" + else: + print "#%d" % a, "finished" + aspeed += chunk.getSpeed() + print "sum", aspeed/1024 + if finished == conns: + print "- finished" + break + sleep(1) + except KeyboardInterrupt: + for chunk in chunks: + chunk.abort = True + sys.exit() diff --git a/module/network/HTTPDownload.py b/module/network/HTTPDownload.py new file mode 100644 index 000000000..78dc00d72 --- /dev/null +++ b/module/network/HTTPDownload.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + @author: mkaay +""" + +from HTTPChunk import HTTPChunk +from helper import * +from os.path import exists, getsize +from os import remove +from shutil import move, copyfileobj + +from cookielib import CookieJar + +class WrongFormat(Exception): + pass + +class ChunkInfo(): + def __init__(self, name): + self.name = name + self.size = None + self.loaded = False + self.chunks = [] + + def setSize(self, size): + self.size = int(size) + + def addChunk(self, name, range, encoding): + self.chunks.append((name, range, encoding)) + + def clear(self): + self.chunks = [] + self.loaded = False + + def save(self): + fh = open("%s.chunks" % self.name, "w") + fh.write("name:%s\n" % self.name) + fh.write("size:%s\n" % self.size) + for i, c in enumerate(self.chunks): + fh.write("#%d:\n" % i) + fh.write("\tname:%s\n" % c[0]) + fh.write("\tencoding:%s\n" % c[2]) + fh.write("\trange:%i-%i\n" % c[1]) + + @staticmethod + def load(name): + if not exists("%s.chunks" % name): + raise IOError() + fh = open("%s.chunks" % name, "r") + name = fh.readline()[:-1] + size = fh.readline()[:-1] + if name.startswith("name:") and size.startswith("size:"): + name = name[5:] + size = size[5:] + else: + raise WrongFormat() + ci = ChunkInfo(name) + ci.loaded = True + ci.setSize(size) + while True: + if not fh.readline(): #skip line + break + name = fh.readline()[1:-1] + encoding = fh.readline()[1:-1] + range = fh.readline()[1:-1] + if name.startswith("name:") and encoding.startswith("encoding:") and range.startswith("range:"): + name = name[5:] + encoding = encoding[9:] + range = range[6:].split("-") + else: + raise WrongFormat() + + ci.addChunk(name, (long(range[0]), long(range[1])), encoding) + return ci + + def removeInfo(self): + remove("%s.chunks" % self.name) + + def getCount(self): + return len(self.chunks) + + def getChunkName(self, index): + return self.chunks[index][0] + + def getChunkRange(self, index): + return self.chunks[index][1] + + def getChunkEncoding(self, index): + return self.chunks[index][2] + +class HTTPDownload(): + def __init__(self, url, filename, get={}, post={}, referer=None, cookies=True, customHeaders={}, bucket=None, interface=None, proxies={}): + self.url = url + self.filename = filename + self.interface = interface + self.proxies = proxies + + self.get = get + self.post = post + + self.referer = referer + self.cookies = cookies + + self.customHeaders = customHeaders + + self.bucket = bucket + + self.deferred = Deferred() + + self.finished = False + self.size = None + + self.cookieJar = CookieJar() + + self.chunks = [] + try: + self.info = ChunkInfo.load(filename) + except IOError: + self.info = ChunkInfo(filename) + self.noChunkSupport = False + + @property + def arrived(self): + arrived = 0 + for i in range(self.info.getCount()): + arrived += getsize(self.info.getChunkName(i)) #ugly, but difficult to calc otherwise due chunk resume + return arrived + + def abort(self): + for chunk in self.chunks: + chunk.abort = True + + def getSpeed(self): + speed = 0 + for chunk in self.chunks: + speed += chunk.getSpeed() + return speed + + @property + def speed(self): + return self.getSpeed() + + def _copyChunks(self): + fo = open(self.filename, "wb") + for i in range(self.info.getCount()): + encoding = self.info.getChunkEncoding(i) + + decompress = lambda data: data + if encoding == "gzip": + gz = zlib.decompressobj(16+zlib.MAX_WBITS) + decompress = lambda data: gz.decompress(data) + if encoding == "deflate": + df = zlib.decompressobj(-zlib.MAX_WBITS) + decompress = lambda data: df.decompress(data) + + fname = "%s.chunk%d" % (self.filename, i) + fi = open(fname, "rb") + while True: + data = fi.read(512*1024) + if not data: + break + fo.write(decompress(data)) + fi.close() + remove(fname) + fo.close() + self.info.removeInfo() + self.deferred.callback() + + def _createChunk(self, fh, range=None): + chunk = HTTPChunk(self.url, fh, get=self.get, post=self.post, + referer=self.referer, cookies=self.cookies, + customHeaders=self.customHeaders, + bucket=self.bucket, range=range, + interface=self.interface, proxies=self.proxies) + chunk.cookieJar = self.cookieJar + return chunk + + def download(self, chunks=1, resume=False): + if chunks > 0: + dg = DeferredGroup() + if self.info.loaded and not self.info.getCount() == chunks: + self.info.clear() + crange = None + if resume: + if self.info.getCount() == chunks and exists("%s.chunk0" % (self.filename, )): + crange = self.info.getChunkRange(0) + crange = (crange[0]+getsize("%s.chunk0" % (self.filename, )), crange[1]) + + if crange is None or crange[1]-crange[0] > 0: + fh = open("%s.chunk0" % (self.filename, ), "ab" if crange else "wb") + chunk = self._createChunk(fh, range=crange) + self.chunks.append(chunk) + d = chunk.download() + dg.addDeferred(d) + + if not self.info.loaded: + size = chunk.size + chunksize = size/chunks + lastchunk = chunksize + + chunk.range = (0, chunksize-1) + chunk.noRangeHeader = True + self.size = chunk.size + self.info.setSize(self.size) + chunk.size = chunksize + self.info.addChunk("%s.chunk0" % (self.filename, ), chunk.range, chunk.getEncoding()) + + lastchunk = size - chunksize*(chunks-1) + else: + self.size = self.info.size + self.firstchunk = chunk + + for i in range(1, chunks): + cont = False + if not self.info.loaded: #first time load + if i+1 == chunks: + rng = (i*chunksize, i*chunksize+lastchunk) + else: + rng = (i*chunksize, (i+1)*chunksize-1) + else: #not finished + rng = self.info.getChunkRange(i) + if resume and exists("%s.chunk%d" % (self.filename, i)): #continue chunk + rng = (rng[0]+getsize("%s.chunk%d" % (self.filename, i)), rng[1]) + cont = True + + if rng[1]-rng[0] <= 0: #chunk done + continue + + fh = open("%s.chunk%d" % (self.filename, i), "ab" if cont else "wb") + chunk = self._createChunk(fh, range=rng) + self.chunks.append(chunk) + d = chunk.download() + if not chunk.resp.getcode() == 206 and i == 1: #no range supported, tell chunk0 to download everything + chunk.abort = True + self.noChunkSupport = True + self.firstchunk.size = self.size + self.firstchunk.range = None + self.info.clear() + self.info.addChunk("%s.chunk0" % (self.filename, ), (0, self.firstchunk.size), chunk.getEncoding()) + break + dg.addDeferred(d) + + if not self.info.loaded: + self.info.addChunk("%s.chunk%d" % (self.filename, i), chunk.range, chunk.getEncoding()) + + self.info.save() + dg.addCallback(self._copyChunks) + if len(self.chunks) == 0: + dg.callback() + return self.deferred + else: + raise Exception("no chunks") + +if __name__ == "__main__": + import sys + from Bucket import Bucket + bucket = Bucket() + #bucket.setRate(200*1000) + bucket = None + + url = "http://mirror.sov.uk.goscomb.net/ubuntu-releases/maverick/ubuntu-10.10-desktop-i386.iso" + + finished = False + def err(*a, **b): + print a, b + def callb(*a, **b): + global finished + finished = True + print a, b + + print "starting" + + dwnld = HTTPDownload(url, "ubuntu.iso") + d = dwnld.download(chunks=1, resume=True) + d.addCallback(callb) + d.addErrback(err) + + try: + while True: + for a, chunk in enumerate(dwnld.chunks): + if not chunk.finished: + print "#%d" % a, chunk.getSpeed()/1024, "kb/s", "size", int(float(chunk.arrived)/chunk.size*100), "%" + else: + print "#%d" % a, "finished" + print "sum", dwnld.speed/1024, dwnld.arrived, "/", dwnld.size, int(float(dwnld.arrived)/dwnld.size*100), "%" + if finished: + print "- finished" + break + sleep(1) + except KeyboardInterrupt: + dwnld.abort() + sys.exit() diff --git a/module/network/MultipartPostHandler.py b/module/network/MultipartPostHandler.py index 6804bcc90..113fd7cf9 100644 --- a/module/network/MultipartPostHandler.py +++ b/module/network/MultipartPostHandler.py @@ -136,4 +136,4 @@ def main(): validateFile("http://www.google.com") if __name__=="__main__": - main()
\ No newline at end of file + main() diff --git a/module/network/helper.py b/module/network/helper.py new file mode 100644 index 000000000..4b7119a2b --- /dev/null +++ b/module/network/helper.py @@ -0,0 +1,111 @@ +from threading import Thread +from time import time, sleep + +def inttime(): + return int(time()) + +class AlreadyCalled(Exception): + pass + +def callInThread(f, *args, **kwargs): + class FThread(Thread): + def __init__(self): + Thread.__init__(self) + self.d = Deferred() + def run(self): + ret = f(*args, **kwargs) + self.d.callback(ret) + t = FThread() + t.start() + return t.d + +class Deferred(): + def __init__(self): + self.call = [] + self.err = [] + self.result = () + self.errresult = () + + def addCallback(self, f, *cargs, **ckwargs): + self.call.append((f, cargs, ckwargs)) + if self.result: + args, kwargs = self.result + args+=tuple(cargs) + kwargs.update(ckwargs) + callInThread(f, *args, **kwargs) + + def addErrback(self, f, *cargs, **ckwargs): + self.err.append((f, cargs, ckwargs)) + if self.errresult: + args, kwargs = self.errresult + args+=tuple(cargs) + kwargs.update(ckwargs) + callInThread(f, *args, **kwargs) + + def callback(self, *args, **kwargs): + if self.result: + raise AlreadyCalled + self.result = (args, kwargs) + for f, cargs, ckwargs in self.call: + args+=tuple(cargs) + kwargs.update(ckwargs) + callInThread(f, *args, **kwargs) + + def error(self, *args, **kwargs): + self.errresult = (args, kwargs) + for f, cargs, ckwargs in self.err: + args+=tuple(cargs) + kwargs.update(ckwargs) + callInThread(f, *args, **kwargs) + +#decorator +def threaded(f): + def ret(*args, **kwargs): + return callInThread(f, *args, **kwargs) + return ret + +def waitFor(d): + class Waiter(): + waiting = True + args = () + err = None + + def wait(self): + d.addCallback(self.callb) + d.addErrback(self.errb) + while self.waiting: + sleep(0.5) + if self.err: + raise Exception(self.err) + return self.args + + def callb(self, *args, **kwargs): + self.waiting = False + self.args = (args, kwargs) + + def errb(self, *args, **kwargs): + self.waiting = False + self.err = (args, kwargs) + w = Waiter() + return w.wait() + +class DeferredGroup(Deferred): + def __init__(self, group=[]): + Deferred.__init__(self) + self.group = group + self.done = 0 + + for d in self.group: + d.addCallback(self._cb) + d.addErrback(self.error) + + def addDeferred(self, d): + d.addCallback(self._cb) + d.addErrback(self.error) + self.group.append(d) + + def _cb(self, *args, **kwargs): + self.done += 1 + if len(self.group) == self.done: + self.callback() + diff --git a/module/network/socks.py b/module/network/socks.py new file mode 100644 index 000000000..626ffe176 --- /dev/null +++ b/module/network/socks.py @@ -0,0 +1,442 @@ +"""SocksiPy - Python SOCKS module. +Version 1.00 + +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +This module provides a standard socket-like interface for Python +for tunneling connections through SOCKS proxies. + +""" + +""" + +Minor modifications made by Christopher Gilbert (http://motomastyle.com/) +for use in PyLoris (http://pyloris.sourceforge.net/) + +Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) +mainly to merge bug fixes found in Sourceforge + +""" + +import socket + +if getattr(socket, 'socket', None) is None: + raise ImportError('socket.socket missing, proxy support unusable') + +import struct +import sys + +PROXY_TYPE_SOCKS4 = 1 +PROXY_TYPE_SOCKS5 = 2 +PROXY_TYPE_HTTP = 3 + +_defaultproxy = None + +# Small hack for Python 2.x +if sys.version_info[0] <= 2: + def bytes(obj, enc=None): + return obj + +class ProxyError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class GeneralProxyError(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class Socks5AuthError(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class Socks5Error(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class Socks4Error(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class HTTPError(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +_generalerrors = ("success", + "invalid data", + "not connected", + "not available", + "bad proxy type", + "bad input") + +_socks5errors = ("succeeded", + "general SOCKS server failure", + "connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported", + "Unknown error") + +_socks5autherrors = ("succeeded", + "authentication is required", + "all offered authentication methods were rejected", + "unknown username or invalid password", + "unknown error") + +_socks4errors = ("request granted", + "request rejected or failed", + ("request rejected because SOCKS server cannot connect to " + "identd on the client"), + ("request rejected because the client program and identd" + " report different user-ids"), + "unknown error") + + +def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, + username=None, password=None): + """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets a default proxy which all further socksocket objects will use, + unless explicitly changed. + """ + global _defaultproxy + _defaultproxy = (proxytype, addr, port, rdns, username, password) + + +class socksocket(socket.socket): + """socksocket([family[, type[, proto]]]) -> socket object + + Open a SOCKS enabled socket. The parameters are the same as + those of the standard socket init. In order for SOCKS to work, + you must specify family=AF_INET, type=SOCK_STREAM and proto=0. + """ + + def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, + proto=0, _sock=None): + socket.socket.__init__(self, family, type, proto, _sock) + if _defaultproxy != None: + self.__proxy = _defaultproxy + else: + self.__proxy = (None, None, None, None, None, None) + self.__proxysockname = None + self.__proxypeername = None + + def __decode(self, bytes): + if getattr(bytes, 'decode', False): + try: + bytes = bytes.decode() + except Exception: + pass + return bytes + + def __encode(self, bytes): + if getattr(bytes, 'encode', False): + try: + bytes = bytes.encode() + except Exception: + pass + return bytes + + def __recvall(self, count): + """__recvall(count) -> data + Receive EXACTLY the number of bytes requested from the socket. + Blocks until the required number of bytes have been received. + """ + data = bytes("") + while len(data) < count: + d = self.recv(count - len(data)) + if not d: + raise GeneralProxyError( + (0, "connection closed unexpectedly")) + data = data + self.__decode(d) + return data + + def sendall(self, bytes): + socket.socket.sendall(self, self.__encode(bytes)) + + def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, + username=None, password=None): + """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets the proxy to be used. + proxytype - The type of the proxy to be used. Three types + are supported: PROXY_TYPE_SOCKS4 (including socks4a), + PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP + addr - The address of the server (IP or DNS). + port - The port of the server. Defaults to 1080 for SOCKS + servers and 8080 for HTTP proxy servers. + rdns - Should DNS queries be preformed on the remote side + (rather than the local side). The default is True. + Note: This has no effect with SOCKS4 servers. + username - Username to authenticate with to the server. + The default is no authentication. + password - Password to authenticate with to the server. + Only relevant when username is also provided. + """ + self.__proxy = (proxytype, addr, port, rdns, username, password) + + def __negotiatesocks5(self, destaddr, destport): + """__negotiatesocks5(self,destaddr,destport) + Negotiates a connection through a SOCKS5 server. + """ + # First we'll send the authentication packages we support. + if (self.__proxy[4] != None) and (self.__proxy[5] != None): + # The username/password details were supplied to the + # setproxy method so we support the USERNAME/PASSWORD + # authentication (in addition to the standard none). + self.sendall("\x05\x02\x00\x02") + else: + # No username/password were entered, therefore we + # only support connections with no authentication. + self.sendall("\x05\x01\x00") + # We'll receive the server's response to determine which + # method was selected + chosenauth = self.__recvall(2) + if chosenauth[0] != "\x05": + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + # Check the chosen authentication method + if chosenauth[1] == "\x00": + # No authentication is required + pass + elif chosenauth[1] == "\x02": + # Okay, we need to perform a basic username/password + # authentication. + self.sendall("\x01" + chr(len(self.__proxy[4])) + self.__proxy[4] + + chr(len(self.__proxy[5])) + self.__proxy[5]) + authstat = self.__recvall(2) + if authstat[0] != "\x01": + # Bad response + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if authstat[1] != "\x00": + # Authentication failed + self.close() + raise Socks5AuthError((3, _socks5autherrors[3])) + # Authentication succeeded + else: + # Reaching here is always bad + self.close() + if chosenauth[1] == "\xFF": + raise Socks5AuthError((2, _socks5autherrors[2])) + else: + raise GeneralProxyError((1, _generalerrors[1])) + # Now we can request the actual connection + req = "\x05\x01\x00" + # If the given destination address is an IP address, we'll + # use the IPv4 address request even if remote resolving was specified. + try: + ipaddr = socket.inet_aton(destaddr) + req = req + "\x01" + ipaddr + except socket.error: + # Well it's not an IP number, so it's probably a DNS name. + if self.__proxy[3] == True: + # Resolve remotely + ipaddr = None + req = req + "\x03" + chr(len(destaddr)) + destaddr + else: + # Resolve locally + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + req = req + "\x01" + ipaddr + req = req + self.__decode(struct.pack(">H", destport)) + self.sendall(req) + # Get the response + resp = self.__recvall(4) + if resp[0] != "\x05": + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + elif resp[1] != "\x00": + # Connection failed + self.close() + if ord(resp[1]) <= 8: + raise Socks5Error((ord(resp[1]), _socks5errors[ord(resp[1])])) + else: + raise Socks5Error((9, _socks5errors[9])) + # Get the bound address/port + elif resp[3] == "\x01": + boundaddr = self.__recvall(4) + elif resp[3] == "\x03": + resp = resp + self.recv(1) + boundaddr = self.__recvall(ord(resp[4])) + else: + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + d = bytes(self.__recvall(2), 'utf8') + d = str(d) #python2.5 no unicode in struct + boundport = struct.unpack(">H", d)[0] + self.__proxysockname = boundaddr, boundport + if ipaddr != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def getproxysockname(self): + """getsockname() -> address info + Returns the bound IP address and port number at the proxy. + """ + return self.__proxysockname + + def getproxypeername(self): + """getproxypeername() -> address info + Returns the IP and port number of the proxy. + """ + return socket.socket.getpeername(self) + + def getpeername(self): + """getpeername() -> address info + Returns the IP address and port number of the destination + machine (note: getproxypeername returns the proxy) + """ + return self.__proxypeername + + def __negotiatesocks4(self, destaddr, destport): + """__negotiatesocks4(self,destaddr,destport) + Negotiates a connection through a SOCKS4 server. + """ + # Check if the destination address provided is an IP address + rmtrslv = False + try: + ipaddr = socket.inet_aton(destaddr) + except socket.error: + # It's a DNS name. Check where it should be resolved. + if self.__proxy[3] == True: + ipaddr = "\x00\x00\x00\x01" + rmtrslv = True + else: + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + # Construct the request packet + req = "\x04\x01" + self.__decode(struct.pack(">H", destport)) + ipaddr + # The username parameter is considered userid for SOCKS4 + if self.__proxy[4] != None: + req = req + self.__proxy[4] + req = req + "\x00" + # DNS name if remote resolving is required + # NOTE: This is actually an extension to the SOCKS4 protocol + # called SOCKS4A and may not be supported in all cases. + if rmtrslv==True: + req = req + destaddr + "\x00" + self.sendall(req) + # Get the response from the server + resp = self.__recvall(8) + if resp[0] != "\x00": + # Bad data + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if resp[1] != "\x5A": + # Server returned an error + self.close() + if ord(resp[1]) in (91,92,93): + self.close() + raise Socks4Error((ord(resp[1]), _socks4errors[ord(resp[1])-90])) + else: + raise Socks4Error((94,_socks4errors[4])) + # Get the bound address/port + self.__proxysockname = (socket.inet_ntoa(resp[4:]),struct.unpack(">H",bytes(resp[2:4],'utf8'))[0]) + if rmtrslv != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr),destport) + else: + self.__proxypeername = (destaddr, destport) + + def __negotiatehttp(self, destaddr, destport): + """__negotiatehttp(self,destaddr,destport) + Negotiates a connection through an HTTP server. + """ + # If we need to resolve locally, we do this now + if self.__proxy[3] == False: + addr = socket.gethostbyname(destaddr) + else: + addr = destaddr + self.sendall(("CONNECT %s:%s HTTP/1.1\r\n" + "Host: %s\r\n\r\n") % (addr, destport, destaddr)) + # We read the response until we get the string "\r\n\r\n" + resp = self.recv(1) + while resp.find("\r\n\r\n") == -1: + resp = resp + self.recv(1) + # We just need the first line to check if the connection + # was successful + statusline = resp.splitlines()[0].split(" ", 2) + if statusline[0] not in ("HTTP/1.0", "HTTP/1.1"): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + try: + statuscode = int(statusline[1]) + except ValueError: + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if statuscode != 200: + self.close() + raise HTTPError((statuscode, statusline[2])) + self.__proxysockname = ("0.0.0.0", 0) + self.__proxypeername = (addr, destport) + + def connect(self, destpair): + """connect(self,despair) + Connects to the specified destination through a proxy. + destpar - A tuple of the IP/DNS address and the port number. + (identical to socket's connect). + To select the proxy server use setproxy(). + """ + # Do a minimal input check first + # TODO(durin42): seriously? type checking? do we care? + if ((not isinstance(destpair, (list, tuple))) or len(destpair) < 2 + or not isinstance(destpair[0], str) or not isinstance(destpair[1], int)): + raise GeneralProxyError((5, _generalerrors[5])) + if self.__proxy[0] == PROXY_TYPE_SOCKS5: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + socket.socket.connect(self,(self.__proxy[1], portnum)) + self.__negotiatesocks5(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_SOCKS4: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + socket.socket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatesocks4(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_HTTP: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 8080 + socket.socket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatehttp(destpair[0], destpair[1]) + elif self.__proxy[0] == None: + socket.socket.connect(self, (destpair[0], destpair[1])) + else: + raise GeneralProxyError((4, _generalerrors[4])) diff --git a/pyLoadGui.py b/pyLoadGui.py index 0ee865dfe..dce496768 100755 --- a/pyLoadGui.py +++ b/pyLoadGui.py @@ -47,7 +47,7 @@ from module.gui.CoreConfigParser import ConfigParser try: import pynotify except ImportError: - pass + print "pynotify not installed, falling back to qt tray notification" class main(QObject): def __init__(self): @@ -269,8 +269,13 @@ class main(QObject): view.setSelectionBehavior(QAbstractItemView.SelectRows) view.setSelectionMode(QAbstractItemView.ExtendedSelection) self.queue = view.model() + self.connect(self.queue, SIGNAL("updateCount"), self.slotUpdateCount) self.queue.start() - + + def slotUpdateCount(self, pc, fc): + self.mainWindow.packageCount.setText("%i" % pc) + self.mainWindow.fileCount.setText("%i" % fc) + def refreshServerStatus(self): """ refresh server status and overall speed in the status bar @@ -280,13 +285,12 @@ class main(QObject): return self.serverStatus.update(s) if self.serverStatus["pause"]: - self.serverStatus["status"] = _("paused") + self.mainWindow.status.setText(_("paused")) else: - self.serverStatus["status"] = _("running") - self.serverStatus["speed"] = int(self.serverStatus["speed"]) - text = _("status: %(status)s | speed: %(speed)s kb/s | free space: %(freespace)s MiB") % self.serverStatus + self.mainWindow.status.setText(_("running")) + self.mainWindow.speed.setText("%i kb/s" % self.serverStatus["speed"]) + self.mainWindow.space.setText("%i MiB" % self.serverStatus["freespace"]) self.mainWindow.actions["toggle_status"].setChecked(not self.serverStatus["pause"]) - self.mainWindow.serverStatus.setText(text) def refreshLog(self): """ @@ -445,6 +449,7 @@ class main(QObject): self.connector.setAddr(("core", self.core)) self.startMain() + self.notification.showMessage(_("connected to %s") % data["host"]) def refreshConnections(self): """ @@ -656,17 +661,17 @@ class main(QObject): try: if event[0] == "update" and event[2] == "file": info = self.connector.getLinkInfo(event[3]) - if info["status_type"] == "finished": - self.emit(SIGNAL("showMessage"), _("Finished downloading of '%s'") % info["status_filename"]) - elif info["status_type"] == "downloading": - self.emit(SIGNAL("showMessage"), _("Started downloading '%s'") % info["status_filename"]) - elif info["status_type"] == "failed": - self.emit(SIGNAL("showMessage"), _("Failed downloading '%s'!") % info["status_filename"]) + if info.has_key(str(event[3])): + info = info[str(event[3])] + if info["statusmsg"] == "finished": + self.emit(SIGNAL("showMessage"), _("Finished downloading of '%s'") % info["name"]) + elif info["statusmsg"] == "failed": + self.emit(SIGNAL("showMessage"), _("Failed downloading '%s'!") % info["name"]) if event[0] == "insert" and event[2] == "file": info = self.connector.getLinkInfo(event[3]) - self.emit(SIGNAL("showMessage"), _("Added '%s' to queue") % info["status_filename"]) + self.emit(SIGNAL("showMessage"), _("Added '%s' to queue") % info["name"]) except: - pass + print "can't send notification" elif event[1] == "collector": self.packageCollector.addEvent(event) @@ -752,7 +757,7 @@ class Notification(QObject): try: self.usePynotify = pynotify.init("icon-summary-body") except: - pass + print "init error" def showMessage(self, body): if self.usePynotify: |