[epiphany] Import adblock extension into the source tree



commit 253ce5aa8e437687f329d3d51469fdabda3ee8d7
Author: Xan Lopez <xan igalia com>
Date:   Sat Sep 29 10:10:37 2012 +0200

    Import adblock extension into the source tree
    
    This commit imports the adblock extension from 'epiphany-extensions'
    with as few modifications as possible. Further modifications and
    simplifications are possible, but for now we just load it
    unconditionally, enabled.
    
    We have it in the src/ directory because it depends on some high-level
    objects in Epiphany (EphyExtension, EphyWindow), but in the future
    we'll probably move it to embed/, transforming it from an extension
    into just the specific implementation of our EphyAdblockManager
    object.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=681657

 src/Makefile.am              |    7 +
 src/adblock-ui.c             |  390 ++++++++++++++++++
 src/adblock-ui.h             |   62 +++
 src/ephy-adblock-extension.c |  297 ++++++++++++++
 src/ephy-adblock-extension.h |   55 +++
 src/ephy-shell.c             |    9 +
 src/epiphany.gresource.xml   |    1 +
 src/resources/adblock.ui     |  198 ++++++++++
 src/uri-tester.c             |  895 ++++++++++++++++++++++++++++++++++++++++++
 src/uri-tester.h             |   72 ++++
 10 files changed, 1986 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index ada8e47..2f35995 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -17,7 +17,9 @@ header_DATA = \
 	$(INST_H_FILES)
 
 NOINST_H_FILES = \
+	adblock-ui.h				\
 	ephy-action-helper.h			\
+	ephy-adblock-extension.h		\
 	ephy-combined-stop-reload-action.h	\
 	ephy-encoding-dialog.h			\
 	ephy-encoding-menu.h			\
@@ -37,6 +39,7 @@ NOINST_H_FILES = \
 	pdm-dialog.h				\
 	popup-commands.h			\
 	prefs-dialog.h				\
+	uri-tester.h				\
 	window-commands.h
 
 INST_H_FILES = \
@@ -51,7 +54,9 @@ INST_H_FILES = \
 	$(NULL)
 
 libephymain_la_SOURCES = \
+	adblock-ui.c				\
 	ephy-action-helper.c			\
+	ephy-adblock-extension.c		\
 	ephy-completion-model.c			\
 	ephy-completion-model.h			\
 	ephy-combined-stop-reload-action.c	\
@@ -79,6 +84,7 @@ libephymain_la_SOURCES = \
 	popup-commands.c			\
 	prefs-dialog.c				\
 	window-commands.c			\
+	uri-tester.c				\
 	$(INST_H_FILES)				\
 	$(NOINST_H_FILES)			\
 	$(NULL)
@@ -112,6 +118,7 @@ libephymain_la_CFLAGS = \
 	$(AM_CFLAGS)
 
 UI_FILES = \
+	resources/adblock.ui                      \
 	resources/epiphany-bookmark-editor-ui.xml \
 	resources/epiphany-history-window-ui.xml  \
 	resources/epiphany-ui.xml		  \
diff --git a/src/adblock-ui.c b/src/adblock-ui.c
new file mode 100644
index 0000000..d41ed53
--- /dev/null
+++ b/src/adblock-ui.c
@@ -0,0 +1,390 @@
+/*
+ *  Copyright  2011 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  Some parts of this file based on the Midori's 'adblock' extension,
+ *  licensed with the GNU Lesser General Public License 2.1, Copyright
+ *  (C) 2009-2010 Christian Dywan <christian twotoasts de> and 2009
+ *  Alexander Butenko <a butenka gmail com>. Check Midori's web site
+ *  at http://www.twotoasts.de
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "adblock-ui.h"
+
+#include "ephy-adblock.h"
+#include "ephy-adblock-manager.h"
+#include "ephy-debug.h"
+#include "ephy-embed-shell.h"
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#define ADBLOCK_UI_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE((object), TYPE_ADBLOCK_UI, AdblockUIPrivate))
+
+#define ADBLOCK_FILTER_VALID(__filter)              \
+  (__filter && (g_str_has_prefix (__filter, "http") \
+                || g_str_has_prefix (__filter, "file")))
+
+struct _AdblockUIPrivate
+{
+  GtkWidget *dialog;
+
+  /* The dialog buttons. */
+  GtkEntry *new_filter;
+  GtkButton *add, *edit, *remove;
+
+  /* Data. */
+  GtkTreeView *treeview;
+  GtkTreeSelection *selection;
+  GtkListStore *store;
+
+  /* The uri tester. */
+  UriTester *tester;
+
+  /* Whether something has actually changed. */
+  gboolean dirty;
+};
+
+enum
+{
+  PROP_0,
+  PROP_TESTER,
+};
+
+enum
+{
+  COL_FILTER_URI,
+  N_COLUMNS
+};
+
+G_DEFINE_DYNAMIC_TYPE (AdblockUI, adblock_ui, EPHY_TYPE_DIALOG);
+
+/* Private functions. */
+
+static gboolean
+adblock_ui_foreach_save (GtkTreeModel *model,
+                         GtkTreePath  *path,
+                         GtkTreeIter  *iter,
+                         GSList       **filters)
+{
+  char *filter = NULL;
+
+  gtk_tree_model_get (model, iter, COL_FILTER_URI, &filter, -1);
+  *filters = g_slist_prepend (*filters, filter);
+
+  return FALSE;
+}
+
+static void
+adblock_ui_save (AdblockUI *dialog)
+{
+  GSList *filters = NULL;
+  gtk_tree_model_foreach (GTK_TREE_MODEL (dialog->priv->store),
+                          (GtkTreeModelForeachFunc)adblock_ui_foreach_save,
+                          &filters);
+
+  uri_tester_set_filters (dialog->priv->tester, filters);
+}
+
+static void
+adblock_ui_response_cb (GtkWidget *widget,
+                        int response,
+                        AdblockUI *dialog)
+{
+  if (response == GTK_RESPONSE_CLOSE && dialog->priv->dirty)
+    {
+      EphyAdBlockManager *manager;
+
+      adblock_ui_save (dialog);
+
+      /* Ask uri tester to reload all its patterns. */
+      uri_tester_reload (dialog->priv->tester);
+
+      /* Ask manager to emit a signal that rules have changed. */
+      manager = EPHY_ADBLOCK_MANAGER (ephy_embed_shell_get_adblock_manager (embed_shell));
+
+      g_signal_emit_by_name (manager, "rules_changed", NULL);
+    }
+
+  g_object_unref (dialog);
+}
+
+static void
+adblock_ui_add_filter (AdblockUI *dialog)
+{
+  GtkTreeIter iter;
+
+  const char *new_filter = gtk_entry_get_text (dialog->priv->new_filter);
+
+  if (ADBLOCK_FILTER_VALID (new_filter))
+    {
+      GtkListStore *store = dialog->priv->store;
+
+      gtk_list_store_append (store, &iter);
+      gtk_list_store_set (store, &iter, COL_FILTER_URI, new_filter, -1);
+
+      /* Makes the pattern field blank. */
+      gtk_entry_set_text (dialog->priv->new_filter, "");
+
+      dialog->priv->dirty = TRUE;
+    }
+  else
+    {
+      GtkWidget *error_dialog = NULL;
+      error_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog->priv->dialog),
+                                             GTK_DIALOG_MODAL,
+                                             GTK_MESSAGE_ERROR,
+                                             GTK_BUTTONS_OK,
+                                             "%s",
+                                             _("Invalid filter"));
+      gtk_dialog_run (GTK_DIALOG (error_dialog));
+      gtk_widget_destroy (error_dialog);
+
+      gtk_entry_set_text (dialog->priv->new_filter, "");
+    }
+}
+
+static void
+adblock_ui_add_cb (GtkButton *button,
+                   AdblockUI *dialog)
+{
+  adblock_ui_add_filter (dialog);
+}
+
+static void
+adblock_ui_edit_cb (GtkButton *button,
+                    AdblockUI *dialog)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GtkTreeSelection *selection;
+
+  selection = dialog->priv->selection;
+
+  if (gtk_tree_selection_get_selected (selection, &model, &iter))
+    {
+      char* path = gtk_tree_model_get_string_from_iter (model, &iter);
+      GtkTreePath* tree_path = gtk_tree_path_new_from_string (path);
+      GtkTreeView *treeview = dialog->priv->treeview;
+      GtkTreeViewColumn *column = gtk_tree_view_get_column (treeview,
+                                                            COL_FILTER_URI);
+
+      gtk_tree_view_set_cursor (treeview, tree_path, column, TRUE);
+      gtk_tree_path_free (tree_path);
+      g_free (path);
+    }
+}
+
+static void
+adblock_ui_remove_cb (GtkButton *button,
+                      AdblockUI *dialog)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GtkTreeSelection *selection;
+
+  selection = dialog->priv->selection;
+
+  if (gtk_tree_selection_get_selected (selection, &model, &iter))
+    {
+      gtk_list_store_remove (GTK_LIST_STORE(model), &iter);
+      gtk_entry_set_text (dialog->priv->new_filter, "");
+
+      dialog->priv->dirty = TRUE;
+    }
+}
+
+static void
+adblock_ui_cell_edited_cb (GtkCellRendererText *cell,
+                           char               *path_string,
+                           char               *new_filter,
+                           AdblockUI           *dialog)
+{
+  GtkTreeModel *model = GTK_TREE_MODEL (dialog->priv->store);
+  GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
+  GtkTreeIter iter;
+
+  gtk_tree_model_get_iter (model, &iter, path);
+  gtk_list_store_set (dialog->priv->store, &iter, COL_FILTER_URI, new_filter, -1);
+  gtk_tree_path_free (path);
+
+  dialog->priv->dirty = TRUE;
+}
+
+static void
+adblock_ui_build_treeview (AdblockUI *dialog)
+{
+  GtkCellRenderer *renderer;
+
+  dialog->priv->store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING);
+
+  renderer = gtk_cell_renderer_text_new ();
+  g_object_set(renderer, "editable", TRUE, NULL);
+  g_signal_connect(renderer,
+                   "edited",
+                   (GCallback) adblock_ui_cell_edited_cb,
+                   (gpointer)dialog);
+
+  gtk_tree_view_insert_column_with_attributes (dialog->priv->treeview,
+                                               COL_FILTER_URI, _("Filter URI"),
+                                               renderer,
+                                               "text", COL_FILTER_URI,
+                                               NULL);
+
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (dialog->priv->store),
+                                        COL_FILTER_URI,
+                                        GTK_SORT_ASCENDING);
+
+  gtk_tree_view_set_model (dialog->priv->treeview, GTK_TREE_MODEL (dialog->priv->store));
+  gtk_tree_view_set_search_column (dialog->priv->treeview, COL_FILTER_URI);
+
+  g_object_unref (dialog->priv->store);
+
+  dialog->priv->selection = gtk_tree_view_get_selection (dialog->priv->treeview);
+  gtk_tree_selection_set_mode (dialog->priv->selection, GTK_SELECTION_SINGLE);
+
+  dialog->priv->dirty = FALSE;
+}
+
+static void
+adblock_ui_populate_store (AdblockUI *dialog)
+{
+  GSList *filters = NULL;
+  GSList *item = NULL;
+  const char *filter_uri = NULL;
+  GtkTreeIter iter;
+
+  filters = uri_tester_get_filters (dialog->priv->tester);
+  for (item = filters; item; item = g_slist_next (item))
+    {
+      filter_uri = (const char *) item->data;
+
+      gtk_list_store_append (dialog->priv->store, &iter);
+      gtk_list_store_set (dialog->priv->store, &iter, COL_FILTER_URI, filter_uri, -1);
+    }
+}
+
+static void
+adblock_ui_init (AdblockUI *dialog)
+{
+  LOG ("AdblockUI initialising");
+  dialog->priv = ADBLOCK_UI_GET_PRIVATE (dialog);
+}
+
+static void
+adblock_ui_constructed (GObject *object)
+{
+  AdblockUI *dialog;
+  AdblockUIPrivate *priv;
+  EphyDialog *edialog;
+
+  dialog = ADBLOCK_UI (object);
+  edialog = EPHY_DIALOG (object);
+
+  priv = dialog->priv;
+
+  ephy_dialog_construct (EPHY_DIALOG (edialog),
+                         "/org/gnome/epiphany/adblock.ui",
+                         "adblock-ui",
+                         GETTEXT_PACKAGE);
+
+  ephy_dialog_get_controls (edialog,
+                            "adblock-ui", &priv->dialog,
+                            "new_filter_entry", &priv->new_filter,
+                            "treeview", &priv->treeview,
+                            "add_button", &priv->add,
+                            "edit_button", &priv->edit,
+                            "remove_button", &priv->remove,
+                            NULL);
+
+  g_signal_connect (priv->dialog, "response",
+                    G_CALLBACK (adblock_ui_response_cb), dialog);
+
+  g_signal_connect (priv->add, "clicked",
+                    G_CALLBACK (adblock_ui_add_cb), dialog);
+  g_signal_connect (priv->edit, "clicked",
+                    G_CALLBACK (adblock_ui_edit_cb), dialog);
+  g_signal_connect (priv->remove, "clicked",
+                    G_CALLBACK (adblock_ui_remove_cb), dialog);
+  g_signal_connect (priv->new_filter, "activate",
+                    G_CALLBACK (adblock_ui_add_cb), dialog);
+
+  /* Build and fill. */
+  adblock_ui_build_treeview (dialog);
+  adblock_ui_populate_store (dialog);
+
+  /* Chain up. */
+  G_OBJECT_CLASS (adblock_ui_parent_class)->constructed (object);
+}
+
+static void
+adblock_ui_set_property (GObject *object,
+                         guint prop_id,
+                         const GValue *value,
+                         GParamSpec *pspec)
+{
+  AdblockUI *dialog = ADBLOCK_UI (object);
+
+  switch (prop_id)
+    {
+    case PROP_TESTER:
+      dialog->priv->tester = g_value_get_object (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+adblock_ui_class_init (AdblockUIClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = adblock_ui_constructed;
+  object_class->set_property = adblock_ui_set_property;
+
+  g_object_class_install_property
+    (object_class,
+     PROP_TESTER,
+     g_param_spec_object ("tester",
+                          "UriTester",
+                          "UriTester",
+                          TYPE_URI_TESTER,
+                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+  g_type_class_add_private (object_class, sizeof (AdblockUIPrivate));
+}
+
+static void adblock_ui_class_finalize (AdblockUIClass *klass)
+{
+}
+
+/* Public functions. */
+
+void adblock_ui_register (GTypeModule *module)
+{
+  adblock_ui_register_type (module);
+}
+
+AdblockUI *
+adblock_ui_new (UriTester *tester)
+{
+  return g_object_new (TYPE_ADBLOCK_UI,
+                       "tester", tester,
+                       NULL);
+}
diff --git a/src/adblock-ui.h b/src/adblock-ui.h
new file mode 100644
index 0000000..b308e84
--- /dev/null
+++ b/src/adblock-ui.h
@@ -0,0 +1,62 @@
+/*
+ *  Copyright  2011 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ADBLOCK_UI_H
+#define ADBLOCK_UI_H
+
+#include "ephy-dialog.h"
+#include "uri-tester.h"
+
+#include <glib-object.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_ADBLOCK_UI         (adblock_ui_get_type ())
+#define ADBLOCK_UI(o)           (G_TYPE_CHECK_INSTANCE_CAST((o), TYPE_ADBLOCK_UI, AdblockUI))
+#define ADBLOCK_UI_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), TYPE_ADBLOCK_UI, AdblockUIClass))
+#define IS_ADBLOCK_UI(o)        (G_TYPE_CHECK_INSTANCE_TYPE((o), TYPE_ADBLOCK_UI))
+#define IS_ADBLOCK_UI_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE((k), TYPE_ADBLOCK_UI))
+#define ADBLOCK_UI_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), TYPE_ADBLOCK_UI, AdblockUIClass))
+
+typedef struct _AdblockUI        AdblockUI;
+typedef struct _AdblockUIClass   AdblockUIClass;
+typedef struct _AdblockUIPrivate AdblockUIPrivate;
+
+struct _AdblockUI
+{
+  EphyDialog parent;
+
+  /*< private >*/
+  AdblockUIPrivate *priv;
+};
+
+struct _AdblockUIClass
+{
+  EphyDialogClass parent_class;
+};
+
+GType      adblock_ui_get_type (void);
+
+void       adblock_ui_register (GTypeModule *module);
+
+AdblockUI *adblock_ui_new      (UriTester *tester);
+
+G_END_DECLS
+
+#endif
diff --git a/src/ephy-adblock-extension.c b/src/ephy-adblock-extension.c
new file mode 100644
index 0000000..93068f2
--- /dev/null
+++ b/src/ephy-adblock-extension.c
@@ -0,0 +1,297 @@
+/*
+ *  Copyright  2011 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  Some parts of this file based on the previous 'adblock' extension,
+ *  licensed with the GNU General Public License 2 and later versions,
+ *  Copyright (C) 2003 Marco Pesenti Gritti, Christian Persch.
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "ephy-adblock-extension.h"
+
+#include "adblock-ui.h"
+#include "ephy-adblock.h"
+#include "ephy-adblock-manager.h"
+#include "ephy-debug.h"
+#include "ephy-embed-shell.h"
+#include "ephy-extension.h"
+#include "ephy-file-helpers.h"
+#include "ephy-window.h"
+#include "uri-tester.h"
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#define EPHY_ADBLOCK_EXTENSION_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_ADBLOCK_EXTENSION, EphyAdblockExtensionPrivate))
+
+#define WINDOW_DATA_KEY     "EphyAdblockExtensionWindowData"
+#define STATUSBAR_EVBOX_KEY "EphyAdblockExtensionStatusbarEvbox"
+#define EXTENSION_KEY       "EphyAdblockExtension"
+#define AD_BLOCK_ICON_NAME  "ad-blocked"
+
+typedef struct
+{
+  EphyAdblockExtension *extension;
+  EphyWindow           *window;
+
+  GtkActionGroup       *action_group;
+  guint                 ui_id;
+} WindowData;
+
+struct EphyAdblockExtensionPrivate
+{
+  UriTester *tester;
+  AdblockUI *ui;
+};
+
+static void ephy_adblock_extension_iface_init (EphyExtensionIface *iface);
+static void ephy_adblock_adblock_iface_init (EphyAdBlockIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EphyAdblockExtension,
+                         ephy_adblock_extension,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_ADBLOCK,
+                                                ephy_adblock_adblock_iface_init)
+                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_EXTENSION,
+                                                ephy_adblock_extension_iface_init))
+
+/* Private functions. */
+
+static void
+ephy_adblock_extension_init (EphyAdblockExtension *extension)
+{
+  LOG ("EphyAdblockExtension initialising");
+
+  extension->priv = EPHY_ADBLOCK_EXTENSION_GET_PRIVATE (extension);
+  extension->priv->tester = uri_tester_new ();
+}
+
+static void
+ephy_adblock_extension_dispose (GObject *object)
+{
+  EphyAdblockExtension *extension = NULL;
+
+  LOG ("EphyAdblockExtension disposing");
+
+  extension = EPHY_ADBLOCK_EXTENSION (object);
+  g_clear_object (&extension->priv->ui);
+  g_clear_object (&extension->priv->tester);
+
+  G_OBJECT_CLASS (ephy_adblock_extension_parent_class)->dispose (object);
+}
+
+static void
+ephy_adblock_extension_finalize (GObject *object)
+{
+  LOG ("EphyAdblockExtension finalising");
+
+  G_OBJECT_CLASS (ephy_adblock_extension_parent_class)->finalize (object);
+}
+
+static void
+ephy_adblock_extension_class_init (EphyAdblockExtensionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ephy_adblock_extension_dispose;
+  object_class->finalize = ephy_adblock_extension_finalize;
+
+  g_type_class_add_private (object_class, sizeof (EphyAdblockExtensionPrivate));
+}
+
+static gboolean
+ephy_adblock_impl_should_load (EphyAdBlock *blocker,
+                               EphyEmbed *embed,
+                               const char *url,
+                               AdUriCheckType type)
+{
+  EphyAdblockExtension *self = NULL;
+  EphyWebView* web_view = NULL;
+  const char *address = NULL;
+
+  LOG ("ephy_adblock_impl_should_load checking %s", url);
+
+  self = EPHY_ADBLOCK_EXTENSION (blocker);
+  g_return_val_if_fail (self != NULL, TRUE);
+
+  web_view = ephy_embed_get_web_view (embed);
+  address = ephy_web_view_get_address (web_view);
+
+  return !uri_tester_test_uri (self->priv->tester, url, address, type);
+}
+
+static void
+ephy_adblock_impl_edit_rule (EphyAdBlock *blocker,
+                             const char *url,
+                             gboolean allowed)
+{
+  EphyAdblockExtension *self = NULL;
+  EphyAdblockExtensionPrivate *priv = NULL;
+
+  LOG ("ephy_adblock_impl_edit_rule %s with state %d", url, allowed);
+
+  self = EPHY_ADBLOCK_EXTENSION (blocker);
+  priv = self->priv;
+
+  if (priv->ui == NULL)
+    {
+      AdblockUI **ui;
+
+      /*
+       * TODO: url and allowed should be passed to the UI,
+       * so the user can actually do something with it.
+       */
+      priv->ui = adblock_ui_new (priv->tester);
+      ui = &priv->ui;
+
+      g_object_add_weak_pointer ((gpointer)priv->ui,
+                                 (gpointer *) ui);
+
+      ephy_dialog_set_parent (EPHY_DIALOG (priv->ui), NULL);
+    }
+
+  ephy_dialog_show (EPHY_DIALOG (priv->ui));
+}
+
+static void
+ephy_adblock_adblock_iface_init (EphyAdBlockIface *iface)
+{
+  iface->should_load = ephy_adblock_impl_should_load;
+  iface->edit_rule = ephy_adblock_impl_edit_rule;
+}
+
+static void
+ephy_adblock_extension_edit_cb (GtkAction *action, EphyWindow *window)
+{
+  WindowData *data = NULL;
+  EphyAdblockExtensionPrivate *priv = NULL;
+
+  data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+  g_return_if_fail (data != NULL);
+
+  priv = data->extension->priv;
+
+  if (priv->ui == NULL)
+    {
+      AdblockUI **ui;
+
+      priv->ui = adblock_ui_new (priv->tester);
+      ui = &priv->ui;
+
+      g_object_add_weak_pointer ((gpointer)priv->ui,
+                                 (gpointer *) ui);
+    }
+
+  ephy_dialog_set_parent (EPHY_DIALOG (priv->ui), GTK_WIDGET (window));
+  ephy_dialog_show (EPHY_DIALOG (priv->ui));
+}
+
+static const GtkActionEntry edit_entries[] = {
+  { "EphyAdblockExtensionEdit", NULL,
+    N_("Ad Blocker"), NULL,
+    N_("Configure Ad Blocker filters"),
+    G_CALLBACK (ephy_adblock_extension_edit_cb) }
+};
+
+static void
+impl_attach_window (EphyExtension *ext,
+                    EphyWindow *window)
+{
+  WindowData *data = NULL;
+  GtkUIManager *manager = NULL;
+
+  /* Add adblock editor's menu entry. */
+  data = g_new (WindowData, 1);
+  g_object_set_data_full (G_OBJECT (window),
+                          WINDOW_DATA_KEY,
+                          data,
+                          g_free);
+
+  data->extension = EPHY_ADBLOCK_EXTENSION (ext);
+  data->window = window;
+
+  data->action_group = gtk_action_group_new ("EphyAdblockExtension");
+  gtk_action_group_set_translation_domain (data->action_group,
+                                           GETTEXT_PACKAGE);
+  gtk_action_group_add_actions (data->action_group, edit_entries,
+                                G_N_ELEMENTS(edit_entries), window);
+
+  manager = GTK_UI_MANAGER (ephy_window_get_ui_manager (window));
+
+  gtk_ui_manager_insert_action_group (manager, data->action_group, -1);
+
+  /* UI manager references the new action group. */
+  g_object_unref (data->action_group);
+
+  data->ui_id = gtk_ui_manager_new_merge_id (manager);
+
+  gtk_ui_manager_add_ui (manager,
+                         data->ui_id,
+                         "/ui/PagePopup/ExtensionsMenu",
+                         "EphyAdblockExtensionEdit",
+                         "EphyAdblockExtensionEdit",
+                         GTK_UI_MANAGER_MENUITEM,
+                         FALSE);
+
+  /* Remember the xtension attached to that window. */
+  g_object_set_data (G_OBJECT (window), EXTENSION_KEY, ext);
+}
+
+static void
+impl_detach_window (EphyExtension *ext,
+                    EphyWindow *window)
+{
+  WindowData *data = NULL;
+  GtkUIManager *manager = NULL;
+
+  /* Remove editor UI. */
+  data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+  g_assert (data != NULL);
+
+  manager = GTK_UI_MANAGER (ephy_window_get_ui_manager (window));
+
+  gtk_ui_manager_remove_ui (manager, data->ui_id);
+  gtk_ui_manager_remove_action_group (manager, data->action_group);
+
+  g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL);
+}
+
+static void
+impl_attach_tab (EphyExtension *ext,
+                 EphyWindow *window,
+                 EphyEmbed *embed)
+{
+
+}
+
+static void
+impl_detach_tab (EphyExtension *ext,
+                 EphyWindow *window,
+                 EphyEmbed *embed)
+{
+
+}
+
+static void
+ephy_adblock_extension_iface_init (EphyExtensionIface *iface)
+{
+  iface->attach_window = impl_attach_window;
+  iface->detach_window = impl_detach_window;
+  iface->attach_tab = impl_attach_tab;
+  iface->detach_tab = impl_detach_tab;
+}
+
diff --git a/src/ephy-adblock-extension.h b/src/ephy-adblock-extension.h
new file mode 100644
index 0000000..46bea1b
--- /dev/null
+++ b/src/ephy-adblock-extension.h
@@ -0,0 +1,55 @@
+/*
+ *  Copyright  2011 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef EPHY_ADBLOCK_EXTENSION_H
+#define EPHY_ADBLOCK_EXTENSION_H
+
+#include <glib-object.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_ADBLOCK_EXTENSION         (ephy_adblock_extension_get_type ())
+#define EPHY_ADBLOCK_EXTENSION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_ADBLOCK_EXTENSION, EphyAdblockExtension))
+#define EPHY_ADBLOCK_EXTENSION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_ADBLOCK_EXTENSION, EphyAdblockExtensionClass))
+#define EPHY_IS_ADBLOCK_EXTENSION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_ADBLOCK_EXTENSION))
+#define EPHY_IS_ADBLOCK_EXTENSION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_ADBLOCK_EXTENSION))
+#define EPHY_ADBLOCK_EXTENSION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_ADBLOCK_EXTENSION, EphyAdblockExtensionClass))
+
+typedef struct EphyAdblockExtension        EphyAdblockExtension;
+typedef struct EphyAdblockExtensionClass   EphyAdblockExtensionClass;
+typedef struct EphyAdblockExtensionPrivate EphyAdblockExtensionPrivate;
+
+struct EphyAdblockExtensionClass
+{
+  GObjectClass parent_class;
+};
+
+struct EphyAdblockExtension
+{
+  GObject parent_instance;
+
+  /*< private >*/
+  EphyAdblockExtensionPrivate *priv;
+};
+
+GType ephy_adblock_extension_get_type (void);
+
+G_END_DECLS
+
+#endif
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index 88f843e..e88e9a0 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -23,6 +23,8 @@
 #include "config.h"
 #include "ephy-shell.h"
 
+#include "ephy-adblock-extension.h"
+#include "ephy-adblock-manager.h"
 #include "ephy-bookmarks-editor.h"
 #include "ephy-bookmarks-import.h"
 #include "ephy-debug.h"
@@ -1055,6 +1057,8 @@ ephy_shell_get_prefs_dialog (EphyShell *shell)
 void
 _ephy_shell_create_instance (EphyEmbedShellMode mode)
 {
+  EphyAdBlockManager *adblock_manager;
+
   g_assert (ephy_shell == NULL);
 
   ephy_shell = EPHY_SHELL (g_object_new (EPHY_TYPE_SHELL,
@@ -1063,6 +1067,11 @@ _ephy_shell_create_instance (EphyEmbedShellMode mode)
                                          NULL));
   /* FIXME weak ref */
   g_assert (ephy_shell != NULL);
+
+  /* FIXME not the best place to have this */
+  adblock_manager = EPHY_ADBLOCK_MANAGER (ephy_embed_shell_get_adblock_manager (embed_shell));
+  ephy_adblock_manager_set_blocker (adblock_manager,
+                                    g_object_new (EPHY_TYPE_ADBLOCK_EXTENSION, NULL));
 }
 
 /**
diff --git a/src/epiphany.gresource.xml b/src/epiphany.gresource.xml
index b756466..c04d537 100644
--- a/src/epiphany.gresource.xml
+++ b/src/epiphany.gresource.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
   <gresource prefix="/org/gnome/epiphany">
+    <file preprocess="xml-stripblanks" compressed="true">adblock.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">epiphany.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">prefs-dialog.ui</file>
     <file preprocess="xml-stripblanks">epiphany-application-menu.ui</file>
diff --git a/src/resources/adblock.ui b/src/resources/adblock.ui
new file mode 100644
index 0000000..8831069
--- /dev/null
+++ b/src/resources/adblock.ui
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 2.6 -->
+  <object class="GtkDialog" id="adblock-ui">
+    <property name="can_focus">False</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Adblock Editor</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="skip_pager_hint">True</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">5</property>
+        <child>
+          <object class="GtkLabel" id="description_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Type the address of a preconfigured filter list in the text entry to be added to the list. Find more lists at easylist.adblockplus.org</property>
+            <property name="use_markup">True</property>
+            <property name="wrap">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="padding">6</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="close">
+                <property name="label">gtk-close</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="hbox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkBox" id="left_vbox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkEntry" id="new_filter_entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">â</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="treeview_scroller">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="shadow_type">in</property>
+                    <child>
+                      <object class="GtkTreeView" id="treeview">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <child internal-child="selection">
+                          <object class="GtkTreeSelection" id="treeview-selection1"/>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="padding">4</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="right_vbox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="filler_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="add_button">
+                    <property name="label">gtk-add</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="use_stock">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="edit_button">
+                    <property name="label">gtk-edit</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="use_stock">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="remove_button">
+                    <property name="label">gtk-remove</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="use_stock">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="padding">4</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="padding">12</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-7">close</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/src/uri-tester.c b/src/uri-tester.c
new file mode 100644
index 0000000..bae25e4
--- /dev/null
+++ b/src/uri-tester.c
@@ -0,0 +1,895 @@
+/*
+ *  Copyright  2011 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  Some parts of this file based on the Midori's 'adblock' extension,
+ *  licensed with the GNU Lesser General Public License 2.1, Copyright
+ *  (C) 2009-2010 Christian Dywan <christian twotoasts de> and 2009
+ *  Alexander Butenko <a butenka gmail com>. Check Midori's web site
+ *  at http://www.twotoasts.de
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "uri-tester.h"
+
+#include "ephy-debug.h"
+#include "ephy-file-helpers.h"
+
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+#include <string.h>
+#include <webkit/webkit.h>
+
+#define DEFAULT_FILTER_URL "http://adblockplus.mozdev.org/easylist/easylist.txt";
+#define FILTERS_LIST_FILENAME "filters.list"
+#define SIGNATURE_SIZE 8
+#define UPDATE_FREQUENCY 24 * 60 * 60 /* In seconds */
+
+#define URI_TESTER_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), TYPE_URI_TESTER, UriTesterPrivate))
+
+struct _UriTesterPrivate
+{
+  GSList *filters;
+
+  GHashTable *pattern;
+  GHashTable *keys;
+  GHashTable *optslist;
+  GHashTable *urlcache;
+
+  GString *blockcss;
+  GString *blockcssprivate;
+};
+
+enum
+{
+  PROP_0,
+  PROP_FILTERS,
+};
+
+G_DEFINE_TYPE (UriTester, uri_tester, G_TYPE_OBJECT);
+
+/* Private functions. */
+
+static void uri_tester_class_init (UriTesterClass *klass);
+static void uri_tester_init (UriTester *dialog);
+
+static GString *
+uri_tester_fixup_regexp (const char *prefix, char *src);
+
+static gboolean
+uri_tester_parse_file_at_uri (UriTester *tester, const char *fileuri);
+
+static char *
+uri_tester_ensure_data_dir ()
+{
+  char *folder = NULL;
+
+  /* Ensure adblock's dir is there. */
+  folder = g_build_filename (ephy_dot_dir (), "extensions", "data", "adblock", NULL);
+  g_mkdir_with_parents (folder, 0700);
+
+  return folder;
+}
+
+static char*
+uri_tester_get_fileuri_for_url (const char *url)
+{
+  char *filename = NULL;
+  char *folder = NULL;
+  char *path = NULL;
+  char *uri = NULL;
+
+  if (!strncmp (url, "file", 4))
+    return g_strndup (url + 7, strlen (url) - 7);
+
+  folder = uri_tester_ensure_data_dir ();
+  filename = g_compute_checksum_for_string (G_CHECKSUM_MD5, url, -1);
+
+  path = g_build_filename (folder, filename, NULL);
+  uri = g_filename_to_uri (path, NULL, NULL);
+
+  g_free (filename);
+  g_free (path);
+  g_free (folder);
+
+  return uri;
+}
+
+static void
+uri_tester_download_notify_status_cb (WebKitDownload *download,
+                                      GParamSpec *pspec,
+                                      UriTester *tester)
+{
+  const char *dest = NULL;
+
+  if (webkit_download_get_status (download) != WEBKIT_DOWNLOAD_STATUS_FINISHED)
+    return;
+
+  LOG ("Download from %s to %s completed",
+       webkit_download_get_uri (download),
+       webkit_download_get_destination_uri (download));
+
+  /* Parse the file from disk. */
+  dest = webkit_download_get_destination_uri (download);
+  uri_tester_parse_file_at_uri (tester, dest);
+}
+
+static void
+uri_tester_retrieve_filter (UriTester *tester, const char *url, const char *fileuri)
+{
+  WebKitNetworkRequest *request = NULL;
+  WebKitDownload *download = NULL;
+
+  g_return_if_fail (IS_URI_TESTER (tester));
+  g_return_if_fail (url != NULL);
+  g_return_if_fail (fileuri != NULL);
+
+  request = webkit_network_request_new (url);
+  download = webkit_download_new (request);
+  g_object_unref (request);
+
+  webkit_download_set_destination_uri (download, fileuri);
+
+  g_signal_connect (download, "notify::status",
+                    G_CALLBACK (uri_tester_download_notify_status_cb), tester);
+
+  webkit_download_start (download);
+}
+
+static gboolean
+uri_tester_filter_is_valid (const char *fileuri)
+{
+  GFile *file = NULL;
+  GFileInfo *file_info = NULL;
+  gboolean result;
+
+  /* Now check if the local file is too old. */
+  file = g_file_new_for_uri (fileuri);
+  file_info = g_file_query_info (file,
+                                 G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 NULL);
+  result = FALSE;
+  if (file_info)
+    {
+      GTimeVal current_time;
+      GTimeVal mod_time;
+
+      g_get_current_time (&current_time);
+      g_file_info_get_modification_time (file_info, &mod_time);
+
+      if (current_time.tv_sec > mod_time.tv_sec)
+        {
+          gint64 expire_time = mod_time.tv_sec + UPDATE_FREQUENCY;
+          result = current_time.tv_sec < expire_time;
+        }
+      g_object_unref (file_info);
+    }
+
+  g_object_unref (file);
+
+  return result;
+}
+
+static void
+uri_tester_load_patterns (UriTester *tester)
+{
+  GSList *filter = NULL;
+  char *url = NULL;
+  char *fileuri = NULL;
+
+  /* Load patterns from the list of filters. */
+  for (filter = tester->priv->filters; filter; filter = g_slist_next(filter))
+    {
+      url = (char*)filter->data;
+      fileuri = uri_tester_get_fileuri_for_url (url);
+
+      if (!uri_tester_filter_is_valid (fileuri))
+        uri_tester_retrieve_filter (tester, url, fileuri);
+      else
+        uri_tester_parse_file_at_uri (tester, fileuri);
+
+      g_free (fileuri);
+    }
+}
+
+static void
+uri_tester_load_filters (UriTester *tester)
+{
+  GSList *list = NULL;
+  char *data_dir = NULL;
+  char *filepath = NULL;
+
+  data_dir =  uri_tester_ensure_data_dir ();
+  filepath = g_build_filename (data_dir, FILTERS_LIST_FILENAME, NULL);
+
+  if (g_file_test (filepath, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
+    {
+      GFile *file = NULL;
+      char *contents = NULL;
+      gsize length = 0;
+      GError *error = NULL;
+
+      file = g_file_new_for_path (filepath);
+      if (g_file_load_contents (file, NULL, &contents, &length, NULL, &error))
+        {
+          char **urls_array = NULL;
+          char *url = NULL;
+          int i = 0;
+
+          urls_array = g_strsplit (contents, ";", -1);
+          for (i = 0; urls_array [i]; i++)
+            {
+              url = g_strstrip (g_strdup (urls_array[i]));
+              if (!g_str_equal (url, ""))
+                list = g_slist_prepend (list, url);
+            }
+          g_strfreev (urls_array);
+
+          g_free (contents);
+        }
+
+      if (error)
+        {
+          LOG ("Error loading filters from %s: %s", filepath, error->message);
+          g_error_free (error);
+        }
+
+      g_object_unref (file);
+    }
+  else
+    {
+      /* No file exists yet, so use the default filter and save it. */
+      list = g_slist_prepend (list, g_strdup (DEFAULT_FILTER_URL));
+    }
+
+  g_free (filepath);
+
+  uri_tester_set_filters (tester, g_slist_reverse(list));
+}
+
+static void
+uri_tester_save_filters (UriTester *tester)
+{
+  FILE *file = NULL;
+  char *data_dir = NULL;
+  char *filepath = NULL;
+
+  data_dir =  uri_tester_ensure_data_dir ();
+  filepath = g_build_filename (data_dir, FILTERS_LIST_FILENAME, NULL);
+
+  if ((file = g_fopen (filepath, "w")))
+    {
+      GSList *item = NULL;
+      char *filter = NULL;
+
+      for (item = tester->priv->filters; item; item = g_slist_next (item))
+        {
+          filter = g_strdup_printf ("%s;", (char*)item->data);
+          fputs (filter, file);
+          g_free (filter);
+        }
+      fclose (file);
+    }
+  g_free (filepath);
+}
+
+static inline int
+uri_tester_check_rule (UriTester  *tester,
+                       GRegex     *regex,
+                       const char *patt,
+                       const char *req_uri,
+                       const char *page_uri)
+{
+  char *opts;
+
+  if (!g_regex_match_full (regex, req_uri, -1, 0, 0, NULL, NULL))
+    return FALSE;
+
+  opts = g_hash_table_lookup (tester->priv->optslist, patt);
+  if (opts && g_regex_match_simple (",third-party", opts,
+                                    G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY))
+    {
+      if (page_uri && g_regex_match_full (regex, page_uri, -1, 0, 0, NULL, NULL))
+        return FALSE;
+    }
+  /* TODO: Domain opt check */
+  LOG ("blocked by pattern regexp=%s -- %s", g_regex_get_pattern (regex), req_uri);
+  return TRUE;
+}
+
+static inline gboolean
+uri_tester_is_matched_by_pattern (UriTester  *tester,
+                                  const char *req_uri,
+                                  const char *page_uri)
+{
+  GHashTableIter iter;
+  gpointer patt, regex;
+
+  g_hash_table_iter_init (&iter, tester->priv->pattern);
+  while (g_hash_table_iter_next (&iter, &patt, &regex))
+    {
+      if (uri_tester_check_rule(tester, regex, patt, req_uri, page_uri))
+        return TRUE;
+    }
+  return FALSE;
+}
+
+static inline gboolean
+uri_tester_is_matched_by_key (UriTester  *tester,
+                              const char *opts,
+                              const char *req_uri,
+                              const char *page_uri)
+{
+  UriTesterPrivate *priv = NULL;
+  char *uri;
+  int len;
+  int pos = 0;
+  GList *regex_bl = NULL;
+  GString *guri;
+  gboolean ret = FALSE;
+  char sig[SIGNATURE_SIZE + 1];
+
+  priv = tester->priv;
+
+  memset (&sig[0], 0, sizeof (sig));
+  /* Signatures are made on pattern, so we need to convert url to a pattern as well */
+  guri = uri_tester_fixup_regexp ("", (char*)req_uri);
+  uri = guri->str;
+  len = guri->len;
+
+  for (pos = len - SIGNATURE_SIZE; pos >= 0; pos--)
+    {
+      GRegex *regex;
+      strncpy (sig, uri + pos, SIGNATURE_SIZE);
+      regex = g_hash_table_lookup (priv->keys, sig);
+
+      /* Dont check if regex is already blacklisted */
+      if (!regex || g_list_find (regex_bl, regex))
+        continue;
+      ret = uri_tester_check_rule (tester, regex, sig, req_uri, page_uri);
+      if (ret)
+        break;
+      regex_bl = g_list_prepend (regex_bl, regex);
+    }
+  g_string_free (guri, TRUE);
+  g_list_free (regex_bl);
+  return ret;
+}
+
+static gboolean
+uri_tester_is_matched (UriTester  *tester,
+                       const char *opts,
+                       const char *req_uri,
+                       const char *page_uri)
+{
+  UriTesterPrivate *priv = NULL;
+  char *value;
+
+  priv = tester->priv;
+
+  /* Check cached URLs first. */
+  if ((value = g_hash_table_lookup (priv->urlcache, req_uri)))
+    return (value[0] != '0') ? TRUE : FALSE;
+
+  /* Look for a match either by key or by pattern. */
+  if (uri_tester_is_matched_by_key (tester, opts, req_uri, page_uri))
+    {
+      g_hash_table_insert (priv->urlcache, g_strdup (req_uri), g_strdup("1"));
+      return TRUE;
+    }
+
+  /* Matching by pattern is pretty expensive, so do it if needed only. */
+  if (uri_tester_is_matched_by_pattern (tester, req_uri, page_uri))
+    {
+      g_hash_table_insert (priv->urlcache, g_strdup (req_uri), g_strdup("1"));
+      return TRUE;
+    }
+
+  g_hash_table_insert (priv->urlcache, g_strdup (req_uri), g_strdup("0"));
+  return FALSE;
+}
+
+static GString *
+uri_tester_fixup_regexp (const char *prefix, char *src)
+{
+  GString *str;
+  int len = 0;
+
+  if (!src)
+    return NULL;
+
+  str = g_string_new (prefix);
+
+  /* lets strip first .* */
+  if (src[0] == '*')
+    {
+      (void)*src++;
+    }
+
+  do
+    {
+      switch (*src)
+        {
+        case '*':
+          g_string_append (str, ".*");
+          break;
+          /*case '.':
+            g_string_append (str, "\\.");
+            break;*/
+        case '?':
+          g_string_append (str, "\\?");
+          break;
+        case '|':
+          /* FIXME: We actually need to match :[0-9]+ or '/'. Sign means
+             "here could be port number or nothing". So bla.com^ will match
+             bla.com/ or bla.com:8080/ but not bla.com.au/ */
+        case '^':
+        case '+':
+          break;
+        default:
+          g_string_append_printf (str,"%c", *src);
+          break;
+        }
+      src++;
+    }
+  while (*src);
+
+  len = str->len;
+  /* We dont need .* in the end of url. Thats stupid */
+  if (str->str && str->str[len-1] == '*' && str->str[len-2] == '.')
+    g_string_erase (str, len-2, 2);
+
+  return str;
+}
+
+static gboolean
+uri_tester_compile_regexp (UriTester *tester,
+                           GString   *gpatt,
+                           char      *opts)
+{
+  GRegex *regex;
+  GError *error = NULL;
+  char *patt;
+  int len;
+
+  if (!gpatt)
+    return FALSE;
+
+  patt = gpatt->str;
+  len = gpatt->len;
+
+  /* TODO: Play with optimization flags */
+  regex = g_regex_new (patt, G_REGEX_OPTIMIZE,
+                       G_REGEX_MATCH_NOTEMPTY, &error);
+  if (error)
+    {
+      g_warning ("%s: %s", G_STRFUNC, error->message);
+      g_error_free (error);
+      g_regex_unref (regex);
+      return TRUE;
+    }
+
+  if (!g_regex_match_simple ("^/.*[\\^\\$\\*].*/$", patt, G_REGEX_UNGREEDY, G_REGEX_MATCH_NOTEMPTY))
+    {
+      int signature_count = 0;
+      int pos = 0;
+      char *sig;
+
+      for (pos = len - SIGNATURE_SIZE; pos >= 0; pos--) {
+        sig = g_strndup (patt + pos, SIGNATURE_SIZE);
+        if (!g_regex_match_simple ("[\\*]", sig, G_REGEX_UNGREEDY, G_REGEX_MATCH_NOTEMPTY) &&
+            !g_hash_table_lookup (tester->priv->keys, sig))
+          {
+            LOG ("sig: %s %s", sig, patt);
+            g_hash_table_insert (tester->priv->keys, g_strdup (sig), g_regex_ref (regex));
+            g_hash_table_insert (tester->priv->optslist, g_strdup (sig), g_strdup (opts));
+            signature_count++;
+          }
+        else
+          {
+            if (g_regex_match_simple ("^\\*", sig, G_REGEX_UNGREEDY, G_REGEX_MATCH_NOTEMPTY) &&
+                !g_hash_table_lookup (tester->priv->pattern, patt))
+              {
+                LOG ("patt2: %s %s", sig, patt);
+                g_hash_table_insert (tester->priv->pattern, g_strdup (patt), g_regex_ref (regex));
+                g_hash_table_insert (tester->priv->optslist, g_strdup (patt), g_strdup (opts));
+              }
+          }
+        g_free (sig);
+      }
+      g_regex_unref (regex);
+
+      if (signature_count > 1 && g_hash_table_lookup (tester->priv->pattern, patt))
+        {
+          g_hash_table_steal (tester->priv->pattern, patt);
+          return TRUE;
+        }
+
+      return FALSE;
+    }
+  else
+    {
+      LOG ("patt: %s%s", patt, "");
+      /* Pattern is a regexp chars */
+      g_hash_table_insert (tester->priv->pattern, g_strdup (patt), regex);
+      g_hash_table_insert (tester->priv->optslist, g_strdup (patt), g_strdup (opts));
+      return FALSE;
+    }
+}
+
+static char*
+uri_tester_add_url_pattern (UriTester *tester,
+                            char      *prefix,
+                            char      *type,
+                            char      *line)
+{
+    char **data;
+    char *patt;
+    GString *format_patt;
+    char *opts;
+    gboolean should_free;
+
+    data = g_strsplit (line, "$", -1);
+    if (!data || !data[0])
+    {
+        g_strfreev (data);
+        return NULL;
+    }
+
+    if (data[1] && data[2])
+    {
+        patt = g_strconcat (data[0], data[1], NULL);
+        opts = g_strconcat (type, ",", data[2], NULL);
+    }
+    else if (data[1])
+    {
+        patt = data[0];
+        opts = g_strconcat (type, ",", data[1], NULL);
+    }
+    else
+    {
+        patt = data[0];
+        opts = type;
+    }
+
+    if (g_regex_match_simple ("subdocument", opts,
+                              G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY))
+    {
+        if (data[1] && data[2])
+            g_free (patt);
+        if (data[1])
+            g_free (opts);
+        g_strfreev (data);
+        return NULL;
+    }
+
+    format_patt = uri_tester_fixup_regexp (prefix, patt);
+
+    LOG ("got: %s opts %s", format_patt->str, opts);
+    should_free = uri_tester_compile_regexp (tester, format_patt, opts);
+
+    if (data[1] && data[2])
+        g_free (patt);
+    if (data[1])
+        g_free (opts);
+    g_strfreev (data);
+
+    return g_string_free (format_patt, should_free);
+}
+
+static inline void
+uri_tester_frame_add (UriTester *tester, char *line)
+{
+  const char *separator = " , ";
+
+  (void)*line++;
+  (void)*line++;
+  if (strchr (line, '\'')
+      || (strchr (line, ':')
+          && !g_regex_match_simple (".*\\[.*:.*\\].*", line,
+                                    G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY)))
+    {
+      return;
+    }
+  g_string_append (tester->priv->blockcss, separator);
+  g_string_append (tester->priv->blockcss, line);
+}
+
+static inline void
+uri_tester_frame_add_private (UriTester  *tester,
+                              const char *line,
+                              const char *sep)
+{
+  char **data;
+  data = g_strsplit (line, sep, 2);
+
+  if (!(data[1] && *data[1])
+      ||  strchr (data[1], '\'')
+      || (strchr (data[1], ':')
+          && !g_regex_match_simple (".*\\[.*:.*\\].*", data[1],
+                                    G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY)))
+    {
+      g_strfreev (data);
+      return;
+    }
+
+  if (strchr (data[0], ','))
+    {
+      char **domains;
+      int i;
+
+      domains = g_strsplit (data[0], ",", -1);
+      for (i = 0; domains[i]; i++)
+        {
+          g_string_append_printf (tester->priv->blockcssprivate, ";sites['%s']+=',%s'",
+                                  g_strstrip (domains[i]), data[1]);
+        }
+      g_strfreev (domains);
+    }
+  else
+    {
+      g_string_append_printf (tester->priv->blockcssprivate, ";sites['%s']+=',%s'",
+                              data[0], data[1]);
+    }
+  g_strfreev (data);
+}
+
+static char*
+uri_tester_parse_line (UriTester *tester, char *line)
+{
+  if (!line)
+    return NULL;
+  g_strchomp (line);
+  /* Ignore comments and new lines */
+  if (line[0] == '!')
+    return NULL;
+  /* FIXME: No support for whitelisting */
+  if (line[0] == '@' && line[1] == '@')
+    return NULL;
+  /* FIXME: No support for [include] and [exclude] tags */
+  if (line[0] == '[')
+    return NULL;
+
+  /* Skip garbage */
+  if (line[0] == ' ' || !line[0])
+    return NULL;
+
+  /* Got CSS block hider */
+  if (line[0] == '#' && line[1] == '#' )
+    {
+      uri_tester_frame_add (tester, line);
+      return NULL;
+    }
+  /* Got CSS block hider. Workaround */
+  if (line[0] == '#')
+    return NULL;
+
+  /* Got per domain CSS hider rule */
+  if (strstr (line, "##"))
+    {
+      uri_tester_frame_add_private (tester, line, "##");
+      return NULL;
+    }
+
+  /* Got per domain CSS hider rule. Workaround */
+  if (strchr (line, '#'))
+    {
+      uri_tester_frame_add_private (tester, line, "#");
+      return NULL;
+    }
+  /* Got URL blocker rule */
+  if (line[0] == '|' && line[1] == '|' )
+    {
+      (void)*line++;
+      (void)*line++;
+      return uri_tester_add_url_pattern (tester, "", "fulluri", line);
+    }
+  if (line[0] == '|')
+    {
+      (void)*line++;
+      return uri_tester_add_url_pattern (tester, "^", "fulluri", line);
+    }
+  return uri_tester_add_url_pattern (tester, "", "uri", line);
+}
+
+static gboolean
+uri_tester_parse_file_at_uri (UriTester *tester, const char *fileuri)
+{
+  FILE *file;
+  char line[2000];
+  char *path = NULL;
+  gboolean result = FALSE;
+
+  path = g_filename_from_uri (fileuri, NULL, NULL);
+  if ((file = g_fopen (path, "r")))
+    {
+      while (fgets (line, 2000, file))
+        g_free (uri_tester_parse_line (tester, line));
+      fclose (file);
+
+      result = TRUE;
+    }
+  g_free (path);
+
+  return result;
+}
+
+static void
+uri_tester_init (UriTester *tester)
+{
+  UriTesterPrivate *priv = NULL;
+
+  LOG ("UriTester initializing %p", tester);
+
+  priv = URI_TESTER_GET_PRIVATE (tester);
+  tester->priv = priv;
+
+  priv->filters = NULL;
+  priv->pattern = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                         (GDestroyNotify)g_free,
+                                         (GDestroyNotify)g_regex_unref);
+  priv->keys = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                      (GDestroyNotify)g_free,
+                                      (GDestroyNotify)g_regex_unref);
+  priv->optslist = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                          NULL,
+                                          (GDestroyNotify)g_free);
+  priv->urlcache = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                          (GDestroyNotify)g_free,
+                                          (GDestroyNotify)g_free);
+
+  priv->blockcss = g_string_new ("z-non-exist");
+  priv->blockcssprivate = g_string_new ("");
+
+  uri_tester_load_filters (tester);
+  uri_tester_load_patterns (tester);
+}
+
+static void
+uri_tester_set_property (GObject *object,
+                         guint prop_id,
+                         const GValue *value,
+                         GParamSpec *pspec)
+{
+  UriTester *tester = URI_TESTER (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILTERS:
+      uri_tester_set_filters (tester, (GSList*) g_value_get_pointer (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+uri_tester_finalize (GObject *object)
+{
+  UriTesterPrivate *priv = URI_TESTER_GET_PRIVATE (URI_TESTER (object));
+
+  LOG ("UriTester finalizing %p", object);
+
+  g_slist_foreach (priv->filters, (GFunc) g_free, NULL);
+  g_slist_free (priv->filters);
+
+  g_hash_table_destroy (priv->pattern);
+  g_hash_table_destroy (priv->keys);
+  g_hash_table_destroy (priv->optslist);
+  g_hash_table_destroy (priv->urlcache);
+
+  g_string_free (priv->blockcss, TRUE);
+  g_string_free (priv->blockcssprivate, TRUE);
+
+  G_OBJECT_CLASS (uri_tester_parent_class)->finalize (object);
+}
+
+static void
+uri_tester_class_init (UriTesterClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = uri_tester_set_property;
+  object_class->finalize = uri_tester_finalize;
+
+  g_object_class_install_property
+    (object_class,
+     PROP_FILTERS,
+     g_param_spec_pointer ("filters",
+                           "filters",
+                           "filters",
+                           G_PARAM_WRITABLE));
+
+  g_type_class_add_private (object_class, sizeof (UriTesterPrivate));
+}
+
+UriTester *
+uri_tester_new (void)
+{
+  return g_object_new (TYPE_URI_TESTER, NULL);
+}
+
+gboolean
+uri_tester_test_uri (UriTester *tester,
+                     const char *req_uri,
+                     const char *page_uri,
+                     AdUriCheckType type)
+{
+  /* Don't block top level documents. */
+  if (type == AD_URI_CHECK_TYPE_DOCUMENT)
+    return FALSE;
+
+  return uri_tester_is_matched (tester, NULL, req_uri, page_uri);
+}
+
+void
+uri_tester_set_filters (UriTester *tester, GSList *filters)
+{
+  UriTesterPrivate *priv = tester->priv;
+
+  if (priv->filters)
+    {
+      g_slist_foreach (priv->filters, (GFunc) g_free, NULL);
+      g_slist_free (priv->filters);
+    }
+
+  /* Update private variable and save to disk. */
+  priv->filters = filters;
+  uri_tester_save_filters (tester);
+}
+
+GSList *
+uri_tester_get_filters (UriTester *tester)
+{
+  return tester->priv->filters;
+}
+
+void
+uri_tester_reload (UriTester *tester)
+{
+  GDir *g_data_dir = NULL;
+  const char *data_dir = NULL;
+
+  /* Remove data files in the data dir first. */
+  data_dir = uri_tester_ensure_data_dir ();
+
+  g_data_dir = g_dir_open (data_dir, 0, NULL);
+  if (g_data_dir)
+    {
+      const char *filename = NULL;
+      char *filepath = NULL;
+
+      while ((filename = g_dir_read_name (g_data_dir)))
+        {
+          /* Omit the list of filters. */
+          if (!g_strcmp0 (filename, FILTERS_LIST_FILENAME))
+            continue;
+
+          filepath = g_build_filename (data_dir, filename, NULL);
+          g_unlink (filepath);
+
+          g_free (filepath);
+        }
+
+      g_dir_close (g_data_dir);
+    }
+
+  /* Load patterns from current filters. */
+  uri_tester_load_patterns (tester);
+}
diff --git a/src/uri-tester.h b/src/uri-tester.h
new file mode 100644
index 0000000..d8fb85a
--- /dev/null
+++ b/src/uri-tester.h
@@ -0,0 +1,72 @@
+/*
+ *  Copyright  2011 Igalia S.L.
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef URI_TESTER_H
+#define URI_TESTER_H
+
+#include "ephy-adblock.h"
+
+#include <glib-object.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_URI_TESTER         (uri_tester_get_type ())
+#define URI_TESTER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TYPE_URI_TESTER, UriTester))
+#define URI_TESTER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), TYPE_URI_TESTER, UriTesterClass))
+#define IS_URI_TESTER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TYPE_URI_TESTER))
+#define IS_URI_TESTER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), TYPE_URI_TESTER))
+#define URI_TESTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TYPE_URI_TESTER, UriTesterClass))
+
+typedef struct _UriTester        UriTester;
+typedef struct _UriTesterClass   UriTesterClass;
+typedef struct _UriTesterPrivate UriTesterPrivate;
+
+struct _UriTester
+{
+  GObject parent_instance;
+
+  /*< private >*/
+  UriTesterPrivate *priv;
+};
+
+struct _UriTesterClass
+{
+  GObjectClass parent_class;
+};
+
+GType      uri_tester_get_type    (void);
+
+void       uri_tester_register    (GTypeModule *module);
+
+UriTester *uri_tester_new         (void);
+
+gboolean   uri_tester_test_uri    (UriTester *tester,
+                                   const char *req_uri,
+                                   const char *page_uri,
+                                   AdUriCheckType type);
+
+void       uri_tester_set_filters (UriTester *tester, GSList *filters);
+
+GSList    *uri_tester_get_filters (UriTester *tester);
+
+void       uri_tester_reload      (UriTester *tester);
+
+G_END_DECLS
+
+#endif /* URI_TESTER_H */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]