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.commands.utils as cmdutils
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 fix_rfc2622
from qutebrowser.utils.usertypes import PromptMode, Question, Timer
@ -375,7 +376,7 @@ class DownloadManager(QObject):
Args:
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))
download = DownloadItem(reply, self)
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
# 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 unittest
from unittest.mock import Mock
import qutebrowser.utils.misc as utils
import qutebrowser.utils.http as httputils
from qutebrowser.test.stubs import FakeNetworkReply
@ -40,7 +40,7 @@ class AttachmentTestCase(unittest.TestCase):
def _check_filename(self, header, filename):
"""Check if the passed header has the given filename."""
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.assertEqual(cd_filename, filename)
self.assertFalse(cd_inline)
@ -48,14 +48,14 @@ class AttachmentTestCase(unittest.TestCase):
def _check_ignored(self, header):
"""Check if the passed header is ignored."""
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.assertTrue(cd_inline)
def _check_unnamed(self, header):
"""Check if the passed header results in an unnamed attachment."""
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.assertFalse(cd_inline)
@ -70,14 +70,14 @@ class InlineTests(unittest.TestCase):
def _check_filename(self, header, filename):
"""Check if the passed header has the given filename."""
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.assertTrue(cd_inline)
def _check_ignored(self, header):
"""Check if the passed header is ignored."""
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.assertTrue(cd_inline)
@ -135,7 +135,7 @@ class AttachmentTests(AttachmentTestCase):
UA should offer to download the resource.
"""
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.assertEqual(cd_filename, DEFAULT_NAME)
@ -894,8 +894,8 @@ class OurTests(AttachmentTestCase):
def setUpModule():
"""Mock out logging in utils."""
utils.log = Mock()
"""Mock out logging in httputils."""
httputils.logger = Mock()
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 functools import reduce
import rfc6266
from PyQt5.QtCore import QCoreApplication, QStandardPaths, Qt
from PyQt5.QtGui import QKeySequence, QColor
from pkg_resources import resource_string
import qutebrowser
import qutebrowser.utils.log as log
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)
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):
"""Convert a Qt::Key member to a meaningful name.