From 44d1056e5497064b21f7c40f04fa640d8f834ad3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 7 Sep 2016 18:29:19 +0200 Subject: [PATCH] QtWebEngine: Implement custom HTTP headers --- doc/help/settings.asciidoc | 6 --- qutebrowser/browser/shared.py | 21 ++++++++ qutebrowser/browser/webengine/interceptor.py | 4 ++ .../browser/webkit/network/networkmanager.py | 22 ++------ qutebrowser/config/configdata.py | 9 ++-- scripts/dev/check_coverage.py | 2 + tests/end2end/features/misc.feature | 18 ++++++- tests/unit/browser/test_shared.py | 51 +++++++++++++++++++ 8 files changed, 102 insertions(+), 31 deletions(-) create mode 100644 tests/unit/browser/test_shared.py diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 3abf5fb34..18513ea84 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -711,16 +711,12 @@ Valid values: Default: +pass:[true]+ -This setting is only available with the QtWebKit backend. - [[network-accept-language]] === accept-language Value to send in the `accept-language` header. Default: +pass:[en-US,en]+ -This setting is only available with the QtWebKit backend. - [[network-referer-header]] === referer-header Send the Referer header @@ -804,8 +800,6 @@ Set custom headers for qutebrowser HTTP requests. Default: empty -This setting is only available with the QtWebKit backend. - == completion Options related to completion and command history. diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index e00bc584b..d2d9489ed 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -18,3 +18,24 @@ # along with qutebrowser. If not, see . """Various utilities shared between webpage/webview subclasses.""" + +from qutebrowser.config import config + + +def custom_headers(): + """Get the combined custom headers.""" + headers = {} + dnt = b'1' if config.get('network', 'do-not-track') else b'0' + headers[b'DNT'] = dnt + headers[b'X-Do-Not-Track'] = dnt + + custom_headers = config.get('network', 'custom-headers') + if custom_headers is not None: + for header, value in custom_headers.items(): + headers[header.encode('ascii')] = value.encode('ascii') + + accept_language = config.get('network', 'accept-language') + if accept_language is not None: + headers[b'Accept-Language'] = accept_language.encode('ascii') + + return sorted(headers.items()) diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py index 5a1c58fc9..82ee73f77 100644 --- a/qutebrowser/browser/webengine/interceptor.py +++ b/qutebrowser/browser/webengine/interceptor.py @@ -22,6 +22,7 @@ from PyQt5.QtWidgets import QApplication from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor +from qutebrowser.browser import shared from qutebrowser.utils import utils, log @@ -59,3 +60,6 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor): log.webview.info("Request to {} blocked by host blocker.".format( info.requestUrl().host())) info.block(True) + + for header, value in shared.custom_headers(): + info.setHttpHeader(header, value) diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index e2938d904..afeddcfd5 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -31,6 +31,7 @@ from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslError, from qutebrowser.config import config from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils, urlutils, debug) +from qutebrowser.browser import shared from qutebrowser.browser.webkit.network import qutescheme, networkreply from qutebrowser.browser.webkit.network import filescheme @@ -449,6 +450,9 @@ class NetworkManager(QNetworkAccessManager): if result is not None: return result + for header, value in shared.custom_headers(): + req.setRawHeader(header, value) + host_blocker = objreg.get('host-blocker') if (op == QNetworkAccessManager.GetOperation and host_blocker.is_blocked(req.url())): @@ -458,20 +462,6 @@ class NetworkManager(QNetworkAccessManager): req, HOSTBLOCK_ERROR_STRING, QNetworkReply.ContentAccessDenied, self) - if config.get('network', 'do-not-track'): - dnt = '1'.encode('ascii') - else: - dnt = '0'.encode('ascii') - req.setRawHeader('DNT'.encode('ascii'), dnt) - req.setRawHeader('X-Do-Not-Track'.encode('ascii'), dnt) - - # Load custom headers - custom_headers = config.get('network', 'custom-headers') - - if custom_headers is not None: - for header, value in custom_headers.items(): - req.setRawHeader(header.encode('ascii'), value.encode('ascii')) - # There are some scenarios where we can't figure out current_url: # - There's a generic NetworkManager, e.g. for downloads # - The download was in a tab which is now closed. @@ -490,10 +480,6 @@ class NetworkManager(QNetworkAccessManager): self.set_referer(req, current_url) - accept_language = config.get('network', 'accept-language') - if accept_language is not None: - req.setRawHeader('Accept-Language'.encode('ascii'), - accept_language.encode('ascii')) if PYQT_VERSION < 0x050301: # WORKAROUND (remove this when we bump the requirements to 5.3.1) # diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 2f6b699e1..3979edf19 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -388,13 +388,11 @@ def data(readonly=False): ('network', sect.KeyValue( ('do-not-track', - SettingValue(typ.Bool(), 'true', - backends=[usertypes.Backend.QtWebKit]), + SettingValue(typ.Bool(), 'true'), "Value to send in the `DNT` header."), ('accept-language', - SettingValue(typ.String(none_ok=True), 'en-US,en', - backends=[usertypes.Backend.QtWebKit]), + SettingValue(typ.String(none_ok=True), 'en-US,en'), "Value to send in the `accept-language` header."), ('referer-header', @@ -437,8 +435,7 @@ def data(readonly=False): "Whether to try to pre-fetch DNS entries to speed up browsing."), ('custom-headers', - SettingValue(typ.HeaderDict(none_ok=True), '', - backends=[usertypes.Backend.QtWebKit]), + SettingValue(typ.HeaderDict(none_ok=True), ''), "Set custom headers for qutebrowser HTTP requests."), readonly=readonly diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index 0e973b6bd..8529788f2 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -74,6 +74,8 @@ PERFECT_FILES = [ ('tests/unit/browser/test_signalfilter.py', 'qutebrowser/browser/signalfilter.py'), + ('tests/unit/browser/test_shared.py', + 'qutebrowser/browser/shared.py'), # ('tests/unit/browser/test_tab.py', # 'qutebrowser/browser/tab.py'), diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 0b24982e8..5551b9006 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -394,12 +394,28 @@ Feature: Various utility commands. ## Custom headers - @qtwebengine_todo: Custom headers are not implemented yet Scenario: Setting a custom header When I set network -> custom-headers to {"X-Qute-Test": "testvalue"} And I open headers Then the header X-Qute-Test should be set to testvalue + Scenario: DNT header + When I set network -> do-not-track to true + And I open headers + Then the header Dnt should be set to 1 + And the header X-Do-Not-Track should be set to 1 + + Scenario: DNT header (off) + When I set network -> do-not-track to false + And I open headers + Then the header Dnt should be set to 0 + And the header X-Do-Not-Track should be set to 0 + + Scenario: Accept-Language header + When I set network -> accept-language to en,de + And I open headers + Then the header Accept-Language should be set to en,de + ## :messages @qtwebengine_todo: qute:log is not implemented yet diff --git a/tests/unit/browser/test_shared.py b/tests/unit/browser/test_shared.py new file mode 100644 index 000000000..da81c2059 --- /dev/null +++ b/tests/unit/browser/test_shared.py @@ -0,0 +1,51 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Florian Bruhin (The Compiler) +# +# 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 . + +import pytest + +from qutebrowser.browser import shared + + +@pytest.mark.parametrize('dnt, accept_language, custom_headers, expected', [ + # DNT + (True, None, {}, {b'DNT': b'1', b'X-Do-Not-Track': b'1'}), + (False, None, {}, {b'DNT': b'0', b'X-Do-Not-Track': b'0'}), + # Accept-Language + (False, 'de, en', {}, {b'DNT': b'0', b'X-Do-Not-Track': b'0', + b'Accept-Language': b'de, en'}), + # Custom headers + (False, None, {'X-Qute': 'yes'}, {b'DNT': b'0', b'X-Do-Not-Track': b'0', + b'X-Qute': b'yes'}), + # Mixed + (False, 'de, en', {'X-Qute': 'yes'}, {b'DNT': b'0', + b'X-Do-Not-Track': b'0', + b'Accept-Language': b'de, en', + b'X-Qute': b'yes'}), +]) +def test_custom_headers(config_stub, dnt, accept_language, custom_headers, + expected): + config_stub.data = { + 'network': { + 'do-not-track': dnt, + 'accept-language': accept_language, + 'custom-headers': custom_headers, + } + } + expected_items = sorted(expected.items()) + assert shared.custom_headers() == expected_items