--- a/WebBrowser/Network/NetworkUrlInterceptor.py Thu May 10 18:38:06 2018 +0200 +++ b/WebBrowser/Network/NetworkUrlInterceptor.py Thu May 10 18:42:17 2018 +0200 @@ -10,8 +10,9 @@ from __future__ import unicode_literals -from PyQt5.QtCore import QMutex, QMutexLocker -from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor +from PyQt5.QtCore import QMutex, QMutexLocker, QUrl +from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor, \ + QWebEngineUrlRequestInfo from ..WebBrowserPage import WebBrowserPage @@ -62,35 +63,6 @@ for interceptor in self.__interceptors: interceptor.interceptRequest(info) - def __setRefererHeader(self, info): - """ - Private method to set the 'Referer' header depending on the configured - rule set. - - @param info URL request information - @type QWebEngineUrlRequestInfo - """ - # TODO: extend referrer handling like that: - # 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 not self.__sendReferer: - info.setHttpHeader(b"Referer", b"") - def installUrlInterceptor(self, interceptor): """ Public method to install an URL interceptor. @@ -122,10 +94,174 @@ locker = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ self.__doNotTrack = Preferences.getWebBrowser("DoNotTrack") - self.__sendReferer = Preferences.getWebBrowser("SendReferer") + 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