Thu, 10 May 2018 18:42:17 +0200
WebBrowser, NetworkUrlInterceptor: added code to control the sending of the "Referer" header (similiar to Firefox)
--- a/Preferences/ConfigurationPages/WebBrowserPage.py Thu May 10 18:38:06 2018 +0200 +++ b/Preferences/ConfigurationPages/WebBrowserPage.py Thu May 10 18:42:17 2018 +0200 @@ -91,8 +91,12 @@ Preferences.getWebBrowser("PluginsEnabled")) self.doNotTrackCheckBox.setChecked( Preferences.getWebBrowser("DoNotTrack")) - self.sendRefererCheckBox.setChecked( - Preferences.getWebBrowser("SendReferer")) + self.refererSendComboBox.setCurrentIndex( + Preferences.getWebBrowser("RefererSendReferer")) + self.refererDefaultPolicyCcomboBox.setCurrentIndex( + Preferences.getWebBrowser("RefererDefaultPolicy")) + self.refererTrimmingPolicyComboBox.setCurrentIndex( + Preferences.getWebBrowser("RefererTrimmingPolicy")) self.diskCacheCheckBox.setChecked( Preferences.getWebBrowser("DiskCacheEnabled")) @@ -260,8 +264,14 @@ "DoNotTrack", self.doNotTrackCheckBox.isChecked()) Preferences.setWebBrowser( - "SendReferer", - self.sendRefererCheckBox.isChecked()) + "RefererSendReferer", + self.refererSendComboBox.currentIndex()) + Preferences.setWebBrowser( + "RefererDefaultPolicy", + self.refererDefaultPolicyCcomboBox.currentIndex()) + Preferences.setWebBrowser( + "RefererTrimmingPolicy", + self.refererTrimmingPolicyComboBox.currentIndex()) Preferences.setWebBrowser( "DiskCacheEnabled",
--- a/Preferences/ConfigurationPages/WebBrowserPage.ui Thu May 10 18:38:06 2018 +0200 +++ b/Preferences/ConfigurationPages/WebBrowserPage.ui Thu May 10 18:42:17 2018 +0200 @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>616</width> + <width>650</width> <height>2000</height> </rect> </property> @@ -448,41 +448,173 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <item> - <widget class="QCheckBox" name="sendRefererCheckBox"> - <property name="toolTip"> - <string>Select to send referer headers to the server</string> - </property> - <property name="text"> - <string>Send Referer header to servers</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_6"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="refererWhitelistButton"> - <property name="toolTip"> - <string>Press to edit the list of whitelisted hosts</string> - </property> - <property name="text"> - <string>Edit Referer Whitelist ...</string> - </property> - </widget> - </item> - </layout> + <widget class="QGroupBox" name="groupBox_9"> + <property name="title"> + <string>Referer Headers</string> + </property> + <layout class="QGridLayout" name="gridLayout_7"> + <item row="0" column="0"> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>Send Referer:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="refererSendComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select when to send a referer header (independent of origin)</string> + </property> + <property name="whatsThis"> + <string><b>Send Referer</b> +<p>Select, when to send a referer header. This is independent of the requested URL. If a referer header is to be sent, further rules will be applied.</p></string> + </property> + <item> + <property name="text"> + <string>never</string> + </property> + </item> + <item> + <property name="text"> + <string>on click only</string> + </property> + </item> + <item> + <property name="text"> + <string>always</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Default Policy:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="refererDefaultPolicyCcomboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select when to send a referer header depending on origin</string> + </property> + <property name="whatsThis"> + <string><b>Default Policy</b> +<p>Select when to send a referer header depending on origin.</p> +<ul> +<li>no referer - don't send a referer header</li> +<li>only when same origin - don't send a referer header if the origin of the target is different</li> +<li>only origin when cross origin - send trimmed referer header only when same origin, the referer origin when the target is potentially trustworthy and no header otherwise</li> +<li>no referer when downgrading - don't send a referer header when the target is not potentially trustworthy</li> +</ul></string> + </property> + <item> + <property name="text"> + <string>no referer</string> + </property> + </item> + <item> + <property name="text"> + <string>only when same origin</string> + </property> + </item> + <item> + <property name="text"> + <string>only origin when cross origin</string> + </property> + </item> + <item> + <property name="text"> + <string>no referer when downgrading</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Trimming Policy:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="refererTrimmingPolicyComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select how the referer URL shall be trimmed</string> + </property> + <property name="whatsThis"> + <string><b>Trimming Policy</b> +<p>Select how the referer URL shall be trimmed. The selected rule is applied, when sending a complete referer URL.</p> +<ul> +<li>send full URL (no trimming) - this does not perform any trimming beyond removing the user information and any fragment</li> +<li>send the URL without its query string - this removes the user information and the query string</li> +<li>only send the origin - this shortens the referer URL to the origin, i.e. scheme, host and port only</li> +</ul></string> + </property> + <item> + <property name="text"> + <string>send full URL (no trimming)</string> + </property> + </item> + <item> + <property name="text"> + <string>send the URL without its query string</string> + </property> + </item> + <item> + <property name="text"> + <string>only send the origin</string> + </property> + </item> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>268</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="refererWhitelistButton"> + <property name="toolTip"> + <string>Press to edit the list of whitelisted hosts</string> + </property> + <property name="text"> + <string>Edit Referer Whitelist ...</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> </item> </layout> </widget> @@ -969,7 +1101,9 @@ <tabstop>jsClipboardCheckBox</tabstop> <tabstop>pluginsCheckBox</tabstop> <tabstop>doNotTrackCheckBox</tabstop> - <tabstop>sendRefererCheckBox</tabstop> + <tabstop>refererSendComboBox</tabstop> + <tabstop>refererDefaultPolicyCcomboBox</tabstop> + <tabstop>refererTrimmingPolicyComboBox</tabstop> <tabstop>refererWhitelistButton</tabstop> <tabstop>xssAuditingCheckBox</tabstop> <tabstop>insecureContentsCheckBox</tabstop>
--- a/Preferences/__init__.py Thu May 10 18:38:06 2018 +0200 +++ b/Preferences/__init__.py Thu May 10 18:42:17 2018 +0200 @@ -1093,7 +1093,9 @@ "DiskCacheSize": 50, # 50 MB "SslExceptionsDB": "{}", # empty JSON dictionary "DoNotTrack": False, - "SendReferer": True, + "RefererSendReferer": 2, # send always + "RefererDefaultPolicy": 3, # don't send a referer when downgrading + "RefererTrimmingPolicy": 0, # send full URL (no trimming) "SendRefererWhitelist": ["qt-apps.org", "kde-apps.org"], "AcceptCookies": 2, # CookieJar.AcceptOnlyFromSitesNavigatedTo "KeepCookiesUntil": 0, # CookieJar.KeepUntilExpire @@ -2994,6 +2996,8 @@ "DiskCacheSize", "AcceptCookies", "KeepCookiesUntil", "AdBlockUpdatePeriod", "TabManagerGroupByType", "SessionAutoSaveInterval", "NewTabBehavior", + "RefererSendReferer", "RefererDefaultPolicy", + "RefererTrimmingPolicy", ]: return int(prefClass.settings.value( "WebBrowser/" + key, prefClass.webBrowserDefaults[key])) @@ -3010,7 +3014,7 @@ "SyncPasswords", "SyncUserAgents", "SyncSpeedDial", "SyncEncryptData", "SyncEncryptPasswordsOnly", "ShowPreview", "WebInspectorEnabled", "DiskCacheEnabled", - "DoNotTrack", "SendReferer", "FilterTrackingCookies", + "DoNotTrack", "FilterTrackingCookies", "AdBlockEnabled", "AdBlockUseLimitedEasyList", "PluginsEnabled", "FullScreenSupportEnabled", "AutoScrollEnabled", "ScreenCaptureEnabled",
--- 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