diff options
author | Christian Dywan <christian@twotoasts.de> | 2018-10-23 14:17:33 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-23 14:17:33 +0200 |
commit | b8ddfb73b61333b445e1dde112914d31a0e2849e (patch) | |
tree | f7d72e614d2e95bc82e6de438bcc1b5eacb6bbd8 /extensions | |
parent | 13ee3464575916e5c71a4e2590aaaf362092ed26 (diff) | |
download | midori-git-b8ddfb73b61333b445e1dde112914d31a0e2849e.tar.gz |
Port Adblock extension to Peas API (#109)
- A button in the toolbar allows enabling and disabling
- Settings to select active filter lists
- Adblock-compatible filter lists will be parsed
Improvements:
- More robust abp URI parsing
- Always include default filters in preferences
- Automatically reload after toggling enabled state
Omissions:
- CSS element hiding
- Automatic updating
- Custom filters
Notes:
- Pass built-in extension path in init
- Pick up extension sources from folders
- Generic Peas.Activatable exposes WebPage
Closes: #43
![screenshot from 2018-10-21 19-41-57](https://user-images.githubusercontent.com/1204189/47270302-630f5900-d56a-11e8-9b72-e5b1d1c9fd6d.png)
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/CMakeLists.txt | 22 | ||||
-rw-r--r-- | extensions/adblock.plugin.in | 6 | ||||
-rw-r--r-- | extensions/adblock/extension.vala | 184 | ||||
-rw-r--r-- | extensions/adblock/filter.vala | 48 | ||||
-rw-r--r-- | extensions/adblock/keys.vala | 43 | ||||
-rw-r--r-- | extensions/adblock/options.vala | 32 | ||||
-rw-r--r-- | extensions/adblock/pattern.vala | 26 | ||||
-rw-r--r-- | extensions/adblock/settings.vala | 108 | ||||
-rw-r--r-- | extensions/adblock/subscription.vala | 361 | ||||
-rw-r--r-- | extensions/adblock/whitelist.vala | 30 |
10 files changed, 851 insertions, 9 deletions
diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index ee8be2e1..500ac6b6 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -9,10 +9,21 @@ include_directories( ${CMAKE_BINARY_DIR} "${CMAKE_BINARY_DIR}/core" ) -file(GLOB EXTENSIONS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c *.vala) + +file(GLOB EXTENSIONS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *) foreach(UNIT_SRC ${EXTENSIONS}) - if (${UNIT_SRC} MATCHES "(.vala)$") + # Extension sources may be in folders + string(FIND ${UNIT_SRC} "." UNIT_EXTENSION) + if (UNIT_EXTENSION EQUAL -1) + set(UNIT ${UNIT_SRC}) + file(GLOB UNIT_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${UNIT_SRC}/*.vala") + elseif (${UNIT_SRC} MATCHES "(.vala)$") string(REPLACE ".vala" "" UNIT ${UNIT_SRC}) + else () + message(STATUS "Skipping ${UNIT_SRC}") + continue() + endif () + include(ValaPrecompile) vala_precompile(UNIT_SRC_C ${UNIT} ${UNIT_SRC} @@ -30,13 +41,6 @@ foreach(UNIT_SRC ${EXTENSIONS}) set_target_properties(${UNIT} PROPERTIES COMPILE_FLAGS "${VALA_CFLAGS}" ) - else() - string(REPLACE ".c" "" UNIT ${UNIT_SRC}) - add_library(${UNIT} MODULE ${UNIT_SRC}) - set_target_properties(${UNIT} PROPERTIES - COMPILE_FLAGS ${CFLAGS} - ) - endif() target_link_libraries(${UNIT} ${DEPS_LIBRARIES} ${DEPS_GTK_LIBRARIES} diff --git a/extensions/adblock.plugin.in b/extensions/adblock.plugin.in new file mode 100644 index 00000000..17de8b50 --- /dev/null +++ b/extensions/adblock.plugin.in @@ -0,0 +1,6 @@ +[Plugin] +Module=adblock +IAge=3 +Icon=security-high-symbolic +_Name=Advertisement blocker +_Description=Block advertisements according to a filter list diff --git a/extensions/adblock/extension.vala b/extensions/adblock/extension.vala new file mode 100644 index 00000000..d0976b20 --- /dev/null +++ b/extensions/adblock/extension.vala @@ -0,0 +1,184 @@ +/* + Copyright (C) 2009-2018 Christian Dywan <christian@twotoats.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 class Button : Gtk.Button { + public string icon_name { get; protected set; } + Settings settings = Settings.get_default (); + + construct { + action_name = "win.adblock-status"; + var image = new Gtk.Image.from_icon_name (icon_name, Gtk.IconSize.BUTTON); + bind_property ("icon-name", image, "icon-name"); + image.use_fallback = true; + image.show (); + add (image); + settings.notify["enabled"].connect (update_icon); + update_icon (); + show (); + } + + void update_icon () { + icon_name = "security-%s-symbolic".printf (settings.enabled ? "high" : "low"); + } + + public Button (Midori.Browser browser) { + var action = new SimpleAction ("adblock-status", null); + action.activate.connect (() => { + settings.enabled = !settings.enabled; + browser.tab.reload (); + }); + browser.notify["uri"].connect (() => { + action.set_enabled (browser.uri.has_prefix ("http")); + }); + browser.add_action (action); + browser.application.set_accels_for_action ("win.adblock-status", { }); + } + } + + public class Frontend : Object, Midori.BrowserActivatable { + public Midori.Browser browser { owned get; set; } + + public void activate () { + var button = new Button (browser); + browser.add_button (button); + deactivate.connect (() => { + button.destroy (); + }); + + WebKit.WebContext.get_default ().register_uri_scheme ("abp", (request) => { + if (request.get_uri ().has_prefix ("abp:subscribe?location=")) { + // abp://subscripe?location=http://example.com&title=foo + var sub = new Subscription (request.get_uri ().substring (23, -1)); + debug ("Adding %s to filters\n", sub.uri); + Settings.get_default ().add (sub); + sub.active = true; + request.get_web_view ().stop_loading (); + } else { + request.finish_error (new FileError.NOENT (_("Invalid URI"))); + } + }); + } + } + + public class RequestFilter : Peas.ExtensionBase, Peas.Activatable { + public Object object { owned get; construct; } + + public void activate () { + string? page_uri; + object.get ("uri", out page_uri); + object.connect ("signal::send-request", handle_request, page_uri); + } + + bool handle_request (Object request, Object? response, string? page_uri) { + string? uri; + request.get ("uri", out uri); + if (page_uri == null) { + // Note: 'this' is the WebKit.WebPage object in this context + get ("uri", out page_uri); + } + return get_directive_for_uri (uri, page_uri) == Directive.BLOCK; + } + + Directive get_directive_for_uri (string request_uri, string page_uri) { + var settings = Settings.get_default (); + + if (!settings.enabled) + return Directive.ALLOW; + + // Always allow the main page + if (request_uri == page_uri) + return Directive.ALLOW; + + // No adblock on non-http(s) schemes + if (!request_uri.has_prefix ("http")) + return Directive.ALLOW; + + Directive? directive = null; + foreach (var sub in settings) { + directive = sub.get_directive (request_uri, page_uri); + if (directive != null) + break; + } + + if (directive == null) { + directive = Directive.ALLOW; + } + return directive; + } + + public void deactivate () { + } + + public void update_state () { + } + } + + public class Preferences : Object, Midori.PreferencesActivatable { + public Midori.Preferences preferences { owned get; set; } + + public void activate () { + var box = new Midori.LabelWidget (_("Configure Advertisement filters")); + var listbox = new Gtk.ListBox (); + listbox.selection_mode = Gtk.SelectionMode.NONE; + var settings = Settings.get_default (); + foreach (var sub in settings) { + var row = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4); + var button = new Gtk.CheckButton.with_label (sub.title); + button.tooltip_text = sub.uri; + sub.bind_property ("active", button, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + row.pack_start (button); + if (!settings.default_filters.contains (sub.uri.split ("&")[0])) { + var remove = new Gtk.Button.from_icon_name ("list-remove-symbolic"); + remove.relief = Gtk.ReliefStyle.NONE; + remove.clicked.connect (() => { + settings.remove (sub); + row.destroy (); + }); + row.pack_end (remove, false); + } + listbox.insert (row, -1); + } + + // Provide guidance to adding additional filters via the abp: scheme + var label = new Gtk.Label ( + _("You can find more lists by visiting following sites:\n %s, %s\n").printf ( + "<a href=\"https://adblockplus.org/en/subscriptions\">AdblockPlus</a>", + "<a href=\"https://easylist.to\">EasyList</a>")); + label.use_markup = true; + label.activate_link.connect ((uri) => { + var files = new File[1]; + files[0] = File.new_for_uri (uri); + Application.get_default ().open (files, ""); + return true; + }); + listbox.insert (label, -1); + + box.add (listbox); + box.show_all (); + preferences.add (_("Privacy"), box); + deactivate.connect (() => { + box.destroy (); + }); + } + } +} + +[ModuleInit] +public void peas_register_types(TypeModule module) { + ((Peas.ObjectModule)module).register_extension_type ( + typeof (Midori.BrowserActivatable), typeof (Adblock.Frontend)); + ((Peas.ObjectModule)module).register_extension_type ( + typeof (Peas.Activatable), typeof (Adblock.RequestFilter)); + ((Peas.ObjectModule)module).register_extension_type ( + typeof (Midori.PreferencesActivatable), typeof (Adblock.Preferences)); +} diff --git a/extensions/adblock/filter.vala b/extensions/adblock/filter.vala new file mode 100644 index 00000000..50e23d35 --- /dev/null +++ b/extensions/adblock/filter.vala @@ -0,0 +1,48 @@ +/* + Copyright (C) 2009-2012 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 abstract class Filter : Feature { + Options optslist; + protected HashTable<string, Regex?> rules; + + public virtual void insert (string sig, Regex regex) { + rules.insert (sig, regex); + } + + public virtual Regex? lookup (string sig) { + return rules.lookup (sig); + } + + public virtual uint size () { + return rules.size (); + } + + protected Filter (Options options) { + optslist = options; + rules = new HashTable<string, Regex> (str_hash, str_equal); + } + + protected bool check_rule (Regex regex, string pattern, string request_uri, string page_uri) throws Error { + if (!regex.match_full (request_uri)) + return false; + + var opts = optslist.lookup (pattern); + if (opts != null && Regex.match_simple (",third-party", opts, + RegexCompileFlags.CASELESS, RegexMatchFlags.NOTEMPTY)) + if (page_uri != null && regex.match_full (page_uri)) + return false; + debug ("blocked by pattern regexp=%s -- %s", regex.get_pattern (), request_uri); + return true; + } + } +} diff --git a/extensions/adblock/keys.vala b/extensions/adblock/keys.vala new file mode 100644 index 00000000..d25eb788 --- /dev/null +++ b/extensions/adblock/keys.vala @@ -0,0 +1,43 @@ +/* + 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 class Keys : Filter { + List<Regex> blacklist; + + public Keys (Options options) { + base (options); + blacklist = new List<Regex> (); + } + + public override Directive? match (string request_uri, string page_uri) throws Error { + string? uri = fixup_regex ("", request_uri); + if (uri == null) + return null; + + int signature_size = 8; + int pos, l = uri.length; + for (pos = l - signature_size; pos >= 0; pos--) { + string signature = uri.offset (pos).ndup (signature_size); + var regex = rules.lookup (signature); + if (regex == null || blacklist.find (regex) != null) + continue; + + if (check_rule (regex, uri, request_uri, page_uri)) + return Directive.BLOCK; + blacklist.prepend (regex); + } + + return null; + } + } +} diff --git a/extensions/adblock/options.vala b/extensions/adblock/options.vala new file mode 100644 index 00000000..039ba4a5 --- /dev/null +++ b/extensions/adblock/options.vala @@ -0,0 +1,32 @@ +/* + Copyright (C) 2014 Christian Dywan <christian@twotoasts.de> + + 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 class Options : Object { + HashTable<string, string?> optslist; + + public Options () { + clear (); + } + + public void insert (string sig, string? opts) { + optslist.insert (sig, opts); + } + + public string? lookup (string sig) { + return optslist.lookup (sig); + } + + public void clear () { + optslist = new HashTable<string, string?> (str_hash, str_equal); + } + } +} diff --git a/extensions/adblock/pattern.vala b/extensions/adblock/pattern.vala new file mode 100644 index 00000000..b95943aa --- /dev/null +++ b/extensions/adblock/pattern.vala @@ -0,0 +1,26 @@ +/* + 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 class Pattern : Filter { + public Pattern (Options options) { + base (options); + } + + public override Directive? match (string request_uri, string page_uri) throws Error { + foreach (unowned string patt in rules.get_keys ()) + if (check_rule (rules.lookup (patt), patt, request_uri, page_uri)) + return Directive.BLOCK; + return null; + } + } +} diff --git a/extensions/adblock/settings.vala b/extensions/adblock/settings.vala new file mode 100644 index 00000000..3f3af1a8 --- /dev/null +++ b/extensions/adblock/settings.vala @@ -0,0 +1,108 @@ +/* + Copyright (C) 2018 Christian Dywan <christian@twotoats.de> + + 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 class Settings : Midori.Settings { + static Settings? _default = null; + public string default_filters = "https://easylist.to/easylist/easylist.txt&title=EasyList;https://easylist.to/easylist/easyprivacy.txt&title=EasyPrivacy"; + List<Subscription> subscriptions; + + public static Settings get_default () { + if (_default == null) { + string filename = Path.build_filename (Environment.get_user_config_dir (), + Config.PROJECT_NAME, "extensions", "libadblock.so", "config"); + _default = new Settings (filename); + } + return _default; + } + + Settings (string filename) { + Object (filename: filename); + string[] filters = get_string ("settings", "filters", default_filters).split (";"); + foreach (unowned string filter in filters) { + if (filter == "") { + continue; + } + + string uri = filter; + if (filter.has_prefix ("http-/")) { + uri = "http:" + filter.substring (5); + } else if (filter.has_prefix ("file-/")) { + uri = "file:" + filter.substring (5); + } else if (filter.has_prefix ("http-:")) { + uri = "https" + filter.substring (5); + } + add (new Subscription (uri, filter == uri)); + } + // Always add the default filters in case they were removed + foreach (unowned string uri in default_filters.split (";")) { + add (new Subscription (uri)); + } + } + + public bool enabled { get { + return !get_boolean ("settings", "disabled", false); + } set { + set_boolean ("settings", "disabled", !value, false); + } } + + /* foreach support */ + public new unowned Subscription? get (uint index) { + return subscriptions.nth_data (index); + } + public uint size { get; private set; } + + public bool contains (Subscription subscription) { + foreach (unowned Subscription sub in subscriptions) { + if (sub.file.get_path () == subscription.file.get_path ()) + return true; + } + return false; + } + + public void add (Subscription sub) { + if (contains (sub)) { + return; + } + + sub.notify["active"].connect (active_changed); + subscriptions.append (sub); + size++; + } + + void active_changed () { + var filters = new StringBuilder (); + foreach (unowned Subscription sub in subscriptions) { + if (sub.uri.has_prefix ("http:") && !sub.active) { + filters.append ("http-" + sub.uri.substring (4)); + } else if (sub.uri.has_prefix ("file:") && !sub.active) { + filters.append ("file-" + sub.uri.substring (5)); + } else if (sub.uri.has_prefix ("https:") && !sub.active) { + filters.append ("http-" + sub.uri.substring (5)); + } else { + filters.append (sub.uri); + } + filters.append_c (';'); + } + + if (filters.str.has_suffix (";")) + filters.truncate (filters.len - 1); + set_string ("settings", "filters", filters.str); + } + + public void remove (Subscription sub) { + subscriptions.remove (sub); + size--; + sub.notify["active"].disconnect (active_changed); + active_changed (); + } + } +} diff --git a/extensions/adblock/subscription.vala b/extensions/adblock/subscription.vala new file mode 100644 index 00000000..4bc191ed --- /dev/null +++ b/extensions/adblock/subscription.vala @@ -0,0 +1,361 @@ +/* + Copyright (C) 2009-2018 Christian Dywan <christian@twotoats.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 static string? fixup_regex (string prefix, string? src) { + if (src == null) { + return null; + } + + var fixed = new StringBuilder (); + fixed.append (prefix); + + uint i = 0, l = src.length; + if (src[0] == '*') { + i++; + } + 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; + } + + public abstract class Feature : Object { + public virtual Directive? match (string request_uri, string page_uri) throws Error { + return null; + } + } + + public class Subscription : Object { + public string uri { get; protected construct set; } + string? _title = null; + public string? title { get { + if (_title == null) { + ensure_headers (); + if (_title == null) { + // Fallback to title from the URI + string[] parts = Soup.URI.decode (uri).split ("&"); + foreach (string part in parts) { + if (part.has_prefix ("title=")) { + _title = part.substring (6, -1); + break; + } + } + if (_title == null) { + _title = uri.substring (uri.index_of ("://") + 3, -1); + } + } + } + return _title; + } } + public bool active { get; set; default = true; } + + HashTable<string, Directive?>? cache = null; + List<Feature> features; + Options optslist; + Whitelist whitelist; + Keys keys; + Pattern pattern; + public File file { get; protected set; } + + public Subscription (string uri, bool active=false) { + Object (uri: uri, active: active); + } + + construct { + // Consider the URI without an optional query + string base_uri = uri.split ("&")[0]; + if (uri.has_prefix ("file://")) { + file = File.new_for_uri (base_uri); + } else { + string cache_dir = Path.build_filename (Environment.get_user_cache_dir (), Config.PROJECT_NAME, "adblock"); + string filename = Checksum.compute_for_string (ChecksumType.MD5, base_uri, -1); + file = File.new_for_path (Path.build_filename (cache_dir, filename)); + } + } + + public void add_feature (Feature feature) { + features.append (feature); + size++; + } + + /* foreach support */ + public new unowned Feature? get (uint index) { + return features.nth_data (index); + } + public uint size { get; private set; } + + void clear () { + cache = new HashTable<string, Directive?> (str_hash, str_equal); + optslist = new Options (); + whitelist = new Whitelist (optslist); + add_feature (whitelist); + keys = new Keys (optslist); + add_feature (keys); + pattern = new Pattern (optslist); + add_feature (pattern); + } + + /* + * Parse headers only. + */ + public void ensure_headers () { + if (!ensure_downloaded (true)) { + return; + } + + queue_parse.begin (true); + } + + /* + * Attempt to parse filters unless inactive or already cached. + */ + public bool ensure_parsed () { + if (!active || cache != null) { + return active; + } + + // Skip if this hasn't been downloaded (yet) + if (!file.query_exists ()) { + return false; + } + + queue_parse.begin (); + return true; + } + + bool ensure_downloaded (bool headers_only) { + if (file.query_exists ()) { + return true; + } + + try { + file.get_parent ().make_directory_with_parents (); + } catch (Error error) { + // It's no error if the folder already exists + } + + var download = WebKit.WebContext.get_default ().download_uri (uri.split ("&")[0]); + download.allow_overwrite = true; + download.set_destination (file.get_uri ()); + download.finished.connect (() => { + queue_parse.begin (true); + }); + return false; + } + + async void queue_parse (bool headers_only=false) { + try { + yield parse (headers_only); + } catch (Error error) { + critical ("Failed to parse %s%s: %s", headers_only ? "headers for " : "", uri, error.message); + } + } + + /* + * Parse either headers or filters depending on the flag. + */ + async void parse (bool headers_only=false) throws Error { + debug ("Parsing %s, caching %s", uri, file.get_path ()); + clear (); + var stream = new DataInputStream (file.read ()); + string? line; + while ((line = stream.read_line (null)) != null) { + if (line == null) { + continue; + } + string chomped = line.chomp (); + if (chomped == "") { + continue; + } + if (line[0] == '!' && headers_only) { + parse_header (chomped); + } else if (!headers_only) { + parse_line (chomped); + } + } + } + + void parse_header (string header) { + /* Headers come in two forms + ! Foo: Bar + ! Some freeform text + */ + string key = header; + string value = ""; + if (header.contains (":")) { + string[] parts = header.split (":", 2); + if (parts[0] != null && parts[0] != "" + && parts[1] != null && parts[1] != "") { + key = parts[0].substring (2, -1); + value = parts[1].substring (1, -1); + } + } + debug ("Header '%s' says '%s'", key, value); + if (key == "Title") { + _title = value; + } + } + + void parse_line (string line) throws Error { + if (line.has_prefix ("@@")) { + if (line.contains("$") && line.contains ("domain")) + return; + if (line.has_prefix ("@@||")) { + add_url_pattern ("^", "whitelist", line.offset (4)); + } else if (line.has_prefix ("@@|")) { + add_url_pattern ("^", "whitelist", line.offset (3)); + } else { + add_url_pattern ("", "whitelist", line.offset (2)); + } + return; + } + if (line[0] == '[') { + return; + } + + // CSS block hiding + if (line.has_prefix ("##")) { + // Not implemented here so just skip + return; + } + if (line[0] == '#') + return; + + if ("#@#" in line) + return; + + // Per domain CSS hiding rule + if ("##" in line || "#" in line) { + // Not implemented here so just skip + return; + } + + /* URL blocker rule */ + if (line.has_prefix ("|")) { + if (line.contains("$")) + return; + + if (line.has_prefix ("||")) { + add_url_pattern ("", "fulluri", line.offset (2)); + } else { + add_url_pattern ("^", "fulluri", line.offset (1)); + } + return; + } + + add_url_pattern ("", "uri", line); + return; + } + + void add_url_pattern (string prefix, string type, string line) throws Error { + string[]? data = line.split ("$", 2); + if (data == null || data[0] == null) + return; + + string patt, opts; + patt = data[0]; + opts = type; + + if (data[1] != null) + opts = type + "," + data[1]; + + if (Regex.match_simple ("subdocument", opts, + RegexCompileFlags.CASELESS, RegexMatchFlags.NOTEMPTY)) { + return; + } + + string format_patt = fixup_regex (prefix, patt); + debug ("got: %s opts %s", format_patt, opts); + compile_regexp (format_patt, opts); + } + + void compile_regexp (string? patt, string opts) throws Error { + if (patt == null) { + return; + } + + var regex = new Regex (patt, RegexCompileFlags.OPTIMIZE, RegexMatchFlags.NOTEMPTY); + // is pattern is already a regular expression? + if (Regex.match_simple ("^/.*[\\^\\$\\*].*/$", patt, + RegexCompileFlags.UNGREEDY, RegexMatchFlags.NOTEMPTY) + || (opts != null && opts.contains ("whitelist"))) { + debug ("patt: %s", patt); + if (opts.contains ("whitelist")) { + whitelist.insert (patt, regex); + } else { + pattern.insert (patt, regex); + } + optslist.insert (patt, opts); + } else { + int pos = 0, len; + int signature_size = 8; + string sig; + len = patt.length; + + // Chop up pattern into substrings for faster matching + for (pos = len - signature_size; pos >= 0; pos--) { + sig = patt.offset (pos).ndup (signature_size); + // No * nor \\, doesn't look like regex, save chunk as "key" + if (!Regex.match_simple ("[\\*]", sig, RegexCompileFlags.UNGREEDY, RegexMatchFlags.NOTEMPTY) && keys.lookup (sig) == null) { + keys.insert (sig, regex); + optslist.insert (sig, opts); + } else { + // Starts with * or \\: save as regex + if ((sig.has_prefix ("*") || sig.has_prefix("\\")) && pattern.lookup (sig) == null) { + pattern.insert (sig, regex); + optslist.insert (sig, opts); + } + } + } + } + } + + public Directive? get_directive (string request_uri, string page_uri) { + if (!ensure_parsed ()) { + return null; + } + + Directive? directive = cache.lookup (request_uri); + if (directive != null) { + debug ("%s for %s (%s)", directive.to_string (), request_uri, page_uri); + return directive; + } + return null; + } + } +} diff --git a/extensions/adblock/whitelist.vala b/extensions/adblock/whitelist.vala new file mode 100644 index 00000000..f088a2df --- /dev/null +++ b/extensions/adblock/whitelist.vala @@ -0,0 +1,30 @@ +/* + Copyright (C) 2014 Christian Dywan <christian@twotoasts.de> + Copyright (C) 2014 Paweł Forysiuk <tuxator@o2.pl> + + 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 class Whitelist : Filter { + public Whitelist (Options options) { + base (options); + } + + public override Directive? match (string request_uri, string page_uri) throws Error { + foreach (unowned string white in rules.get_keys ()) { + var regex = rules.lookup (white); + if (!regex.match_full (request_uri)) + return null; + if (Regex.match_simple (regex.get_pattern (), request_uri, RegexCompileFlags.UNGREEDY, RegexMatchFlags.NOTEMPTY)) + return Directive.ALLOW; + } + return null; + } + } +} |