VERSION 28

Should compile cleanly on any WebKit release >= 2.38.3

Remove tracking parameters arising from search query URL-tampering
performed by the Google, Bing and Yandex search engines.

Requires patch to disable CORS in order to build cleanly, although it
does not strictly depend on it.

Since the second version (v2) it also prevents Google from tracking
selected search results.

Fixed parsing of Google.co.uk in hostIsGoogle() and hostIsBing() (v12).

Improved filtering for Bing (v12).

Google: sanitize not only www.google.* but all *.google.* (v16)

Remove tracking parameters for Yandex (v17)

Remove tracking parameters for Yahoo (v18)

Sanitize Startpage URL (v21)

Compatibility with Google Scholar (v26 and v27)

Compatibility with Google Translate and Google Flights (v28)

To avoid any tracking issue with similar end-of-life products, DuckDuckGo
or Startpage search engines are recommended, as they are neat, simple and
the former works even without Javascript.

Guido Trentalancia

diff -pru webkitgtk-2.44.2-orig/Source/WebCore/loader/cache/CachedResourceLoader.cpp webkitgtk-2.44.2/Source/WebCore/loader/cache/CachedResourceLoader.cpp
--- webkitgtk-2.44.2-orig/Source/WebCore/loader/cache/CachedResourceLoader.cpp	2024-06-24 17:06:47.845961479 +0200
+++ webkitgtk-2.44.2/Source/WebCore/loader/cache/CachedResourceLoader.cpp	2024-06-24 17:24:54.338992149 +0200
@@ -86,7 +86,10 @@
 #include "UserStyleSheet.h"
 #include <pal/SessionID.h>
 #include <wtf/text/CString.h>
+#include <wtf/text/StringBuilder.h>
+#include <wtf/text/StringView.h>
 #include <wtf/text/WTFString.h>
+#include <wtf/Vector.h>
 
 #if ENABLE(APPLICATION_MANIFEST)
 #include "CachedApplicationManifest.h"
@@ -981,9 +984,462 @@ RefPtr<DocumentLoader> CachedResourceLoa
     return m_documentLoader.get();
 }
 
+static size_t hostIsGoogle(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    hostVector = host.split((UChar) '.');
+    size = hostVector.size();
+
+    // host is generally of length 3 with the exception of google.co.uk and maybe a few others
+    if (size < 3 || size > 4)
+        return 0;
+
+    StringView *subdomain1 = new StringView(hostVector[0]);
+    StringView *subdomain2 = new StringView(hostVector[1]);
+    if (equal(*subdomain2, WTF::StringView::fromLatin1("google")))
+        return size;
+
+    return 0;
+}
+
+static size_t hostIsBing(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    hostVector = host.split((UChar) '.');
+    size = hostVector.size();
+
+    // host is generally of length 3 with the exception of bing.co.uk and maybe a few others
+    if (size < 3 || size > 4)
+        return 0;
+
+    StringView *subdomain3 = new StringView(hostVector[0]);
+    StringView *subdomain2 = new StringView(hostVector[1]);
+    if (equal(*subdomain3, WTF::StringView::fromLatin1("www")) && equal(*subdomain2, WTF::StringView::fromLatin1("bing")))
+        return size;
+
+    return 0;
+}
+
+static size_t hostIsYahoo(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    hostVector = host.split((UChar) '.');
+    size = hostVector.size();
+
+    // host is of length 3 or 4
+    if (size < 3 || size > 4)
+        return 0;
+
+    if (equal(StringView(hostVector[size-3]), WTF::StringView::fromLatin1("search")) && equal(StringView(hostVector[size-2]), WTF::StringView::fromLatin1("yahoo")) && equal(StringView(hostVector[size-1]), WTF::StringView::fromLatin1("com")))
+        return size;
+
+    return 0;
+}
+
+static size_t hostIsStartpage(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    hostVector = host.split((UChar) '.');
+    size = hostVector.size();
+
+    // host is of length 3
+    if (size != 3)
+        return 0;
+
+    if (equal(StringView(hostVector[1]), WTF::StringView::fromLatin1("startpage")) && equal(StringView(hostVector[2]), WTF::StringView::fromLatin1("com")))
+        return size;
+
+    return 0;
+}
+
+static size_t hostIsYandex(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    hostVector = host.split((UChar) '.');
+    size = hostVector.size();
+
+    // host is generally of length 2 with the exception of yandex.co.uk and maybe a few others
+    if (size < 2 || size > 4)
+        return 0;
+
+    StringView *subdomain1 = new StringView(hostVector[0]);
+    StringView *subdomain2 = new StringView(hostVector[1]);
+    if (equal(*subdomain1, WTF::StringView::fromLatin1("yandex")) || equal(*subdomain2, WTF::StringView::fromLatin1("yandex")))
+        return size;
+
+    return 0;
+}
+
+// URL percent decoding
+String percentDecodeUrl(const String& url)
+{
+    // The exclamation mark might be encoded
+    String string0 = makeStringByReplacingAll(url, WTF::StringView::fromLatin1("%21"), WTF::StringView::fromLatin1("!"));
+
+    // The hash character might be encoded
+    String string1 = makeStringByReplacingAll(string0, WTF::StringView::fromLatin1("%23"), WTF::StringView::fromLatin1("#"));
+
+    // The dollar sign might be encoded
+    String string2 = makeStringByReplacingAll(string1, WTF::StringView::fromLatin1("%24"), WTF::StringView::fromLatin1("$"));
+
+    // The ampersand might be encoded
+    String string3 = makeStringByReplacingAll(string2, WTF::StringView::fromLatin1("%26"), WTF::StringView::fromLatin1("&"));
+
+    // The quote might be encoded
+    String string4 = makeStringByReplacingAll(string3, WTF::StringView::fromLatin1("%27"), WTF::StringView::fromLatin1("'"));
+
+    // The left parenthesis might be encoded
+    String string5 = makeStringByReplacingAll(string4, WTF::StringView::fromLatin1("%28"), WTF::StringView::fromLatin1("("));
+
+    // The right parenthesis might be encoded
+    String string6 = makeStringByReplacingAll(string5, WTF::StringView::fromLatin1("%29"), WTF::StringView::fromLatin1(")"));
+
+    // The asterisk might be encoded
+    String string7 = makeStringByReplacingAll(string6, WTF::StringView::fromLatin1("%2A"), WTF::StringView::fromLatin1("*"));
+
+    // The plus sign might be encoded
+    String string8 = makeStringByReplacingAll(string7, WTF::StringView::fromLatin1("%2B"), WTF::StringView::fromLatin1("+"));
+
+    // The comma might be encoded
+    String string9 = makeStringByReplacingAll(string8, WTF::StringView::fromLatin1("%2C"), WTF::StringView::fromLatin1(","));
+
+    // The slash might be encoded
+    String string10 = makeStringByReplacingAll(string9, WTF::StringView::fromLatin1("%2F"), WTF::StringView::fromLatin1("/"));
+
+    // The colon might be encoded
+    String string11 = makeStringByReplacingAll(string10, WTF::StringView::fromLatin1("%3A"), WTF::StringView::fromLatin1(":"));
+
+    // The semi-colon might be encoded
+    String string12 = makeStringByReplacingAll(string11, WTF::StringView::fromLatin1("%3B"), WTF::StringView::fromLatin1(";"));
+
+    // The equal sign might be encoded
+    String string13 = makeStringByReplacingAll(string12, WTF::StringView::fromLatin1("%3D"), WTF::StringView::fromLatin1("="));
+
+    // The question mark might be encoded
+    String string14 = makeStringByReplacingAll(string13, WTF::StringView::fromLatin1("%3F"), WTF::StringView::fromLatin1("?"));
+
+    // The at sign might be encoded
+    String string15 = makeStringByReplacingAll(string14, WTF::StringView::fromLatin1("%40"), WTF::StringView::fromLatin1("@"));
+
+    // The left squared-parenthesis might be encoded
+    String string16 = makeStringByReplacingAll(string15, WTF::StringView::fromLatin1("%5B"), WTF::StringView::fromLatin1("["));
+
+    // The right squared-parenthesis might be encoded
+    String string17 = makeStringByReplacingAll(string16, WTF::StringView::fromLatin1("%5D"), WTF::StringView::fromLatin1("]"));
+
+    // Decode unreserved characters
+    String string18 = makeStringByReplacingAll(string17, WTF::StringView::fromLatin1("%22"), WTF::StringView::fromLatin1("\""));
+    String string19 = makeStringByReplacingAll(string18, WTF::StringView::fromLatin1("%25"), WTF::StringView::fromLatin1("%"));
+    String string20 = makeStringByReplacingAll(string19, WTF::StringView::fromLatin1("%2D"), WTF::StringView::fromLatin1("-"));
+    String string21 = makeStringByReplacingAll(string20, WTF::StringView::fromLatin1("%2E"), WTF::StringView::fromLatin1("."));
+    String string22 = makeStringByReplacingAll(string21, WTF::StringView::fromLatin1("%3C"), WTF::StringView::fromLatin1("<"));
+    String string23 = makeStringByReplacingAll(string22, WTF::StringView::fromLatin1("%3E"), WTF::StringView::fromLatin1(">"));
+    String string24 = makeStringByReplacingAll(string23, WTF::StringView::fromLatin1("%5C"), WTF::StringView::fromLatin1("\\"));
+    String string25 = makeStringByReplacingAll(string24, WTF::StringView::fromLatin1("%5E"), WTF::StringView::fromLatin1("^"));
+    String string26 = makeStringByReplacingAll(string25, WTF::StringView::fromLatin1("%5F"), WTF::StringView::fromLatin1("_"));
+    String string27 = makeStringByReplacingAll(string26, WTF::StringView::fromLatin1("%60"), WTF::StringView::fromLatin1("`"));
+    String string28 = makeStringByReplacingAll(string27, WTF::StringView::fromLatin1("%7B"), WTF::StringView::fromLatin1("{"));
+    String string29 = makeStringByReplacingAll(string28, WTF::StringView::fromLatin1("%7C"), WTF::StringView::fromLatin1("|"));
+    String string30 = makeStringByReplacingAll(string29, WTF::StringView::fromLatin1("%7D"), WTF::StringView::fromLatin1("}"));
+    String string31 = makeStringByReplacingAll(string30, WTF::StringView::fromLatin1("%7E"), WTF::StringView::fromLatin1("~"));
+
+    return string31;
+}
+
+// Remove Google, Bing, Yahoo, Startpage and Yandex tracking parameters from the URL
+static void sanitizeUrl(URL& url)
+{
+    size_t i, size, urlsize;
+    Vector<String> urlQueryVector;
+    Vector<String> referralUrlVector;
+    Vector<String> referralUrlPathQueryVector;
+    Vector<String> referralUrlFragmentIdentifierVector;
+    bool foundReferral, firstQueryElement;
+    String urlString;
+    String referralUrl;
+    StringBuilder stringBuilder;
+
+    if ((urlsize = hostIsGoogle(url.host().toString())) > 0) {
+        urlString = url.query().toString();
+
+        urlQueryVector = urlString.split((UChar) '&');
+        size = urlQueryVector.size();
+
+        if (!url.path().toString().startsWith(WTF::StringView::fromLatin1("/url")) && !url.path().toString().startsWith(WTF::StringView::fromLatin1("/scholar")) && !url.path().toString().startsWith(WTF::StringView::fromLatin1("/travel")) && !url.host().toString().startsWith(WTF::StringView::fromLatin1("translate"))) { // starts with "/search" or another string
+            if (size <= 1)
+                return;
+
+            firstQueryElement = true;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("q=")) ||
+                        urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("start=")) ||
+                        urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("hl=")) ||
+                        urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("tbm=")) ||
+                        urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("nfpr="))) {
+                    if (!firstQueryElement)
+                        stringBuilder.append('&');
+                    stringBuilder.append(urlQueryVector[i]);
+                    firstQueryElement = false;
+                }
+            }
+            url.setQuery(stringBuilder.toString().isEmpty() ? StringView() : StringView(stringBuilder.toString()));
+        } else if (url.path().toString().startsWith(WTF::StringView::fromLatin1("/url")) && !url.host().toString().startsWith(WTF::StringView::fromLatin1("translate"))) { // it is a referral to an external URL (starts with "/url")
+            foundReferral = false;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("q="))) {
+                    referralUrl = urlQueryVector[i].right(urlQueryVector[i].length()-2);
+                    if (referralUrl.length() > 0) {
+                        foundReferral = true;
+                        break;
+                    }
+                }
+            }
+
+            // Apparently it cannot be converted to a standard URL
+            if (!foundReferral)
+                return;
+
+            String referralUrl1 = percentDecodeUrl(referralUrl);
+
+            referralUrlVector = referralUrl1.split((UChar) '/');
+            size = referralUrlVector.size();
+            if (size < 2)
+                return;
+
+            url.setProtocol(referralUrlVector[0].left(referralUrlVector[0].length()-1));
+            url.setHostAndPort(referralUrlVector[1]);
+
+            // The standard URL contains an empty path and does not contain a query
+            if (size < 3) {
+                url.setPath(WTF::StringView::fromLatin1("/"));
+                url.setQuery(String());
+                return;
+            }
+
+            // Rebuild the path eventually followed by the query and the fragment identifier
+            stringBuilder.append(referralUrlVector[2]);
+            for (i = urlsize; i < size; ++i) {
+                stringBuilder.append('/');
+                stringBuilder.append(referralUrlVector[i]);
+            }
+
+            // Determine the fragment identifier, if present
+            referralUrlFragmentIdentifierVector = stringBuilder.toString().splitAllowingEmptyEntries((UChar) '#');
+            if (referralUrlFragmentIdentifierVector.size() > 1 && !referralUrlFragmentIdentifierVector[1].isEmpty())
+                url.setFragmentIdentifier(referralUrlFragmentIdentifierVector[1]);
+            else
+                url.removeFragmentIdentifier();
+
+            // Determine the path and eventually the query of the standard URL
+            referralUrlPathQueryVector = referralUrlFragmentIdentifierVector[0].splitAllowingEmptyEntries((UChar) '?');
+            if (!referralUrlPathQueryVector[0].isEmpty())
+                url.setPath(referralUrlPathQueryVector[0]);
+            else
+                url.setPath(WTF::StringView::fromLatin1("/"));
+
+            // Check if the standard URL contains a query
+            if (referralUrlPathQueryVector.size() > 1)
+                url.setQuery(referralUrlPathQueryVector[1]);
+            else
+                url.setQuery(String());
+
+            // Further sanitize the URL, it might be a redirect to a Scholar link
+            sanitizeUrl(url);
+        } else if (url.path().toString().startsWith(WTF::StringView::fromLatin1("/scholar_url")) && !url.host().toString().startsWith(WTF::StringView::fromLatin1("translate"))) { // scholar
+            foundReferral = false;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("url="))) {
+                    referralUrl = urlQueryVector[i].right(urlQueryVector[i].length()-4);
+                    if (referralUrl.length() > 0) {
+                        foundReferral = true;
+                        break;
+                    }
+                }
+            }
+
+            // Apparently it cannot be converted to a standard URL
+            if (!foundReferral)
+                return;
+
+            String referralUrl1 = percentDecodeUrl(referralUrl);
+
+            referralUrlVector = referralUrl1.split((UChar) '/');
+            size = referralUrlVector.size();
+            if (size < 2)
+                return;
+
+            url.setProtocol(referralUrlVector[0].left(referralUrlVector[0].length()-1));
+            url.setHostAndPort(referralUrlVector[1]);
+
+            // The standard URL contains an empty path and does not contain a query
+            if (size < 3) {
+                url.setPath(WTF::StringView::fromLatin1("/"));
+                url.setQuery(String());
+                return;
+            }
+
+            // Rebuild the path eventually followed by the query and the fragment identifier
+            stringBuilder.append(referralUrlVector[2]);
+            for (i = urlsize; i < size; ++i) {
+                stringBuilder.append('/');
+                stringBuilder.append(referralUrlVector[i]);
+            }
+
+            // Determine the fragment identifier, if present
+            referralUrlFragmentIdentifierVector = stringBuilder.toString().splitAllowingEmptyEntries((UChar) '#');
+            if (referralUrlFragmentIdentifierVector.size() > 1 && !referralUrlFragmentIdentifierVector[1].isEmpty())
+                url.setFragmentIdentifier(referralUrlFragmentIdentifierVector[1]);
+            else
+                url.removeFragmentIdentifier();
+
+            // Determine the path and eventually the query of the standard URL
+            referralUrlPathQueryVector = referralUrlFragmentIdentifierVector[0].splitAllowingEmptyEntries((UChar) '?');
+            if (!referralUrlPathQueryVector[0].isEmpty())
+                url.setPath(referralUrlPathQueryVector[0]);
+            else
+                url.setPath(WTF::StringView::fromLatin1("/"));
+
+            // Check if the standard URL contains a query
+            if (referralUrlPathQueryVector.size() > 1)
+                url.setQuery(referralUrlPathQueryVector[1]);
+            else
+                url.setQuery(String());
+        }
+    } else if (url.host().toString().startsWith(WTF::StringView::fromLatin1("translate"))) {
+            if (size <= 1)
+                return;
+
+            firstQueryElement = true;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("text=")) ||
+                        urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("sl=")) ||
+                        urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("tl=")) ||
+                        urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("op="))) {
+                    if (!firstQueryElement)
+                        stringBuilder.append('&');
+                    stringBuilder.append(urlQueryVector[i]);
+                    firstQueryElement = false;
+                }
+            }
+            url.setQuery(stringBuilder.toString().isEmpty() ? StringView() : StringView(stringBuilder.toString()));
+    } else if ((urlsize = hostIsBing(url.host().toString())) > 0) {
+        if (!url.path().toString().startsWith(WTF::StringView::fromLatin1("/search")) &&
+            !url.path().toString().startsWith(WTF::StringView::fromLatin1("/rewardsapp/reportActivity")))
+            return;
+
+        urlQueryVector = url.query().toString().split((UChar) '&');
+        size = urlQueryVector.size();
+
+        if (size <= 1)
+            return;
+
+        firstQueryElement = true;
+        for (i = 0; i < size; ++i) {
+            if (urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("q=")) ||
+                    urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("first="))) {
+                if (!firstQueryElement)
+                    stringBuilder.append('&');
+                stringBuilder.append(urlQueryVector[i]);
+                firstQueryElement = false;
+            }
+        }
+
+        url.setQuery(stringBuilder.toString());
+    } else if ((urlsize = hostIsYahoo(url.host().toString())) > 0) {
+        if (!url.path().toString().startsWith(WTF::StringView::fromLatin1("/search")))
+            return;
+
+        url.setPath(WTF::StringView::fromLatin1("/search"));
+
+        urlQueryVector = url.query().toString().split((UChar) '&');
+        size = urlQueryVector.size();
+
+        if (size <= 1)
+            return;
+
+        firstQueryElement = true;
+        for (i = 0; i < size; ++i) {
+            if (urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("p=")) ||
+                    urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("b="))) {
+                if (!firstQueryElement)
+                    stringBuilder.append('&');
+                stringBuilder.append(urlQueryVector[i]);
+                firstQueryElement = false;
+            }
+        }
+
+        url.setQuery(stringBuilder.toString());
+    } else if ((urlsize = hostIsStartpage(url.host().toString())) > 0) {
+        if (url.hasPath() && (equal(url.path().toString(), WTF::StringView::fromLatin1("/")) || equal(url.path().toString(), WTF::StringView::fromLatin1("/do/settings")) || equal(url.path().toString(), WTF::StringView::fromLatin1("/suggestions")) || (url.path().toString().startsWith(WTF::StringView::fromLatin1("/sp/")) && !equal(url.path().toString(), WTF::StringView::fromLatin1("/sp/search")) && !equal(url.path().toString(), WTF::StringView::fromLatin1("/sp/captcha-block#"))))) {
+            url.setQuery(String());
+        } else if (url.hasPath() && (equal(url.path().toString(), WTF::StringView::fromLatin1("/sp/search")) || equal(url.path().toString(), WTF::StringView::fromLatin1("/do/search")))) {
+            urlQueryVector = url.query().toString().split((UChar) '&');
+            size = urlQueryVector.size();
+
+            if (size <= 1)
+                return;
+
+            firstQueryElement = true;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("query=")) ||
+                        urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("page="))) {
+                    if (!firstQueryElement)
+                        stringBuilder.append('&');
+                    stringBuilder.append(urlQueryVector[i]);
+                    firstQueryElement = false;
+                }
+            }
+
+            url.setQuery(stringBuilder.toString());
+        }
+    } else if ((urlsize = hostIsYandex(url.host().toString())) > 0) {
+        if (url.path().toString().startsWith(WTF::StringView::fromLatin1("/clck/safeclick/data="))) {
+            url.setPath(WTF::StringView::fromLatin1("/"));
+            return;
+        }
+
+        if (!url.path().toString().startsWith(WTF::StringView::fromLatin1("/search")))
+            return;
+
+        urlQueryVector = url.query().toString().split((UChar) '&');
+        size = urlQueryVector.size();
+
+        if (size <= 1)
+            return;
+
+        firstQueryElement = true;
+        for (i = 0; i < size; ++i) {
+            if (urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("text=")) ||
+                    urlQueryVector[i].startsWith(WTF::StringView::fromLatin1("p="))) {
+                if (!firstQueryElement)
+                    stringBuilder.append('&');
+                stringBuilder.append(urlQueryVector[i]);
+                firstQueryElement = false;
+            }
+        }
+
+        url.setQuery(stringBuilder.toString());
+    }
+}
+
 ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ForPreload forPreload, ImageLoading imageLoading)
 {
     URL url = request.resourceRequest().url();
+
+    // Remove Google, Bing, Yahoo, Startpage and Yandex tracking parameters from the URL
+    sanitizeUrl(url);
+    request.mutableResourceRequest().setURL(url);
+
     if (!frame() || !frame()->page()) {
         CACHEDRESOURCELOADER_RELEASE_LOG("requestResource: failed because no frame or page");
         return makeUnexpected(ResourceError { errorDomainWebKitInternal, 0, url, "Invalid loader state"_s });
diff -pru webkitgtk-2.44.2-orig/Source/WebCore/loader/cache/CachedResourceRequest.h webkitgtk-2.44.2/Source/WebCore/loader/cache/CachedResourceRequest.h
--- webkitgtk-2.44.2-orig/Source/WebCore/loader/cache/CachedResourceRequest.h	2024-06-24 17:06:47.845961479 +0200
+++ webkitgtk-2.44.2/Source/WebCore/loader/cache/CachedResourceRequest.h	2024-05-16 17:50:06.494314007 +0200
@@ -52,6 +52,7 @@ public:
     CachedResourceRequest(ResourceRequest&&, const ResourceLoaderOptions&, std::optional<ResourceLoadPriority> = std::nullopt, String&& charset = String());
 
     ResourceRequest&& releaseResourceRequest() { return WTFMove(m_resourceRequest); }
+    ResourceRequest& mutableResourceRequest() { return m_resourceRequest; }
     const ResourceRequest& resourceRequest() const { return m_resourceRequest; }
     ResourceRequest& resourceRequest() { return m_resourceRequest; }
 
