diff -r f99d60d6b59b -r 2602857055c5 eric6/WebBrowser/Network/NetworkUrlInterceptor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/WebBrowser/Network/NetworkUrlInterceptor.py Sun Apr 14 15:09:21 2019 +0200 @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a class to handle URL requests before they get processed +by QtWebEngine. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QMutex, QMutexLocker, QUrl +from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor, \ + QWebEngineUrlRequestInfo + +from ..WebBrowserPage import WebBrowserPage + +import Preferences + + +class NetworkUrlInterceptor(QWebEngineUrlRequestInterceptor): + """ + Class implementing an URL request handler. + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object + @type QObject + """ + super(NetworkUrlInterceptor, self).__init__(parent) + + self.__interceptors = [] + self.__mutex = QMutex() + + self.__loadSettings() + + def interceptRequest(self, info): + """ + Public method handling an URL request. + + @param info URL request information + @type QWebEngineUrlRequestInfo + """ + locker = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ + + # Do Not Track feature + if self.__doNotTrack: + info.setHttpHeader(b"DNT", b"1") + info.setHttpHeader(b"X-Do-Not-Track", b"1") + + # Send referrer header? + if info.requestUrl().host() not in \ + Preferences.getWebBrowser("SendRefererWhitelist"): + self.__setRefererHeader(info) + + # User Agents header + userAgent = WebBrowserPage.userAgentForUrl(info.requestUrl()) + info.setHttpHeader(b"User-Agent", userAgent.encode()) + + for interceptor in self.__interceptors: + interceptor.interceptRequest(info) + + def installUrlInterceptor(self, interceptor): + """ + Public method to install an URL interceptor. + + @param interceptor URL interceptor to be installed + @type UrlInterceptor + """ + locker = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ + + if interceptor not in self.__interceptors: + self.__interceptors.append(interceptor) + + def removeUrlInterceptor(self, interceptor): + """ + Public method to remove an URL interceptor. + + @param interceptor URL interceptor to be removed + @type UrlInterceptor + """ + locker = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ + + if interceptor in self.__interceptors: + self.__interceptors.remove(interceptor) + + def __loadSettings(self): + """ + Private method to load the Network Manager settings. + """ + locker = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ + + self.__doNotTrack = Preferences.getWebBrowser("DoNotTrack") + self.__sendReferer = Preferences.getWebBrowser("RefererSendReferer") + self.__refererDefaultPolicy = \ + Preferences.getWebBrowser("RefererDefaultPolicy") + self.__refererTrimmingPolicy = \ + Preferences.getWebBrowser("RefererTrimmingPolicy") + + def preferencesChanged(self): + """ + Public slot to handle a change of preferences. + """ + self.__loadSettings() + + def __setRefererHeader(self, info): + """ + Private method to set the 'Referer' header depending on the configured + rule set. + + @param info URL request information + @type QWebEngineUrlRequestInfo + @see <a href="https://wiki.mozilla.org/Security/Referrer"> + Mozilla Referrer</a> + @see <a href="https://www.w3.org/TR/referrer-policy/"> + W3C Referrer Policy</a> + """ + # 1. SendReferer: + # 0 = never + # 1 = only on click (NavigationTypeLink) + # 2 = always (default) + # 2. RefererTrimmingPolicy: + # 0 = send full URL (no trimming) (default) + # 1 = send the URL without its query string + # 2 = only send the origin (ensure trailing /) + # 3. RefererDefaultPolicy: + # set the default referrer policy (which can be overriden by + # the site) + # 0 = no-referrer + # 1 = same-origin + # 2 = strict-origin-when-cross-origin + # 3 = no-referrer-when-downgrade (default) + # see: https://wiki.mozilla.org/Security/Referrer + # see: https://www.w3.org/TR/referrer-policy/ + + if self.__sendReferer == 0: + # never send referer header + info.setHttpHeader(b"Referer", b"") + elif (self.__sendReferer == 1 and + info.navigationType() != + QWebEngineUrlRequestInfo.NavigationTypeLink): + # send referer header only on click + info.setHttpHeader(b"Referer", b"") + else: + # send referer header always applying further policies + url = info.firstPartyUrl() + reqUrl = info.requestUrl() + if self.__refererDefaultPolicy == 0: + # no-referrer + refererUrl = b"" + elif self.__refererDefaultPolicy == 1: + # same-origin + if self.__sameOrigin(url, reqUrl): + refererUrl = self.__trimmedReferer(url) + else: + refererUrl = b"" + elif self.__refererDefaultPolicy == 2: + # strict-origin-when-cross-origin + if self.__sameOrigin(url, reqUrl): + refererUrl = self.__trimmedReferer(url) + elif url.scheme() in ("https", "wss"): + if self.__potentiallyTrustworthy(url): + refererUrl = self.__refererOrigin(url) + else: + refererUrl = b"" + else: + refererUrl = self.__refererOrigin(url) + else: + # no-referrer-when-downgrade + if url.scheme() in ("https", "wss") and \ + not self.__potentiallyTrustworthy(url): + refererUrl = b"" + else: + refererUrl = self.__trimmedReferer(url) + + info.setHttpHeader(b"Referer", refererUrl) + + def __sameOrigin(self, url1, url2): + """ + Private method to test the "same origin" policy. + + @param url1 first URL for the test + @type QUrl + @param url2 second URL for the test + @type QUrl + @return flag indicating that both URLs have the same origin + @rtype bool + """ + origin1 = url1.url(QUrl.RemoveUserInfo | QUrl.RemovePath) + origin2 = url2.url(QUrl.RemoveUserInfo | QUrl.RemovePath) + + return origin1 == origin2 + + def __potentiallyTrustworthy(self, url): + """ + Private method to check, if the given URL is potentially trustworthy. + + @param url URL to be checked + @type QUrl + @return flag indicating a potentially trustworthy URL + @rtype bool + """ + if url.scheme() == "data": + return False + + if url.toString() in ("about:blank", "about:srcdoc"): + return True + + origin = url.adjusted(QUrl.RemoveUserInfo | QUrl.RemovePath) + + if origin.isEmpty() or origin.scheme() == "": + return False + if origin.scheme() in ("https", "wss"): + return True + if origin.host().startswith("127.") or origin.host().endswith(":1"): + return True + if origin.host() == "localhost" or \ + origin.host().endswith(".localhost"): + return True + if origin.scheme() == "file": + return True + if origin.scheme() in ("qrc", "qthelp", "eric"): + return True + + return False + + def __trimmedReferer(self, url): + """ + Private method to generate the trimmed referer header URL. + + @param url URL to be trimmed as a referer header + @type QUrl + @return trimmed referer header URL + @rtype QByteArray or bytes + """ + if self.__refererTrimmingPolicy == 0: + # send full URL (no trimming) (default) + refererUrl = url.toEncoded( + QUrl.RemoveUserInfo | QUrl.RemoveFragment) + elif self.__refererTrimmingPolicy == 1: + # send the URL without its query string + refererUrl = url.toEncoded( + QUrl.RemoveUserInfo | QUrl.RemoveFragment | + QUrl.RemoveQuery) + else: + # only send the origin (ensure trailing /) + refererUrl = self.__refererOrigin(url) + + return refererUrl + + def __refererOrigin(self, url): + """ + Private method to generate an origin referer header URL. + + @param url URL to generate the header from + @type QUrl + @return origin referer header URL + @rtype QByteArray or bytes + """ + referer = url.toEncoded(QUrl.RemoveUserInfo | QUrl.RemovePath) + if not referer.endsWith(b"/"): + referer += b"/" + + return referer