summaryrefslogtreecommitdiff
path: root/extensions
diff options
context:
space:
mode:
authorChristian Dywan <christian@twotoasts.de>2018-10-23 14:17:33 +0200
committerGitHub <noreply@github.com>2018-10-23 14:17:33 +0200
commitb8ddfb73b61333b445e1dde112914d31a0e2849e (patch)
treef7d72e614d2e95bc82e6de438bcc1b5eacb6bbd8 /extensions
parent13ee3464575916e5c71a4e2590aaaf362092ed26 (diff)
downloadmidori-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.txt22
-rw-r--r--extensions/adblock.plugin.in6
-rw-r--r--extensions/adblock/extension.vala184
-rw-r--r--extensions/adblock/filter.vala48
-rw-r--r--extensions/adblock/keys.vala43
-rw-r--r--extensions/adblock/options.vala32
-rw-r--r--extensions/adblock/pattern.vala26
-rw-r--r--extensions/adblock/settings.vala108
-rw-r--r--extensions/adblock/subscription.vala361
-rw-r--r--extensions/adblock/whitelist.vala30
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;
+ }
+ }
+}