Add a new qutebrowser.utils.http module

This commit is contained in:
Florian Bruhin 2014-08-11 15:56:18 +02:00
parent 50604de24d
commit be2604cacd
4 changed files with 72 additions and 48 deletions

View File

@ -31,6 +31,7 @@ import qutebrowser.config.config as config
import qutebrowser.utils.message as message import qutebrowser.utils.message as message
import qutebrowser.commands.utils as cmdutils import qutebrowser.commands.utils as cmdutils
import qutebrowser.utils.misc as utils import qutebrowser.utils.misc as utils
from qutebrowser.utils.http import parse_content_disposition
from qutebrowser.utils.log import downloads as logger from qutebrowser.utils.log import downloads as logger
from qutebrowser.utils.log import fix_rfc2622 from qutebrowser.utils.log import fix_rfc2622
from qutebrowser.utils.usertypes import PromptMode, Question, Timer from qutebrowser.utils.usertypes import PromptMode, Question, Timer
@ -375,7 +376,7 @@ class DownloadManager(QObject):
Args: Args:
reply: The QNetworkReply to download. reply: The QNetworkReply to download.
""" """
_inline, suggested_filename = utils.parse_content_disposition(reply) _inline, suggested_filename = parse_content_disposition(reply)
logger.debug("fetch: {} -> {}".format(reply.url(), suggested_filename)) logger.debug("fetch: {} -> {}".format(reply.url(), suggested_filename))
download = DownloadItem(reply, self) download = DownloadItem(reply, self)
download.finished.connect(partial(self.on_finished, download)) download.finished.connect(partial(self.on_finished, download))

View File

@ -17,13 +17,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Tests for qutebrowser.utils.misc.parse_content_disposition.""" """Tests for qutebrowser.utils.http.parse_content_disposition."""
import os import os
import unittest import unittest
from unittest.mock import Mock from unittest.mock import Mock
import qutebrowser.utils.misc as utils import qutebrowser.utils.http as httputils
from qutebrowser.test.stubs import FakeNetworkReply from qutebrowser.test.stubs import FakeNetworkReply
@ -40,7 +40,7 @@ class AttachmentTestCase(unittest.TestCase):
def _check_filename(self, header, filename): def _check_filename(self, header, filename):
"""Check if the passed header has the given filename.""" """Check if the passed header has the given filename."""
reply = FakeNetworkReply(header) reply = FakeNetworkReply(header)
cd_inline, cd_filename = utils.parse_content_disposition(reply) cd_inline, cd_filename = httputils.parse_content_disposition(reply)
self.assertIsNotNone(cd_filename) self.assertIsNotNone(cd_filename)
self.assertEqual(cd_filename, filename) self.assertEqual(cd_filename, filename)
self.assertFalse(cd_inline) self.assertFalse(cd_inline)
@ -48,14 +48,14 @@ class AttachmentTestCase(unittest.TestCase):
def _check_ignored(self, header): def _check_ignored(self, header):
"""Check if the passed header is ignored.""" """Check if the passed header is ignored."""
reply = FakeNetworkReply(header) reply = FakeNetworkReply(header)
cd_inline, cd_filename = utils.parse_content_disposition(reply) cd_inline, cd_filename = httputils.parse_content_disposition(reply)
self.assertEqual(cd_filename, DEFAULT_NAME) self.assertEqual(cd_filename, DEFAULT_NAME)
self.assertTrue(cd_inline) self.assertTrue(cd_inline)
def _check_unnamed(self, header): def _check_unnamed(self, header):
"""Check if the passed header results in an unnamed attachment.""" """Check if the passed header results in an unnamed attachment."""
reply = FakeNetworkReply(header) reply = FakeNetworkReply(header)
cd_inline, cd_filename = utils.parse_content_disposition(reply) cd_inline, cd_filename = httputils.parse_content_disposition(reply)
self.assertEqual(cd_filename, DEFAULT_NAME) self.assertEqual(cd_filename, DEFAULT_NAME)
self.assertFalse(cd_inline) self.assertFalse(cd_inline)
@ -70,14 +70,14 @@ class InlineTests(unittest.TestCase):
def _check_filename(self, header, filename): def _check_filename(self, header, filename):
"""Check if the passed header has the given filename.""" """Check if the passed header has the given filename."""
reply = FakeNetworkReply(header) reply = FakeNetworkReply(header)
cd_inline, cd_filename = utils.parse_content_disposition(reply) cd_inline, cd_filename = httputils.parse_content_disposition(reply)
self.assertEqual(cd_filename, filename) self.assertEqual(cd_filename, filename)
self.assertTrue(cd_inline) self.assertTrue(cd_inline)
def _check_ignored(self, header): def _check_ignored(self, header):
"""Check if the passed header is ignored.""" """Check if the passed header is ignored."""
reply = FakeNetworkReply(header) reply = FakeNetworkReply(header)
cd_inline, cd_filename = utils.parse_content_disposition(reply) cd_inline, cd_filename = httputils.parse_content_disposition(reply)
self.assertEqual(cd_filename, DEFAULT_NAME) self.assertEqual(cd_filename, DEFAULT_NAME)
self.assertTrue(cd_inline) self.assertTrue(cd_inline)
@ -135,7 +135,7 @@ class AttachmentTests(AttachmentTestCase):
UA should offer to download the resource. UA should offer to download the resource.
""" """
reply = FakeNetworkReply('attachment') reply = FakeNetworkReply('attachment')
cd_inline, cd_filename = utils.parse_content_disposition(reply) cd_inline, cd_filename = httputils.parse_content_disposition(reply)
self.assertFalse(cd_inline) self.assertFalse(cd_inline)
self.assertEqual(cd_filename, DEFAULT_NAME) self.assertEqual(cd_filename, DEFAULT_NAME)
@ -894,8 +894,8 @@ class OurTests(AttachmentTestCase):
def setUpModule(): def setUpModule():
"""Mock out logging in utils.""" """Mock out logging in httputils."""
utils.log = Mock() httputils.logger = Mock()
if __name__ == '__main__': if __name__ == '__main__':

60
qutebrowser/utils/http.py Normal file
View File

@ -0,0 +1,60 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser 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.
#
# qutebrowser 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 qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Other utilities which don't fit anywhere else. """
import os.path
import rfc6266
from qutebrowser.utils.log import misc as logger
def parse_content_disposition(reply):
"""Parse a content_disposition header.
Args:
reply: The QNetworkReply to get a filename for.
Return:
A (is_inline, filename) tuple.
"""
is_inline = True
filename = None
# First check if the Content-Disposition header has a filename
# attribute.
if reply.hasRawHeader('Content-Disposition'):
# We use the unsafe variant of the filename as we sanitize it via
# os.path.basename later.
try:
content_disposition = rfc6266.parse_headers(
bytes(reply.rawHeader('Content-Disposition')), relaxed=True)
filename = content_disposition.filename_unsafe
except UnicodeDecodeError as e:
logger.warning("Error while getting filename: {}: {}".format(
e.__class__.__name__, e))
filename = None
else:
is_inline = content_disposition.is_inline
# Then try to get filename from url
if not filename:
filename = reply.url().path()
# If that fails as well, use a fallback
if not filename:
filename = 'qutebrowser-download'
return is_inline, os.path.basename(filename)

View File

@ -28,13 +28,11 @@ from urllib.parse import urljoin, urlencode
from collections import OrderedDict from collections import OrderedDict
from functools import reduce from functools import reduce
import rfc6266
from PyQt5.QtCore import QCoreApplication, QStandardPaths, Qt from PyQt5.QtCore import QCoreApplication, QStandardPaths, Qt
from PyQt5.QtGui import QKeySequence, QColor from PyQt5.QtGui import QKeySequence, QColor
from pkg_resources import resource_string from pkg_resources import resource_string
import qutebrowser import qutebrowser
import qutebrowser.utils.log as log
from qutebrowser.utils.qt import qt_version_check, qt_ensure_valid from qutebrowser.utils.qt import qt_version_check, qt_ensure_valid
@ -304,41 +302,6 @@ def format_size(size, base=1024, suffix=''):
return '{:.02f}{}{}'.format(size, prefixes[-1], suffix) return '{:.02f}{}{}'.format(size, prefixes[-1], suffix)
def parse_content_disposition(reply):
"""Parse a content_disposition header.
Args:
reply: The QNetworkReply to get a filename for.
Return:
A (is_inline, filename) tuple.
"""
is_inline = True
filename = None
# First check if the Content-Disposition header has a filename
# attribute.
if reply.hasRawHeader('Content-Disposition'):
# We use the unsafe variant of the filename as we sanitize it via
# os.path.basename later.
try:
content_disposition = rfc6266.parse_headers(
bytes(reply.rawHeader('Content-Disposition')), relaxed=True)
filename = content_disposition.filename_unsafe
except UnicodeDecodeError as e:
log.misc.warning("Error while getting filename: {}: {}".format(
e.__class__.__name__, e))
filename = None
else:
is_inline = content_disposition.is_inline
# Then try to get filename from url
if not filename:
filename = reply.url().path()
# If that fails as well, use a fallback
if not filename:
filename = 'qutebrowser-download'
return is_inline, os.path.basename(filename)
def key_to_string(key): def key_to_string(key):
"""Convert a Qt::Key member to a meaningful name. """Convert a Qt::Key member to a meaningful name.