866 lines
34 KiB
Vala
866 lines
34 KiB
Vala
|
/*
|
||
|
Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
|
||
|
Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Lesser General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2.1 of the License, or (at your option) any later version.
|
||
|
|
||
|
See the file COPYING for the full license text.
|
||
|
*/
|
||
|
|
||
|
namespace Adblock {
|
||
|
public enum Directive {
|
||
|
ALLOW,
|
||
|
BLOCK
|
||
|
}
|
||
|
|
||
|
public enum State {
|
||
|
ENABLED,
|
||
|
DISABLED,
|
||
|
BLOCKED
|
||
|
}
|
||
|
|
||
|
public string? parse_subscription_uri (string? uri) {
|
||
|
if (uri == null)
|
||
|
return null;
|
||
|
|
||
|
if (uri.has_prefix ("http") || uri.has_prefix ("abp") || uri.has_prefix ("file"))
|
||
|
{
|
||
|
string sub_uri = uri;
|
||
|
if (uri.has_prefix ("abp:")) {
|
||
|
uri.replace ("abp://", "abp:");
|
||
|
if (uri.has_prefix ("abp:subscribe?location=")) {
|
||
|
/* abp://subscripe?location=http://example.com&title=foo */
|
||
|
string[] parts = uri.substring (23, -1).split ("&", 2);
|
||
|
sub_uri = parts[0];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
string decoded_uri = Soup.URI.decode (sub_uri);
|
||
|
return decoded_uri;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public class Extension : Midori.Extension {
|
||
|
internal Config config;
|
||
|
internal Subscription custom;
|
||
|
internal StringBuilder hider_selectors;
|
||
|
internal StatusIcon status_icon;
|
||
|
internal SubscriptionManager manager;
|
||
|
internal bool debug_element;
|
||
|
#if !USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
|
||
|
internal string? js_hider_function_body;
|
||
|
#endif
|
||
|
|
||
|
#if HAVE_WEBKIT2
|
||
|
#if !HAVE_WEBKIT2_3_91
|
||
|
public Extension.WebExtension (WebKit.WebExtension web_extension) {
|
||
|
init ();
|
||
|
web_extension.page_created.connect (page_created);
|
||
|
}
|
||
|
|
||
|
void page_created (WebKit.WebPage web_page) {
|
||
|
web_page.send_request.connect (send_request);
|
||
|
}
|
||
|
|
||
|
bool send_request (WebKit.WebPage web_page, WebKit.URIRequest request, WebKit.URIResponse? redirected_response) {
|
||
|
return request_handled (request.uri, web_page.uri);
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
public Extension () {
|
||
|
GLib.Object (name: _("Advertisement blocker"),
|
||
|
description: _("Block advertisements according to a filter list"),
|
||
|
version: "2.0",
|
||
|
authors: "Christian Dywan <christian@twotoasts.de>");
|
||
|
activate.connect (extension_activated);
|
||
|
deactivate.connect (extension_deactivated);
|
||
|
open_preferences.connect (extension_preferences);
|
||
|
}
|
||
|
|
||
|
void extension_preferences () {
|
||
|
manager.add_subscription (null);
|
||
|
}
|
||
|
|
||
|
void extension_activated (Midori.App app) {
|
||
|
#if HAVE_WEBKIT2
|
||
|
string cache_dir = Environment.get_user_cache_dir ();
|
||
|
string wk2path = Path.build_path (Path.DIR_SEPARATOR_S, cache_dir, "wk2ext");
|
||
|
Midori.Paths.mkdir_with_parents (wk2path);
|
||
|
string filename = "libadblock." + GLib.Module.SUFFIX;
|
||
|
var wk2link = File.new_for_path (wk2path).get_child (filename);
|
||
|
var library = File.new_for_path (Midori.Paths.get_lib_path (PACKAGE_NAME)).get_child (filename);
|
||
|
try {
|
||
|
wk2link.make_symbolic_link (library.get_path ());
|
||
|
} catch (IOError.EXISTS exist_error) {
|
||
|
/* It's no error if the file already exists. */
|
||
|
} catch (Error error) {
|
||
|
critical ("Failed to create WebKit2 link: %s", error.message);
|
||
|
}
|
||
|
#endif
|
||
|
init ();
|
||
|
foreach (var browser in app.get_browsers ())
|
||
|
browser_added (browser);
|
||
|
app.add_browser.connect (browser_added);
|
||
|
app.remove_browser.connect (browser_removed);
|
||
|
}
|
||
|
|
||
|
void extension_deactivated () {
|
||
|
var app = get_app ();
|
||
|
foreach (var browser in app.get_browsers ())
|
||
|
browser_removed (browser);
|
||
|
app.add_browser.disconnect (browser_added);
|
||
|
app.remove_browser.disconnect (browser_removed);
|
||
|
}
|
||
|
|
||
|
void browser_added (Midori.Browser browser) {
|
||
|
foreach (var tab in browser.get_tabs ())
|
||
|
tab_added (tab);
|
||
|
browser.add_tab.connect (tab_added);
|
||
|
browser.remove_tab.connect (tab_removed);
|
||
|
|
||
|
browser.add_action (status_icon);
|
||
|
}
|
||
|
|
||
|
void browser_removed (Midori.Browser browser) {
|
||
|
foreach (var tab in browser.get_tabs ())
|
||
|
tab_removed (tab);
|
||
|
browser.add_tab.disconnect (tab_added);
|
||
|
browser.remove_tab.disconnect (tab_removed);
|
||
|
browser.remove_action (status_icon);
|
||
|
}
|
||
|
|
||
|
void tab_added (Midori.View view) {
|
||
|
view.navigation_requested.connect (navigation_requested);
|
||
|
#if !HAVE_WEBKIT2
|
||
|
view.web_view.resource_request_starting.connect (resource_requested);
|
||
|
#endif
|
||
|
view.notify["load-status"].connect (load_status_changed);
|
||
|
view.context_menu.connect (context_menu);
|
||
|
}
|
||
|
|
||
|
void tab_removed (Midori.View view) {
|
||
|
#if !HAVE_WEBKIT2
|
||
|
view.web_view.resource_request_starting.disconnect (resource_requested);
|
||
|
#endif
|
||
|
view.navigation_requested.disconnect (navigation_requested);
|
||
|
view.notify["load-status"].disconnect (load_status_changed);
|
||
|
view.context_menu.disconnect (context_menu);
|
||
|
}
|
||
|
|
||
|
void load_status_changed (Object object, ParamSpec pspec) {
|
||
|
var view = object as Midori.View;
|
||
|
if (config.enabled) {
|
||
|
if (view.load_status == Midori.LoadStatus.FINISHED)
|
||
|
inject_css (view, view.uri);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void context_menu (WebKit.HitTestResult hit_test_result, Midori.ContextAction menu) {
|
||
|
string label, uri;
|
||
|
if ((hit_test_result.context & WebKit.HitTestResultContext.IMAGE) != 0) {
|
||
|
label = _("Bl_ock image");
|
||
|
uri = hit_test_result.image_uri;
|
||
|
} else if ((hit_test_result.context & WebKit.HitTestResultContext.LINK) != 0) {
|
||
|
label = _("Bl_ock link");
|
||
|
uri = hit_test_result.link_uri;
|
||
|
} else
|
||
|
return;
|
||
|
var action = new Gtk.Action ("BlockElement", label, null, null);
|
||
|
action.activate.connect ((action) => {
|
||
|
CustomRulesEditor custom_rules_editor = new CustomRulesEditor (custom);
|
||
|
custom_rules_editor.set_uri (uri);
|
||
|
custom_rules_editor.show();
|
||
|
});
|
||
|
menu.add (action);
|
||
|
}
|
||
|
|
||
|
#if !HAVE_WEBKIT2
|
||
|
void resource_requested (WebKit.WebView web_view, WebKit.WebFrame frame,
|
||
|
WebKit.WebResource resource, WebKit.NetworkRequest request, WebKit.NetworkResponse? response) {
|
||
|
|
||
|
if (request_handled (request.uri, web_view.uri)) {
|
||
|
request.set_uri ("about:blank");
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
bool navigation_requested (Midori.Tab tab, string uri) {
|
||
|
if (uri.has_prefix ("abp:")) {
|
||
|
string parsed_uri = parse_subscription_uri (uri);
|
||
|
manager.add_subscription (parsed_uri);
|
||
|
return true;
|
||
|
}
|
||
|
status_icon.set_state (config.enabled ? State.ENABLED : State.DISABLED);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
#if USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
|
||
|
string? get_hider_css_for_blocked_resources () {
|
||
|
if (hider_selectors.str == "")
|
||
|
return null;
|
||
|
|
||
|
/* Hide elements that were blocked, otherwise we will get "broken image" icon */
|
||
|
var code = new StringBuilder (hider_selectors.str);
|
||
|
string hider_css;
|
||
|
if (debug_element)
|
||
|
hider_css = " { background-color: red; border: 4px solid green; }";
|
||
|
else
|
||
|
hider_css = " { visiblility: hidden; width: 0; height: 0; }";
|
||
|
|
||
|
code.truncate (code.len -3);
|
||
|
code.append (hider_css);
|
||
|
if (debug_element)
|
||
|
stdout.printf ("hider css: %s\n", code.str);
|
||
|
return code.str;
|
||
|
}
|
||
|
#else
|
||
|
string? fetch_js_hider_function_body () {
|
||
|
string filename = Midori.Paths.get_res_filename ("adblock/element_hider.js");
|
||
|
File js_file = GLib.File.new_for_path (filename);
|
||
|
try {
|
||
|
uint8[] function_body;
|
||
|
js_file.load_contents (null, out function_body, null);
|
||
|
return (string)function_body;
|
||
|
}
|
||
|
catch (Error error) {
|
||
|
warning ("Error while loading adblock hider js: %s\n", error.message);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
string? get_hider_js_for_blocked_resorces () {
|
||
|
if (hider_selectors.str == "")
|
||
|
return null;
|
||
|
|
||
|
if (js_hider_function_body == null || js_hider_function_body == "")
|
||
|
return null;
|
||
|
|
||
|
var js = new StringBuilder ("(function() {");
|
||
|
js.append (js_hider_function_body);
|
||
|
js.append ("var uris=new Array ();");
|
||
|
js.append (hider_selectors.str);
|
||
|
js.append (" hideElementBySrc (uris);})();");
|
||
|
|
||
|
return js.str;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
string[]? get_domains_for_uri (string uri) {
|
||
|
if (uri == null)
|
||
|
return null;
|
||
|
string[]? domains = null;
|
||
|
string domain = Midori.URI.parse_hostname (uri, null);
|
||
|
string[] subdomains = domain.split (".");
|
||
|
if (subdomains == null)
|
||
|
return null;
|
||
|
int cnt = subdomains.length - 1;
|
||
|
var subdomain = new StringBuilder (subdomains[cnt]);
|
||
|
subdomain.prepend_c ('.');
|
||
|
cnt--;
|
||
|
while (cnt >= 0) {
|
||
|
subdomain.prepend (subdomains[cnt]);
|
||
|
domains += subdomain.str;
|
||
|
subdomain.prepend_c ('.');
|
||
|
cnt--;
|
||
|
}
|
||
|
return domains;
|
||
|
}
|
||
|
|
||
|
string? get_hider_css_rules_for_uri (string page_uri) {
|
||
|
if (page_uri == null)
|
||
|
return null;
|
||
|
string[]? domains = get_domains_for_uri (page_uri);
|
||
|
if (domains == null)
|
||
|
return null;
|
||
|
var code = new StringBuilder ();
|
||
|
int blockscnt = 0;
|
||
|
string? style = null;
|
||
|
foreach (unowned Subscription sub in config) {
|
||
|
foreach (unowned Feature feature in sub) {
|
||
|
var element = feature as Element;
|
||
|
if (element != null) {
|
||
|
foreach (unowned string subdomain in domains) {
|
||
|
style = element.lookup (subdomain);
|
||
|
if (style != null) {
|
||
|
code.append (style);
|
||
|
code.append_c (',');
|
||
|
blockscnt++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (blockscnt == 0)
|
||
|
return null;
|
||
|
code.truncate (code.len - 1);
|
||
|
|
||
|
string hider_css;
|
||
|
if (debug_element)
|
||
|
hider_css = " { background-color: red !important; border: 4px solid green !important; }";
|
||
|
else
|
||
|
hider_css = " { display: none !important }";
|
||
|
|
||
|
code.append (hider_css);
|
||
|
if (debug_element)
|
||
|
stdout.printf ("css: %s\n", code.str);
|
||
|
|
||
|
return code.str;
|
||
|
}
|
||
|
|
||
|
void inject_css (Midori.View view, string page_uri) {
|
||
|
/* Don't block ads on internal pages */
|
||
|
if (!Midori.URI.is_http (page_uri))
|
||
|
return;
|
||
|
|
||
|
if ("adblock:element" in (Environment.get_variable ("MIDORI_DEBUG") ?? ""))
|
||
|
debug_element = true;
|
||
|
else
|
||
|
debug_element = status_icon.debug_element_toggled;
|
||
|
|
||
|
#if USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
|
||
|
string? blocked_css = get_hider_css_for_blocked_resources ();
|
||
|
if (blocked_css != null)
|
||
|
view.inject_stylesheet (blocked_css);
|
||
|
#else
|
||
|
string? blocked_js = get_hider_js_for_blocked_resorces ();
|
||
|
if (blocked_js != null)
|
||
|
view.execute_script (blocked_js, null);
|
||
|
#endif
|
||
|
string? style = get_hider_css_rules_for_uri (page_uri);
|
||
|
if (style != null)
|
||
|
view.inject_stylesheet (style);
|
||
|
}
|
||
|
|
||
|
internal void init () {
|
||
|
hider_selectors = new StringBuilder ();
|
||
|
load_config ();
|
||
|
manager = new SubscriptionManager (config);
|
||
|
status_icon = new StatusIcon (config, manager);
|
||
|
foreach (unowned Subscription sub in config) {
|
||
|
try {
|
||
|
sub.parse ();
|
||
|
} catch (GLib.Error error) {
|
||
|
warning ("Error parsing %s: %s", sub.uri, error.message);
|
||
|
}
|
||
|
}
|
||
|
config.notify["size"].connect (subscriptions_added_removed);
|
||
|
manager.description_label.activate_link.connect (open_link);
|
||
|
#if !USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
|
||
|
js_hider_function_body = fetch_js_hider_function_body ();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
bool open_link (string uri) {
|
||
|
var browser = get_app ().browser;
|
||
|
var view = browser.add_uri (uri);
|
||
|
browser.tab = view;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void subscriptions_added_removed (ParamSpec pspec) {
|
||
|
hider_selectors = new StringBuilder ();
|
||
|
}
|
||
|
|
||
|
void load_config () {
|
||
|
#if HAVE_WEBKIT2
|
||
|
string config_dir = Path.build_filename (GLib.Environment.get_user_config_dir (), PACKAGE_NAME, "extensions", "libadblock." + GLib.Module.SUFFIX);
|
||
|
Midori.Paths.mkdir_with_parents (config_dir);
|
||
|
#else
|
||
|
string config_dir = Midori.Paths.get_extension_config_dir ("adblock");
|
||
|
#endif
|
||
|
string presets = Midori.Paths.get_extension_preset_filename ("adblock", "config");
|
||
|
string filename = Path.build_filename (config_dir, "config");
|
||
|
config = new Config (filename, presets);
|
||
|
string custom_list = GLib.Path.build_filename (config_dir, "custom.list");
|
||
|
try {
|
||
|
custom = new Subscription (Filename.to_uri (custom_list, null));
|
||
|
custom.mutable = false;
|
||
|
custom.title = _("Custom");
|
||
|
config.add (custom);
|
||
|
} catch (Error error) {
|
||
|
custom = null;
|
||
|
warning ("Failed to add custom list %s: %s", custom_list, error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Adblock.Directive get_directive_for_uri (string request_uri, string page_uri) {
|
||
|
if (!config.enabled)
|
||
|
return Directive.ALLOW;
|
||
|
|
||
|
/* Always allow the main page */
|
||
|
if (request_uri == page_uri)
|
||
|
return Directive.ALLOW;
|
||
|
|
||
|
/* Skip adblock on internal pages */
|
||
|
if (Midori.URI.is_blank (page_uri))
|
||
|
return Directive.ALLOW;
|
||
|
|
||
|
/* Skip adblock on favicons and non http schemes */
|
||
|
if (!Midori.URI.is_http (request_uri) || request_uri.has_suffix ("favicon.ico"))
|
||
|
return Directive.ALLOW;
|
||
|
|
||
|
Directive? directive = null;
|
||
|
foreach (unowned Subscription sub in config) {
|
||
|
directive = sub.get_directive (request_uri, page_uri);
|
||
|
if (directive != null)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (directive == null)
|
||
|
directive = Directive.ALLOW;
|
||
|
else if (directive == Directive.BLOCK) {
|
||
|
status_icon.set_state (State.BLOCKED);
|
||
|
#if USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
|
||
|
hider_selectors.append ("img[src*=\"%s\"] , iframe[src*=\"%s\"] , ".printf (request_uri, request_uri));
|
||
|
#else
|
||
|
hider_selectors.append (" uris.push ('%s');\n".printf (request_uri));
|
||
|
#endif
|
||
|
}
|
||
|
return directive;
|
||
|
}
|
||
|
|
||
|
internal bool request_handled (string request_uri, string page_uri) {
|
||
|
return get_directive_for_uri (request_uri, page_uri) == Directive.BLOCK;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void debug (string format, ...) {
|
||
|
bool debug_match = "adblock:match" in (Environment.get_variable ("MIDORI_DEBUG") ?? "");
|
||
|
if (!debug_match)
|
||
|
return;
|
||
|
|
||
|
var args = va_list ();
|
||
|
stdout.vprintf (format + "\n", args);
|
||
|
}
|
||
|
|
||
|
internal static string? fixup_regex (string prefix, string? src) {
|
||
|
if (src == null)
|
||
|
return null;
|
||
|
|
||
|
var fixed = new StringBuilder ();
|
||
|
fixed.append(prefix);
|
||
|
|
||
|
uint i = 0;
|
||
|
if (src[0] == '*')
|
||
|
i++;
|
||
|
uint l = src.length;
|
||
|
while (i < l) {
|
||
|
char c = src[i];
|
||
|
switch (c) {
|
||
|
case '*':
|
||
|
fixed.append (".*"); break;
|
||
|
case '|':
|
||
|
case '^':
|
||
|
case '+':
|
||
|
break;
|
||
|
case '?':
|
||
|
case '[':
|
||
|
case ']':
|
||
|
case '.':
|
||
|
case '(':
|
||
|
case ')':
|
||
|
fixed.append_printf ("\\%c", c); break;
|
||
|
default:
|
||
|
fixed.append_c (c); break;
|
||
|
}
|
||
|
i++;
|
||
|
}
|
||
|
return fixed.str;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if HAVE_WEBKIT2
|
||
|
#if !HAVE_WEBKIT2_3_91
|
||
|
Adblock.Extension? filter;
|
||
|
public static void webkit_web_extension_initialize (WebKit.WebExtension web_extension) {
|
||
|
filter = new Adblock.Extension.WebExtension (web_extension);
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
public Midori.Extension extension_init () {
|
||
|
return new Adblock.Extension ();
|
||
|
}
|
||
|
|
||
|
static string? tmp_folder = null;
|
||
|
string get_test_file (string contents) {
|
||
|
if (tmp_folder == null)
|
||
|
tmp_folder = Midori.Paths.make_tmp_dir ("adblockXXXXXX");
|
||
|
string checksum = Checksum.compute_for_string (ChecksumType.MD5, contents);
|
||
|
string file = Path.build_path (Path.DIR_SEPARATOR_S, tmp_folder, checksum);
|
||
|
try {
|
||
|
FileUtils.set_contents (file, contents, -1);
|
||
|
} catch (Error file_error) {
|
||
|
GLib.error (file_error.message);
|
||
|
}
|
||
|
return file;
|
||
|
}
|
||
|
|
||
|
struct TestCaseConfig {
|
||
|
public string content;
|
||
|
public uint size;
|
||
|
public bool enabled;
|
||
|
}
|
||
|
|
||
|
const TestCaseConfig[] configs = {
|
||
|
{ "", 0, true },
|
||
|
{ "[settings]", 0, true },
|
||
|
{ "[settings]\nfilters=foo;", 1, true },
|
||
|
{ "[settings]\nfilters=foo;\ndisabled=true", 1, false }
|
||
|
};
|
||
|
|
||
|
void test_adblock_config () {
|
||
|
assert (new Adblock.Config (null, null).size == 0);
|
||
|
|
||
|
foreach (var conf in configs) {
|
||
|
var config = new Adblock.Config (get_test_file (conf.content), null);
|
||
|
if (config.size != conf.size)
|
||
|
error ("Wrong size %s rather than %s:\n%s",
|
||
|
config.size.to_string (), conf.size.to_string (), conf.content);
|
||
|
if (config.enabled != conf.enabled)
|
||
|
error ("Wrongly got enabled=%s rather than %s:\n%s",
|
||
|
config.enabled.to_string (), conf.enabled.to_string (), conf.content);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct TestCaseSub {
|
||
|
public string uri;
|
||
|
public bool active;
|
||
|
}
|
||
|
|
||
|
const TestCaseSub[] subs = {
|
||
|
{ "http://foo.com", true },
|
||
|
{ "http://bar.com", false },
|
||
|
{ "https://spam.com", true },
|
||
|
{ "https://eggs.com", false },
|
||
|
{ "file:///bla", true },
|
||
|
{ "file:///blub", false }
|
||
|
};
|
||
|
|
||
|
void test_adblock_subs () {
|
||
|
var config = new Adblock.Config (get_test_file ("""
|
||
|
[settings]
|
||
|
filters=http://foo.com;http-//bar.com;https://spam.com;http-://eggs.com;file:///bla;file-///blub;http://foo.com;
|
||
|
"""), null);
|
||
|
|
||
|
assert (config.enabled);
|
||
|
foreach (var sub in subs) {
|
||
|
bool found = false;
|
||
|
foreach (unowned Adblock.Subscription subscription in config) {
|
||
|
if (subscription.uri == sub.uri) {
|
||
|
assert (subscription.active == sub.active);
|
||
|
found = true;
|
||
|
}
|
||
|
}
|
||
|
if (!found)
|
||
|
error ("%s not found", sub.uri);
|
||
|
}
|
||
|
|
||
|
/* 6 unique URLs, 1 duplicate */
|
||
|
assert (config.size == 6);
|
||
|
/* Duplicates aren't added again either */
|
||
|
assert (!config.add (new Adblock.Subscription ("https://spam.com")));
|
||
|
|
||
|
/* Saving the config and loading it should give back identical results */
|
||
|
config.save ();
|
||
|
var copy = new Adblock.Config (config.path, null);
|
||
|
assert (copy.size == config.size);
|
||
|
assert (copy.enabled == config.enabled);
|
||
|
for (int i = 0; i < config.size; i++) {
|
||
|
assert (copy[i].uri == config[i].uri);
|
||
|
assert (copy[i].active == config[i].active);
|
||
|
}
|
||
|
/* Enabled status should be saved and loaded */
|
||
|
config.enabled = false;
|
||
|
copy = new Adblock.Config (config.path, null);
|
||
|
assert (copy.enabled == config.enabled);
|
||
|
/* Flipping individual active values should be retained after saving */
|
||
|
foreach (unowned Adblock.Subscription sub in config)
|
||
|
sub.active = !sub.active;
|
||
|
copy = new Adblock.Config (config.path, null);
|
||
|
for (uint i = 0; i < config.size; i++) {
|
||
|
if (config[i].active != copy[i].active) {
|
||
|
string contents;
|
||
|
try {
|
||
|
FileUtils.get_contents (config.path, out contents, null);
|
||
|
} catch (Error file_error) {
|
||
|
error (file_error.message);
|
||
|
}
|
||
|
error ("%s is %s but should be %s:\n%s",
|
||
|
copy[i].uri, copy[i].active ? "active" : "disabled", config[i].active ? "active" : "disabled", contents);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Adding and removing works, changes size */
|
||
|
var s = new Adblock.Subscription ("http://en.de");
|
||
|
assert (config.add (s));
|
||
|
assert (config.size == 7);
|
||
|
config.remove (s);
|
||
|
assert (config.size == 6);
|
||
|
/* If it was removed before we should be able to add it again */
|
||
|
assert (config.add (s));
|
||
|
assert (config.size == 7);
|
||
|
}
|
||
|
|
||
|
void test_adblock_init () {
|
||
|
/* No config */
|
||
|
var extension = new Adblock.Extension ();
|
||
|
extension.init ();
|
||
|
assert (extension.config.enabled);
|
||
|
/* Defaults plus custom */
|
||
|
if (extension.config.size != 3)
|
||
|
error ("Expected 3 initial subs, got %s".printf (
|
||
|
extension.config.size.to_string ()));
|
||
|
assert (extension.status_icon.state == Adblock.State.ENABLED);
|
||
|
|
||
|
/* Add new subscription */
|
||
|
string path = Midori.Paths.get_res_filename ("adblock.list");
|
||
|
string uri;
|
||
|
try {
|
||
|
uri = Filename.to_uri (path, null);
|
||
|
} catch (Error error) {
|
||
|
GLib.error (error.message);
|
||
|
}
|
||
|
var sub = new Adblock.Subscription (uri);
|
||
|
extension.config.add (sub);
|
||
|
assert (extension.status_icon.state == Adblock.State.ENABLED);
|
||
|
assert (extension.config.size == 4);
|
||
|
try {
|
||
|
sub.parse ();
|
||
|
} catch (GLib.Error error) {
|
||
|
GLib.error (error.message);
|
||
|
}
|
||
|
/* The page itself never hits */
|
||
|
assert (!extension.request_handled ("https://ads.bogus.name/blub", "https://ads.bogus.name/blub"));
|
||
|
/* Favicons don't either */
|
||
|
assert (!extension.request_handled ("https://foo.com", "https://ads.bogus.name/blub/favicon.ico"));
|
||
|
assert (extension.status_icon.state == Adblock.State.ENABLED);
|
||
|
/* Some sanity checks to be sure there's no earlier problem */
|
||
|
assert (sub.title == "Exercise");
|
||
|
assert (sub.get_directive ("https://ads.bogus.name/blub", "") == Adblock.Directive.BLOCK);
|
||
|
/* A rule hit should add to the cache */
|
||
|
assert (extension.request_handled ("https://ads.bogus.name/blub", "https://foo.com"));
|
||
|
assert (extension.status_icon.state == Adblock.State.BLOCKED);
|
||
|
assert (extension.hider_selectors.str != "");
|
||
|
/* Disabled means no request should be handled */
|
||
|
extension.config.enabled = false;
|
||
|
assert (!extension.request_handled ("https://ads.bogus.name/blub", "https://foo.com"));
|
||
|
// FIXME: assert (extension.status_icon.state == Adblock.State.DISABLED);
|
||
|
/* Removing a subscription should clear the cached CSS */
|
||
|
extension.config.remove (sub);
|
||
|
assert (extension.hider_selectors.str == "");
|
||
|
assert (extension.config.size == 3);
|
||
|
/* Now let's add a custom rule */
|
||
|
extension.config.enabled = true;
|
||
|
extension.custom.add_rule ("/adpage.");
|
||
|
assert (extension.custom.get_directive ("http://www.engadget.com/_uac/adpage.html", "http://foo.com") == Adblock.Directive.BLOCK);
|
||
|
assert (extension.request_handled ("http://www.engadget.com/_uac/adpage.html", "http://foo.com"));
|
||
|
assert (extension.status_icon.state == Adblock.State.BLOCKED);
|
||
|
/* Second attempt, from cache, same result */
|
||
|
assert (extension.custom.get_directive ("http://www.engadget.com/_uac/adpage.html", "http://foo.com") == Adblock.Directive.BLOCK);
|
||
|
assert (extension.request_handled ("http://www.engadget.com/_uac/adpage.html", "http://foo.com"));
|
||
|
/* Another custom rule */
|
||
|
extension.custom.add_rule ("/images/*.png");
|
||
|
assert (extension.custom.get_directive ("http://alpha.beta.com/images/yota.png", "https://foo.com") == Adblock.Directive.BLOCK);
|
||
|
assert (extension.request_handled ("http://alpha.beta.com/images/yota.png", "https://foo.com"));
|
||
|
/* Second attempt, from cache, same result */
|
||
|
assert (extension.request_handled ("http://alpha.beta.com/images/yota.png", "https://foo.com"));
|
||
|
/* Similar uri but .jpg should pass */
|
||
|
assert (!extension.request_handled ("http://alpha.beta.com/images/yota.jpg", "https://foo.com"));
|
||
|
assert (extension.custom.get_directive ("http://alpha.beta.com/images/yota.jpg", "https://foo.com") != Adblock.Directive.BLOCK);
|
||
|
/* Add whitelist rule */
|
||
|
extension.custom.add_rule ("@@http://alpha.beta.com/images/drop*bear.png");
|
||
|
assert (!extension.request_handled ("http://alpha.beta.com/images/drop-bear.png", "https://foo.com"));
|
||
|
assert (!extension.request_handled ("http://alpha.beta.com/images/dropzone_bear.png", "https://foo.com"));
|
||
|
assert (extension.custom.get_directive ("http://alpha.beta.com/images/drop-bear.png", "https://foo.com") != Adblock.Directive.BLOCK);
|
||
|
/* Doesn't match whitelist, matches *.png rule, should be blocked */
|
||
|
assert (extension.request_handled ("http://alpha.beta.com/images/bear.png", "https://foo.com"));
|
||
|
assert (extension.custom.get_directive ("http://alpha.beta.com/images/bear.png", "https://foo.com") == Adblock.Directive.BLOCK);
|
||
|
}
|
||
|
|
||
|
struct TestCaseLine {
|
||
|
public string line;
|
||
|
public string fixed;
|
||
|
}
|
||
|
|
||
|
const TestCaseLine[] lines = {
|
||
|
{ null, null },
|
||
|
{ "!", "!" },
|
||
|
{ "@@", "@@" },
|
||
|
{ "##", "##" },
|
||
|
{ "[", "\\[" },
|
||
|
{ "+advert/", "advert/" },
|
||
|
{ "*foo", "foo" },
|
||
|
{ "f*oo", "f.*oo" },
|
||
|
{ "?foo", "\\?foo" },
|
||
|
{ "foo?", "foo\\?" },
|
||
|
{ ".*foo/bar", "..*foo/bar" },
|
||
|
{ ".*.*.*.info/popup", "\\..*\\..*\\..*\\.info/popup" },
|
||
|
{ "http://bla.blub/*", "http://bla.blub/.*" },
|
||
|
{ "bag?r[]=*cpa", "bag\\?r\\[\\]=.*cpa" },
|
||
|
{ "(facebookLike,", "\\(facebookLike," }
|
||
|
};
|
||
|
|
||
|
void test_adblock_fixup_regexp () {
|
||
|
foreach (var line in lines) {
|
||
|
Katze.assert_str_equal (line.line, Adblock.fixup_regex ("", line.line), line.fixed);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct TestCasePattern {
|
||
|
public string uri;
|
||
|
public Adblock.Directive directive;
|
||
|
}
|
||
|
|
||
|
const TestCasePattern[] patterns = {
|
||
|
{ "http://www.engadget.com/_uac/adpage.html", Adblock.Directive.BLOCK },
|
||
|
{ "http://test.dom/test?var=1", Adblock.Directive.BLOCK },
|
||
|
{ "http://ads.foo.bar/teddy", Adblock.Directive.BLOCK },
|
||
|
{ "http://ads.fuu.bar/teddy", Adblock.Directive.ALLOW },
|
||
|
{ "https://ads.bogus.name/blub", Adblock.Directive.BLOCK },
|
||
|
// FIXME { "http://ads.bla.blub/kitty", Adblock.Directive.BLOCK },
|
||
|
// FIXME { "http://ads.blub.boing/soda", Adblock.Directive.BLOCK },
|
||
|
{ "http://ads.foo.boing/beer", Adblock.Directive.ALLOW },
|
||
|
{ "https://testsub.engine.adct.ru/test?id=1", Adblock.Directive.BLOCK },
|
||
|
{ "http://test.ltd/addyn/test/test?var=adtech;&var2=1", Adblock.Directive.BLOCK },
|
||
|
{ "http://add.doubleclick.net/pfadx/aaaa.mtvi", Adblock.Directive.BLOCK },
|
||
|
{ "http://add.doubleclick.net/pfadx/aaaa.mtv", Adblock.Directive.ALLOW },
|
||
|
{ "http://objects.tremormedia.com/embed/xml/list.xml?r=", Adblock.Directive.BLOCK },
|
||
|
{ "http://qq.videostrip.c/sub/admatcherclient.php", Adblock.Directive.ALLOW },
|
||
|
{ "http://qq.videostrip.com/sub/admatcherclient.php", Adblock.Directive.BLOCK },
|
||
|
{ "http://qq.videostrip.com/sub/admatcherclient.php", Adblock.Directive.BLOCK },
|
||
|
{ "http://br.gcl.ru/cgi-bin/br/test", Adblock.Directive.BLOCK },
|
||
|
{ "https://bugs.webkit.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=substring&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailtype1=substring&email1=&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=&chfieldto=Now&chfieldvalue=&query_based_on=gtkport&field0-0-0=keywords&type0-0-0=anywordssubstr&value0-0-0=Gtk%20Cairo%20soup&field0-0-1=short_desc&type0-0-1=anywordssubstr&value0-0-1=Gtk%20Cairo%20soup%20autoconf%20automake%20autotool&field0-0-2=component&type0-0-2=equals&value0-0-2=WebKit%20Gtk", Adblock.Directive.ALLOW },
|
||
|
{ "http://www.engadget.com/2009/09/24/google-hits-android-rom-modder-with-a-cease-and-desist-letter/", Adblock.Directive.ALLOW },
|
||
|
{ "http://karibik-invest.com/es/bienes_raices/search.php?sqT=19&sqN=&sqMp=&sqL=0&qR=1&sqMb=&searchMode=1&action=B%FAsqueda", Adblock.Directive.ALLOW },
|
||
|
{ "http://google.com", Adblock.Directive.ALLOW }
|
||
|
};
|
||
|
|
||
|
string pretty_directive (Adblock.Directive? directive) {
|
||
|
if (directive == null)
|
||
|
return "none";
|
||
|
return directive.to_string ();
|
||
|
}
|
||
|
|
||
|
void test_adblock_pattern () {
|
||
|
string path = Midori.Paths.get_res_filename ("adblock.list");
|
||
|
string uri;
|
||
|
try {
|
||
|
uri = Filename.to_uri (path, null);
|
||
|
} catch (Error error) {
|
||
|
GLib.error (error.message);
|
||
|
}
|
||
|
var sub = new Adblock.Subscription (uri);
|
||
|
try {
|
||
|
sub.parse ();
|
||
|
} catch (Error error) {
|
||
|
GLib.error (error.message);
|
||
|
}
|
||
|
foreach (var pattern in patterns) {
|
||
|
Adblock.Directive? directive = sub.get_directive (pattern.uri, "");
|
||
|
if (directive == null)
|
||
|
directive = Adblock.Directive.ALLOW;
|
||
|
if (directive != pattern.directive) {
|
||
|
error ("%s expected for %s but got %s",
|
||
|
pretty_directive (pattern.directive), pattern.uri, pretty_directive (directive));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
string pretty_date (DateTime? date) {
|
||
|
if (date == null)
|
||
|
return "N/A";
|
||
|
return date.to_string ();
|
||
|
}
|
||
|
|
||
|
struct TestUpdateExample {
|
||
|
public string content;
|
||
|
public bool result;
|
||
|
public bool valid;
|
||
|
}
|
||
|
|
||
|
const TestUpdateExample[] examples = {
|
||
|
{ "[Adblock Plus 1.1]\n! Last modified: 05 Sep 2010 11:00 UTC\n! This list expires after 48 hours\n", true, true },
|
||
|
{ "[Adblock Plus 1.1]\n! Last modified: 05.09.2010 11:00 UTC\n! Expires: 2 days (update frequency)\n", true, true },
|
||
|
{ "[Adblock Plus 1.1]\n! Updated: 05 Nov 2024 11:00 UTC\n! Expires: 5 days (update frequency)\n", false, true },
|
||
|
{ "[Adblock]\n! dutchblock v3\n! This list expires after 14 days\n|http://b*.mookie1.com/\n", false, true },
|
||
|
{ "[Adblock Plus 2.0]\n! Last modification time (GMT): 2012.11.05 13:33\n! Expires: 5 days (update frequency)\n", true, true },
|
||
|
{ "[Adblock Plus 2.0]\n! Last modification time (GMT): 2012.11.05 13:33\n", true, true },
|
||
|
{ "[Adblock]\n ! dummy, i dont have any dates\n", false, true },
|
||
|
/* non-standard update time metadata as found in http://abp.mozilla-hispano.org/nauscopio/filtros.txt */
|
||
|
{ "[Adblock Plus 2.0]\n ! Last modified: Oct 26, 2013 18:00 UTC\n ! This list expires after 5 days\n! Last modified by maty: 12Oct2013\n! \n", false, true },
|
||
|
{ "\n", false, false }
|
||
|
};
|
||
|
|
||
|
void test_subscription_update () {
|
||
|
string uri;
|
||
|
FileIOStream iostream;
|
||
|
File file;
|
||
|
try {
|
||
|
file = File.new_tmp ("midori_adblock_update_test_XXXXXX", out iostream);
|
||
|
uri = file.get_uri ();
|
||
|
} catch (Error error) {
|
||
|
GLib.error (error.message);
|
||
|
}
|
||
|
var sub = new Adblock.Subscription (uri);
|
||
|
var updater = new Adblock.Updater ();
|
||
|
sub.add_feature (updater);
|
||
|
|
||
|
foreach (var example in examples) {
|
||
|
try {
|
||
|
file.replace_contents (example.content.data, null, false, FileCreateFlags.NONE, null);
|
||
|
sub.clear ();
|
||
|
sub.parse ();
|
||
|
} catch (Error error) {
|
||
|
GLib.error (error.message);
|
||
|
}
|
||
|
if (example.valid != sub.valid)
|
||
|
error ("Subscription expected to be %svalid but %svalid:\n%s",
|
||
|
example.valid ? "" : "in", sub.valid ? "" : "in", example.content);
|
||
|
if (example.result != updater.needs_update)
|
||
|
error ("Update%s expected for:\n%s\nLast Updated: %s\nExpires: %s",
|
||
|
example.result ? "" : " not", example.content,
|
||
|
pretty_date (updater.last_updated), pretty_date (updater.expires));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct TestSubUri {
|
||
|
public string? src_uri;
|
||
|
public string? dst_uri;
|
||
|
}
|
||
|
|
||
|
const TestSubUri[] suburis =
|
||
|
{
|
||
|
{ null, null },
|
||
|
{ "not-a-link", null },
|
||
|
{ "http://some.uri", "http://some.uri" },
|
||
|
{ "abp:subscribe?location=https%3A%2F%2Feasylist-downloads.adblockplus.org%2Fabpindo%2Beasylist.txt&title=ABPindo%2BEasyList", "https://easylist-downloads.adblockplus.org/abpindo+easylist.txt" }
|
||
|
};
|
||
|
|
||
|
void test_subscription_uri_parsing () {
|
||
|
string? parsed_uri;
|
||
|
foreach (var example in suburis) {
|
||
|
parsed_uri = Adblock.parse_subscription_uri (example.src_uri);
|
||
|
if (parsed_uri != example.dst_uri)
|
||
|
error ("Subscription expected to be %svalid but %svalid:\n%s",
|
||
|
example.dst_uri, parsed_uri, example.src_uri);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void extension_test () {
|
||
|
Test.add_func ("/extensions/adblock2/config", test_adblock_config);
|
||
|
Test.add_func ("/extensions/adblock2/subs", test_adblock_subs);
|
||
|
Test.add_func ("/extensions/adblock2/init", test_adblock_init);
|
||
|
Test.add_func ("/extensions/adblock2/parse", test_adblock_fixup_regexp);
|
||
|
Test.add_func ("/extensions/adblock2/pattern", test_adblock_pattern);
|
||
|
Test.add_func ("/extensions/adblock2/update", test_subscription_update);
|
||
|
Test.add_func ("/extensions/adblock2/subsparse", test_subscription_uri_parsing);
|
||
|
}
|
||
|
|