summaryrefslogtreecommitdiff
path: root/qutebrowser/browser
diff options
context:
space:
mode:
Diffstat (limited to 'qutebrowser/browser')
-rw-r--r--qutebrowser/browser/qutescheme.py34
-rw-r--r--qutebrowser/browser/webengine/interceptor.py18
-rw-r--r--qutebrowser/browser/webengine/webenginequtescheme.py23
-rw-r--r--qutebrowser/browser/webkit/network/filescheme.py4
-rw-r--r--qutebrowser/browser/webkit/network/networkmanager.py14
-rw-r--r--qutebrowser/browser/webkit/network/webkitqutescheme.py29
6 files changed, 103 insertions, 19 deletions
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py
index 8866f1643..436a96e19 100644
--- a/qutebrowser/browser/qutescheme.py
+++ b/qutebrowser/browser/qutescheme.py
@@ -31,10 +31,18 @@ import textwrap
import mimetypes
import urllib
import collections
+import base64
+
+try:
+ import secrets
+except ImportError:
+ # New in Python 3.6
+ secrets = None
import pkg_resources
import sip
from PyQt5.QtCore import QUrlQuery, QUrl
+from PyQt5.QtNetwork import QNetworkReply
import qutebrowser
from qutebrowser.config import config, configdata, configexc, configdiff
@@ -45,6 +53,7 @@ from qutebrowser.misc import objects
pyeval_output = ":pyeval was never called"
spawn_output = ":spawn was never called"
+csrf_token = None
_HANDLERS = {}
@@ -447,13 +456,30 @@ def _qute_settings_set(url):
@add_handler('settings')
def qute_settings(url):
"""Handler for qute://settings. View/change qute configuration."""
+ global csrf_token
+
if url.path() == '/set':
+ if url.password() != csrf_token:
+ message.error("Invalid CSRF token for qute://settings!")
+ raise QuteSchemeError("Invalid CSRF token!",
+ QNetworkReply.ContentAccessDenied)
return _qute_settings_set(url)
- html = jinja.render('settings.html', title='settings',
- configdata=configdata,
- confget=config.instance.get_str)
- return 'text/html', html
+ # Requests to qute://settings/set should only be allowed from
+ # qute://settings. As an additional security precaution, we generate a CSRF
+ # token to use here.
+ if secrets:
+ csrf_token = secrets.token_urlsafe()
+ else:
+ # On Python < 3.6, from secrets.py
+ token = base64.urlsafe_b64encode(os.urandom(32))
+ csrf_token = token.rstrip(b'=').decode('ascii')
+
+ src = jinja.render('settings.html', title='settings',
+ configdata=configdata,
+ confget=config.instance.get_str,
+ csrf_token=csrf_token)
+ return 'text/html', src
@add_handler('bindings')
diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py
index 480e8ee85..80563f172 100644
--- a/qutebrowser/browser/webengine/interceptor.py
+++ b/qutebrowser/browser/webengine/interceptor.py
@@ -19,7 +19,9 @@
"""A request interceptor taking care of adblocking and custom headers."""
-from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
+from PyQt5.QtCore import QUrl
+from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
+ QWebEngineUrlRequestInfo)
from qutebrowser.config import config
from qutebrowser.browser import shared
@@ -54,6 +56,20 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
Args:
info: QWebEngineUrlRequestInfo &info
"""
+ url = info.requestUrl()
+ firstparty = info.firstPartyUrl()
+
+ if ((url.scheme(), url.host(), url.path()) ==
+ ('qute', 'settings', '/set')):
+ if (firstparty != QUrl('qute://settings/') or
+ info.resourceType() !=
+ QWebEngineUrlRequestInfo.ResourceTypeXhr):
+ log.webview.warning("Blocking malicious request from {} to {}"
+ .format(firstparty.toDisplayString(),
+ url.toDisplayString()))
+ info.block(True)
+ return
+
# FIXME:qtwebengine only block ads for NavigationTypeOther?
if self._host_blocker.is_blocked(info.requestUrl()):
log.webview.info("Request to {} blocked by host blocker.".format(
diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py
index ac583a671..04a89e2e2 100644
--- a/qutebrowser/browser/webengine/webenginequtescheme.py
+++ b/qutebrowser/browser/webengine/webenginequtescheme.py
@@ -45,8 +45,29 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
job: QWebEngineUrlRequestJob
"""
url = job.requestUrl()
- assert job.requestMethod() == b'GET'
+
+ # Only the browser itself or qute:// pages should access any of those
+ # URLs.
+ # The request interceptor further locks down qute://settings/set.
+ try:
+ initiator = job.initiator()
+ except AttributeError:
+ # Added in Qt 5.11
+ pass
+ else:
+ if initiator.isValid() and initiator.scheme() != 'qute':
+ log.misc.warning("Blocking malicious request from {} to {}"
+ .format(initiator.toDisplayString(),
+ url.toDisplayString()))
+ job.fail(QWebEngineUrlRequestJob.RequestDenied)
+ return
+
+ if job.requestMethod() != b'GET':
+ job.fail(QWebEngineUrlRequestJob.RequestDenied)
+ return
+
assert url.scheme() == 'qute'
+
log.misc.debug("Got request for {}".format(url.toDisplayString()))
try:
mimetype, data = qutescheme.data_for_url(url)
diff --git a/qutebrowser/browser/webkit/network/filescheme.py b/qutebrowser/browser/webkit/network/filescheme.py
index 840ed6a4a..a29674e25 100644
--- a/qutebrowser/browser/webkit/network/filescheme.py
+++ b/qutebrowser/browser/webkit/network/filescheme.py
@@ -111,11 +111,13 @@ def dirbrowser_html(path):
return html.encode('UTF-8', errors='xmlcharrefreplace')
-def handler(request):
+def handler(request, _operation, _current_url):
"""Handler for a file:// URL.
Args:
request: QNetworkRequest to answer to.
+ _operation: The HTTP operation being done.
+ _current_url: The page we're on currently.
Return:
A QNetworkReply for directories, None for files.
diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py
index 53508aaa6..a9a591b60 100644
--- a/qutebrowser/browser/webkit/network/networkmanager.py
+++ b/qutebrowser/browser/webkit/network/networkmanager.py
@@ -371,13 +371,6 @@ class NetworkManager(QNetworkAccessManager):
req, proxy_error, QNetworkReply.UnknownProxyError,
self)
- scheme = req.url().scheme()
- if scheme in self._scheme_handlers:
- result = self._scheme_handlers[scheme](req)
- if result is not None:
- result.setParent(self)
- return result
-
for header, value in shared.custom_headers():
req.setRawHeader(header, value)
@@ -406,5 +399,12 @@ class NetworkManager(QNetworkAccessManager):
# the webpage shutdown here.
current_url = QUrl()
+ scheme = req.url().scheme()
+ if scheme in self._scheme_handlers:
+ result = self._scheme_handlers[scheme](req, op, current_url)
+ if result is not None:
+ result.setParent(self)
+ return result
+
self.set_referer(req, current_url)
return super().createRequest(op, req, outgoing_data)
diff --git a/qutebrowser/browser/webkit/network/webkitqutescheme.py b/qutebrowser/browser/webkit/network/webkitqutescheme.py
index d732b6ab0..b6f99437a 100644
--- a/qutebrowser/browser/webkit/network/webkitqutescheme.py
+++ b/qutebrowser/browser/webkit/network/webkitqutescheme.py
@@ -21,27 +21,46 @@
import mimetypes
-from PyQt5.QtNetwork import QNetworkReply
+from PyQt5.QtCore import QUrl
+from PyQt5.QtNetwork import QNetworkReply, QNetworkAccessManager
from qutebrowser.browser import pdfjs, qutescheme
from qutebrowser.browser.webkit.network import networkreply
from qutebrowser.utils import log, usertypes, qtutils
-def handler(request):
+def handler(request, operation, current_url):
"""Scheme handler for qute:// URLs.
Args:
request: QNetworkRequest to answer to.
+ operation: The HTTP operation being done.
+ current_url: The page we're on currently.
Return:
A QNetworkReply.
"""
+ if operation != QNetworkAccessManager.GetOperation:
+ return networkreply.ErrorNetworkReply(
+ request, "Unsupported request type",
+ QNetworkReply.ContentOperationNotPermittedError)
+
+ url = request.url()
+
+ if ((url.scheme(), url.host(), url.path()) ==
+ ('qute', 'settings', '/set')):
+ if current_url != QUrl('qute://settings/'):
+ log.webview.warning("Blocking malicious request from {} to {}"
+ .format(current_url.toDisplayString(),
+ url.toDisplayString()))
+ return networkreply.ErrorNetworkReply(
+ request, "Invalid qute://settings request",
+ QNetworkReply.ContentAccessDenied)
+
try:
- mimetype, data = qutescheme.data_for_url(request.url())
+ mimetype, data = qutescheme.data_for_url(url)
except qutescheme.NoHandlerFound:
- errorstr = "No handler found for {}!".format(
- request.url().toDisplayString())
+ errorstr = "No handler found for {}!".format(url.toDisplayString())
return networkreply.ErrorNetworkReply(
request, errorstr, QNetworkReply.ContentNotFoundError)
except qutescheme.QuteSchemeOSError as e: