WebBrowser, NetworkUrlInterceptor: added code to control the sending of the "Referer" header (similiar to Firefox)

Thu, 10 May 2018 18:42:17 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 10 May 2018 18:42:17 +0200
changeset 6281
9f7bbfd6545d
parent 6280
1d4d790414b2
child 6282
ed511d6b2547

WebBrowser, NetworkUrlInterceptor: added code to control the sending of the "Referer" header (similiar to Firefox)

Preferences/ConfigurationPages/WebBrowserPage.py file | annotate | diff | comparison | revisions
Preferences/ConfigurationPages/WebBrowserPage.ui file | annotate | diff | comparison | revisions
Preferences/__init__.py file | annotate | diff | comparison | revisions
WebBrowser/Network/NetworkUrlInterceptor.py file | annotate | diff | comparison | revisions
--- 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>&lt;b&gt;Send Referer&lt;/b&gt;
+&lt;p&gt;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.&lt;/p&gt;</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>&lt;b&gt;Default Policy&lt;/b&gt;
+&lt;p&gt;Select when to send a referer header depending on origin.&lt;/p&gt;
+&lt;ul&gt;
+&lt;li&gt;no referer - don't send a referer header&lt;/li&gt;
+&lt;li&gt;only when same origin - don't send a referer header if the origin of the target is different&lt;/li&gt;
+&lt;li&gt;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&lt;/li&gt;
+&lt;li&gt;no referer when downgrading - don't send a referer header when the target is not potentially trustworthy&lt;/li&gt;
+&lt;/ul&gt;</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>&lt;b&gt;Trimming Policy&lt;/b&gt;
+&lt;p&gt;Select how the referer URL shall be trimmed. The selected rule is applied, when sending a complete referer URL.&lt;/p&gt;
+&lt;ul&gt;
+&lt;li&gt;send full URL (no trimming) - this does not perform any trimming beyond removing the user information and any fragment&lt;/li&gt;
+&lt;li&gt;send the URL without its query string - this removes the user information and the query string&lt;/li&gt;
+&lt;li&gt;only send the origin - this shortens the referer URL to the origin, i.e. scheme, host and port only&lt;/li&gt;
+&lt;/ul&gt;</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

eric ide

mercurial