diff --git a/deluge/httpdownloader.py b/deluge/httpdownloader.py index 52ff0ebb9..f8d1e2a0e 100644 --- a/deluge/httpdownloader.py +++ b/deluge/httpdownloader.py @@ -30,31 +30,29 @@ log = logging.getLogger(__name__) class CompressionDecoder(client.GzipDecoder): - """A compression decoder for gzip, x-gzip and deflate""" + """A compression decoder for gzip, x-gzip and deflate.""" def deliverBody(self, protocol): # NOQA: N802 - self.original.deliverBody(CompressionDecoderProtocol(protocol, self.original)) + self.original.deliverBody( + CompressionDecoderProtocol(protocol, self.original)) class CompressionDecoderProtocol(client._GzipProtocol): - """A compression decoder protocol for CompressionDecoder""" + """A compression decoder protocol for CompressionDecoder.""" def __init__(self, protocol, response): super(CompressionDecoderProtocol, self).__init__(protocol, response) self._zlibDecompress = zlib.decompressobj(32 + zlib.MAX_WBITS) class BodyHandler(HTTPClientParser, object): - """An HTTP parser that saves the response on a file""" + """An HTTP parser that saves the response to a file.""" def __init__(self, request, finished, length, agent): - """ + """BodyHandler init. - :param request: the request to which this parser is for - :type request: twisted.web.iweb.IClientRequest - :param finished: a Deferred to handle the the finished response - :type finished: twisted.internet.defer.Deferred - :param length: the length of the response - :type length: int - :param agent: the agent from which the request was sent - :type agent: twisted.web.iweb.IAgent + Args: + request (t.w.i.IClientRequest): The parser request. + finished (Deferred): A Deferred to handle the finished response. + length (int): The length of the response. + agent (t.w.i.IAgent): The agent from which the request was sent. """ super(BodyHandler, self).__init__(request, finished) self.agent = agent @@ -67,7 +65,8 @@ class BodyHandler(HTTPClientParser, object): self.current_length += len(data) self.data += data if self.agent.part_callback: - self.agent.part_callback(data, self.current_length, self.total_length) + self.agent.part_callback( + data, self.current_length, self.total_length) def connectionLost(self, reason): # NOQA: N802 with open(self.agent.filename, 'wb') as _file: @@ -79,23 +78,26 @@ class BodyHandler(HTTPClientParser, object): @implementer(IAgent) class HTTPDownloaderAgent(object): - """ - A File Downloader Agent - """ + """A File Downloader Agent.""" def __init__( - self, agent, filename, part_callback=None, - force_filename=False, allow_compression=True, handle_redirect=True, + self, + agent, + filename, + part_callback=None, + force_filename=False, + allow_compression=True, + handle_redirect=True, ): - """ - :param agent: the agent which will send the requests - :type agent: twisted.web.client.Agent - :param filename: the filename to save the file as - :type filename: string - :param force_filename: forces use of the supplied filename, regardless of header content - :type force_filename: bool - :param part_callback: a function to be called when a part of data - is received, it's signature should be: func(data, current_length, total_length) - :type part_callback: function + """HTTPDownloaderAgent init. + + Args: + agent (t.w.c.Agent): The agent which will send the requests. + filename (str): The filename to save the file as. + force_filename (bool): Forces use of the supplied filename, + regardless of header content. + part_callback (func): A function to be called when a part of data + is received, it's signature should be: + func(data, current_length, total_length) """ self.handle_redirect = handle_redirect @@ -120,15 +122,21 @@ class HTTPDownloaderAgent(object): finished.errback(Failure(error)) else: headers = response.headers - body_length = int(headers.getRawHeaders(b'content-length', default=[0])[0]) + body_length = int( + headers.getRawHeaders(b'content-length', default=[0])[0]) - if headers.hasHeader(b'content-disposition') and not self.force_filename: - content_disp = headers.getRawHeaders(b'content-disposition')[0].decode('utf-8') + if ( + headers.hasHeader(b'content-disposition') + and not self.force_filename + ): + content_disp = headers.getRawHeaders( + b'content-disposition')[0].decode('utf-8') content_disp_params = cgi.parse_header(content_disp)[1] if 'filename' in content_disp_params: new_file_name = content_disp_params['filename'] new_file_name = sanitise_filename(new_file_name) - new_file_name = os.path.join(os.path.split(self.filename)[0], new_file_name) + new_file_name = os.path.join( + os.path.split(self.filename)[0], new_file_name) count = 1 fileroot = os.path.splitext(new_file_name)[0] @@ -140,20 +148,22 @@ class HTTPDownloaderAgent(object): self.filename = new_file_name - response.deliverBody(BodyHandler(response.request, finished, body_length, self)) + response.deliverBody( + BodyHandler(response.request, finished, body_length, self)) return finished def request(self, method, uri, headers=None, body_producer=None): - """ + """Issue a new request to the wrapped agent. - :param method: the HTTP method to use - :param uri: the url to download from - :type uri: string - :param headers: any optional headers to send - :type headers: twisted.web.http_headers.Headers - :param body_producer: - :return: + Args: + method (bytes): The HTTP method to use. + uri (bytes): The url to download from. + headers (t.w.h.Headers, optional): Any extra headers to send. + body_producer (t.w.i.IBodyProducer, optional): Request body data. + + Returns: + Deferred: The filename of the of the downloaded file. """ if headers is None: headers = Headers() @@ -174,14 +184,14 @@ class HTTPDownloaderAgent(object): def sanitise_filename(filename): - """ - Sanitises a filename to use as a download destination file. + """Sanitises a filename to use as a download destination file. + Logs any filenames that could be considered malicious. - :param filename: the filename to sanitise - :type filename: string - :returns: the sanitised filename - :rtype: string + filename (str): The filename to sanitise. + + Returns: + str: The sanitised filename. """ # Remove any quotes @@ -189,43 +199,52 @@ def sanitise_filename(filename): if os.path.basename(filename) != filename: # Dodgy server, log it - log.warning('Potentially malicious server: trying to write to file: %s', filename) + log.warning( + 'Potentially malicious server: trying to write to file: %s', + filename, + ) # Only use the basename filename = os.path.basename(filename) filename = filename.strip() if filename.startswith('.') or ';' in filename or '|' in filename: # Dodgy server, log it - log.warning('Potentially malicious server: trying to write to file: %s', filename) + log.warning( + 'Potentially malicious server: trying to write to file: %s', + filename, + ) return filename def _download_file( - url, filename, callback=None, headers=None, - force_filename=False, allow_compression=True, handle_redirects=True, + url, filename, + callback=None, + headers=None, + force_filename=False, + allow_compression=True, + handle_redirects=True, ): - """ - Downloads a file from a specific URL and returns a Deferred. A callback - function can be specified to be called as parts are received. + """Downloads a file from a specific URL and returns a Deferred. + + A callback function can be specified to be called as parts are received. Args: - url (str): The url to download from - filename (str): The filename to save the file as - callback (func): A function to be called when a part of data is received, + url (str): The url to download from. + filename (str): The filename to save the file as. + callback (func): A function to be called when partial data is received, it's signature should be: func(data, current_length, total_length) - headers (dict): Any optional headers to send - force_filename (bool): force us to use the filename specified rather than - one the server may suggest - allow_compression (bool): Allows gzip & deflate decoding + headers (dict): Any optional headers to send. + force_filename (bool): Force using the filename specified rather than + one the server may suggest. + allow_compression (bool): Allows gzip & deflate decoding. Returns: - Deferred: the filename of the downloaded file + Deferred: The filename of the downloaded file. Raises: t.w.e.PageRedirect t.w.e.Error: for all other HTTP response errors - """ agent = client.Agent(reactor) @@ -237,7 +256,14 @@ def _download_file( if handle_redirects: agent = client.RedirectAgent(agent) - agent = HTTPDownloaderAgent(agent, filename, callback, force_filename, allow_compression, handle_redirects) + agent = HTTPDownloaderAgent( + agent, + filename, + callback, + force_filename, + allow_compression, + handle_redirects, + ) # The Headers init expects dict values to be a list. if headers: @@ -249,31 +275,35 @@ def _download_file( def download_file( - url, filename, callback=None, headers=None, force_filename=False, - allow_compression=True, handle_redirects=True, + url, + filename, + callback=None, + headers=None, + force_filename=False, + allow_compression=True, + handle_redirects=True, ): - """ - Downloads a file from a specific URL and returns a Deferred. A callback - function can be specified to be called as parts are received. + """Downloads a file from a specific URL and returns a Deferred. + + A callback function can be specified to be called as parts are received. Args: - url (str): The url to download from - filename (str): The filename to save the file as - callback (func): A function to be called when a part of data is received, - it's signature should be: func(data, current_length, total_length) - headers (dict): Any optional headers to send - force_filename (bool): force us to use the filename specified rather than - one the server may suggest - allow_compression (bool): Allows gzip & deflate decoding - handle_redirects (bool): If HTTP redirects should be handled automatically + url (str): The url to download from. + filename (str): The filename to save the file as. + callback (func): A function to be called when partial data is received, + it's signature should be: func(data, current_length, total_length). + headers (dict): Any optional headers to send. + force_filename (bool): Force the filename specified rather than one the + server may suggest. + allow_compression (bool): Allows gzip & deflate decoding. + handle_redirects (bool): HTTP redirects handled automatically or not. Returns: - Deferred: the filename of the downloaded file + Deferred: The filename of the downloaded file. Raises: - t.w.e.PageRedirect: Unless handle_redirects=True - t.w.e.Error: for all other HTTP response errors - + t.w.e.PageRedirect: If handle_redirects is False. + t.w.e.Error: For all other HTTP response errors. """ def on_download_success(result): log.debug('Download success!')