Files
cromite/build/patches/Bromite-AdBlock-engine-for-SystemWebView.patch
T
2019-09-16 08:39:49 +02:00

573 lines
18 KiB
Diff

From: csagan5 <32685696+csagan5@users.noreply.github.com>
Date: Sat, 14 Sep 2019 10:14:54 +0200
Subject: Bromite AdBlock engine for SystemWebView
---
android_webview/browser/net/aw_network_delegate.cc | 71 ++++
android_webview/browser/net/aw_network_delegate.h | 3 +
net/BUILD.gn | 7 +
net/url_request/adblock_intercept.cc | 389 +++++++++++++++++++++
net/url_request/adblock_intercept.h | 35 ++
5 files changed, 505 insertions(+)
create mode 100644 net/url_request/adblock_intercept.cc
create mode 100644 net/url_request/adblock_intercept.h
diff --git a/android_webview/browser/net/aw_network_delegate.cc b/android_webview/browser/net/aw_network_delegate.cc
--- a/android_webview/browser/net/aw_network_delegate.cc
+++ b/android_webview/browser/net/aw_network_delegate.cc
@@ -20,9 +20,11 @@
#include "net/base/proxy_server.h"
#include "net/http/http_response_headers.h"
#include "net/proxy_resolution/proxy_info.h"
+#include "net/url_request/adblock_intercept.h"
#include "net/url_request/url_request.h"
using content::BrowserThread;
+using content::ResourceRequestInfo;
namespace android_webview {
@@ -49,6 +51,75 @@ AwNetworkDelegate::AwNetworkDelegate() {}
AwNetworkDelegate::~AwNetworkDelegate() {
}
+#define TRANSPARENT1PXGIF "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
+#define EMPTYJS "data:text/javascript;base64,Cg=="
+#define EMPTYCSS "data:text/css;base64,Cg=="
+
+static bool requestIntercepted(net::URLRequest* request, GURL* new_url) {
+ bool block = false, isValidUrl;
+
+ // skip invalid URLs and browser-initiated requests (which have no initiator)
+ auto initiator = request->initiator();
+ isValidUrl = request->url().is_valid() && initiator.has_value();
+ std::string scheme = request->url().scheme();
+ if (isValidUrl && scheme.length()) {
+ std::transform(scheme.begin(), scheme.end(), scheme.begin(), ::tolower);
+ if ("http" != scheme && "https" != scheme) {
+ isValidUrl = false;
+ }
+ }
+ ResourceRequestInfo* info;
+ if (isValidUrl) {
+ info = ResourceRequestInfo::ForRequest(request);
+ }
+
+ // there are no per-site nor global ad/content settings when using the SystemWebView
+ bool adblock_enabled = true;
+ if (isValidUrl && info) {
+ auto resource_type = info->GetResourceType();
+
+ if (adblock_enabled
+ && content::ResourceType::kMainFrame != resource_type
+ && net::adblock_intercept(request->url(), initiator->host(), resource_type)) {
+ block = true;
+ }
+
+ if (block) {
+ switch (resource_type) {
+ case content::ResourceType::kImage:
+ case content::ResourceType::kFavicon:
+ *new_url = GURL(TRANSPARENT1PXGIF);
+ break;
+ case content::ResourceType::kScript:
+ *new_url = GURL(EMPTYJS);
+ break;
+ case content::ResourceType::kStylesheet:
+ *new_url = GURL(EMPTYCSS);
+ break;
+ default:
+ *new_url = GURL("");
+ return true;
+ }
+ }
+ } // valid URL and info
+ return false;
+}
+#undef TRANSPARENT1PXGIF
+#undef EMPTYJS
+#undef EMPTYCSS
+
+int AwNetworkDelegate::OnBeforeURLRequest(net::URLRequest* request,
+ net::CompletionOnceCallback callback,
+ GURL* new_url) {
+ if (request) {
+ // most requests will be modified rather than intercepted
+ if (requestIntercepted(request, new_url))
+ return net::ERR_BLOCKED_BY_ADMINISTRATOR;
+ } // request
+
+ return net::OK;
+}
+
int AwNetworkDelegate::OnBeforeStartTransaction(
net::URLRequest* request,
net::CompletionOnceCallback callback,
diff --git a/android_webview/browser/net/aw_network_delegate.h b/android_webview/browser/net/aw_network_delegate.h
--- a/android_webview/browser/net/aw_network_delegate.h
+++ b/android_webview/browser/net/aw_network_delegate.h
@@ -22,6 +22,9 @@ class AwNetworkDelegate : public net::NetworkDelegateImpl {
private:
// NetworkDelegate implementation.
+ int OnBeforeURLRequest(net::URLRequest* request,
+ net::CompletionOnceCallback callback,
+ GURL* new_url) override;
int OnBeforeStartTransaction(net::URLRequest* request,
net::CompletionOnceCallback callback,
net::HttpRequestHeaders* headers) override;
diff --git a/net/BUILD.gn b/net/BUILD.gn
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -1842,6 +1842,13 @@ component("net") {
"url_request/websocket_handshake_userdata_key.h",
]
+ if (is_android) {
+ sources += [
+ "url_request/adblock_intercept.cc",
+ "url_request/adblock_intercept.h"
+ ]
+ }
+
if (enable_reporting) {
sources += [
"network_error_logging/network_error_logging_service.cc",
diff --git a/net/url_request/adblock_intercept.cc b/net/url_request/adblock_intercept.cc
new file mode 100644
--- /dev/null
+++ b/net/url_request/adblock_intercept.cc
@@ -0,0 +1,389 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "adblock_intercept.h"
+
+#ifdef ADB_TESTER
+#include <cstddef>
+#include <cstdlib>
+#include <string.h>
+
+#include "log.h"
+#include <tld.h>
+
+#else
+
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include <android/log.h>
+
+#endif
+
+#include "net/url_request/adblock_entries.h"
+
+namespace net {
+
+const char *LOG_TAG = "Bromite";
+
+#ifdef ADB_TESTER
+int adblock_rules_count() { return ADBLOCK_ENTRY_COUNT; }
+#endif
+
+// True if the given canonical |host| is "[www.]<domain_in_lower_case>.<TLD>"
+// with a valid TLD. If |subdomain_permission| is ALLOW_SUBDOMAIN, we check
+// against host "*.<domain_in_lower_case>.<TLD>" instead. Will return the TLD
+// string in |tld|, if specified and the |host| can be parsed.
+static bool is_first_party(const char *l_host, int len, const char *l_url_host,
+ int b_len) {
+ size_t tld_length;
+
+#ifdef ADB_TESTER
+ char *found_tld;
+
+ if (TLD_SUCCESS != tld_get_z(l_host, &found_tld))
+ return false;
+ tld_length = strlen(found_tld);
+ if (tld_length == 0)
+ return false;
+#else
+ tld_length = net::registry_controlled_domains::GetCanonicalHostRegistryLength(
+ l_host, net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+ if ((tld_length == 0) || (tld_length == std::string::npos))
+ return false;
+#endif
+
+ const char *tld = l_host + len - tld_length;
+
+ // Removes any subdomain from origin host.
+ int i = len - tld_length - 2, top_i = i;
+ if (i < 0) {
+ return false;
+ }
+ const char *domain = l_host;
+ for (; i >= 0; i--) {
+ if (l_host[i] == '.') {
+ int p_len = top_i - i;
+ // skip "co" in "co.uk", "org" in "org.uk"
+ if (p_len <= 3) {
+ tld -= p_len + 1;
+ continue;
+ }
+
+ // segment is long enough, accept it at as a domain
+ domain = l_host + i;
+ len -= i;
+ break;
+ }
+ }
+
+#ifdef ADBLOCK_LOG
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
+ "%s: extracted domain suffix: \"%s\" (TLD=\"%s\")",
+ l_host, domain, tld);
+#endif
+
+ // Check if supplied URL host matches, including the dot.
+ if (b_len < len) {
+ return false;
+ }
+ for (int i = 0; i < len; i++) {
+ if (l_url_host[b_len - 1 - i] != domain[len - 1 - i])
+ return false;
+ }
+
+ // pass with flying colors
+ return true;
+}
+
+static char *strtolower(const char *str, int len, bool &should_free) {
+ char *ret = NULL;
+ for (int i = 0; i < len; i++) {
+ if ((65 <= str[i]) && (str[i] <= 90)) {
+ // first time a difference is found, allocate buffer and copy previous
+ // characters (if any)
+ if (ret == NULL) {
+ ret = (char *)malloc(len + 1);
+ if (i != 0)
+ memcpy(ret, str, i);
+ }
+ // convert character to lower case
+ ret[i] = str[i] + 32;
+ } else {
+ if (ret != NULL)
+ ret[i] = str[i];
+ }
+ }
+
+ if (ret != NULL) {
+ ret[len] = '\0';
+ should_free = true;
+ return ret;
+ }
+
+ // return source, unchanged
+ return (char *)str;
+}
+
+static char *strtosep(const char *str, int len) {
+ char *ret = (char *)malloc(len + 3);
+ ret[0] = '^';
+ for (int i = 0; i < len; i++) {
+ if ((str[i] == ':') || (str[i] == '/') || (str[i] == '?') ||
+ (str[i] == '&') || (str[i] == '=')) {
+ ret[i + 1] = '^';
+ } else {
+ ret[i + 1] = str[i];
+ }
+ }
+ // the index 'len' character is set by the previous loop
+ ret[len + 1] = '^';
+ ret[len + 2] = '\0';
+ return ret;
+}
+
+static bool url_matches(const char *c_url, char *c_url_sep, char *c_url_lower,
+ char *c_url_lower_sep, adblock_entry *entry) {
+ bool match = false;
+ // select comparison string based on case and separator presence (separator
+ // takes some shortcuts)
+ bool match_case = ((entry->flags & ADBLOCK_FLAG_MATCH_CASE) != 0);
+ bool match_separator = ((entry->flags & ADBLOCK_FLAG_HAS_SEPARATOR) != 0);
+ const char *match_url =
+ match_case ? (match_separator ? c_url_sep : c_url)
+ : (match_separator ? c_url_lower_sep : c_url_lower);
+
+#ifdef ADBLOCK_LOG_MORE
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "[case:%d][sep:%d][%s]",
+ match_case, match_separator, match_url);
+#endif
+ // check for all match parts at >= position of last match
+ const char *last = match_url;
+ for (int m = 0; const char *url_match = entry->matches[m]; m++) {
+ bool is_last_match = entry->matches[m + 1] == NULL;
+ const char *pos = strstr(last, url_match);
+ match = (pos != NULL);
+
+#ifdef ADBLOCK_LOG_MORE
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "[%s][found:%d][match:%d]",
+ entry->matches[m], pos == NULL ? 0 : 1, match ? 1 : 0);
+#endif
+ // check if the url starts with the first match part
+ if (match && (m == 0) && ((entry->flags & ADBLOCK_FLAG_MATCH_BEGIN) != 0) &&
+ (pos != match_url))
+ match = false;
+
+ // check if the url ends with the last match part
+ if (match && is_last_match &&
+ ((entry->flags & ADBLOCK_FLAG_MATCH_END) != 0) &&
+ (pos != &match_url[strlen(match_url) - strlen(entry->matches[m])]))
+ match = false;
+
+ // check domain match
+ if (match && (m == 0) &&
+ ((entry->flags & ADBLOCK_FLAG_MATCH_DOMAIN) != 0) &&
+ (pos != match_url) && (pos[-1] != '^') && (pos[-1] != '.') &&
+ (pos[-1] != '/'))
+ match = false;
+
+ // short circuit
+ if (!match)
+ break;
+ }
+ return match;
+}
+
+bool url_match_domain(adblock_entry *entry, const std::string &origin_host) {
+ bool match_domain = true;
+ // check for a negative domain match
+ if (entry->domains_skip) {
+ if (origin_host.empty()) {
+ // skip this rule, cannot match on domain
+ return false;
+ }
+ for (int d = 0; const char *domain = entry->domains_skip[d]; d++) {
+ if (domain == origin_host) {
+ match_domain = false;
+ break;
+ }
+ }
+ }
+
+ // check for a required positive domain match
+ if (entry->domains) {
+ if (origin_host.empty()) {
+ // skip this rule, cannot match on domain
+ return false;
+ }
+ for (int d = 0; const char *domain = entry->domains[d]; d++) {
+ if (domain != origin_host) {
+ match_domain = false;
+ break;
+ }
+ }
+ }
+ return match_domain;
+}
+
+static bool url_match_party(adblock_entry *entry, const char *l_origin_host,
+ int origin_host_len, const char *l_url_host,
+ int url_host_len, bool &checked_fp, bool &fp) {
+ bool wanted_fp;
+ if ((entry->flags & ADBLOCK_FLAG_THIRD_PARTY) != 0) {
+ wanted_fp = false;
+ } else if ((entry->flags & ADBLOCK_FLAG_FIRST_PARTY) != 0) {
+ wanted_fp = true;
+ } else {
+ // no-op
+ return true;
+ }
+
+ if (origin_host_len == 0) {
+ // cannot match this rule, no origin host to determine first/third party
+ return false;
+ }
+
+ if (!checked_fp) {
+ // is the URL a first-party to the current page's host?
+ fp = is_first_party(l_origin_host, origin_host_len, l_url_host,
+ url_host_len);
+
+ checked_fp = true;
+#ifdef ADB_TESTER
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
+ "is_first_party(\"%s\", \"%s\") = %s", l_origin_host,
+ l_url_host, fp ? "true" : "false");
+#endif
+ }
+
+ return fp == wanted_fp;
+}
+
+static bool resource_type_match(adblock_entry *entry,
+ content::ResourceType resource_type) {
+ bool exclude;
+ if ((entry->flags & ADBLOCK_FLAG_RESOURCE_TYPE_IN) != 0) {
+ exclude = false;
+ } else if ((entry->flags & ADBLOCK_FLAG_RESOURCE_TYPE_NOT_IN) != 0) {
+ exclude = true;
+ } else {
+ // no resource type matching
+ return true;
+ }
+
+ // use a bitwise trick to test for the current resource type
+ bool found = (entry->flags & (int(resource_type) << 16));
+ if (!exclude)
+ return found;
+ return !found;
+}
+
+int adblock_intercept(const GURL &url, const std::string &origin_host,
+ content::ResourceType resource_type) {
+ // these are verified on caller when not testing
+#ifdef ADB_TESTER
+ if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) {
+ return 0;
+ }
+#endif
+
+ bool free1 = false, free2 = false, free3 = false, free4 = false;
+ int url_len = url.spec().size();
+ const char *c_url = url.spec().c_str();
+ char *c_url_lower = strtolower(c_url, url_len, free1);
+ char *c_url_sep = strtosep(c_url, url_len);
+ char *c_url_lower_sep = strtolower(c_url_sep, url_len + 2, free4);
+
+ // might be empty in case of no host
+ const char *origin_host_cstr = origin_host.c_str();
+ int origin_host_len = origin_host.size(), url_host_len = url.host().size();
+
+ // lower-case version of origin and url hosts
+ const char *c_origin_host_lower =
+ strtolower(origin_host_cstr, origin_host_len, free2),
+ *c_url_host_lower =
+ strtolower(url.host().c_str(), url_host_len, free3);
+
+#ifdef ADBLOCK_LOG
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "[%s with host '%s'] [%s]",
+ c_url, url.host().c_str(), origin_host_cstr);
+#endif
+
+ bool checked_fp = false, fp = false;
+
+ bool intercept = false;
+ for (int i = 0; i < ADBLOCK_ENTRY_COUNT; i++) {
+ adblock_entry *entry = &ADBLOCK_ENTRIES[i];
+
+ // no use checking rules when we're intercepting, or exceptions when not
+ bool check =
+ (!intercept && ((entry->flags & ADBLOCK_FLAG_EXCEPTION) == 0)) ||
+ (intercept && ((entry->flags & ADBLOCK_FLAG_EXCEPTION) != 0));
+ if (!check)
+ continue;
+
+ // check for resource type
+ if (!resource_type_match(entry, resource_type))
+ continue;
+
+ // check for domain matches, a quick branch out if not matching
+ if (!url_match_domain(entry, origin_host))
+ continue;
+
+ // check on the URL matcher
+ if (!url_matches(c_url, c_url_sep, c_url_lower, c_url_lower_sep, entry))
+ continue;
+
+ // finally check first/third-party
+ if (!url_match_party(entry, c_origin_host_lower, origin_host_len,
+ c_url_host_lower, url_host_len, checked_fp, fp))
+ continue;
+
+#ifdef ADBLOCK_LOG
+ if (!intercept) {
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
+ "--> intercept (#%d: \"%s\") (%x)", i,
+ entry->matches[0], entry->flags);
+ } else {
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "--> pass (%d) (#%d)", i,
+ entry->flags);
+ }
+#endif
+ intercept = !intercept;
+ } // for each entry
+
+ if (free3)
+ free((void *)c_url_host_lower);
+ if (free2)
+ free((void *)c_origin_host_lower);
+ free(c_url_sep);
+ if (free1)
+ free(c_url_lower);
+ if (free4)
+ free(c_url_lower_sep);
+
+ if (intercept) {
+#ifdef ADBLOCK_LOG
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "blocked");
+#endif
+ return 1;
+ }
+#ifdef ADBLOCK_LOG
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "pass");
+#endif
+ return 0;
+}
+
+} // namespace net
diff --git a/net/url_request/adblock_intercept.h b/net/url_request/adblock_intercept.h
new file mode 100644
--- /dev/null
+++ b/net/url_request/adblock_intercept.h
@@ -0,0 +1,35 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef NET_URL_REQUEST_ADBLOCK_INTERCEPT_H_
+#define NET_URL_REQUEST_ADBLOCK_INTERCEPT_H_
+
+#include "content/public/common/resource_type.h"
+#include "url/gurl.h"
+
+namespace net {
+
+#ifdef ADB_TESTER
+int adblock_rules_count();
+#endif
+
+int adblock_intercept(const GURL &url, const std::string &origin_host,
+ content::ResourceType resource_type);
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_ADBLOCK_INTERCEPT_H_
--
2.11.0