diff options
-rw-r--r-- | src/Makefile.am | 7 | ||||
-rw-r--r-- | src/adblock-ui.c | 390 | ||||
-rw-r--r-- | src/adblock-ui.h | 62 | ||||
-rw-r--r-- | src/ephy-adblock-extension.c | 297 | ||||
-rw-r--r-- | src/ephy-adblock-extension.h | 55 | ||||
-rw-r--r-- | src/ephy-shell.c | 9 | ||||
-rw-r--r-- | src/epiphany.gresource.xml | 1 | ||||
-rw-r--r-- | src/resources/adblock.ui | 198 | ||||
-rw-r--r-- | src/uri-tester.c | 895 | ||||
-rw-r--r-- | src/uri-tester.h | 72 |
10 files changed, 1986 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index ada8e4716..2f3599538 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 000000000..d41ed53c1 --- /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 000000000..b308e849f --- /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 000000000..93068f2c5 --- /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 000000000..46bea1bca --- /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 88f843e40..e88e9a086 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 b75646667..c04d5370b 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 000000000..883106926 --- /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 000000000..bae25e43a --- /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 (¤t_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, ®ex)) + { + 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 000000000..d8fb85aec --- /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 */ |