Add request filter API for host blocking

Closes https://github.com/qutebrowser/qutebrowser-extensions/issues/8
This commit is contained in:
Florian Bruhin 2018-12-10 14:39:07 +01:00
parent 3d6f604739
commit 7ad7623d73
7 changed files with 115 additions and 17 deletions

View File

@ -0,0 +1,37 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018 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/>.
"""APIs related to intercepting/blocking requests."""
import typing
import attr
from PyQt5.QtCore import QUrl
from qutebrowser.extensions import requests
# pylint: disable=unused-import
from qutebrowser.extensions.requests import Request
def register_filter(reqfilter: requests.RequestFilterType) -> None:
"""Register a request filter.
Whenever a request happens, the filter gets called with a Request object.
"""
requests.register_filter(reqfilter)

View File

@ -119,9 +119,15 @@ class HostBlocker:
return False return False
host = url.host() host = url.host()
return ((host in self._blocked_hosts or blocked = ((host in self._blocked_hosts or
host in self._config_blocked_hosts) and host in self._config_blocked_hosts) and
not _is_whitelisted_url(url)) not _is_whitelisted_url(url))
if blocked:
logger.info("Request to {} blocked by host blocker."
.format(url.host()))
return blocked
def _read_hosts_file(self, filename, target): def _read_hosts_file(self, filename, target):
"""Read hosts from the given filename. """Read hosts from the given filename.

View File

@ -26,15 +26,15 @@ from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.browser import shared from qutebrowser.browser import shared
from qutebrowser.utils import utils, log, debug from qutebrowser.utils import utils, log, debug
from qutebrowser.extensions import requests
class RequestInterceptor(QWebEngineUrlRequestInterceptor): class RequestInterceptor(QWebEngineUrlRequestInterceptor):
"""Handle ad blocking and custom headers.""" """Handle ad blocking and custom headers."""
def __init__(self, host_blocker, args, parent=None): def __init__(self, args, parent=None):
super().__init__(parent) super().__init__(parent)
self._host_blocker = host_blocker
self._args = args self._args = args
def install(self, profile): def install(self, profile):
@ -84,9 +84,10 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
return return
# FIXME:qtwebengine only block ads for NavigationTypeOther? # FIXME:qtwebengine only block ads for NavigationTypeOther?
if self._host_blocker.is_blocked(url, first_party): request = requests.Request(first_party_url=first_party,
log.webview.info("Request to {} blocked by host blocker.".format( request_url=url)
url.host())) requests.run_filters(request)
if request.is_blocked:
info.block(True) info.block(True)
for header, value in shared.custom_headers(url=url): for header, value in shared.custom_headers(url=url):

View File

@ -60,10 +60,8 @@ def init():
_qute_scheme_handler.install(webenginesettings.private_profile) _qute_scheme_handler.install(webenginesettings.private_profile)
log.init.debug("Initializing request interceptor...") log.init.debug("Initializing request interceptor...")
host_blocker = objreg.get('host-blocker')
args = objreg.get('args') args = objreg.get('args')
req_interceptor = interceptor.RequestInterceptor( req_interceptor = interceptor.RequestInterceptor(args=args, parent=app)
host_blocker, args=args, parent=app)
req_interceptor.install(webenginesettings.default_profile) req_interceptor.install(webenginesettings.default_profile)
req_interceptor.install(webenginesettings.private_profile) req_interceptor.install(webenginesettings.private_profile)

View File

@ -39,6 +39,7 @@ from PyQt5.QtCore import QUrl
from qutebrowser.browser import downloads from qutebrowser.browser import downloads
from qutebrowser.browser.webkit import webkitelem from qutebrowser.browser.webkit import webkitelem
from qutebrowser.utils import log, objreg, message, usertypes, utils, urlutils from qutebrowser.utils import log, objreg, message, usertypes, utils, urlutils
from qutebrowser.extensions import requests
@attr.s @attr.s
@ -354,8 +355,9 @@ class _Downloader:
# qute, see the comments/discussion on # qute, see the comments/discussion on
# https://github.com/qutebrowser/qutebrowser/pull/962#discussion_r40256987 # https://github.com/qutebrowser/qutebrowser/pull/962#discussion_r40256987
# and https://github.com/qutebrowser/qutebrowser/issues/1053 # and https://github.com/qutebrowser/qutebrowser/issues/1053
host_blocker = objreg.get('host-blocker') request = requests.Request(first_party_url=None, request_url=url)
if host_blocker.is_blocked(url): requests.run_filters(request)
if request.is_blocked:
log.downloads.debug("Skipping {}, host-blocked".format(url)) log.downloads.debug("Skipping {}, host-blocked".format(url))
# We still need an empty file in the output, QWebView can be pretty # We still need an empty file in the output, QWebView can be pretty
# picky about displaying a file correctly when not all assets are # picky about displaying a file correctly when not all assets are

View File

@ -38,6 +38,7 @@ if MYPY:
from qutebrowser.utils import (message, log, usertypes, utils, objreg, from qutebrowser.utils import (message, log, usertypes, utils, objreg,
urlutils, debug) urlutils, debug)
from qutebrowser.browser import shared from qutebrowser.browser import shared
from qutebrowser.extensions import requests
from qutebrowser.browser.webkit import certificateerror from qutebrowser.browser.webkit import certificateerror
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply, from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
filescheme) filescheme)
@ -405,10 +406,10 @@ class NetworkManager(QNetworkAccessManager):
# the webpage shutdown here. # the webpage shutdown here.
current_url = QUrl() current_url = QUrl()
host_blocker = objreg.get('host-blocker') request = requests.Request(first_party_url=current_url,
if host_blocker.is_blocked(req.url(), current_url): request_url=req.url())
log.webview.info("Request to {} blocked by host blocker.".format( requests.run_filters(request)
req.url().host())) if request.is_blocked:
return networkreply.ErrorNetworkReply( return networkreply.ErrorNetworkReply(
req, HOSTBLOCK_ERROR_STRING, QNetworkReply.ContentAccessDenied, req, HOSTBLOCK_ERROR_STRING, QNetworkReply.ContentAccessDenied,
self) self)

View File

@ -0,0 +1,53 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018 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/>.
"""Infrastructure for filtering requests."""
import typing
import attr
@attr.s
class Request:
"""A request which can be blocked."""
first_party_url = attr.ib() # type: QUrl
request_url = attr.ib() # type: QUrl
is_blocked = attr.ib(False) # type: bool
def block(self):
"""Block this request."""
self.is_blocked = True
RequestFilterType = typing.Callable[[Request], None]
_request_filters = [] # type: typing.List[RequestFilterType]
def register_filter(reqfilter: RequestFilterType) -> None:
_request_filters.append(reqfilter)
def run_filters(info):
for reqfilter in _request_filters:
reqfilter(info)