VERSION 28

Remove tracking parameters arising from search query URL-tampering
performed by the Google, Bing, Yahoo 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)

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 kitkat-orig/external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceFetcher.cpp kitkat/external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceFetcher.cpp
--- kitkat-orig/external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceFetcher.cpp	2023-10-01 17:07:37.410379513 +0200
+++ kitkat/external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceFetcher.cpp	2023-08-25 22:38:50.945835230 +0200
@@ -607,6 +607,10 @@ ResourcePtr<Resource> ResourceFetcher::r
 
     KURL url = request.resourceRequest().url();
 
+    // Remove Google, Bing, Yahoo, Startpage and Yandex tracking parameters from the URL
+    WTF::sanitizeUrl(url);
+    request.mutableResourceRequest().setURL(url);
+
     WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource '%s', charset '%s', priority=%d, forPreload=%u, type=%s", url.elidedString().latin1().data(), request.charset().latin1().data(), request.priority(), request.forPreload(), ResourceTypeName(type));
 
     // If only the fragment identifiers differ, it is the same resource.
diff -pru kitkat-orig/external/chromium_org/third_party/WebKit/Source/wtf/InternetDomain.cpp kitkat/external/chromium_org/third_party/WebKit/Source/wtf/InternetDomain.cpp
--- kitkat-orig/external/chromium_org/third_party/WebKit/Source/wtf/InternetDomain.cpp	2023-10-01 17:07:37.410379513 +0200
+++ kitkat/external/chromium_org/third_party/WebKit/Source/wtf/InternetDomain.cpp	2023-10-01 17:18:01.325397125 +0200
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "InternetDomain.h"
 
+#include "platform/weborigin/KURL.h"
 #include "wtf/text/StringBuilder.h"
 #include "wtf/Vector.h"
 
@@ -59,4 +60,451 @@ String topDomain(const String& domain)
     return stringBuilder.toString();
 }
 
+size_t hostIsGoogle(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    host.split('.', hostVector);
+    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;
+
+    if (hostVector[1].lower() == "google")
+        return size;
+
+    return 0;
+}
+
+size_t hostIsBing(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    host.split('.', hostVector);
+    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;
+
+    if (hostVector[0].lower() == "www" && hostVector[1].lower() == "bing")
+        return size;
+
+    return 0;
+}
+
+size_t hostIsYahoo(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    host.split('.', hostVector);
+    size = hostVector.size();
+
+    // host is of length 3 or 4
+    if (size < 3 || size > 4)
+        return 0;
+
+    if (hostVector[size-3].lower() == "search" && hostVector[size-2].lower() == "yahoo" && hostVector[size-1].lower() == "com")
+        return size;
+
+    return 0;
+}
+
+size_t hostIsStartpage(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    host.split('.', hostVector);
+    size = hostVector.size();
+
+    // host is of length 3
+    if (size != 3)
+        return 0;
+
+    if (hostVector[1].lower() == "startpage" && hostVector[2].lower() == "com")
+        return size;
+
+    return 0;
+}
+
+size_t hostIsYandex(const String& host)
+{
+    size_t size;
+    Vector<String> hostVector;
+
+    host.split('.', hostVector);
+    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;
+
+    if (hostVector[0].lower() == "yandex" || hostVector[1].lower() == "yandex")
+        return size;
+
+    return 0;
+}
+
+// URL percent decoding
+void percentDecodeUrl(String& url)
+{
+    // The exclamation mark might be encoded
+    url.replace("%21", "!");
+
+    // The hash character might be encoded
+    url.replace("%23", "#");
+
+    // The dollar sign might be encoded
+    url.replace("%24", "$");
+
+    // The ampersand might be encoded
+    url.replace("%26", "&");
+
+    // The quote might be encoded
+    url.replace("%27", "'");
+
+    // The left parenthesis might be encoded
+    url.replace("%28", "(");
+
+    // The right parenthesis might be encoded
+    url.replace("%29", ")");
+
+    // The asterisk might be encoded
+    url.replace("%2A", "*");
+
+    // The plus sign might be encoded
+    url.replace("%2B", "+");
+
+    // The comma might be encoded
+    url.replace("%2C", ",");
+
+    // The slash might be encoded
+    url.replace("%2F", "/");
+
+    // The colon might be encoded
+    url.replace("%3A", ":");
+
+    // The semi-colon might be encoded
+    url.replace("%3B", ";");
+
+    // The equal sign might be encoded
+    url.replace("%3D", "=");
+
+    // The question mark might be encoded
+    url.replace("%3F", "?");
+
+    // The at sign might be encoded
+    url.replace("%40", "@");
+
+    // The left squared-parenthesis might be encoded
+    url.replace("%5B", "[");
+
+    // The right squared-parenthesis might be encoded
+    url.replace("%5D", "]");
+
+    // Decode unreserved characters
+    url.replace("%22", "\"");
+    url.replace("%25", "%");
+    url.replace("%2D", "-");
+    url.replace("%2E", ".");
+    url.replace("%3C", "<");
+    url.replace("%3E", ">");
+    url.replace("%5C", "\\");
+    url.replace("%5E", "^");
+    url.replace("%5F", "_");
+    url.replace("%60", "`");
+    url.replace("%7B", "{");
+    url.replace("%7C", "|");
+    url.replace("%7D", "}");
+    url.replace("%7E", "~");
+}
+
+// Remove Google, Bing, Yahoo, Startpage and Yandex tracking parameters from the URL
+void sanitizeUrl(WebCore::KURL& 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())) > 0) {
+        urlString = url.query();
+
+        urlString.split('&', urlQueryVector);
+        size = urlQueryVector.size();
+
+        if (!url.path().startsWith("/url", true) && !url.path().startsWith("/scholar", true) && !url.path().startsWith("/travel", true) && !url.host().startsWith("translate", true)) { // starts with "/search" or another string
+            if (size <= 1)
+                return;
+
+            firstQueryElement = true;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith("q=", true) ||
+                        urlQueryVector[i].startsWith("start=", true) ||
+                        urlQueryVector[i].startsWith("hl=", true) ||
+                        urlQueryVector[i].startsWith("tbm=", true) ||
+                        urlQueryVector[i].startsWith("nfpr=", true)) {
+                    if (!firstQueryElement)
+                        stringBuilder.append('&');
+                    stringBuilder.append(urlQueryVector[i]);
+                    firstQueryElement = false;
+                }
+            }
+
+            url.setQuery(stringBuilder.toString().isEmpty() ? String() : String(stringBuilder.toString()));
+        } else if (url.path().startsWith("/url", true) && !url.host().startsWith("translate", true)) { // it is a referral to an external URL (starts with "/url")
+            foundReferral = false;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith("q=", true)) {
+                    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;
+
+            percentDecodeUrl(referralUrl);
+
+            referralUrl.split('/', referralUrlVector);
+            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("/");
+                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
+            stringBuilder.toString().split('#', true, referralUrlFragmentIdentifierVector);
+            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
+            referralUrlFragmentIdentifierVector[0].split('?', true, referralUrlPathQueryVector);
+            if (!referralUrlPathQueryVector[0].isEmpty())
+                url.setPath(referralUrlPathQueryVector[0]);
+            else
+                url.setPath("/");
+
+            // 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().startsWith("/scholar_url", true) && !url.host().startsWith("translate", true)) { // scholar
+            foundReferral = false;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith("url=", true)) {
+                    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;
+
+            percentDecodeUrl(referralUrl);
+
+            referralUrl.split('/', referralUrlVector);
+            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("/");
+                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
+            stringBuilder.toString().split('#', true, referralUrlFragmentIdentifierVector);
+            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
+            referralUrlFragmentIdentifierVector[0].split('?', true, referralUrlPathQueryVector);
+            if (!referralUrlPathQueryVector[0].isEmpty())
+                url.setPath(referralUrlPathQueryVector[0]);
+            else
+                url.setPath("/");
+
+            // Check if the standard URL contains a query
+            if (referralUrlPathQueryVector.size() > 1)
+                url.setQuery(referralUrlPathQueryVector[1]);
+            else
+                url.setQuery(String());
+        }
+    } else if (url.host().startsWith("translate", true)) { // translate
+            if (size <= 1)
+                return;
+
+            firstQueryElement = true;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith("text=", true) ||
+                        urlQueryVector[i].startsWith("sl=", true) ||
+                        urlQueryVector[i].startsWith("tl=", true) ||
+                        urlQueryVector[i].startsWith("op=", true)) {
+                    if (!firstQueryElement)
+                        stringBuilder.append('&');
+                    stringBuilder.append(urlQueryVector[i]);
+                    firstQueryElement = false;
+                }
+            }
+
+            url.setQuery(stringBuilder.toString().isEmpty() ? String() : String(stringBuilder.toString()));
+    } else if ((urlsize = hostIsBing(url.host())) > 0) {
+        if (url.hasPath()) {
+            if (!url.path().startsWith("/search", true) && !url.path().startsWith("/rewardsapp/reportActivity", true))
+                return;
+
+            url.query().split('&', urlQueryVector);
+            size = urlQueryVector.size();
+
+            if (size <= 1)
+                return;
+
+            firstQueryElement = true;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith("q=", true) ||
+                        urlQueryVector[i].startsWith("first=", true)) {
+                    if (!firstQueryElement)
+                        stringBuilder.append('&');
+                    stringBuilder.append(urlQueryVector[i]);
+                    firstQueryElement = false;
+                }
+            }
+            
+            url.setQuery(stringBuilder.toString());
+        }
+    } else if ((urlsize = hostIsYahoo(url.host())) > 0) {
+        if (url.hasPath()) {
+            if (!url.path().startsWith("/search", true))
+                return;
+
+            url.setPath("/search");
+
+            url.query().split('&', urlQueryVector);
+            size = urlQueryVector.size();
+
+            if (size <= 1)
+                return;
+
+            firstQueryElement = true;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith("p=", true) ||
+                        urlQueryVector[i].startsWith("b=", true)) {
+                    if (!firstQueryElement)
+                        stringBuilder.append('&');
+                    stringBuilder.append(urlQueryVector[i]);
+                    firstQueryElement = false;
+                }
+            }
+            
+            url.setQuery(stringBuilder.toString());
+        }
+    } else if ((urlsize = hostIsStartpage(url.host())) > 0) {
+        if (url.hasPath() && (url.path() == "/" || url.path() == "/do/settings" || url.path() == "/suggestions" || (url.path().startsWith("/sp/") && url.path() != "/sp/search" && url.path() != "/sp/captcha-block#"))) {
+            url.setQuery(String());
+        } else if (url.path() == "/sp/search" || url.path() == "/do/search") {
+            url.query().split('&', urlQueryVector);
+            size = urlQueryVector.size();
+
+            if (size <= 1)
+                return;
+
+            firstQueryElement = true;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith("query=", true) ||
+                        urlQueryVector[i].startsWith("page=", true)) {
+                    if (!firstQueryElement)
+                        stringBuilder.append('&');
+                    stringBuilder.append(urlQueryVector[i]);
+                    firstQueryElement = false;
+                }
+            }
+            
+            url.setQuery(stringBuilder.toString());
+        }
+    } else if ((urlsize = hostIsYandex(url.host())) > 0) {
+        if (url.path().startsWith("/clck/safeclick/data=")) {
+            url.setPath("/");
+            return;
+        }
+
+        if (url.hasPath()) {
+            if (!url.path().startsWith("/search", true))
+                return;
+
+            url.query().split('&', urlQueryVector);
+            size = urlQueryVector.size();
+
+            if (size <= 1)
+                return;
+
+            firstQueryElement = true;
+            for (i = 0; i < size; ++i) {
+                if (urlQueryVector[i].startsWith("text=", true) ||
+                        urlQueryVector[i].startsWith("p=", true)) {
+                    if (!firstQueryElement)
+                        stringBuilder.append('&');
+                    stringBuilder.append(urlQueryVector[i]);
+                    firstQueryElement = false;
+                }
+            }
+            
+            url.setQuery(stringBuilder.toString());
+        }
+    }
+}
+
 } // namespace WTF
diff -pru kitkat-orig/external/chromium_org/third_party/WebKit/Source/wtf/InternetDomain.h kitkat/external/chromium_org/third_party/WebKit/Source/wtf/InternetDomain.h
--- kitkat-orig/external/chromium_org/third_party/WebKit/Source/wtf/InternetDomain.h	2023-10-01 17:07:37.410379513 +0200
+++ kitkat/external/chromium_org/third_party/WebKit/Source/wtf/InternetDomain.h	2023-08-25 22:38:50.946835230 +0200
@@ -25,9 +25,21 @@
 #include "wtf/text/WTFString.h"
 #include "wtf/WTFExport.h"
 
+namespace WebCore {
+
+class KURL;
+
+} // namespace WebCore
+
 namespace WTF {
 
 WTF_EXPORT String topDomain(const String& domain);
+size_t hostIsGoogle(const String& host);
+size_t hostIsBing(const String& host);
+size_t hostIsYahoo(const String& host);
+size_t hostIsStartpage(const String& host);
+size_t hostIsYandex(const String& host);
+WTF_EXPORT void sanitizeUrl(WebCore::KURL& url);
 
 } // namespace WTF
 
