diff --git a/pkg/PKGBUILD.qutebrowser-git b/pkg/PKGBUILD.qutebrowser-git index 73d03efbf..b6557a390 100644 --- a/pkg/PKGBUILD.qutebrowser-git +++ b/pkg/PKGBUILD.qutebrowser-git @@ -9,7 +9,7 @@ arch=(any) url="http://www.qutebrowser.org/" license=('GPL') depends=('python>=3.3' 'python-setuptools' 'python-pyqt5>=5.2' 'qt5-base>=5.2' - 'qt5-webkit>=5.2' 'libxkbcommon-x11') + 'qt5-webkit>=5.2' 'libxkbcommon-x11' 'python-rfc6266') makedepends=('python' 'python-setuptools') optdepends=('python-colorlog: colored logging output' 'python-ipdb: better debugging') diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 05c8bffbb..1229edbc9 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -24,6 +24,7 @@ import os.path from functools import partial from collections import deque +import rfc6266 from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QCoreApplication from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply @@ -32,12 +33,16 @@ import qutebrowser.utils.message as message import qutebrowser.utils.url as urlutils import qutebrowser.commands.utils as cmdutils from qutebrowser.utils.log import downloads as logger +from qutebrowser.utils.log import fix_rfc2622 from qutebrowser.utils.usertypes import PromptMode, Question, Timer from qutebrowser.utils.misc import (interpolate_color, format_seconds, - format_size, get_http_header) + format_size) from qutebrowser.commands.exceptions import CommandError +fix_rfc2622() + + class DownloadItem(QObject): """A single download currently running. @@ -328,7 +333,11 @@ class DownloadManager(QObject): """ # First check if the Content-Disposition header has a filename # attribute. - filename = get_http_header(reply, 'Content-Disposition', 'filename') + if reply.hasRawHeader('Content-Disposition'): + # We use the unsafe variant of the filename as we sanitize it via + # os.path.basename later. + filename = rfc6266.parse_headers( + bytes(reply.rawHeader('Content-Disposition'))).filename_unsafe # Then try to get filename from url if not filename: filename = reply.url().path() diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index c5c924ad8..a8fc630e7 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -163,6 +163,7 @@ class BrowserPage(QWebPage): start_download: Emitted with the QNetworkReply associated with the passed request. """ + from qutebrowser.utils.debug import set_trace; set_trace() reply = self.networkAccessManager().get(request) self.start_download.emit(reply) diff --git a/qutebrowser/utils/earlyinit.py b/qutebrowser/utils/earlyinit.py index 7ae16dcfa..1565f805a 100644 --- a/qutebrowser/utils/earlyinit.py +++ b/qutebrowser/utils/earlyinit.py @@ -218,3 +218,37 @@ def check_pkg_resources(): msgbox.exec_() app.quit() sys.exit(1) + + +def check_rfc6266(): + """Check if rfc6266 is installed.""" + from PyQt5.QtWidgets import QApplication, QMessageBox + try: + import rfc6266 # pylint: disable=unused-variable + except ImportError: + app = QApplication(sys.argv) + msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!", + textwrap.dedent(""" + Fatal error: rfc6266 is required to run qutebrowser but could + not be imported! Maybe it's not installed? + + On Debian/Ubuntu: + No package available, try: + pip install rfc6266 + + On Archlinux: + pacman -S python-rfc6266 + + On Windows: + pip install rfc6266 + + For other distributions: + Check your package manager for similiarly named packages. + """).strip()) + if '--debug' in sys.argv: + print(file=sys.stderr) + traceback.print_exc() + msgbox.resize(msgbox.sizeHint()) + msgbox.exec_() + app.quit() + sys.exit(1) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 3ae182a5a..6d719a7ed 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -72,6 +72,7 @@ downloads = getLogger('downloads') js = getLogger('js') qt = getLogger('qt') style = getLogger('style') +rfc6266 = getLogger('rfc6266') ram_handler = None @@ -86,18 +87,37 @@ def init_log(args): raise ValueError("Invalid log level: {}".format(args.loglevel)) console, ram = _init_handlers(numeric_level, args.color, args.loglines) - if args.logfilter is not None and numeric_level <= logging.DEBUG: - console.addFilter(LogFilter(args.logfilter.split(','))) root = getLogger() if console is not None: + if args.logfilter is not None and numeric_level <= logging.DEBUG: + console.addFilter(LogFilter(args.logfilter.split(','))) + console.addFilter(LeplFilter()) root.addHandler(console) if ram is not None: root.addHandler(ram) + console.addFilter(LeplFilter()) root.setLevel(logging.NOTSET) logging.captureWarnings(True) qInstallMessageHandler(qt_message_handler) +def fix_rfc2622(): + """Fix the rfc6266 logger. + + In rfc2622 <= v0.04, a NullHandler class is added as handler, instead of an + object, which causes an exception later. + + This was fixed in [1], but since v0.05 is not out yet, we work around the + issue by deleting the wrong handler. + + This should be executed after init_log is done and rfc6266 is imported, but + before using it. + + [1]: https://github.com/g2p/rfc6266/commit/cad58963ed13f5e1068fcc9e4326123b6b2bdcf8 + """ + rfc6266.removeHandler(logging.NullHandler) + + def _init_handlers(level, color, ram_capacity): """Init log handlers. @@ -255,6 +275,22 @@ class LogFilter(logging.Filter): return False +class LeplFilter(logging.Filter): + + """Filter to filter debug log records by the lepl library.""" + + def filter(self, record): + """Determine if the specified record is to be logged.""" + if (record.levelno == logging.INFO and + record.name == 'lepl.lexer.rewriters.AddLexer'): + # Special useless info message triggered by rfc6266 + return False + if record.levelno > logging.DEBUG: + # More important than DEBUG, so we won't filter at all + return True + return not record.name.startswith('lepl.') + + class RAMHandler(logging.Handler): """Logging handler which keeps the messages in a deque in RAM. diff --git a/qutebrowser/utils/misc.py b/qutebrowser/utils/misc.py index 4e8173326..b042a9678 100644 --- a/qutebrowser/utils/misc.py +++ b/qutebrowser/utils/misc.py @@ -28,8 +28,6 @@ import os import re import sys import shlex -import email -import email.policy import os.path import operator import urllib.request @@ -397,33 +395,6 @@ def check_print_compat(): return not (os.name == 'nt' and qt_version_check('5.3.0', operator.lt)) -def get_http_header(reply, headername, param=None): - """Get a parameter from a HTTP header. - - Note we use the email value to get a HTTP header, because they're both MIME - headers and email supports that. - - Args: - reply: The QNetworkReply to get the header from. - headername: The name of the header. - param: The name of the param to get, or None to get the whole contents. - - Return: - The data as a string, or None if the data wasn't found. - - FIXME add tests - """ - if not reply.hasRawHeader(headername): - return None - header = (headername.encode('ascii') + b': ' + - bytes(reply.rawHeader(headername))) - msg = email.message_from_bytes(header, policy=email.policy.HTTP) - if param is not None: - return msg.get_param(param, header=headername) - else: - return msg.get(headername, None) - - class EventLoop(QEventLoop): """A thin wrapper around QEventLoop. diff --git a/scripts/setupcommon.py b/scripts/setupcommon.py index 52c1664a0..6863d2aba 100644 --- a/scripts/setupcommon.py +++ b/scripts/setupcommon.py @@ -92,6 +92,7 @@ setupdata = { "QtWebKit."), 'long_description': read_file('README'), 'url': 'http://www.qutebrowser.org/', + 'requires': 'rfc6266', 'author': _get_constant('author'), 'author_email': _get_constant('email'), 'license': _get_constant('license'),