Merge lp:~midori/midori/wk2ab into lp:midori

Proposed by Cris Dywan
Status: Merged
Approved by: Paweł Forysiuk
Approved revision: 6325
Merged at revision: 6558
Proposed branch: lp:~midori/midori/wk2ab
Merge into: lp:midori
Diff against target: 3520 lines (+1443/-1934)
18 files modified
data/adblock.list (+54/-0)
extensions/CMakeLists.txt (+18/-2)
extensions/adblock.c (+0/-1930)
extensions/adblock/config.vala (+107/-0)
extensions/adblock/element.vala (+36/-0)
extensions/adblock/extension.vala (+545/-0)
extensions/adblock/filter.vala (+52/-0)
extensions/adblock/keys.vala (+47/-0)
extensions/adblock/options.vala (+32/-0)
extensions/adblock/pattern.vala (+26/-0)
extensions/adblock/subscriptions.vala (+328/-0)
extensions/adblock/updater.vala (+139/-0)
extensions/adblock/whitelist.vala (+30/-0)
katze/midori-paths.vala (+1/-0)
midori/midori-app.c (+1/-1)
midori/midori.vapi (+1/-0)
midori/webkit2gtk-3.0.vapi (+16/-0)
po/POTFILES.in (+10/-1)
To merge this branch: bzr merge lp:~midori/midori/wk2ab
Reviewer Review Type Date Requested Status
Paweł Forysiuk Approve
Review via email: mp+178349@code.launchpad.net

Commit message

Rewrite Adblock more modularly, add Whitelist support

Description of the change

TODO:
- Expose filter metadata to user (name, last updated, time to next update)
- Check checksum hash of the subscription after downloading
- Remove subscriptions
- Open local files in editor
- Ability to disable adblock for site/for all sites via icon
- Save pre-parsed pre filtered ruleset into file as .preparsed
- Save matched/ cached uris from previous runs

[https://easylist-downloads.adblockplus.org/easylist.txt]
enabled=1
expires=48206903549424
retries=1
homepage=https://easylist-downloads.adblockplus.org
title=EasyList

Test cases https://hg.adblockplus.org/adblockplustests/file/3e166c282a1d/chrome/content/tests/elemhide.js

To post a comment you must log in.
lp:~midori/midori/wk2ab updated
6325. By Cris Dywan

Rename adblock2 to adblock

Revision history for this message
Paweł Forysiuk (tuxator) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'data/adblock.list'
--- data/adblock.list 1970-01-01 00:00:00 +0000
+++ data/adblock.list 2014-02-20 20:31:17 +0000
@@ -0,0 +1,54 @@
1[Adblock Plus 2.0]
2! Version: 201402142200
3! Title: Exercise
4! Last modified: 11 Feb 2014 22:00 UTC
5! Expires: 3 days (update frequency)
6! Homepage: http://www.midori-browser.org
7! Licence: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
8! Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
9!
10! Yadayada http://example.com/ e-mail (somebody@example.com).
11!
12!-----Spam eggs--------!
13! *** la:le/lu_foo_bar.txt ***
14|/http://ads.blub.boing/*$domain=xxx.com
15|/http://ads.blub.boing/*$domain=xxx.com,foo.fr,coco.at
16/market.php?$domain=adf.ly|foo.com
17/?placement=$script,domain=putlocker.com|sockshare.com
18
19! Some basic filters
20*ads.foo.bar*
21*ads.bogus.name*
22||^http://ads.bla.blub/*
23engine.adct.ru/*?
24/addyn|*|adtech;
25doubleclick.net/pfadx/*.mtvi
26objects.tremormedia.com/embed/xml/*.xml?r=
27videostrip.com^*/admatcherclient.
28test.dom/test?var
29/adpage.
30br.gcl.ru/cgi-bin/br/
31_300x600.
32_rectangle_ads.
33+adverts/
34-2/ads/
35
36! CSS elements
37old.tv,delicio.us,longc.at###box
38##.zRightAdNote
39###advertisingModule160x600
40##a[href$="/vghd.shtml"]
41imagetwist.com###left[align="center"] > center > a[target="_blank"]
42
43! Options
44||videobox.com/?tid=$popup
45||sexsearchcom.com^$popup,third-party
46||206.217.206.137^$third-party
47/spopunder^$popup
48||putlocker.com^*.php?*title$subdocument
49://ads.$popup
50
51! Whitelist
52@@||hortifor.com/images/*120x60$~third-party
53@@||stickam.com/wb/www/category/300x250/$image
54@@||adultadworld.com/adhandler/$subdocument
055
=== modified file 'extensions/CMakeLists.txt'
--- extensions/CMakeLists.txt 2013-09-08 19:56:46 +0000
+++ extensions/CMakeLists.txt 2014-02-20 20:31:17 +0000
@@ -15,7 +15,6 @@
15file(GLOB EXTENSIONS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *)15file(GLOB EXTENSIONS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *)
16if (HALF_BRO_INCOM_WEBKIT2)16if (HALF_BRO_INCOM_WEBKIT2)
17 list(REMOVE_ITEM EXTENSIONS17 list(REMOVE_ITEM EXTENSIONS
18 "adblock.c"
19 "cookie-permissions"18 "cookie-permissions"
20 "addons.c"19 "addons.c"
21 "formhistory"20 "formhistory"
@@ -46,13 +45,30 @@
46 string(FIND ${UNIT_SRC} "." UNIT_EXTENSION)45 string(FIND ${UNIT_SRC} "." UNIT_EXTENSION)
47 if (UNIT_EXTENSION EQUAL -1)46 if (UNIT_EXTENSION EQUAL -1)
48 file(GLOB UNIT_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${UNIT_SRC}/*.c")47 file(GLOB UNIT_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${UNIT_SRC}/*.c")
48 file(GLOB UNIT_FILES_VALA RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${UNIT_SRC}/*.vala")
49 if (UNIT_FILES_VALA)
50 include(ValaPrecompile)
51 vala_precompile(UNIT_SRC_C ${UNIT_SRC}
52 ${UNIT_FILES_VALA}
53 PACKAGES
54 ${PKGS}
55 OPTIONS
56 ${VALAFLAGS}
57 CUSTOM_VAPIS
58 ${EXTRA_VAPIS}
59 "${CMAKE_SOURCE_DIR}/midori/midori.vapi"
60 "${CMAKE_SOURCE_DIR}/katze/katze.vapi"
61 "${CMAKE_BINARY_DIR}/midori/${LIBMIDORI}.vapi"
62 )
63 set(UNIT_FILES ${UNIT_FILES} ${UNIT_SRC_C})
64 endif ()
49 if (UNIT_FILES)65 if (UNIT_FILES)
50 add_library(${UNIT_SRC} MODULE ${UNIT_FILES})66 add_library(${UNIT_SRC} MODULE ${UNIT_FILES})
51 target_link_libraries(${UNIT_SRC}67 target_link_libraries(${UNIT_SRC}
52 ${LIBMIDORI}68 ${LIBMIDORI}
53 )69 )
54 set_target_properties(${UNIT_SRC} PROPERTIES70 set_target_properties(${UNIT_SRC} PROPERTIES
55 COMPILE_FLAGS ${CFLAGS}71 COMPILE_FLAGS ${VALA_CFLAGS}
56 )72 )
57 install(TARGETS ${UNIT_SRC}73 install(TARGETS ${UNIT_SRC}
58 LIBRARY DESTINATION ${EXTENSIONDIR}74 LIBRARY DESTINATION ${EXTENSIONDIR}
5975
=== added directory 'extensions/adblock'
=== removed file 'extensions/adblock.c'
--- extensions/adblock.c 2013-11-09 17:56:58 +0000
+++ extensions/adblock.c 1970-01-01 00:00:00 +0000
@@ -1,1930 +0,0 @@
1/*
2 Copyright (C) 2009-2012 Christian Dywan <christian@twotoasts.de>
3 Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 See the file COPYING for the full license text.
11*/
12#include <midori/midori.h>
13#include "midori-core.h"
14#include <glib/gstdio.h>
15
16#include "config.h"
17#if HAVE_UNISTD_H
18 #include <unistd.h>
19#endif
20
21#define SIGNATURE_SIZE 8
22#define USE_PATTERN_MATCHING 1
23#define CUSTOM_LIST_NAME "custom.list"
24#define ADBLOCK_FILTER_VALID(__filter) \
25 (__filter && (g_str_has_prefix (__filter, "http") \
26 || g_str_has_prefix (__filter, "file")))
27#define ADBLOCK_FILTER_SET(__filter,__active) \
28 __filter[4] = __active ? (__filter[5] == ':' ? 's' : ':') : '-'
29#define ADBLOCK_FILTER_IS_SET(__filter) \
30 (__filter[4] != '-' && __filter[5] != '-')
31 #define adblock_debug(dmsg, darg1, darg2) \
32 do { if (midori_debug ("adblock:match")) g_debug (dmsg, darg1, darg2); } while (0)
33
34static GHashTable* pattern = NULL;
35static GHashTable* keys = NULL;
36static GHashTable* optslist = NULL;
37static GHashTable* urlcache = NULL;
38static GHashTable* blockcssprivate = NULL;
39static GHashTable* navigationwhitelist = NULL;
40static GString* blockcss = NULL;
41static GList* update_list = NULL;
42static gboolean update_done = FALSE;
43
44static void
45adblock_parse_file (gchar* path);
46
47static gboolean
48adblock_file_is_up_to_date (gchar* path);
49
50static void
51adblock_reload_rules (MidoriExtension* extension,
52 gboolean custom_only);
53
54static gchar*
55adblock_build_js (const gchar* uri)
56{
57 gchar* domain;
58 const gchar* style;
59 GString* subdomain;
60 GString* code;
61 int cnt = 0, blockscnt = 0;
62 gchar** subdomains;
63
64 domain = midori_uri_parse_hostname (uri, NULL);
65 subdomains = g_strsplit (domain, ".", -1);
66 g_free (domain);
67 if (!subdomains)
68 return NULL;
69
70 code = g_string_new (
71 "window.addEventListener ('DOMContentLoaded',"
72 "function () {"
73 " if (document.getElementById('madblock'))"
74 " return;"
75 " public = '");
76
77 cnt = g_strv_length (subdomains) - 1;
78 subdomain = g_string_new (subdomains [cnt]);
79 g_string_prepend_c (subdomain, '.');
80 cnt--;
81 while (cnt >= 0)
82 {
83 g_string_prepend (subdomain, subdomains[cnt]);
84 if ((style = g_hash_table_lookup (blockcssprivate, subdomain->str)))
85 {
86 g_string_append (code, style);
87 g_string_append_c (code, ',');
88 blockscnt++;
89 }
90 g_string_prepend_c (subdomain, '.');
91 cnt--;
92 }
93 g_string_free (subdomain, TRUE);
94 g_strfreev (subdomains);
95
96 if (blockscnt == 0)
97 return g_string_free (code, TRUE);
98
99 g_string_append (code,
100 " zz-non-existent {display: none !important}';"
101 " var mystyle = document.createElement('style');"
102 " mystyle.setAttribute('type', 'text/css');"
103 " mystyle.setAttribute('id', 'madblock');"
104 " mystyle.appendChild(document.createTextNode(public));"
105 " var head = document.getElementsByTagName('head')[0];"
106 " if (head) head.appendChild(mystyle);"
107 "}, true);");
108 return g_string_free (code, FALSE);
109}
110
111static GString*
112adblock_fixup_regexp (const gchar* prefix,
113 gchar* src);
114
115static void
116adblock_destroy_db ()
117{
118 g_string_free (blockcss, TRUE);
119 blockcss = NULL;
120
121 g_hash_table_destroy (pattern);
122 pattern = NULL;
123 g_hash_table_destroy (optslist);
124 optslist = NULL;
125 g_hash_table_destroy (urlcache);
126 urlcache = NULL;
127 g_hash_table_destroy (blockcssprivate);
128 blockcssprivate = NULL;
129 g_hash_table_destroy (navigationwhitelist);
130 navigationwhitelist = NULL;
131}
132
133static void
134adblock_init_db ()
135{
136 pattern = g_hash_table_new_full (g_str_hash, g_str_equal,
137 (GDestroyNotify)g_free,
138 (GDestroyNotify)g_regex_unref);
139 keys = g_hash_table_new_full (g_str_hash, g_str_equal,
140 (GDestroyNotify)g_free,
141 (GDestroyNotify)g_regex_unref);
142 optslist = g_hash_table_new_full (g_str_hash, g_str_equal,
143 NULL,
144 (GDestroyNotify)g_free);
145 urlcache = g_hash_table_new_full (g_str_hash, g_str_equal,
146 (GDestroyNotify)g_free,
147 (GDestroyNotify)g_free);
148 blockcssprivate = g_hash_table_new_full (g_str_hash, g_str_equal,
149 (GDestroyNotify)g_free,
150 (GDestroyNotify)g_free);
151 navigationwhitelist = g_hash_table_new_full (g_direct_hash, g_str_equal,
152 NULL,
153 (GDestroyNotify)g_free);
154
155 blockcss = g_string_new ("z-non-exist");
156}
157
158static void
159adblock_download_notify_status_cb (WebKitDownload* download,
160 GParamSpec* pspec,
161 MidoriExtension* extension)
162{
163 if (update_done)
164 return;
165
166 if (webkit_download_get_status (download) == WEBKIT_DOWNLOAD_STATUS_FINISHED)
167 {
168 GList* li = NULL;
169 for (li = update_list; li != NULL; li = g_list_next (li))
170 {
171 gchar* uri = g_strdup (webkit_download_get_destination_uri (download) + 7);
172 if (g_strcmp0 (li->data, uri))
173 update_list = g_list_remove (update_list, li->data);
174 g_free (uri);
175 }
176 }
177
178 if (g_list_length (update_list) == 0)
179 {
180 adblock_reload_rules (extension, FALSE);
181 update_done = TRUE;
182 }
183}
184
185static gchar*
186adblock_get_filename_for_uri (const gchar* uri)
187{
188 gchar* filename;
189 gchar* folder;
190 gchar* path;
191
192 if (!ADBLOCK_FILTER_IS_SET (uri))
193 return NULL;
194
195 if (!strncmp (uri, "file", 4))
196 return g_strndup (uri + 7, strlen (uri) - 7);
197
198 folder = g_build_filename (midori_paths_get_cache_dir (), "adblock", NULL);
199 katze_mkdir_with_parents (folder, 0700);
200
201 filename = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1);
202 path = g_build_filename (folder, filename, NULL);
203
204 g_free (filename);
205 g_free (folder);
206 return path;
207}
208
209static void
210adblock_reload_rules (MidoriExtension* extension,
211 gboolean custom_only)
212{
213 gchar* path;
214 gchar* custom_list;
215 gchar** filters;
216 guint i = 0;
217 MidoriApp* app = midori_extension_get_app (extension);
218 MidoriWebSettings* settings = katze_object_get_object (app, "settings");
219
220 if (pattern)
221 adblock_destroy_db ();
222 adblock_init_db ();
223
224 custom_list = g_build_filename (midori_extension_get_config_dir (extension),
225 CUSTOM_LIST_NAME, NULL);
226 adblock_parse_file (custom_list);
227 g_free (custom_list);
228
229 filters = midori_extension_get_string_list (extension, "filters", NULL);
230 if (!custom_only && filters && *filters)
231 {
232 while (filters[i] != NULL)
233 {
234 path = adblock_get_filename_for_uri (filters[i]);
235 if (!path)
236 {
237 i++;
238 continue;
239 }
240
241 if (!adblock_file_is_up_to_date (path))
242 {
243 WebKitNetworkRequest* request;
244 WebKitDownload* download;
245 gchar* destination = g_filename_to_uri (path, NULL, NULL);
246
247 request = webkit_network_request_new (filters[i]);
248 download = webkit_download_new (request);
249 g_object_unref (request);
250 webkit_download_set_destination_uri (download, destination);
251 update_list = g_list_prepend (update_list, path);
252 g_free (destination);
253 g_signal_connect (download, "notify::status",
254 G_CALLBACK (adblock_download_notify_status_cb), extension);
255 webkit_download_start (download);
256 }
257 else
258 adblock_parse_file (path);
259 g_free (path);
260 i++;
261 }
262 }
263 g_strfreev (filters);
264 g_string_append (blockcss, " {display: none !important}\n");
265
266 midori_web_settings_add_style (settings, "adblock-blockcss", blockcss->str);
267 g_object_unref (settings);
268}
269
270static void
271adblock_preferences_render_tick_cb (GtkTreeViewColumn* column,
272 GtkCellRenderer* renderer,
273 GtkTreeModel* model,
274 GtkTreeIter* iter,
275 MidoriExtension* extension)
276{
277 gchar* filter;
278
279 gtk_tree_model_get (model, iter, 0, &filter, -1);
280
281 g_object_set (renderer,
282 "activatable", ADBLOCK_FILTER_VALID (filter),
283 "active", ADBLOCK_FILTER_VALID (filter) && ADBLOCK_FILTER_IS_SET (filter),
284 NULL);
285
286 g_free (filter);
287}
288
289static void
290adblock_preferences_renderer_text_edited_cb (GtkCellRenderer* renderer,
291 const gchar* tree_path,
292 const gchar* new_text,
293 GtkTreeModel* model)
294{
295 GtkTreeIter iter;
296
297 if (gtk_tree_model_get_iter_from_string (model, &iter, tree_path))
298 gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, new_text, -1);
299}
300
301static void
302adblock_preferences_renderer_toggle_toggled_cb (GtkCellRendererToggle* renderer,
303 const gchar* path,
304 GtkTreeModel* model)
305{
306 GtkTreeIter iter;
307
308 if (gtk_tree_model_get_iter_from_string (model, &iter, path))
309 {
310 gchar* filter;
311
312 gtk_tree_model_get (model, &iter, 0, &filter, -1);
313
314 if (ADBLOCK_FILTER_VALID (filter))
315 {
316 ADBLOCK_FILTER_SET (filter, TRUE);
317 if (gtk_cell_renderer_toggle_get_active (renderer))
318 {
319 if (midori_uri_is_http (filter))
320 {
321 gchar* filename = adblock_get_filename_for_uri (filter);
322 g_unlink (filename);
323 g_free (filename);
324 }
325 ADBLOCK_FILTER_SET (filter, FALSE);
326 }
327
328 gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, filter, -1);
329
330 g_free (filter);
331 }
332 }
333}
334
335static void
336adblock_preferences_render_text_cb (GtkTreeViewColumn* column,
337 GtkCellRenderer* renderer,
338 GtkTreeModel* model,
339 GtkTreeIter* iter,
340 MidoriExtension* extension)
341{
342 gchar* filter;
343
344 gtk_tree_model_get (model, iter, 0, &filter, -1);
345
346 if (ADBLOCK_FILTER_VALID (filter))
347 ADBLOCK_FILTER_SET (filter, TRUE);
348
349 g_object_set (renderer,
350 "text", filter,
351 NULL);
352
353 g_free (filter);
354}
355
356static void
357adblock_preferences_model_row_changed_cb (GtkTreeModel* model,
358 GtkTreePath* path,
359 GtkTreeIter* iter,
360 MidoriExtension* extension)
361{
362 gsize length = gtk_tree_model_iter_n_children (model, NULL);
363 gchar** filters = g_new (gchar*, length + 1);
364 guint i = 0;
365 gboolean need_reload = FALSE;
366
367 if (gtk_tree_model_iter_children (model, iter, NULL))
368 do
369 {
370 gchar* filter;
371 gtk_tree_model_get (model, iter, 0, &filter, -1);
372 if (filter && *filter)
373 {
374 filters[i++] = filter;
375 need_reload = TRUE;
376 }
377 else
378 g_free (filter);
379 }
380 while (gtk_tree_model_iter_next (model, iter));
381 filters[i] = NULL;
382 midori_extension_set_string_list (extension, "filters", filters, i);
383 g_free (filters);
384 if (need_reload)
385 adblock_reload_rules (extension, FALSE);
386}
387
388static void
389adblock_preferences_model_row_deleted_cb (GtkTreeModel* model,
390 GtkTreePath* path,
391 MidoriExtension* extension)
392{
393 GtkTreeIter iter;
394 adblock_preferences_model_row_changed_cb (model, path, &iter, extension);
395}
396
397static void
398adblock_preferences_add_clicked_cb (GtkWidget* button,
399 GtkTreeModel* model)
400{
401 GtkEntry* entry = GTK_IS_ENTRY (button)
402 ? button : g_object_get_data (G_OBJECT (button), "entry");
403 gtk_list_store_insert_with_values (GTK_LIST_STORE (model),
404 NULL, 0, 0, gtk_entry_get_text (entry), -1);
405 gtk_entry_set_text (entry, "");
406}
407
408static void
409adblock_preferences_edit_clicked_cb (GtkWidget* button,
410 GtkTreeViewColumn* column)
411{
412 GdkEvent* event = gtk_get_current_event ();
413 GtkTreeView* treeview = g_object_get_data (G_OBJECT (button), "treeview");
414 GtkTreeModel* model;
415 GtkTreeIter iter;
416 if (katze_tree_view_get_selected_iter (treeview, &model, &iter))
417 {
418 gchar* path = gtk_tree_model_get_string_from_iter (model, &iter);
419 GtkTreePath* tree_path = gtk_tree_path_new_from_string (path);
420 /* gtk_cell_renderer_start_editing */
421 gtk_tree_view_set_cursor (treeview, tree_path, column, TRUE);
422 gtk_tree_path_free (tree_path);
423 g_free (path);
424 }
425 gdk_event_free (event);
426}
427
428static void
429adblock_preferences_remove_clicked_cb (GtkWidget* button,
430 GtkTreeView* treeview)
431{
432 GtkTreeModel* model;
433 GtkTreeIter iter;
434 if (katze_tree_view_get_selected_iter (treeview, &model, &iter))
435 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
436}
437
438static gboolean
439adblock_activate_link_cb (GtkWidget* label,
440 const gchar* uri)
441{
442 MidoriBrowser* browser = midori_browser_get_for_widget (label);
443 GtkWidget* view = midori_browser_add_uri (browser, uri);
444 midori_browser_set_current_tab (browser, view);
445 return TRUE;
446}
447
448static GtkWidget*
449adblock_get_preferences_dialog (MidoriExtension* extension)
450{
451 MidoriApp* app;
452 GtkWidget* browser;
453 const gchar* dialog_title;
454 GtkWidget* dialog;
455 GtkWidget* content_area;
456 gint width, height;
457 GtkWidget* xfce_heading;
458 GtkWidget* hbox;
459 GtkListStore* liststore;
460 GtkWidget* treeview;
461 GtkTreeViewColumn* column;
462 GtkCellRenderer* renderer_text;
463 GtkCellRenderer* renderer_toggle;
464 GtkWidget* scrolled;
465 gchar** filters;
466 GtkWidget* vbox;
467 GtkWidget* button;
468 gchar* description;
469 GtkWidget* entry;
470 #if HAVE_OSX
471 GtkWidget* icon;
472 #endif
473
474 app = midori_extension_get_app (extension);
475 browser = katze_object_get_object (app, "browser");
476
477 dialog_title = _("Configure Advertisement filters");
478 dialog = gtk_dialog_new_with_buttons (dialog_title, GTK_WINDOW (browser),
479 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
480 #if !HAVE_OSX
481 GTK_STOCK_HELP, GTK_RESPONSE_HELP,
482 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
483 #endif
484 NULL);
485 katze_widget_add_class (gtk_dialog_get_widget_for_response (
486 GTK_DIALOG (dialog), GTK_RESPONSE_HELP), "help_button");
487 content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
488 gtk_window_set_icon_name (GTK_WINDOW (dialog), GTK_STOCK_PROPERTIES);
489 /* TODO: Implement some kind of help function */
490 gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
491 GTK_RESPONSE_HELP, FALSE);
492 sokoke_widget_get_text_size (dialog, "M", &width, &height);
493 gtk_window_set_default_size (GTK_WINDOW (dialog), width * 52, -1);
494 g_signal_connect_swapped (dialog, "response",
495 G_CALLBACK (gtk_widget_destroy), dialog);
496 /* TODO: We need mnemonics */
497 if ((xfce_heading = sokoke_xfce_header_new (
498 gtk_window_get_icon_name (GTK_WINDOW (dialog)), dialog_title)))
499 gtk_box_pack_start (GTK_BOX (content_area), xfce_heading, FALSE, FALSE, 0);
500 hbox = gtk_hbox_new (FALSE, 0);
501 gtk_box_pack_start (GTK_BOX (content_area), hbox, TRUE, TRUE, 12);
502 vbox = gtk_vbox_new (FALSE, 0);
503 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 4);
504 button = gtk_label_new (NULL);
505 description = g_strdup_printf (_(
506 "Type the address of a preconfigured filter list in the text entry "
507 "and click \"Add\" to add it to the list. "
508 "You can find more lists at %s."),
509 "<a href=\"http://adblockplus.org/en/subscriptions\">adblockplus.org/en/subscriptions</a> "
510 "<a href=\"http://easylist.adblockplus.org/\">easylist.adblockplus.org</a>");
511 g_signal_connect (button, "activate-link",
512 G_CALLBACK (adblock_activate_link_cb), NULL);
513 gtk_label_set_markup (GTK_LABEL (button), description);
514 g_free (description);
515 gtk_label_set_line_wrap (GTK_LABEL (button), TRUE);
516 gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 4);
517 entry = katze_uri_entry_new (NULL);
518 gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 4);
519 liststore = gtk_list_store_new (1, G_TYPE_STRING);
520 treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (liststore));
521 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
522 column = gtk_tree_view_column_new ();
523 renderer_toggle = gtk_cell_renderer_toggle_new ();
524 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), renderer_toggle, FALSE);
525 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer_toggle,
526 (GtkCellLayoutDataFunc)adblock_preferences_render_tick_cb,
527 extension, NULL);
528 g_signal_connect (renderer_toggle, "toggled",
529 G_CALLBACK (adblock_preferences_renderer_toggle_toggled_cb), liststore);
530 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
531 column = gtk_tree_view_column_new ();
532 renderer_text = gtk_cell_renderer_text_new ();
533 gtk_tree_view_column_pack_start (column, renderer_text, TRUE);
534 g_object_set (renderer_text, "editable", TRUE, NULL);
535 g_signal_connect (renderer_text, "edited",
536 G_CALLBACK (adblock_preferences_renderer_text_edited_cb), liststore);
537 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer_text,
538 (GtkCellLayoutDataFunc)adblock_preferences_render_text_cb,
539 extension, NULL);
540 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
541 scrolled = gtk_scrolled_window_new (NULL, NULL);
542 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
543 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
544 gtk_container_add (GTK_CONTAINER (scrolled), treeview);
545 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
546 GTK_SHADOW_IN);
547 gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 5);
548
549 filters = midori_extension_get_string_list (extension, "filters", NULL);
550 if (filters != NULL)
551 {
552 gsize i = 0;
553 while (filters[i++] != NULL)
554 gtk_list_store_insert_with_values (GTK_LIST_STORE (liststore),
555 NULL, i - 1, 0, filters[i -1], -1);
556 }
557 g_strfreev (filters);
558 g_object_connect (liststore,
559 "signal::row-inserted",
560 adblock_preferences_model_row_changed_cb, extension,
561 "signal::row-changed",
562 adblock_preferences_model_row_changed_cb, extension,
563 "signal::row-deleted",
564 adblock_preferences_model_row_deleted_cb, extension,
565 NULL);
566
567 g_object_unref (liststore);
568 vbox = gtk_vbox_new (FALSE, 4);
569 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 4);
570 button = gtk_button_new_from_stock (GTK_STOCK_ADD);
571 g_object_set_data (G_OBJECT (dialog), "entry", entry);
572 g_object_set_data (G_OBJECT (button), "entry", entry);
573 g_signal_connect (button, "clicked",
574 G_CALLBACK (adblock_preferences_add_clicked_cb), liststore);
575 g_signal_connect (entry, "activate",
576 G_CALLBACK (adblock_preferences_add_clicked_cb), liststore);
577 gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
578 button = gtk_button_new_from_stock (GTK_STOCK_EDIT);
579 g_object_set_data (G_OBJECT (button), "treeview", treeview);
580 g_signal_connect (button, "clicked",
581 G_CALLBACK (adblock_preferences_edit_clicked_cb), column);
582 gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
583 button = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
584 g_signal_connect (button, "clicked",
585 G_CALLBACK (adblock_preferences_remove_clicked_cb), treeview);
586 gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
587 button = gtk_label_new (""); /* This is an invisible separator */
588 gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 8);
589 gtk_widget_set_sensitive (button, FALSE);
590 button = gtk_label_new (""); /* This is an invisible separator */
591 gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 12);
592 button = gtk_button_new_from_stock (GTK_STOCK_GO_DOWN);
593 gtk_widget_set_sensitive (button, FALSE);
594 gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
595 button = gtk_button_new_from_stock (GTK_STOCK_GO_UP);
596 gtk_widget_set_sensitive (button, FALSE);
597 gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
598
599 #if HAVE_OSX
600 hbox = gtk_hbox_new (FALSE, 0);
601 button = gtk_button_new ();
602 icon = gtk_image_new_from_stock (GTK_STOCK_HELP, GTK_ICON_SIZE_BUTTON);
603 gtk_button_set_image (GTK_BUTTON (button), icon);
604 /* TODO: Implement some kind of help function */
605 gtk_widget_set_sensitive (button, FALSE);
606 /* g_signal_connect (button, "clicked",
607 G_CALLBACK (adblock_preferences_help_clicked_cb), dialog); */
608 gtk_box_pack_end (GTK_BOX (hbox),
609 button, FALSE, FALSE, 4);
610 gtk_box_pack_end (GTK_BOX (content_area),
611 hbox, FALSE, FALSE, 0);
612 #endif
613 gtk_widget_show_all (content_area);
614
615 g_object_unref (browser);
616
617 return dialog;
618}
619
620static GtkWidget*
621adblock_show_preferences_dialog (MidoriExtension* extension,
622 const gchar* uri)
623{
624 static GtkWidget* dialog = NULL;
625
626 if (!dialog)
627 {
628 dialog = adblock_get_preferences_dialog (extension);
629 g_signal_connect (dialog, "destroy",
630 G_CALLBACK (gtk_widget_destroyed), &dialog);
631 gtk_widget_show (dialog);
632 }
633 else
634 gtk_window_present (GTK_WINDOW (dialog));
635
636 if (uri != NULL)
637 {
638 GtkWidget* entry = g_object_get_data (G_OBJECT (dialog), "entry");
639 gtk_entry_set_text (GTK_ENTRY (entry), uri);
640 }
641 return dialog;
642}
643
644static void
645adblock_open_preferences_cb (MidoriExtension* extension)
646{
647 adblock_show_preferences_dialog (extension, NULL);
648}
649
650static gint
651adblock_check_rule (GRegex* regex,
652 const gchar* patt,
653 const gchar* req_uri,
654 const gchar* page_uri)
655{
656 gchar* opts;
657
658 if (!g_regex_match_full (regex, req_uri, -1, 0, 0, NULL, NULL))
659 return FALSE;
660
661 opts = g_hash_table_lookup (optslist, patt);
662 if (opts && g_regex_match_simple (",third-party", opts,
663 G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY))
664 {
665 if (page_uri && g_regex_match_full (regex, page_uri, -1, 0, 0, NULL, NULL))
666 return FALSE;
667 }
668 /* TODO: Domain opt check */
669 adblock_debug ("blocked by pattern regexp=%s -- %s", g_regex_get_pattern (regex), req_uri);
670 return TRUE;
671}
672
673static gboolean
674adblock_is_matched_by_pattern (const gchar* req_uri,
675 const gchar* page_uri)
676{
677 GHashTableIter iter;
678 gpointer patt, regex;
679
680 if (USE_PATTERN_MATCHING == 0)
681 return FALSE;
682
683 g_hash_table_iter_init (&iter, pattern);
684 while (g_hash_table_iter_next (&iter, &patt, &regex))
685 {
686 if (adblock_check_rule (regex, patt, req_uri, page_uri))
687 return TRUE;
688 }
689 return FALSE;
690}
691
692static gboolean
693adblock_is_matched_by_key (const gchar* req_uri,
694 const gchar* page_uri)
695{
696 gchar* uri;
697 gint len;
698 int pos = 0;
699 GList* regex_bl = NULL;
700 GString* guri;
701 gboolean ret = FALSE;
702 gchar sig[SIGNATURE_SIZE + 1];
703
704 memset (&sig[0], 0, sizeof (sig));
705 /* Signatures are made on pattern, so we need to convert url to a pattern as well */
706 guri = adblock_fixup_regexp ("", (gchar*)req_uri);
707 uri = guri->str;
708 len = guri->len;
709
710 for (pos = len - SIGNATURE_SIZE; pos >= 0; pos--)
711 {
712 GRegex* regex;
713 strncpy (sig, uri + pos, SIGNATURE_SIZE);
714 regex = g_hash_table_lookup (keys, sig);
715
716 /* Dont check if regex is already blacklisted */
717 if (!regex || g_list_find (regex_bl, regex))
718 continue;
719 ret = adblock_check_rule (regex, sig, req_uri, page_uri);
720 if (ret)
721 break;
722 regex_bl = g_list_prepend (regex_bl, regex);
723 }
724 g_string_free (guri, TRUE);
725 g_list_free (regex_bl);
726 return ret;
727}
728
729static gboolean
730adblock_is_matched (const gchar* req_uri,
731 const gchar* page_uri)
732{
733 gchar* value;
734
735 if ((value = g_hash_table_lookup (urlcache, req_uri)))
736 {
737 if (value[0] == '0')
738 return FALSE;
739 else
740 return TRUE;
741 }
742
743 if (adblock_is_matched_by_key (req_uri, page_uri))
744 {
745 g_hash_table_insert (urlcache, g_strdup (req_uri), g_strdup("1"));
746 return TRUE;
747 }
748
749 if (adblock_is_matched_by_pattern (req_uri, page_uri))
750 {
751 g_hash_table_insert (urlcache, g_strdup (req_uri), g_strdup("1"));
752 return TRUE;
753 }
754 g_hash_table_insert (urlcache, g_strdup (req_uri), g_strdup("0"));
755 return FALSE;
756}
757
758static gchar*
759adblock_prepare_urihider_js (GList* uris)
760{
761 GList* li = NULL;
762 GString* js = g_string_new (
763 "(function() {"
764 "function getElementsByAttribute (strTagName, strAttributeName, arrAttributeValue) {"
765 " var arrElements = document.getElementsByTagName (strTagName);"
766 " var arrReturnElements = new Array();"
767 " for (var j=0; j<arrAttributeValue.length; j++) {"
768 " var strAttributeValue = arrAttributeValue[j];"
769 " for (var i=0; i<arrElements.length; i++) {"
770 " var oCurrent = arrElements[i];"
771 " var oAttribute = oCurrent.getAttribute && oCurrent.getAttribute (strAttributeName);"
772 " if (oAttribute && oAttribute.length > 0 && strAttributeValue.indexOf (oAttribute) != -1)"
773 " arrReturnElements.push (oCurrent);"
774 " }"
775 " }"
776 " return arrReturnElements;"
777 "};"
778 "function hideElementBySrc (uris) {"
779 " var oElements = getElementsByAttribute('img', 'src', uris);"
780 " if (oElements.length == 0)"
781 " oElements = getElementsByAttribute ('iframe', 'src', uris);"
782 " for (var i=0; i<oElements.length; i++) {"
783 " oElements[i].style.visibility = 'hidden !important';"
784 " oElements[i].style.width = '0';"
785 " oElements[i].style.height = '0';"
786 " }"
787 "};"
788 "var uris=new Array ();");
789
790 for (li = uris; li != NULL; li = g_list_next (li))
791 g_string_append_printf (js, "uris.push ('%s');", (gchar*)li->data);
792
793 g_string_append (js, "hideElementBySrc (uris);})();");
794
795 return g_string_free (js, FALSE);
796}
797
798static gboolean
799adblock_navigation_policy_decision_requested_cb (WebKitWebView* web_view,
800 WebKitWebFrame* web_frame,
801 WebKitNetworkRequest* request,
802 WebKitWebNavigationAction* action,
803 WebKitWebPolicyDecision* decision,
804 MidoriExtension* extension)
805{
806 const gchar* uri = webkit_network_request_get_uri (request);
807 if (g_str_has_prefix (uri, "abp:"))
808 {
809 gchar** parts;
810 gchar* filter;
811 if (g_str_has_prefix (uri, "abp:subscribe?location="))
812 uri = &uri[23];
813 else if (g_str_has_prefix (uri, "abp://subscribe?location="))
814 uri = &uri[25];
815 else
816 return FALSE;
817
818 parts = g_strsplit (uri, "&", 2);
819 filter = soup_uri_decode (parts[0]);
820 webkit_web_policy_decision_ignore (decision);
821 adblock_show_preferences_dialog (extension, filter);
822 g_free (filter);
823 g_strfreev (parts);
824 return TRUE;
825 }
826
827 if (web_frame == webkit_web_view_get_main_frame (web_view))
828 {
829 const gchar* req_uri = webkit_network_request_get_uri (request);
830 g_hash_table_replace (navigationwhitelist, web_view, g_strdup (req_uri));
831 }
832 return false;
833}
834
835
836static void
837adblock_resource_request_starting_cb (WebKitWebView* web_view,
838 WebKitWebFrame* web_frame,
839 WebKitWebResource* web_resource,
840 WebKitNetworkRequest* request,
841 WebKitNetworkResponse* response,
842 MidoriView* view)
843{
844 SoupMessage* msg;
845 GList* blocked_uris;
846 const gchar* req_uri;
847 const char *page_uri;
848
849 page_uri = webkit_web_view_get_uri (web_view);
850 /* Skip checks on about: pages */
851 if (midori_uri_is_blank (page_uri))
852 return;
853
854 req_uri = webkit_network_request_get_uri (request);
855
856 if (!g_strcmp0 (req_uri, g_hash_table_lookup (navigationwhitelist, web_view)))
857 return;
858
859 if (!midori_uri_is_http (req_uri)
860 || g_str_has_suffix (req_uri, "favicon.ico"))
861 return;
862
863 msg = webkit_network_request_get_message (request);
864 if (!(msg && !g_strcmp0 (msg->method, "GET")))
865 return;
866
867 if (response != NULL) /* request is caused by redirect */
868 {
869 if (web_frame == webkit_web_view_get_main_frame (web_view))
870 {
871 g_hash_table_replace (navigationwhitelist, web_view, g_strdup (req_uri));
872 return;
873 }
874 }
875
876 if (midori_debug ("adblock:time"))
877 g_test_timer_start ();
878 if (adblock_is_matched (req_uri, page_uri))
879 {
880 blocked_uris = g_object_get_data (G_OBJECT (web_view), "blocked-uris");
881 blocked_uris = g_list_prepend (blocked_uris, g_strdup (req_uri));
882 webkit_network_request_set_uri (request, "about:blank");
883 g_object_set_data (G_OBJECT (web_view), "blocked-uris", blocked_uris);
884 }
885 if (midori_debug ("adblock:time"))
886 g_debug ("match: %f%s", g_test_timer_elapsed (), "seconds");
887}
888
889static void
890adblock_custom_block_image_cb (GtkWidget* widget,
891 MidoriExtension* extension)
892{
893 gchar* custom_list;
894 FILE* list;
895 MidoriApp* app;
896 GtkWidget* browser;
897 GtkWidget* dialog;
898 GtkWidget* content_area;
899 GtkSizeGroup* sizegroup;
900 GtkWidget* hbox;
901 GtkWidget* label;
902 GtkWidget* entry;
903 gchar* title;
904
905 app = midori_extension_get_app (extension);
906 browser = katze_object_get_object (app, "browser");
907
908 title = _("Edit rule");
909 dialog = gtk_dialog_new_with_buttons (title, GTK_WINDOW (browser),
910 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
911 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
912 GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT,
913 NULL);
914 content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
915 gtk_window_set_icon_name (GTK_WINDOW (dialog), GTK_STOCK_ADD);
916 gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
917 gtk_container_set_border_width (GTK_CONTAINER (content_area), 5);
918 sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
919
920 hbox = gtk_hbox_new (FALSE, 8);
921 gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
922 label = gtk_label_new_with_mnemonic (_("_Rule:"));
923 gtk_size_group_add_widget (sizegroup, label);
924 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
925 entry = gtk_entry_new ();
926 gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
927 gtk_entry_set_text (GTK_ENTRY (entry),
928 g_object_get_data (G_OBJECT (widget), "uri"));
929 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
930 gtk_box_pack_start (GTK_BOX (content_area), hbox, FALSE, TRUE, 0);
931 gtk_widget_show_all (hbox);
932
933 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
934 if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT)
935 {
936 gtk_widget_destroy (dialog);
937 return;
938 }
939
940 custom_list = g_build_filename (midori_extension_get_config_dir (extension),
941 CUSTOM_LIST_NAME, NULL);
942 katze_mkdir_with_parents (midori_extension_get_config_dir (extension), 0700);
943 if ((list = g_fopen (custom_list, "a+")))
944 {
945 g_fprintf (list, "%s\n", gtk_entry_get_text (GTK_ENTRY (entry)));
946 fclose (list);
947 adblock_reload_rules (extension, TRUE);
948 g_debug ("%s: Updated custom list\n", G_STRFUNC);
949 }
950 else
951 g_debug ("%s: Failed to open custom list %s\n", G_STRFUNC, custom_list);
952 g_free (custom_list);
953 gtk_widget_destroy (dialog);
954}
955
956static void
957adblock_populate_popup_cb (WebKitWebView* web_view,
958 GtkWidget* menu,
959 MidoriExtension* extension)
960{
961 GtkWidget* menuitem;
962 gchar *uri;
963 gint x, y;
964 GdkEventButton event;
965 WebKitHitTestResultContext context;
966 WebKitHitTestResult* hit_test;
967
968 gdk_window_get_pointer (gtk_widget_get_window(GTK_WIDGET (web_view)), &x, &y, NULL);
969 event.x = x;
970 event.y = y;
971 hit_test = webkit_web_view_get_hit_test_result (web_view, &event);
972 context = katze_object_get_int (hit_test, "context");
973 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE)
974 {
975 uri = katze_object_get_string (hit_test, "image-uri");
976 menuitem = gtk_menu_item_new_with_mnemonic (_("Bl_ock image"));
977 }
978 else if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
979 {
980 uri = katze_object_get_string (hit_test, "link-uri");
981 menuitem = gtk_menu_item_new_with_mnemonic (_("Bl_ock link"));
982 }
983 else
984 return;
985 gtk_widget_show (menuitem);
986 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
987 g_object_set_data_full (G_OBJECT (menuitem), "uri", uri, (GDestroyNotify)g_free);
988 g_signal_connect (menuitem, "activate",
989 G_CALLBACK (adblock_custom_block_image_cb), extension);
990}
991
992static void
993adblock_load_finished_cb (WebKitWebView *web_view,
994 WebKitWebFrame *web_frame,
995 gpointer user_data)
996{
997 GList* uris = g_object_get_data (G_OBJECT (web_view), "blocked-uris");
998 gchar* script;
999 GList* li;
1000
1001 if (g_list_nth_data (uris, 0) == NULL)
1002 return;
1003
1004 script = adblock_prepare_urihider_js (uris);
1005 webkit_web_view_execute_script (web_view, script);
1006 li = NULL;
1007 for (li = uris; li != NULL; li = g_list_next (li))
1008 uris = g_list_remove (uris, li->data);
1009 g_free (script);
1010 g_object_set_data (G_OBJECT (web_view), "blocked-uris", uris);
1011}
1012
1013static void
1014adblock_window_object_cleared_cb (WebKitWebView* web_view,
1015 WebKitWebFrame* web_frame,
1016 JSContextRef js_context,
1017 JSObjectRef js_window)
1018{
1019 const char *page_uri;
1020 gchar* script;
1021
1022 page_uri = webkit_web_frame_get_uri (web_frame);
1023 /* Don't add adblock css into speeddial and about: pages */
1024 if (!midori_uri_is_http (page_uri))
1025 return;
1026
1027 script = adblock_build_js (page_uri);
1028 if (!script)
1029 return;
1030
1031 g_free (sokoke_js_script_eval (js_context, script, NULL));
1032 g_free (script);
1033}
1034
1035static void
1036adblock_add_tab_cb (MidoriBrowser* browser,
1037 MidoriView* view,
1038 MidoriExtension* extension)
1039{
1040 GtkWidget* web_view = midori_view_get_web_view (view);
1041
1042 g_signal_connect (web_view, "window-object-cleared",
1043 G_CALLBACK (adblock_window_object_cleared_cb), 0);
1044
1045 g_signal_connect_after (web_view, "populate-popup",
1046 G_CALLBACK (adblock_populate_popup_cb), extension);
1047 g_signal_connect (web_view, "navigation-policy-decision-requested",
1048 G_CALLBACK (adblock_navigation_policy_decision_requested_cb), extension);
1049 g_signal_connect (web_view, "resource-request-starting",
1050 G_CALLBACK (adblock_resource_request_starting_cb), view);
1051 g_signal_connect (web_view, "load-finished",
1052 G_CALLBACK (adblock_load_finished_cb), view);
1053}
1054
1055static void
1056adblock_remove_tab_cb (MidoriBrowser* browser,
1057 MidoriView* view,
1058 MidoriExtension* extension)
1059{
1060 GtkWidget* web_view = midori_view_get_web_view (view);
1061 g_hash_table_remove (navigationwhitelist, web_view);
1062}
1063
1064static void
1065adblock_deactivate_cb (MidoriExtension* extension,
1066 MidoriBrowser* browser);
1067
1068static void
1069adblock_app_add_browser_cb (MidoriApp* app,
1070 MidoriBrowser* browser,
1071 MidoriExtension* extension)
1072{
1073 GtkWidget* statusbar;
1074 GtkWidget* image;
1075 GList* children;
1076
1077 statusbar = katze_object_get_object (browser, "statusbar");
1078 image = NULL;
1079 /* image = gtk_image_new_from_stock (STOCK_IMAGE, GTK_ICON_SIZE_MENU);
1080 gtk_widget_show (image);
1081 gtk_box_pack_start (GTK_BOX (statusbar), image, FALSE, FALSE, 3); */
1082 g_object_set_data_full (G_OBJECT (browser), "status-image", image,
1083 (GDestroyNotify)gtk_widget_destroy);
1084
1085 children = midori_browser_get_tabs (MIDORI_BROWSER (browser));
1086 for (; children; children = g_list_next (children))
1087 adblock_add_tab_cb (browser, children->data, extension);
1088 g_list_free (children);
1089
1090 g_signal_connect (browser, "add-tab",
1091 G_CALLBACK (adblock_add_tab_cb), extension);
1092 g_signal_connect (browser, "remove-tab",
1093 G_CALLBACK (adblock_remove_tab_cb), extension);
1094 g_signal_connect (extension, "open-preferences",
1095 G_CALLBACK (adblock_open_preferences_cb), extension);
1096 g_signal_connect (extension, "deactivate",
1097 G_CALLBACK (adblock_deactivate_cb), browser);
1098 g_object_unref (statusbar);
1099}
1100
1101static GString*
1102adblock_fixup_regexp (const gchar* prefix,
1103 gchar* src)
1104{
1105 GString* str;
1106 int len = 0;
1107
1108 if (!src)
1109 return NULL;
1110
1111 str = g_string_new (prefix);
1112
1113 /* lets strip first .* */
1114 if (src[0] == '*')
1115 {
1116 (void)*src++;
1117 }
1118
1119 do
1120 {
1121 switch (*src)
1122 {
1123 case '*':
1124 g_string_append (str, ".*");
1125 break;
1126 /*case '.':
1127 g_string_append (str, "\\.");
1128 break;*/
1129 case '?':
1130 g_string_append (str, "\\?");
1131 break;
1132 case '|':
1133 /* FIXME: We actually need to match :[0-9]+ or '/'. Sign means
1134 "here could be port number or nothing". So bla.com^ will match
1135 bla.com/ or bla.com:8080/ but not bla.com.au/ */
1136 case '^':
1137 case '+':
1138 break;
1139 case '[':
1140 g_string_append (str, "\\[");
1141 break;
1142 case ']':
1143 g_string_append (str, "\\]");
1144 break;
1145 case '(':
1146 g_string_append (str, "\\(");
1147 break;
1148 case ')':
1149 g_string_append (str, "\\)");
1150 break;
1151 default:
1152 g_string_append_printf (str,"%c", *src);
1153 break;
1154 }
1155 src++;
1156 }
1157 while (*src);
1158
1159 len = str->len;
1160 /* We dont need .* in the end of url. Thats stupid */
1161 if (str->str && str->str[len-1] == '*' && str->str[len-2] == '.')
1162 g_string_erase (str, len-2, 2);
1163
1164 return str;
1165}
1166
1167static gboolean
1168adblock_compile_regexp (GString* gpatt,
1169 gchar* opts)
1170{
1171 GRegex* regex;
1172 GError* error = NULL;
1173 int pos = 0;
1174 gchar *sig;
1175 gchar *patt;
1176 int len;
1177
1178 if (!gpatt)
1179 return FALSE;
1180
1181 patt = gpatt->str;
1182 len = gpatt->len;
1183
1184 /* TODO: Play with optimization flags */
1185 regex = g_regex_new (patt, G_REGEX_OPTIMIZE,
1186 G_REGEX_MATCH_NOTEMPTY, &error);
1187 if (error)
1188 {
1189 g_warning ("%s: %s", G_STRFUNC, error->message);
1190 g_error_free (error);
1191 return TRUE;
1192 }
1193
1194 if (!g_regex_match_simple ("^/.*[\\^\\$\\*].*/$", patt, G_REGEX_UNGREEDY, G_REGEX_MATCH_NOTEMPTY))
1195 {
1196 int signature_count = 0;
1197
1198 for (pos = len - SIGNATURE_SIZE; pos >= 0; pos--) {
1199 sig = g_strndup (patt + pos, SIGNATURE_SIZE);
1200 if (!g_regex_match_simple ("[\\*]", sig, G_REGEX_UNGREEDY, G_REGEX_MATCH_NOTEMPTY) &&
1201 !g_hash_table_lookup (keys, sig))
1202 {
1203 adblock_debug ("sig: %s %s", sig, patt);
1204 g_hash_table_insert (keys, sig, regex);
1205 g_hash_table_insert (optslist, sig, g_strdup (opts));
1206 signature_count++;
1207 }
1208 else
1209 {
1210 if (g_regex_match_simple ("^\\*", sig, G_REGEX_UNGREEDY, G_REGEX_MATCH_NOTEMPTY) &&
1211 !g_hash_table_lookup (pattern, patt))
1212 {
1213 adblock_debug ("patt2: %s %s", sig, patt);
1214 g_hash_table_insert (pattern, patt, regex);
1215 g_hash_table_insert (optslist, patt, g_strdup (opts));
1216 }
1217 g_free (sig);
1218 }
1219 }
1220 if (signature_count > 1 && g_hash_table_lookup (pattern, patt))
1221 {
1222 g_hash_table_steal (pattern, patt);
1223 return TRUE;
1224 }
1225 return FALSE;
1226 }
1227 else
1228 {
1229 adblock_debug ("patt: %s%s", patt, "");
1230 /* Pattern is a regexp chars */
1231 g_hash_table_insert (pattern, patt, regex);
1232 g_hash_table_insert (optslist, patt, g_strdup (opts));
1233 return FALSE;
1234 }
1235}
1236
1237static gchar*
1238adblock_add_url_pattern (gchar* prefix,
1239 gchar* type,
1240 gchar* line)
1241{
1242 gchar** data;
1243 gchar* patt;
1244 GString* format_patt;
1245 gchar* opts;
1246 gboolean should_free;
1247
1248 data = g_strsplit (line, "$", -1);
1249 if (!data || !data[0])
1250 {
1251 g_strfreev (data);
1252 return NULL;
1253 }
1254
1255 if (data[1] && data[2])
1256 {
1257 patt = g_strconcat (data[0], data[1], NULL);
1258 opts = g_strconcat (type, ",", data[2], NULL);
1259 }
1260 else if (data[1])
1261 {
1262 patt = data[0];
1263 opts = g_strconcat (type, ",", data[1], NULL);
1264 }
1265 else
1266 {
1267 patt = data[0];
1268 opts = type;
1269 }
1270
1271 if (g_regex_match_simple ("subdocument", opts,
1272 G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY))
1273 {
1274 if (data[1] && data[2])
1275 g_free (patt);
1276 if (data[1])
1277 g_free (opts);
1278 g_strfreev (data);
1279 return NULL;
1280 }
1281
1282 format_patt = adblock_fixup_regexp (prefix, patt);
1283
1284 adblock_debug ("got: %s opts %s", format_patt->str, opts);
1285 should_free = adblock_compile_regexp (format_patt, opts);
1286
1287 if (data[1] && data[2])
1288 g_free (patt);
1289 if (data[1])
1290 g_free (opts);
1291 g_strfreev (data);
1292
1293 return g_string_free (format_patt, should_free);
1294}
1295
1296static void
1297adblock_frame_add (gchar* line)
1298{
1299 const gchar* separator = " , ";
1300
1301 (void)*line++;
1302 (void)*line++;
1303 if (strchr (line, '\'')
1304 || (strchr (line, ':')
1305 && !g_regex_match_simple (".*\\[.*:.*\\].*", line,
1306 G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY)))
1307 {
1308 return;
1309 }
1310 g_string_append (blockcss, separator);
1311 g_string_append (blockcss, line);
1312}
1313
1314static void
1315adblock_update_css_hash (gchar* domain,
1316 gchar* value)
1317{
1318 const gchar* olddata;
1319 gchar* newdata;
1320
1321 if ((olddata = g_hash_table_lookup (blockcssprivate, domain)))
1322 {
1323 newdata = g_strconcat (olddata, " , ", value, NULL);
1324 g_hash_table_replace (blockcssprivate, g_strdup (domain), newdata);
1325 }
1326 else
1327 g_hash_table_insert (blockcssprivate, g_strdup (domain), g_strdup (value));
1328}
1329
1330static void
1331adblock_frame_add_private (const gchar* line,
1332 const gchar* sep)
1333{
1334 gchar** data;
1335 data = g_strsplit (line, sep, 2);
1336
1337 if (!(data[1] && *data[1])
1338 || strchr (data[1], '\'')
1339 || (strchr (data[1], ':')
1340 && !g_regex_match_simple (".*\\[.*:.*\\].*", data[1],
1341 G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY)))
1342 {
1343 g_strfreev (data);
1344 return;
1345 }
1346
1347 if (strchr (data[0], ','))
1348 {
1349 gchar** domains;
1350 gint i;
1351
1352 domains = g_strsplit (data[0], ",", -1);
1353 for (i = 0; domains[i]; i++)
1354 {
1355 gchar* domain;
1356
1357 domain = domains[i];
1358 /* Ignore Firefox-specific option */
1359 if (!g_strcmp0 (domain, "~pregecko2"))
1360 continue;
1361 /* FIXME: ~ should negate match */
1362 if (domain[0] == '~')
1363 domain++;
1364 adblock_update_css_hash (g_strstrip (domain), data[1]);
1365 }
1366 g_strfreev (domains);
1367 }
1368 else
1369 {
1370 adblock_update_css_hash (data[0], data[1]);
1371 }
1372 g_strfreev (data);
1373}
1374
1375static gchar*
1376adblock_parse_line (gchar* line)
1377{
1378 /*
1379 * AdblockPlus rule reference based on http://adblockplus.org/en/filters
1380 * Block URL:
1381 * http://example.com/ads/banner123.gif
1382 * http://example.com/ads/banner*.gif
1383 * Partial match for "ad":
1384 * *ad*
1385 * ad
1386 * Block example.com/annoyingflash.swf but not example.com/swf/:
1387 * swf|
1388 * Block bad.example/banner.gif but not good.example/analyze?http://bad.example:
1389 * |http://baddomain.example/
1390 * Block http(s) example.com but not badexample.com or good.example/analyze?http://bad.example:
1391 * ||example.com/banner.gif
1392 * Block example.com/ and example.com:8000/ but not example.com.ar/:
1393 * http://example.com^
1394 * A ^ matches anything that isn't A-Za-z0-0_-.%
1395 * Block example.com:8000/foo.bar?a=12&b=%D1%82%D0%B5:
1396 * ^example.com^
1397 * ^%D1%82%D0%B5^
1398 * ^foo.bar^
1399 * TODO: ^ is partially supported by Midori
1400 * Block banner123 and banner321 with a regex:
1401 * /banner\d+/
1402 * Never block URIs with "advice":
1403 * @@advice
1404 * No blocking at all:
1405 * @@http://example.com
1406 * @@|http://example.com
1407 * TODO: @@ is currently ignored by Midori.
1408 * Element hiding by class:
1409 * ##textad
1410 * ##div.textad
1411 * Element hiding by id:
1412 * ##div#sponsorad
1413 * ##*#sponsorad
1414 * Match example.com/ and something.example.com/ but not example.org/
1415 * example.com##*.sponsor
1416 * Match multiple domains:
1417 * domain1.example,domain2.example,domain3.example##*.sponsor
1418 * Match on any domain but "example.com":
1419 * ~example.com##*.sponsor
1420 * Match on "example.com" except "foo.example.com":
1421 * example.com,~foo.example.com##*.sponsor
1422 * By design rules only apply to full domain names:
1423 * "domain" is NOT equal to "domain.example,domain.test."
1424 * In Firefox rules can apply to browser UI:
1425 * browser##menuitem#javascriptConsole will hide the Console menuitem
1426 * Hide tables with width attribute 80%:
1427 * ##table[width="80%"]
1428 * Hide all div with title attribute containing "adv":
1429 * ##div[title*="adv"]
1430 * Hide div with title starting with "adv" and ending with "ert":
1431 * ##div[title^="adv"][title$="ert"]
1432 * Match tables with width attribute 80% and bgcolor attribute white:
1433 * table[width="80%"][bgcolor="white"]
1434 * TODO: [] is currently ignored by Midori
1435 * Hide anything following div with class "adheader":
1436 * ##div.adheader + *
1437 * Old CSS element hiding syntax, officially deprecated:
1438 * #div(id=foo)
1439 * Match anything but "example.com"
1440 * ~example.com##*.sponsor
1441 * TODO: ~ is currently ignored by Midori
1442 * Match "example.com" domain except "foo.example.com":
1443 * example.com,~foo.example.com##*.sponsor
1444 * ! Comment
1445 * Supported options after a trailing $:
1446 * domain,third-party,~pregecko2
1447 * Official options (not all supported by Midori):
1448 * script,image,stylesheet,object,xmlhttprequest,object-subrequest,
1449 * subdocument,document,elemhide,popup,third-party,sitekey,match-case
1450 * collapse,donottrack,pregecko2
1451 * Deprecated:
1452 * background,xbl,ping,dtd
1453 * Inverse options:
1454 * ~script,~image,~stylesheet,~object,~xmlhttprequest,~collapse,
1455 * ~object-subrequest,~subdocument,~document,~elemhide,~third-party,
1456 * ~pregecko2
1457 **/
1458
1459 /* Skip invalid, empty and comment lines */
1460 if (!(line && line[0] != ' ' && line[0] != '!' && line[0]))
1461 return NULL;
1462
1463 /* FIXME: No support for whitelisting */
1464 if (line[0] == '@' && line[1] == '@')
1465 return NULL;
1466 /* FIXME: No support for [include] and [exclude] tags */
1467 if (line[0] == '[')
1468 return NULL;
1469
1470 g_strchomp (line);
1471
1472 /* Got CSS block hider */
1473 if (line[0] == '#' && line[1] == '#' )
1474 {
1475 adblock_frame_add (line);
1476 return NULL;
1477 }
1478 /* Got CSS block hider. Workaround */
1479 if (line[0] == '#')
1480 return NULL;
1481
1482 /* Got per domain CSS hider rule */
1483 if (strstr (line, "##"))
1484 {
1485 adblock_frame_add_private (line, "##");
1486 return NULL;
1487 }
1488 /* Got per domain CSS hider rule. Workaround */
1489 if (strchr (line, '#'))
1490 {
1491 adblock_frame_add_private (line, "#");
1492 return NULL;
1493 }
1494
1495 /* Got URL blocker rule */
1496 if (line[0] == '|' && line[1] == '|' )
1497 {
1498 (void)*line++;
1499 (void)*line++;
1500 return adblock_add_url_pattern ("", "fulluri", line);
1501 }
1502 if (line[0] == '|')
1503 {
1504 (void)*line++;
1505 return adblock_add_url_pattern ("^", "fulluri", line);
1506 }
1507 return adblock_add_url_pattern ("", "uri", line);
1508}
1509
1510static GDateMonth
1511str_month_name_to_gdate (const gchar* month)
1512{
1513 guint i;
1514 const gchar* months[] = {
1515 "", "January", "February", "March", "April", "May", "June",
1516 "July", "August", "September", "October", "November", "December"
1517 };
1518
1519 for (i = 0; i < G_N_ELEMENTS (months); i++)
1520 {
1521 if (strncmp (month, months[i], 3) == 0)
1522 return i;
1523 }
1524 return 0;
1525}
1526
1527static gboolean
1528adblock_file_is_up_to_date (gchar* path)
1529{
1530 FILE* file;
1531 gchar line[2000];
1532
1533 /* Check a chunk of header for update info */
1534 if ((file = g_fopen (path, "r")))
1535 {
1536 gint days_to_expire = 0;
1537 gchar* timestamp = NULL;
1538 guint i;
1539 gboolean found_meta = FALSE;
1540 gint fs_days_elapsed, days_elapsed = 0, least_days;
1541
1542 for (i = 0; i <= 15; i++)
1543 {
1544 if (!fgets (line, 2000, file))
1545 break;
1546
1547 if (strncmp ("! Expires", line, 9) == 0)
1548 {
1549 gchar** parts = g_strsplit (line, " ", 4);
1550 days_to_expire = atoi (parts[2]);
1551 g_strfreev (parts);
1552 found_meta = TRUE;
1553 }
1554 if (strncmp ("! This list expires after", line, 25) == 0)
1555 {
1556 gchar** parts = g_strsplit (line, " ", 7);
1557
1558 if (strncmp (parts[6], "days", 4) == 0)
1559 days_to_expire = atoi (parts[5]);
1560 if (strncmp (parts[6], "hours", 5) == 0)
1561 days_to_expire = (atoi (parts[5])) / 24;
1562
1563 g_strfreev (parts);
1564 found_meta = TRUE;
1565 }
1566
1567 if (strncmp ("! Last mod", line, 10) == 0
1568 || strncmp ("! Updated", line, 9) == 0)
1569 {
1570 gchar** parts = g_strsplit (line, ":", 2);
1571 timestamp = g_strdup (parts[1] + 1);
1572 g_strchomp (timestamp);
1573 g_strfreev (parts);
1574 found_meta = TRUE;
1575 }
1576 }
1577
1578 if (!found_meta)
1579 {
1580 g_print ("Adblock: no metadata found in %s (broken download?)\n", path);
1581 return FALSE;
1582 }
1583
1584 /* query filesystem about file change, maybe there is no update yet
1585 * or there is no "modified" metadata to check, otherwise we will repeatedly
1586 * download files that have no new updates */
1587 {
1588 GDate* current = g_date_new ();
1589 GDate* fs_mod_date = g_date_new ();
1590 GTimeVal mod_time;
1591 GFile* filter_file = g_file_new_for_path (path);
1592 GFileInfo* info = g_file_query_info (filter_file, "time:modified", 0, NULL, NULL);
1593
1594 g_file_info_get_modification_time (info, &mod_time);
1595 g_date_set_time_t (current, time (NULL));
1596 g_date_set_time_val (fs_mod_date, &mod_time);
1597
1598 fs_days_elapsed = g_date_days_between (fs_mod_date, current);
1599
1600 g_date_free (current);
1601 g_date_free (fs_mod_date);
1602 }
1603
1604 /* If there is no update metadata but file is valid, assume one week */
1605 if ((!days_to_expire && !timestamp) && fs_days_elapsed < 7)
1606 return TRUE;
1607
1608 if (days_to_expire && timestamp != NULL)
1609 {
1610 GDate* current = g_date_new ();
1611 GDate* mod_date = g_date_new ();
1612 gchar** parts;
1613 gboolean use_dots = FALSE;
1614
1615 /* Common dates are 20 Mar 2012, 20.08.2012 */
1616 if (strrchr (timestamp, '.'))
1617 {
1618 use_dots = TRUE;
1619 /* In case of date like '20.08.2012 12:34'
1620 * we should also nuke the time part */
1621 if (strrchr (timestamp, ' '))
1622 {
1623 gchar** part = g_strsplit (timestamp, " ", 2);
1624 parts = g_strsplit (part[0], ".", 4);
1625 g_strfreev (part);
1626 }
1627 else
1628 parts = g_strsplit (timestamp, ".", 4);
1629 }
1630 else
1631 parts = g_strsplit (timestamp, " ", 4);
1632
1633 if (use_dots)
1634 g_date_set_month (mod_date, atoi (parts[1]));
1635 else
1636 g_date_set_month (mod_date, str_month_name_to_gdate (parts[1]));
1637
1638 /* check if first part is year 201(2) or day */
1639 if (strncmp (parts[0], "201", 3) == 0)
1640 {
1641 g_date_set_day (mod_date, atoi (parts[2]));
1642 g_date_set_year (mod_date, atoi (parts[0]));
1643 }
1644 else
1645 {
1646 g_date_set_day (mod_date, atoi (parts[0]));
1647 g_date_set_year (mod_date, atoi (parts[2]));
1648 }
1649 g_strfreev (parts);
1650
1651 g_date_set_time_t (current, time (NULL));
1652 days_elapsed = g_date_days_between (mod_date, current);
1653
1654 g_date_free (current);
1655 g_date_free (mod_date);
1656 g_free (timestamp);
1657 }
1658
1659 /* File from the future? Assume up to date */
1660 if (days_elapsed < 0)
1661 {
1662 g_print ("Adblock: file %s appears to be from the future,"
1663 "check your system clock!\n", path);
1664 return TRUE;
1665 }
1666
1667 least_days = days_elapsed < fs_days_elapsed ? days_elapsed : fs_days_elapsed;
1668 if (least_days < days_to_expire)
1669 return TRUE;
1670 else
1671 return FALSE;
1672
1673 return TRUE;
1674 }
1675 return FALSE;
1676}
1677
1678static void
1679adblock_parse_file (gchar* path)
1680{
1681 FILE* file;
1682 gchar line[2000];
1683
1684 if ((file = g_fopen (path, "r")))
1685 {
1686 while (fgets (line, 2000, file))
1687 adblock_parse_line (line);
1688 fclose (file);
1689 }
1690}
1691
1692static void
1693adblock_deactivate_tabs (MidoriView* view,
1694 MidoriBrowser* browser,
1695 MidoriExtension* extension)
1696{
1697 GtkWidget* web_view = midori_view_get_web_view (view);
1698
1699 g_signal_handlers_disconnect_by_func (
1700 web_view, adblock_window_object_cleared_cb, 0);
1701 g_signal_handlers_disconnect_by_func (
1702 web_view, adblock_populate_popup_cb, extension);
1703 g_signal_handlers_disconnect_by_func (
1704 web_view, adblock_resource_request_starting_cb, view);
1705 g_signal_handlers_disconnect_by_func (
1706 web_view, adblock_load_finished_cb, view);
1707 g_signal_handlers_disconnect_by_func (
1708 web_view, adblock_navigation_policy_decision_requested_cb, extension);
1709}
1710
1711static void
1712adblock_deactivate_cb (MidoriExtension* extension,
1713 MidoriBrowser* browser)
1714{
1715 GList* children;
1716 MidoriApp* app = midori_extension_get_app (extension);
1717 MidoriWebSettings* settings = katze_object_get_object (app, "settings");
1718
1719 g_signal_handlers_disconnect_by_func (
1720 browser, adblock_open_preferences_cb, extension);
1721 g_signal_handlers_disconnect_by_func (
1722 extension, adblock_deactivate_cb, browser);
1723 g_signal_handlers_disconnect_by_func (
1724 app, adblock_app_add_browser_cb, extension);
1725 g_signal_handlers_disconnect_by_func (
1726 browser, adblock_add_tab_cb, extension);
1727 g_signal_handlers_disconnect_by_func (
1728 browser, adblock_remove_tab_cb, extension);
1729
1730 children = midori_browser_get_tabs (MIDORI_BROWSER (browser));
1731 for (; children; children = g_list_next (children))
1732 adblock_deactivate_tabs (children->data, browser, extension);
1733 g_list_free (children);
1734
1735 adblock_destroy_db ();
1736 midori_web_settings_remove_style (settings, "adblock-blockcss");
1737 g_object_unref (settings);
1738}
1739
1740static void
1741adblock_activate_cb (MidoriExtension* extension,
1742 MidoriApp* app)
1743{
1744 KatzeArray* browsers;
1745 MidoriBrowser* browser;
1746
1747 adblock_reload_rules (extension, FALSE);
1748
1749 browsers = katze_object_get_object (app, "browsers");
1750 KATZE_ARRAY_FOREACH_ITEM (browser, browsers)
1751 adblock_app_add_browser_cb (app, browser, extension);
1752 g_signal_connect (app, "add-browser",
1753 G_CALLBACK (adblock_app_add_browser_cb), extension);
1754
1755 g_object_unref (browsers);
1756}
1757
1758static void
1759test_adblock_parse (void)
1760{
1761 adblock_init_db ();
1762 g_assert (!adblock_parse_line (NULL));
1763 g_assert (!adblock_parse_line ("!"));
1764 g_assert (!adblock_parse_line ("@@"));
1765 g_assert (!adblock_parse_line ("##"));
1766 g_assert (!adblock_parse_line ("["));
1767
1768 g_assert_cmpstr (adblock_parse_line ("+advert/"), ==, "advert/");
1769 g_assert_cmpstr (adblock_parse_line ("*foo"), ==, "foo");
1770 g_assert_cmpstr (adblock_parse_line ("f*oo"), ==, "f.*oo");
1771 g_assert_cmpstr (adblock_parse_line ("?foo"), ==, "\\?foo");
1772 g_assert_cmpstr (adblock_parse_line ("foo?"), ==, "foo\\?");
1773
1774 g_assert_cmpstr (adblock_parse_line (".*foo/bar"), ==, "..*foo/bar");
1775 g_assert_cmpstr (adblock_parse_line ("http://bla.blub/*"), ==, "http://bla.blub/");
1776 g_assert_cmpstr (adblock_parse_line ("bag?r[]=*cpa"), ==, "bag\\?r\\[\\]=.*cpa");
1777 g_assert_cmpstr (adblock_parse_line ("(facebookLike,"), ==, "\\(facebookLike,");
1778 adblock_destroy_db ();
1779}
1780
1781static void
1782test_subscription_update (void)
1783{
1784 gint temp;
1785 gchar* filename;
1786
1787 adblock_init_db ();
1788 temp = g_file_open_tmp ("midori_adblock_update_test_XXXXXX", &filename, NULL);
1789 close (temp);
1790
1791 g_file_set_contents (filename, "", -1, NULL);
1792 g_assert (!adblock_file_is_up_to_date (filename));
1793
1794 g_file_set_contents (filename,
1795 "[Adblock Plus 1.1]\n"
1796 "! Checksum: 48f6Qdo4PsNogsurLvQ71w\n"
1797 "! Title: EasyList\n"
1798 "! Last modified: 05 Sep 2010 11:00 UTC\n"
1799 "! This list expires after 48 hours\n",
1800 -1, NULL);
1801 g_assert (!adblock_file_is_up_to_date (filename));
1802
1803 g_file_set_contents (filename,
1804 "[Adblock Plus 1.1]\n"
1805 "! Checksum: 48f6Qdo4PsNogsurLvQ71w\n"
1806 "! Title: EasyList\n"
1807 "! Last modified: 05.09.2010 11:00 UTC\n"
1808 "! Expires: 2 days (update frequency)\n",
1809 -1, NULL);
1810 g_assert (!adblock_file_is_up_to_date (filename));
1811
1812 g_file_set_contents (filename,
1813 "[Adblock Plus 1.1]\n"
1814 "! Checksum: 48f6Qdo4PsNogsurLvQ71w\n"
1815 "! Title: EasyList\n"
1816 "! Updated: 05 Nov 2014 11:00 UTC\n"
1817 "! Expires: 5 days (update frequency)\n",
1818 -1, NULL);
1819 g_assert (adblock_file_is_up_to_date (filename));
1820
1821 g_file_set_contents (filename,
1822 "[Adblock]\n"
1823 "! dutchblock v3\n"
1824 "! This list expires after 14 days\n"
1825 "|http://b*.mookie1.com/\n",
1826 -1, NULL);
1827 g_assert (adblock_file_is_up_to_date (filename));
1828
1829 g_file_set_contents (filename,
1830 "[Adblock Plus 2.0]\n"
1831 "! Checksum: S4reE8XaYTtAFxe2RjgBPg\n"
1832 "! Last modification time (GMT): 2012.11.05 13:33\n"
1833 "! Expires: 5 days (update frequency)\n",
1834 -1, NULL);
1835 g_assert (!adblock_file_is_up_to_date (filename));
1836
1837 g_file_set_contents (filename,
1838 "[Adblock Plus 2.0]\n"
1839 "! Checksum: S4reE8XaYTtAFxe2RjgBPg\n"
1840 "! Last modification time (GMT): 2012.11.05 13:33\n",
1841 -1, NULL);
1842 g_assert (!adblock_file_is_up_to_date (filename));
1843
1844 g_unlink (filename);
1845 g_free (filename);
1846 adblock_destroy_db ();
1847}
1848
1849static void
1850test_adblock_pattern (void)
1851{
1852 gint temp;
1853 gchar* filename;
1854
1855 adblock_init_db ();
1856 temp = g_file_open_tmp ("midori_adblock_match_test_XXXXXX", &filename, NULL);
1857
1858 /* TODO: Update some tests and add new ones. */
1859 g_file_set_contents (filename,
1860 "*ads.foo.bar*\n"
1861 "*ads.bogus.name*\n"
1862 "||^http://ads.bla.blub/*\n"
1863 "|http://ads.blub.boing/*$domain=xxx.com\n"
1864 "engine.adct.ru/*?\n"
1865 "/addyn|*|adtech;\n"
1866 "doubleclick.net/pfadx/*.mtvi\n"
1867 "objects.tremormedia.com/embed/xml/*.xml?r=\n"
1868 "videostrip.com^*/admatcherclient.\n"
1869 "test.dom/test?var\n"
1870 "/adpage.\n"
1871 "br.gcl.ru/cgi-bin/br/",
1872 -1, NULL);
1873
1874 adblock_parse_file (filename);
1875
1876 g_test_timer_start ();
1877 g_assert (adblock_is_matched ("http://www.engadget.com/_uac/adpage.html", ""));
1878 g_assert (adblock_is_matched ("http://test.dom/test?var=1", ""));
1879 g_assert (adblock_is_matched ("http://ads.foo.bar/teddy", ""));
1880 g_assert (!adblock_is_matched ("http://ads.fuu.bar/teddy", ""));
1881 g_assert (adblock_is_matched ("https://ads.bogus.name/blub", ""));
1882 g_assert (adblock_is_matched ("http://ads.bla.blub/kitty", ""));
1883 g_assert (adblock_is_matched ("http://ads.blub.boing/soda", ""));
1884 g_assert (!adblock_is_matched ("http://ads.foo.boing/beer", ""));
1885 g_assert (adblock_is_matched ("https://testsub.engine.adct.ru/test?id=1", ""));
1886 if (USE_PATTERN_MATCHING)
1887 g_assert (adblock_is_matched ("http://test.ltd/addyn/test/test?var=adtech;&var2=1", ""));
1888 g_assert (adblock_is_matched ("http://add.doubleclick.net/pfadx/aaaa.mtvi", ""));
1889 g_assert (!adblock_is_matched ("http://add.doubleclick.net/pfadx/aaaa.mtv", ""));
1890 g_assert (adblock_is_matched ("http://objects.tremormedia.com/embed/xml/list.xml?r=", ""));
1891 g_assert (!adblock_is_matched ("http://qq.videostrip.c/sub/admatcherclient.php", ""));
1892 g_assert (adblock_is_matched ("http://qq.videostrip.com/sub/admatcherclient.php", ""));
1893 g_assert (adblock_is_matched ("http://qq.videostrip.com/sub/admatcherclient.php", ""));
1894 g_assert (adblock_is_matched ("http://br.gcl.ru/cgi-bin/br/test", ""));
1895 g_assert (!adblock_is_matched ("https://bugs.webkit.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=substring&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailtype1=substring&email1=&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=&chfieldto=Now&chfieldvalue=&query_based_on=gtkport&field0-0-0=keywords&type0-0-0=anywordssubstr&value0-0-0=Gtk%20Cairo%20soup&field0-0-1=short_desc&type0-0-1=anywordssubstr&value0-0-1=Gtk%20Cairo%20soup%20autoconf%20automake%20autotool&field0-0-2=component&type0-0-2=equals&value0-0-2=WebKit%20Gtk", ""));
1896 g_assert (!adblock_is_matched ("http://www.engadget.com/2009/09/24/google-hits-android-rom-modder-with-a-cease-and-desist-letter/", ""));
1897 g_assert (!adblock_is_matched ("http://karibik-invest.com/es/bienes_raices/search.php?sqT=19&sqN=&sqMp=&sqL=0&qR=1&sqMb=&searchMode=1&action=B%FAsqueda", ""));
1898 g_assert (!adblock_is_matched ("http://google.com", ""));
1899
1900 g_print ("Search took %f seconds\n", g_test_timer_elapsed ());
1901
1902 close (temp);
1903 g_unlink (filename);
1904 adblock_destroy_db ();
1905}
1906
1907void
1908extension_test (void)
1909{
1910 g_test_add_func ("/extensions/adblock/parse", test_adblock_parse);
1911 g_test_add_func ("/extensions/adblock/pattern", test_adblock_pattern);
1912 g_test_add_func ("/extensions/adblock/update", test_subscription_update);
1913}
1914
1915MidoriExtension*
1916extension_init (void)
1917{
1918 MidoriExtension* extension = g_object_new (MIDORI_TYPE_EXTENSION,
1919 "name", _("Advertisement blocker"),
1920 "description", _("Block advertisements according to a filter list"),
1921 "version", "0.6" MIDORI_VERSION_SUFFIX,
1922 "authors", "Christian Dywan <christian@twotoasts.de>",
1923 NULL);
1924 midori_extension_install_string_list (extension, "filters", NULL, G_MAXSIZE);
1925
1926 g_signal_connect (extension, "activate",
1927 G_CALLBACK (adblock_activate_cb), NULL);
1928
1929 return extension;
1930}
19310
=== added file 'extensions/adblock/config.vala'
--- extensions/adblock/config.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/config.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,107 @@
1/*
2 Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12namespace Adblock {
13 public class Config : GLib.Object {
14 List<Subscription> subscriptions;
15 string? path;
16 KeyFile keyfile;
17 Subscription? custom;
18
19 public Config (string? path) {
20 subscriptions = new GLib.List<Subscription> ();
21
22 this.path = path;
23 if (path == null)
24 return;
25
26 string custom_list = GLib.Path.build_filename (path, "custom.list");
27 try {
28 custom = new Subscription (Filename.to_uri (custom_list, null));
29 subscriptions.append (custom);
30 } catch (Error error) {
31 custom = null;
32 warning ("Failed to add custom list %s: %s", custom_list, error.message);
33 }
34
35 string filename = GLib.Path.build_filename (path, "config");
36 keyfile = new GLib.KeyFile ();
37 try {
38 keyfile.load_from_file (filename, GLib.KeyFileFlags.NONE);
39 string[] filters = keyfile.get_string_list ("settings", "filters");
40 foreach (string filter in filters) {
41 bool active = false;
42 string uri = filter;
43 if (filter.has_prefix ("http-"))
44 uri = "http:" + filter.substring (6);
45 else if (filter.has_prefix ("file-"))
46 uri = "file:" + filter.substring (6);
47 else if (filter.has_prefix ("https-"))
48 uri = "https:" + filter.substring (7);
49 else
50 active = true;
51 Subscription sub = new Subscription (uri);
52 sub.active = active;
53 sub.add_feature (new Updater ());
54 sub.notify["active"].connect (active_changed);
55 subscriptions.append (sub);
56 }
57 } catch (FileError.NOENT exist_error) {
58 /* It's no error if no config file exists */
59 } catch (GLib.Error settings_error) {
60 warning ("Error reading settings from %s: %s\n", filename, settings_error.message);
61 }
62
63 size = subscriptions.length ();
64 }
65
66 void active_changed (Object subscription, ParamSpec pspec) {
67 var filters = new StringBuilder ();
68 foreach (var sub in subscriptions) {
69 if (sub == custom)
70 continue;
71 if (sub.uri.has_prefix ("http:") && !sub.active)
72 filters.append ("http-" + sub.uri.substring (4));
73 else if (sub.uri.has_prefix ("file:") && !sub.active)
74 filters.append ("file-" + sub.uri.substring (4));
75 else if (sub.uri.has_prefix ("https:") && !sub.active)
76 filters.append ("https-" + sub.uri.substring (5));
77 else
78 filters.append (sub.uri);
79 filters.append_c (';');
80 }
81
82 string[] list = (filters.str.slice (0, -1)).split (";");
83 keyfile.set_string_list ("settings", "filters", list);
84 try {
85 string filename = GLib.Path.build_filename (path, "config");
86 FileUtils.set_contents (filename, keyfile.to_data ());
87 } catch (Error error) {
88 warning ("Failed to save settings: %s", error.message);
89 }
90 }
91
92 public void add_custom_rule (string rule) {
93 try {
94 var file = File.new_for_uri (custom.uri);
95 file.append_to (FileCreateFlags.NONE).write (("%s\n".printf (rule)).data);
96 } catch (Error error) {
97 warning ("Failed to add custom rule: %s", error.message);
98 }
99 }
100
101 /* foreach support */
102 public new Subscription? get (uint index) {
103 return subscriptions.nth_data (index);
104 }
105 public uint size { get; private set; }
106 }
107}
0108
=== added file 'extensions/adblock/element.vala'
--- extensions/adblock/element.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/element.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,36 @@
1/*
2 Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12namespace Adblock {
13 public class Element : Feature {
14 public HashTable<string, string> blockcssprivate;
15 bool debug_element;
16
17 public Element () {
18 base ();
19 debug_element = "adblock:element" in (Environment.get_variable ("MIDORI_DEBUG") ?? "");
20 }
21
22 public override void clear () {
23 blockcssprivate = new HashTable<string, string> (str_hash, str_equal);
24 }
25
26 public string? lookup (string domain) {
27 return blockcssprivate.lookup (domain);
28 }
29
30 public void insert (string domain, string value) {
31 if (debug_element)
32 stdout.printf ("Element to be blocked %s => %s\n", domain, value);
33 blockcssprivate.insert (domain, value);
34 }
35 }
36}
037
=== added file 'extensions/adblock/extension.vala'
--- extensions/adblock/extension.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/extension.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,545 @@
1/*
2 Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
3 Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 See the file COPYING for the full license text.
11*/
12
13namespace Adblock {
14 public enum Directive {
15 ALLOW,
16 BLOCK
17 }
18
19 public class Extension : Midori.Extension {
20 Config config;
21 HashTable<string, Directive?> cache;
22
23#if HAVE_WEBKIT2
24 public Extension (WebKit.WebExtension web_extension) {
25 init ();
26 web_extension.page_created.connect (page_created);
27 }
28
29 void page_created (WebKit.WebPage web_page) {
30 web_page.send_request.connect (send_request);
31 }
32
33 bool send_request (WebKit.WebPage web_page, WebKit.URIRequest request, WebKit.URIResponse? redirected_response) {
34 return request_handled (web_page.uri, request.uri);
35 }
36#else
37 public Extension () {
38 GLib.Object (name: _("Advertisement blocker"),
39 description: _("Block advertisements according to a filter list"),
40 version: "2.0",
41 authors: "Christian Dywan <christian@twotoasts.de>");
42 activate.connect (extension_activated);
43 open_preferences.connect (extension_preferences);
44 }
45
46 void extension_preferences () {
47 open_dialog (null);
48 }
49
50 void open_dialog (string? uri) {
51 var dialog = new Gtk.Dialog.with_buttons (_("Configure Advertisement filters"),
52 null,
53#if !HAVE_GTK3
54 Gtk.DialogFlags.NO_SEPARATOR |
55#endif
56 Gtk.DialogFlags.DESTROY_WITH_PARENT,
57 Gtk.STOCK_HELP, Gtk.ResponseType.HELP,
58 Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE);
59#if HAVE_GTK3
60 dialog.get_widget_for_response (Gtk.ResponseType.HELP).get_style_context ().add_class ("help_button");
61#endif
62 dialog.set_icon_name (Gtk.STOCK_PROPERTIES);
63 dialog.set_response_sensitive (Gtk.ResponseType.HELP, false);
64
65 var hbox = new Gtk.HBox (false, 0);
66 (dialog.get_content_area () as Gtk.Box).pack_start (hbox, true, true, 12);
67 var vbox = new Gtk.VBox (false, 0);
68 hbox.pack_start (vbox, true, true, 4);
69 var button = new Gtk.Label (null);
70 string description = """
71 Type the address of a preconfigured filter list in the text entry
72 and click "Add" to add it to the list.
73 You can find more lists at %s %s.
74 """.printf (
75 "<a href=\"http://adblockplus.org/en/subscriptions\">adblockplus.org/en/subscriptions</a>",
76 "<a href=\"http://easylist.adblockplus.org/\">easylist.adblockplus.org</a>");
77 button.activate_link.connect ((uri)=>{
78 var browser = Midori.Browser.get_for_widget (button);
79 var view = browser.add_uri (uri);
80 browser.tab = view;
81 return true;
82 });
83 button.set_markup (description);
84 button.set_line_wrap (true);
85 vbox.pack_start (button, false, false, 4);
86
87 var entry = new Gtk.Entry ();
88 if (uri != null)
89 entry.set_text (uri);
90 vbox.pack_start (entry, false, false, 4);
91
92 var liststore = new Gtk.ListStore (1, typeof (Subscription));
93 var treeview = new Gtk.TreeView.with_model (liststore);
94 treeview.set_headers_visible (false);
95 var column = new Gtk.TreeViewColumn ();
96 var renderer_toggle = new Gtk.CellRendererToggle ();
97 column.pack_start (renderer_toggle, false);
98 column.set_cell_data_func (renderer_toggle, (column, renderer, model, iter) => {
99 Subscription sub;
100 liststore.get (iter, 0, out sub);
101 renderer.set ("active", sub.active,
102 "sensitive", !sub.uri.has_suffix ("custom.list"));
103 });
104 renderer_toggle.toggled.connect ((path) => {
105 Gtk.TreeIter iter;
106 if (liststore.get_iter_from_string (out iter, path)) {
107 Subscription sub;
108 liststore.get (iter, 0, out sub);
109 sub.active = !sub.active;
110 }
111 });
112 treeview.append_column (column);
113
114 column = new Gtk.TreeViewColumn ();
115 var renderer_text = new Gtk.CellRendererText ();
116 column.pack_start (renderer_text, false);
117 renderer_text.set ("editable", true);
118 // TODO: renderer_text.edited.connect
119 column.set_cell_data_func (renderer_text, (column, renderer, model, iter) => {
120 Subscription sub;
121 liststore.get (iter, 0, out sub);
122 renderer.set ("text", sub.uri);
123 });
124 treeview.append_column (column);
125
126 var scrolled = new Gtk.ScrolledWindow (null, null);
127 scrolled.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
128 scrolled.add (treeview);
129 vbox.pack_start (scrolled);
130
131 foreach (Subscription sub in config)
132 liststore.insert_with_values (null, 0, 0, sub);
133 // TODO: row-inserted row-changed row-deleted
134 // TODO vbox with add/ edit/ remove/ down/ up
135
136 dialog.get_content_area ().show_all ();
137
138 dialog.response.connect ((response)=>{ dialog.destroy (); });
139 dialog.show ();
140 }
141
142 void extension_activated (Midori.App app) {
143 init ();
144 foreach (var browser in app.get_browsers ())
145 browser_added (browser);
146 app.add_browser.connect (browser_added);
147 }
148
149 void browser_added (Midori.Browser browser) {
150 foreach (var tab in browser.get_tabs ())
151 tab_added (tab);
152 browser.add_tab.connect (tab_added);
153 }
154
155 void tab_added (Midori.View view) {
156 view.web_view.resource_request_starting.connect (resource_requested);
157 view.web_view.navigation_policy_decision_requested.connect (navigation_requested);
158 view.notify["load-status"].connect ((pspec) => {
159 if (view.load_status == Midori.LoadStatus.FINISHED)
160 inject_css (view, view.uri);
161 });
162 view.context_menu.connect (context_menu);
163 }
164
165 void context_menu (WebKit.HitTestResult hit_test_result, Midori.ContextAction menu) {
166 string label, uri;
167 if ((hit_test_result.context & WebKit.HitTestResultContext.IMAGE) != 0) {
168 label = _("Bl_ock image");
169 uri = hit_test_result.image_uri;
170 } else if ((hit_test_result.context & WebKit.HitTestResultContext.LINK) != 0) {
171 label = _("Bl_ock link");
172 uri = hit_test_result.link_uri;
173 } else
174 return;
175 var action = new Gtk.Action ("BlockElement", label, null, null);
176 action.activate.connect ((action) => {
177 edit_rule_dialog (uri);
178 });
179 menu.add (action);
180 }
181
182 void edit_rule_dialog (string uri) {
183 var dialog = new Gtk.Dialog.with_buttons (_("Edit rule"),
184 null,
185#if !HAVE_GTK3
186 Gtk.DialogFlags.NO_SEPARATOR |
187#endif
188 Gtk.DialogFlags.DESTROY_WITH_PARENT,
189 Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
190 Gtk.STOCK_ADD, Gtk.ResponseType.ACCEPT);
191 dialog.set_icon_name (Gtk.STOCK_ADD);
192 dialog.resizable = false;
193
194 var hbox = new Gtk.HBox (false, 8);
195 var sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
196 hbox.border_width = 5;
197 var label = new Gtk.Label.with_mnemonic (_("_Rule:"));
198 sizegroup.add_widget (label);
199 hbox.pack_start (label, false, false, 0);
200 (dialog.get_content_area () as Gtk.Box).pack_start (hbox, false, true, 0);
201
202 var entry = new Gtk.Entry ();
203 sizegroup.add_widget (entry);
204 entry.activates_default = true;
205 entry.set_text (uri);
206 hbox.pack_start (entry, true, true, 0);
207
208 dialog.get_content_area ().show_all ();
209
210 dialog.set_default_response (Gtk.ResponseType.ACCEPT);
211 if (dialog.run () != Gtk.ResponseType.ACCEPT)
212 return;
213
214 string new_rule = entry.get_text ();
215 dialog.destroy ();
216 config.add_custom_rule (new_rule);
217 }
218
219
220 void resource_requested (WebKit.WebView web_view, WebKit.WebFrame frame,
221 WebKit.WebResource resource, WebKit.NetworkRequest request, WebKit.NetworkResponse? response) {
222
223 if (request_handled (web_view.uri, request.uri))
224 request.set_uri ("about:blank");
225 }
226
227 bool navigation_requested (WebKit.WebFrame frame, WebKit.NetworkRequest request,
228 WebKit.WebNavigationAction action, WebKit.WebPolicyDecision decision) {
229
230 string uri = request.uri;
231 if (uri.has_prefix ("abp:")) {
232 uri = uri.replace ("abp://", "abp:");
233 if (uri.has_prefix ("abp:subscribe?location=")) {
234 /* abp://subscripe?location=http://example.com&title=foo */
235 string[] parts = uri.substring (23, -1).split ("&", 2);
236 decision.ignore ();
237 open_dialog (parts[0]);
238 return true;
239 }
240 }
241 return false;
242 }
243
244 void inject_css (Midori.View view, string page_uri) {
245 /* Don't block ads on internal pages */
246 if (!Midori.URI.is_http (page_uri))
247 return;
248 string domain = Midori.URI.parse_hostname (page_uri, null);
249 string[] subdomains = domain.split (".");
250 if (subdomains == null)
251 return;
252 int cnt = subdomains.length - 1;
253 var subdomain = new StringBuilder (subdomains[cnt]);
254 subdomain.prepend_c ('.');
255 cnt--;
256 var code = new StringBuilder ();
257 bool debug_element = "adblock:element" in (Environment.get_variable ("MIDORI_DEBUG") ?? "");
258 string hider_css;
259
260 /* Hide elements that were blocked, otherwise we will get "broken image" icon */
261 cache.foreach ((key, val) => {
262 if (val == Adblock.Directive.BLOCK)
263 code.append ("img[src*=\"%s\"] , iframe[src*=\"%s\"] , ".printf (key, key));
264 });
265 if (debug_element)
266 hider_css = " { background-color: red; border: 4px solid green; }";
267 else
268 hider_css = " { visiblility: hidden; width: 0; height: 0; }";
269
270 code.truncate (code.len -3);
271 code.append (hider_css);
272 if (debug_element)
273 stdout.printf ("hider css: %s\n", code.str);
274 view.inject_stylesheet (code.str);
275
276 code.erase ();
277 int blockscnt = 0;
278 while (cnt >= 0) {
279 subdomain.prepend (subdomains[cnt]);
280 string? style = null;
281 foreach (Subscription sub in config) {
282 foreach (var feature in sub) {
283 if (feature is Adblock.Element) {
284 style = (feature as Adblock.Element).lookup (subdomain.str);
285 break;
286 }
287 }
288 }
289 if (style != null) {
290 code.append (style);
291 code.append_c (',');
292 blockscnt++;
293 }
294 subdomain.prepend_c ('.');
295 cnt--;
296 }
297 if (blockscnt == 0)
298 return;
299 code.truncate (code.len - 1);
300
301 if (debug_element)
302 hider_css = " { background-color: red !important; border: 4px solid green !important; }";
303 else
304 hider_css = " { display: none !important }";
305
306 code.append (hider_css);
307 view.inject_stylesheet (code.str);
308 if (debug_element)
309 stdout.printf ("css: %s\n", code.str);
310 }
311#endif
312
313 internal void init () {
314 debug ("Adblock2");
315
316 string config_dir = Midori.Paths.get_extension_config_dir ("libadblock.so");
317 config = new Config (config_dir);
318 reload_rules ();
319 }
320
321 void reload_rules () {
322 cache = new HashTable<string, Directive?> (str_hash, str_equal);
323 foreach (Subscription sub in config) {
324 try {
325 sub.parse ();
326 } catch (GLib.Error error) {
327 warning ("Error parsing %s: %s", sub.uri, error.message);
328 }
329 }
330 }
331
332 bool request_handled (string page_uri, string request_uri) {
333 /* Always allow the main page */
334 if (request_uri == page_uri)
335 return false;
336
337 Directive? directive = cache.lookup (request_uri);
338 if (directive == null) {
339 foreach (Subscription sub in config) {
340 directive = sub.get_directive (request_uri, page_uri);
341 if (directive != null)
342 break;
343 }
344 if (directive == null)
345 directive = Directive.ALLOW;
346 cache.insert (request_uri, directive);
347 }
348 return directive == Directive.BLOCK;
349 }
350 }
351
352 static void debug (string format, ...) {
353 bool debug_match = "adblock:match" in (Environment.get_variable ("MIDORI_DEBUG") ?? "");
354 if (!debug_match)
355 return;
356
357 var args = va_list ();
358 stdout.vprintf (format + "\n", args);
359 }
360
361 internal static string? fixup_regex (string prefix, string? src) {
362 if (src == null)
363 return null;
364
365 var fixed = new StringBuilder ();
366 fixed.append(prefix);
367
368 uint i = 0;
369 if (src[0] == '*')
370 i++;
371 uint l = src.length;
372 while (i < l) {
373 char c = src[i];
374 switch (c) {
375 case '*':
376 fixed.append (".*"); break;
377 case '|':
378 case '^':
379 case '+':
380 break;
381 case '?':
382 case '[':
383 case ']':
384 fixed.append_printf ("\\%c", c); break;
385 default:
386 fixed.append_c (c); break;
387 }
388 i++;
389 }
390 return fixed.str;
391 }
392}
393
394#if HAVE_WEBKIT2
395Adblock.Extension? filter;
396public static void webkit_web_extension_initialize (WebKit.WebExtension web_extension) {
397 filter = new Adblock.Extension (web_extension);
398}
399#else
400public Midori.Extension extension_init () {
401 return new Adblock.Extension ();
402}
403#endif
404
405#if !HAVE_WEBKIT2
406struct TestCaseLine {
407 public string line;
408 public string fixed;
409}
410
411const TestCaseLine[] lines = {
412 { null, null },
413 { "!", "!" },
414 { "@@", "@@" },
415 { "##", "##" },
416 { "[", "\\[" },
417 { "+advert/", "advert/" },
418 { "*foo", "foo" },
419 { "f*oo", "f.*oo" },
420 { "?foo", "\\?foo" },
421 { "foo?", "foo\\?" },
422 { ".*foo/bar", "..*foo/bar" },
423 { "http://bla.blub/*", "http://bla.blub/.*" },
424 { "bag?r[]=*cpa", "bag\\?r\\[\\]=.*cpa" },
425 { "(facebookLike,", "(facebookLike," },
426};
427
428void test_adblock_fixup_regexp () {
429 foreach (var line in lines) {
430 Katze.assert_str_equal (line.line, Adblock.fixup_regex ("", line.line), line.fixed);
431 }
432}
433
434struct TestCasePattern {
435 public string uri;
436 public Adblock.Directive directive;
437}
438
439const TestCasePattern[] patterns = {
440 { "http://www.engadget.com/_uac/adpage.html", Adblock.Directive.BLOCK },
441 { "http://test.dom/test?var=1", Adblock.Directive.BLOCK },
442 { "http://ads.foo.bar/teddy", Adblock.Directive.BLOCK },
443 { "http://ads.fuu.bar/teddy", Adblock.Directive.ALLOW },
444 { "https://ads.bogus.name/blub", Adblock.Directive.BLOCK },
445 // FIXME { "http://ads.bla.blub/kitty", Adblock.Directive.BLOCK },
446 // FIXME { "http://ads.blub.boing/soda", Adblock.Directive.BLOCK },
447 { "http://ads.foo.boing/beer", Adblock.Directive.ALLOW },
448 { "https://testsub.engine.adct.ru/test?id=1", Adblock.Directive.BLOCK },
449 { "http://test.ltd/addyn/test/test?var=adtech;&var2=1", Adblock.Directive.BLOCK },
450 { "http://add.doubleclick.net/pfadx/aaaa.mtvi", Adblock.Directive.BLOCK },
451 { "http://add.doubleclick.net/pfadx/aaaa.mtv", Adblock.Directive.ALLOW },
452 { "http://objects.tremormedia.com/embed/xml/list.xml?r=", Adblock.Directive.BLOCK },
453 { "http://qq.videostrip.c/sub/admatcherclient.php", Adblock.Directive.ALLOW },
454 { "http://qq.videostrip.com/sub/admatcherclient.php", Adblock.Directive.BLOCK },
455 { "http://qq.videostrip.com/sub/admatcherclient.php", Adblock.Directive.BLOCK },
456 { "http://br.gcl.ru/cgi-bin/br/test", Adblock.Directive.BLOCK },
457 { "https://bugs.webkit.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=substring&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailtype1=substring&email1=&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=&chfieldto=Now&chfieldvalue=&query_based_on=gtkport&field0-0-0=keywords&type0-0-0=anywordssubstr&value0-0-0=Gtk%20Cairo%20soup&field0-0-1=short_desc&type0-0-1=anywordssubstr&value0-0-1=Gtk%20Cairo%20soup%20autoconf%20automake%20autotool&field0-0-2=component&type0-0-2=equals&value0-0-2=WebKit%20Gtk", Adblock.Directive.ALLOW },
458 { "http://www.engadget.com/2009/09/24/google-hits-android-rom-modder-with-a-cease-and-desist-letter/", Adblock.Directive.ALLOW },
459 { "http://karibik-invest.com/es/bienes_raices/search.php?sqT=19&sqN=&sqMp=&sqL=0&qR=1&sqMb=&searchMode=1&action=B%FAsqueda", Adblock.Directive.ALLOW },
460 { "http://google.com", Adblock.Directive.ALLOW },
461};
462
463string pretty_directive (Adblock.Directive? directive) {
464 if (directive == null)
465 return "none";
466 return directive.to_string ();
467}
468
469void test_adblock_pattern () {
470 string path = Midori.Paths.get_res_filename ("adblock.list");
471 string uri;
472 try {
473 uri = Filename.to_uri (path, null);
474 } catch (Error error) {
475 GLib.error (error.message);
476 }
477 var sub = new Adblock.Subscription (uri);
478 try {
479 sub.parse ();
480 } catch (Error error) {
481 GLib.error (error.message);
482 }
483 foreach (var pattern in patterns) {
484 Adblock.Directive? directive = sub.get_directive (pattern.uri, "");
485 if (directive == null)
486 directive = Adblock.Directive.ALLOW;
487 if (directive != pattern.directive) {
488 error ("%s expected for %s but got %s",
489 pretty_directive (pattern.directive), pattern.uri, pretty_directive (directive));
490 }
491 }
492}
493
494struct TestUpdateExample {
495 public string content;
496 public bool result;
497}
498
499 const TestUpdateExample[] examples = {
500 { "[Adblock Plus 1.1]\n! Last modified: 05 Sep 2010 11:00 UTC\n! This list expires after 48 hours\n", true },
501 { "[Adblock Plus 1.1]\n! Last modified: 05.09.2010 11:00 UTC\n! Expires: 2 days (update frequency)\n", true },
502 { "[Adblock Plus 1.1]\n! Updated: 05 Nov 2024 11:00 UTC\n! Expires: 5 days (update frequency)\n", false },
503 { "[Adblock]\n! dutchblock v3\n! This list expires after 14 days\n|http://b*.mookie1.com/\n", false },
504 { "[Adblock Plus 2.0]\n! Last modification time (GMT): 2012.11.05 13:33\n! Expires: 5 days (update frequency)\n", true },
505 { "[Adblock Plus 2.0]\n! Last modification time (GMT): 2012.11.05 13:33\n", true },
506 { "[Adblock]\n ! dummy, i dont have any dates\n", false },
507 };
508
509void test_subscription_update () {
510 string uri;
511 FileIOStream iostream;
512 File file;
513 try {
514 file = File.new_tmp ("midori_adblock_update_test_XXXXXX", out iostream);
515 uri = file.get_uri ();
516 } catch (Error error) {
517 GLib.error (error.message);
518 }
519 var sub = new Adblock.Subscription (uri);
520 var updater = new Adblock.Updater ();
521 sub.add_feature (updater);
522
523 foreach (var example in examples) {
524 try {
525 file.replace_contents (example.content.data, null, false, FileCreateFlags.NONE, null);
526 updater.last_mod_meta = null;
527 updater.expires_meta = null;
528 sub.parse ();
529 } catch (Error error) {
530 GLib.error (error.message);
531 }
532 if (example.result == true)
533 assert (updater.needs_updating());
534 else
535 assert (!updater.needs_updating());
536 }
537}
538
539public void extension_test () {
540 Test.add_func ("/extensions/adblock2/parse", test_adblock_fixup_regexp);
541 Test.add_func ("/extensions/adblock2/pattern", test_adblock_pattern);
542 Test.add_func ("/extensions/adblock2/update", test_subscription_update);
543}
544#endif
545
0546
=== added file 'extensions/adblock/filter.vala'
--- extensions/adblock/filter.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/filter.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,52 @@
1/*
2 Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
3 Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 See the file COPYING for the full license text.
11*/
12
13namespace Adblock {
14 public abstract class Filter : Feature {
15 Options optslist;
16 protected HashTable<string, Regex?> rules;
17
18 public virtual void insert (string sig, Regex regex) {
19 rules.insert (sig, regex);
20 }
21
22 public virtual Regex? lookup (string sig) {
23 return rules.lookup (sig);
24 }
25
26 public virtual uint size () {
27 return rules.size ();
28 }
29
30 protected Filter (Options options) {
31 optslist = options;
32 clear ();
33 }
34
35 public override void clear () {
36 rules = new HashTable<string, Regex> (str_hash, str_equal);
37 }
38
39 protected bool check_rule (Regex regex, string pattern, string request_uri, string page_uri) throws Error {
40 if (!regex.match_full (request_uri))
41 return false;
42
43 var opts = optslist.lookup (pattern);
44 if (opts != null && Regex.match_simple (",third-party", opts,
45 RegexCompileFlags.CASELESS, RegexMatchFlags.NOTEMPTY))
46 if (page_uri != null && regex.match_full (page_uri))
47 return false;
48 debug ("blocked by pattern regexp=%s -- %s", regex.get_pattern (), request_uri);
49 return true;
50 }
51 }
52}
053
=== added file 'extensions/adblock/keys.vala'
--- extensions/adblock/keys.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/keys.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,47 @@
1/*
2 Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
3 Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 See the file COPYING for the full license text.
11*/
12
13namespace Adblock {
14 public class Keys : Filter {
15 List<Regex> blacklist;
16
17 public Keys (Options options) {
18 base (options);
19 }
20
21 public override void clear () {
22 base.clear ();
23 blacklist = new List<Regex> ();
24 }
25
26 public override Directive? match (string request_uri, string page_uri) throws Error {
27 string? uri = fixup_regex ("", request_uri);
28 if (uri == null)
29 return null;
30
31 int signature_size = 8;
32 int pos, l = uri.length;
33 for (pos = l - signature_size; pos >= 0; pos--) {
34 string signature = uri.offset (pos).ndup (signature_size);
35 var regex = rules.lookup (signature);
36 if (regex == null || blacklist.find (regex) != null)
37 continue;
38
39 if (check_rule (regex, uri, request_uri, page_uri))
40 return Directive.BLOCK;
41 blacklist.prepend (regex);
42 }
43
44 return null;
45 }
46 }
47}
048
=== added file 'extensions/adblock/options.vala'
--- extensions/adblock/options.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/options.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,32 @@
1/*
2 Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 See the file COPYING for the full license text.
10*/
11
12namespace Adblock {
13 public class Options : GLib.Object {
14 HashTable<string, string?> optslist;
15
16 public Options () {
17 clear ();
18 }
19
20 public void insert (string sig, string? opts) {
21 optslist.insert (sig, opts);
22 }
23
24 public string? lookup (string sig) {
25 return optslist.lookup (sig);
26 }
27
28 public void clear () {
29 optslist = new HashTable<string, string?> (str_hash, str_equal);
30 }
31 }
32}
033
=== added file 'extensions/adblock/pattern.vala'
--- extensions/adblock/pattern.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/pattern.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,26 @@
1/*
2 Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
3 Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 See the file COPYING for the full license text.
11*/
12
13namespace Adblock {
14 public class Pattern : Filter {
15 public Pattern (Options options) {
16 base (options);
17 }
18
19 public override Directive? match (string request_uri, string page_uri) throws Error {
20 foreach (var patt in rules.get_keys ())
21 if (check_rule (rules.lookup (patt), patt, request_uri, page_uri))
22 return Directive.BLOCK;
23 return null;
24 }
25 }
26}
027
=== added file 'extensions/adblock/subscriptions.vala'
--- extensions/adblock/subscriptions.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/subscriptions.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,328 @@
1/*
2 Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
3 Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 See the file COPYING for the full license text.
11*/
12
13namespace Adblock {
14 public abstract class Feature : GLib.Object {
15 public virtual bool header (string key, string value) {
16 return false;
17 }
18 public virtual Directive? match (string request_uri, string page_uri) throws Error {
19 return null;
20 }
21 public virtual void clear () {
22 }
23 }
24
25 public class Subscription : GLib.Object {
26 public string? path;
27 public string uri { get; set; default = null; }
28 public bool active { get; set; default = true; }
29 List<Feature> features;
30 public Pattern pattern;
31 public Keys keys;
32 public Options optslist;
33 public Whitelist whitelist;
34 public Element element;
35 WebKit.Download? download;
36
37 public Subscription (string uri) {
38 this.uri = uri;
39
40 this.optslist = new Options ();
41 this.whitelist = new Whitelist (optslist);
42 add_feature (this.whitelist);
43 this.keys = new Keys (optslist);
44 add_feature (this.keys);
45 this.pattern = new Pattern (optslist);
46 add_feature (this.pattern);
47 this.element = new Element ();
48 add_feature (this.element);
49 clear ();
50 }
51
52 public void add_feature (Feature feature) {
53 features.append (feature);
54 size++;
55 }
56
57 /* foreach support */
58 public new Feature? get (uint index) {
59 return features.nth_data (index);
60 }
61 public uint size { get; private set; }
62
63 public void clear () {
64 foreach (var feature in features)
65 feature.clear ();
66 optslist.clear ();
67 }
68
69 internal void parse_line (string? line) throws Error {
70 if (line.has_prefix ("@@")) {
71 if (line.contains("$") && line.contains ("domain"))
72 return;
73 if (line.has_prefix ("@@||"))
74 add_url_pattern ("^", "whitelist", line.offset (4));
75 else if (line.has_prefix ("@@|"))
76 add_url_pattern ("^", "whitelist", line.offset (3));
77 else
78 add_url_pattern ("", "whitelist", line.offset (2));
79 return;
80 }
81 /* TODO: [include] [exclude] */
82 if (line[0] == '[')
83 return;
84
85 /* CSS block hider */
86 if (line.has_prefix ("##")) {
87 /* TODO */
88 return;
89 }
90 if (line[0] == '#')
91 return;
92
93 /* Per domain CSS hider rule */
94 if ("##" in line) {
95 frame_add_private (line, "##");
96 return;
97 }
98 if ("#" in line) {
99 frame_add_private (line, "#");
100 return;
101 }
102
103 /* URL blocker rule */
104 if (line.has_prefix ("|")) {
105 /* TODO: handle options and domains excludes */
106 if (line.contains("$"))
107 return;
108
109 if (line.has_prefix ("||"))
110 add_url_pattern ("", "fulluri", line.offset (2));
111 else
112 add_url_pattern ("^", "fulluri", line.offset (1));
113 return /* add_url_pattern */;
114 }
115
116 add_url_pattern ("", "uri", line);
117 return /* add_url_pattern */;
118 }
119
120 void frame_add_private (string line, string sep) {
121 string[] data = line.split (sep, 2);
122 if (!(data[1] != null && data[1] != "")
123 || data[1].chr (-1, '\'') != null
124 || (data[1].chr (-1, ':') != null
125 && !Regex.match_simple (".*\\[.*:.*\\].*", data[1],
126 RegexCompileFlags.CASELESS, RegexMatchFlags.NOTEMPTY))) {
127 return;
128 }
129
130 if (data[0].chr (-1, ',') != null) {
131 string[] domains = data[0].split (",", -1);
132
133 foreach (string domain in domains) {
134 /* Ignore Firefox-specific option */
135 if (domain == "~pregecko2")
136 continue;
137 /* FIXME: ~ should negate match */
138 if (domain[0] == '~')
139 domain = domain.substring (1, -1);
140 update_css_hash (domain.strip (), data[1]);
141 }
142 }
143 else {
144 update_css_hash (data[0], data[1]);
145 }
146 }
147
148 void update_css_hash (string domain, string value) {
149 string? olddata = element.lookup (domain);
150 if (olddata != null) {
151 string newdata = olddata + " , " + value;
152 element.insert (domain, newdata);
153 } else {
154 element.insert (domain, value);
155 }
156 }
157
158 void add_url_pattern (string prefix, string type, string line) throws Error {
159 string[]? data = line.split ("$", 2);
160 if (data == null || data[0] == null)
161 return;
162
163 string patt, opts;
164 patt = data[0];
165 opts = type;
166
167 if (data[1] != null)
168 opts = type + "," + data[1];
169
170 if (Regex.match_simple ("subdocument", opts,
171 RegexCompileFlags.CASELESS, RegexMatchFlags.NOTEMPTY))
172 return;
173
174 string format_patt = fixup_regex (prefix, patt);
175 debug ("got: %s opts %s", format_patt, opts);
176 compile_regexp (format_patt, opts);
177 /* return format_patt */
178 }
179
180 bool compile_regexp (string? patt, string opts) throws Error {
181 if (patt == null)
182 return false;
183 try {
184 var regex = new Regex (patt, RegexCompileFlags.OPTIMIZE, RegexMatchFlags.NOTEMPTY);
185 /* is pattern is already a regex? */
186 if (Regex.match_simple ("^/.*[\\^\\$\\*].*/$", patt,
187 RegexCompileFlags.UNGREEDY, RegexMatchFlags.NOTEMPTY)
188 || opts != null && opts.contains ("whitelist")) {
189 debug ("patt: %s", patt);
190 if (opts.contains ("whitelist"))
191 this.whitelist.insert (patt, regex);
192 else
193 this.pattern.insert (patt, regex);
194 this.optslist.insert (patt, opts);
195 return false;
196 } else { /* nope, no regex */
197 int pos = 0, len;
198 int signature_size = 8;
199 string sig;
200 len = patt.length;
201
202 /* chop up pattern into substrings for faster matching */
203 for (pos = len - signature_size; pos>=0; pos--)
204 {
205 sig = patt.offset (pos).ndup (signature_size);
206 /* we don't have a * nor \\, does not look like regex, save chunk as "key" */
207 if (!Regex.match_simple ("[\\*]", sig, RegexCompileFlags.UNGREEDY, RegexMatchFlags.NOTEMPTY) && keys.lookup (sig) == null) {
208 this.keys.insert (sig, regex);
209 this.optslist.insert (sig, opts);
210 } else {
211 /* starts with * or \\ - save as regex */
212 if ((sig.has_prefix ("*") || sig.has_prefix("\\")) && this.pattern.lookup (sig) == null) {
213 this.pattern.insert (sig, regex);
214 this.optslist.insert (sig, opts);
215 }
216 }
217 }
218 }
219 return false;
220 }
221 catch (Error error) {
222 warning ("Adblock compile regexp: %s", error.message);
223 return true;
224 }
225 }
226
227 public void parse_header (string header) throws Error {
228 /* Headers come in two forms
229 ! Foo: Bar
230 ! Some freeform text
231 */
232 string key = header;
233 string value = "";
234 if (header.contains (":")) {
235 string[] parts = header.split (":", 2);
236 if (parts[0] != null) {
237 key = parts[0].substring (2, -1);
238 value = parts[1].substring (1, -1);
239 }
240 }
241 debug ("Header '%s' says '%s'", key, value);
242 foreach (var feature in features) {
243 if (feature.header (key, value))
244 break;
245 }
246 }
247
248 void download_status (ParamSpec pspec) {
249 if (download.get_status () != WebKit.DownloadStatus.FINISHED)
250 return;
251
252 download = null;
253 try {
254 parse ();
255 } catch (Error error) {
256 warning ("Error parsing %s: %s", uri, error.message);
257 }
258 }
259
260 public void parse () throws Error
261 {
262 if (!active)
263 return;
264
265 debug ("Parsing %s (%s)", uri, path);
266
267 clear ();
268
269 if (uri.has_prefix ("file://"))
270 path = Filename.from_uri (uri);
271 else {
272 string cache_dir = GLib.Path.build_filename (GLib.Environment.get_home_dir (), ".cache", "midori", "adblock");
273 Midori.Paths.mkdir_with_parents (cache_dir);
274 string filename = Checksum.compute_for_string (ChecksumType.MD5, this.uri, -1);
275 path = GLib.Path.build_filename (cache_dir, filename);
276 }
277
278 File filter_file = File.new_for_path (path);
279 DataInputStream stream;
280 try {
281 stream = new DataInputStream (filter_file.read ());
282 } catch (IOError.NOT_FOUND exist_error) {
283#if HAVE_WEBKIT2
284 /* TODO */
285#else
286 if (download != null)
287 return;
288
289 download = new WebKit.Download (new WebKit.NetworkRequest (uri));
290 download.destination_uri = Filename.to_uri (path, null);
291 download.notify["status"].connect (download_status);
292 debug ("Fetching %s to %s now", uri, download.destination_uri);
293 download.start ();
294#endif
295 return;
296 }
297
298 string? line;
299 while ((line = stream.read_line (null)) != null) {
300 if (line == null)
301 continue;
302 string chomped = line.chomp ();
303 if (chomped == "")
304 continue;
305 if (line[0] == '!')
306 parse_header (chomped);
307 else
308 parse_line (chomped);
309 }
310 }
311
312 public Directive? get_directive (string request_uri, string page_uri) {
313 try {
314 foreach (var feature in features) {
315 Directive? directive = feature.match (request_uri, page_uri);
316 if (directive != null) {
317 debug ("%s gave %s for %s (%s)\n",
318 feature.get_type ().name (), directive.to_string (), request_uri, page_uri);
319 return directive;
320 }
321 }
322 } catch (Error error) {
323 warning ("Adblock match error: %s\n", error.message);
324 }
325 return null;
326 }
327 }
328}
0329
=== added file 'extensions/adblock/updater.vala'
--- extensions/adblock/updater.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/updater.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,139 @@
1/*
2 Copyright (C) 2014 Paweł Forysiuk <tuxator@o2.pl>
3 Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 See the file COPYING for the full license text.
11*/
12
13namespace Adblock {
14 public class Updater : Feature {
15 public string expires_meta { get; set; default = null; }
16 public string last_mod_meta { get; set; default = null; }
17 public int64 update_tstamp { get; set; default = 0; }
18 public int64 last_mod_tstamp { get; set; default = 0; }
19 public int64 last_check_tstamp { get; set; default = 0; }
20
21 public Updater () {
22 }
23
24 public override bool header (string key, string value) {
25 if (key.has_prefix ("Last mod") || key == "Updated") {
26 last_mod_meta = value;
27 return true;
28 } else if (key == "Expires") {
29 /* ! Expires: 5 days (update frequency) */
30 expires_meta = value;
31 return true;
32 } else if (key.has_prefix ("! This list expires after")) {
33 /* ! This list expires after 14 days */
34 expires_meta = key.substring (26, -1);
35 return true;
36 }
37 return false;
38 }
39
40 int get_month_from_string (string? month) {
41 if (month == null)
42 return 0;
43
44 string[] months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
45 for (int i = 0; i<= months.length; i++)
46 {
47 if (month.has_prefix (months[i]))
48 return i+1;
49 }
50 return 0;
51 }
52
53 public bool needs_updating () {
54 DateTime now = new DateTime.now_local ();
55 DateTime expire_date = null;
56 DateTime last_mod_date = null;
57 string? last_mod = last_mod_meta;
58 string? expires = expires_meta;
59
60 /* We have "last modification" metadata */
61 if (last_mod != null) {
62 int h = 0, min = 0, d, m, y;
63 /* Date in a form of: 20.08.2012 12:34 */
64 if (last_mod.contains (".") || last_mod.contains("-")) {
65 string[] parts = last_mod.split (" ", 2);
66 string[] date_parts;
67 string split_char = " ";
68
69 /* contains time part ? */
70 if (parts[1] != "" && parts[1].contains (":")) {
71 string[] time_parts = parts[1].split (":", 2);
72 h = int.parse(time_parts[0]);
73 min = int.parse(time_parts[1]);
74 }
75
76 /* check if dot or dash was used as a delimiter */
77 if (parts[0].contains ("."))
78 split_char = ".";
79 else if (parts[0].contains ("-"))
80 split_char = "-";
81
82 date_parts = parts[0].split (split_char, 3);
83 m = int.parse(date_parts[1]);
84 if (date_parts[2].length == 4) {
85 y = int.parse(date_parts[2]);
86 d = int.parse(date_parts[0]);
87 } else {
88 y = int.parse(date_parts[0]);
89 d = int.parse(date_parts[2]);
90 }
91 } else { /* Date in a form of: 20 Mar 2012 12:34 */
92 string[] parts = last_mod.split (" ", 4);
93 /* contains time part ? */
94 if (parts[3] != null && parts[3].contains (":")) {
95 string[] time_parts = parts[3].split (":", 2);
96 h = int.parse(time_parts[0]);
97 min = int.parse(time_parts[1]);
98 }
99
100 m = get_month_from_string (parts[1]);
101 if (parts[2].length == 4) {
102 y = int.parse(parts[2]);
103 d = int.parse(parts[0]);
104 } else {
105 y = int.parse(parts[0]);
106 d = int.parse(parts[2]);
107 }
108 }
109
110 last_mod_date = new DateTime.local (y, m, d, h, min, 0.0);
111 }
112
113 if (last_mod_date == null)
114 last_mod_date = now;
115
116 /* We have "expires" metadata */
117 if (expires != null) {
118 if (expires.contains ("days")) {
119 string[] parts = expires.split (" ");
120 expire_date = last_mod_date.add_days (int.parse (parts[0]));
121 } else if (expires.contains ("hours")) {
122 string[] parts = expires.split (" ");
123 expire_date = last_mod_date.add_hours (int.parse (parts[0]));
124 }
125 } else {
126 /* No expire metadata found, assume x days */
127 int days_to_expire = 7;
128 expire_date = last_mod_date.add_days (days_to_expire);
129 }
130
131 last_mod_tstamp = last_mod_date.to_unix ();
132 last_check_tstamp = now.to_unix ();
133 update_tstamp = expire_date.to_unix ();
134
135 /* Check if we are past expire date */
136 return now.compare (expire_date) == 1;
137 }
138 }
139}
0140
=== added file 'extensions/adblock/whitelist.vala'
--- extensions/adblock/whitelist.vala 1970-01-01 00:00:00 +0000
+++ extensions/adblock/whitelist.vala 2014-02-20 20:31:17 +0000
@@ -0,0 +1,30 @@
1/*
2 Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
3 Copyright (C) 2014 Paweł Forysiuk <tuxator@o2.pl>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 See the file COPYING for the full license text.
11*/
12
13namespace Adblock {
14 public class Whitelist : Filter {
15 public Whitelist (Options options) {
16 base (options);
17 }
18
19 public override Directive? match (string request_uri, string page_uri) throws Error {
20 foreach (var white in rules.get_keys ()) {
21 var regex = rules.lookup (white);
22 if (!regex.match_full (request_uri))
23 return null;
24 if (Regex.match_simple (regex.get_pattern (), request_uri, RegexCompileFlags.UNGREEDY, RegexMatchFlags.NOTEMPTY))
25 return Directive.ALLOW;
26 }
27 return null;
28 }
29 }
30}
031
=== modified file 'katze/midori-paths.vala'
--- katze/midori-paths.vala 2013-11-22 18:14:18 +0000
+++ katze/midori-paths.vala 2014-02-20 20:31:17 +0000
@@ -143,6 +143,7 @@
143#if HAVE_WEBKIT2143#if HAVE_WEBKIT2
144 WebKit.WebContext.get_default ().set_disk_cache_directory (144 WebKit.WebContext.get_default ().set_disk_cache_directory (
145 Path.build_path (Path.DIR_SEPARATOR_S, cache_dir, "web"));145 Path.build_path (Path.DIR_SEPARATOR_S, cache_dir, "web"));
146
146 var cookie_manager = WebKit.WebContext.get_default ().get_cookie_manager ();147 var cookie_manager = WebKit.WebContext.get_default ().get_cookie_manager ();
147 cookie_manager.set_persistent_storage (Path.build_filename (config, "cookies.db"),148 cookie_manager.set_persistent_storage (Path.build_filename (config, "cookies.db"),
148 WebKit.CookiePersistentStorage.SQLITE);149 WebKit.CookiePersistentStorage.SQLITE);
149150
=== modified file 'midori/midori-app.c'
--- midori/midori-app.c 2014-01-12 18:14:15 +0000
+++ midori/midori-app.c 2014-02-20 20:31:17 +0000
@@ -1256,7 +1256,7 @@
1256midori_debug (const gchar* token)1256midori_debug (const gchar* token)
1257{1257{
1258 static const gchar* debug_token = NULL;1258 static const gchar* debug_token = NULL;
1259 const gchar* debug_tokens = "adblock:match adblock:time startup headers body referer cookies paths hsts unarmed bookmarks mouse app ";1259 const gchar* debug_tokens = "adblock:match adblock:time adblock:element startup headers body referer cookies paths hsts unarmed bookmarks mouse app ";
1260 if (debug_token == NULL)1260 if (debug_token == NULL)
1261 {1261 {
1262 gchar* found_token;1262 gchar* found_token;
12631263
=== modified file 'midori/midori.vapi'
--- midori/midori.vapi 2014-01-26 11:33:02 +0000
+++ midori/midori.vapi 2014-02-20 20:31:17 +0000
@@ -135,6 +135,7 @@
135 public void install_boolean (string name, bool default_value);135 public void install_boolean (string name, bool default_value);
136 public void install_integer (string name, int default_value);136 public void install_integer (string name, int default_value);
137 public void install_string (string name, string default_value);137 public void install_string (string name, string default_value);
138 public void install_string_list (string name, string[]? default_value);
138139
139 public bool get_boolean (string name);140 public bool get_boolean (string name);
140 public int get_integer (string name);141 public int get_integer (string name);
141142
=== modified file 'midori/webkit2gtk-3.0.vapi'
--- midori/webkit2gtk-3.0.vapi 2013-04-08 18:35:37 +0000
+++ midori/webkit2gtk-3.0.vapi 2014-02-20 20:31:17 +0000
@@ -552,6 +552,22 @@
552 [CCode (has_construct_function = false)]552 [CCode (has_construct_function = false)]
553 protected WebViewBase ();553 protected WebViewBase ();
554 }554 }
555 [CCode (cheader_filename = "webkit2/webkit-web-extension.h", type_id = "webkit_web_extension_get_type ()")]
556 public class WebExtension : GLib.Object {
557 [CCode (has_construct_function = false)]
558 protected WebExtension ();
559 public WebKit.WebPage get_page (uint64 page_id);
560 public signal void page_created (WebKit.WebPage web_page);
561 }
562 [CCode (cheader_filename = "webkit2/webkit-web-extension.h", type_id = "webkit_web_extension_get_type ()")]
563 public class WebPage : GLib.Object {
564 [CCode (has_construct_function = false)]
565 protected WebPage ();
566 public uint64 get_id ();
567 public string uri { get; }
568 public signal bool send_request (WebKit.URIRequest request, WebKit.URIResponse? redirected_response);
569 public signal void document_loaded ();
570 }
555 [CCode (cheader_filename = "webkit2/webkit2.h", type_id = "webkit_window_properties_get_type ()")]571 [CCode (cheader_filename = "webkit2/webkit2.h", type_id = "webkit_window_properties_get_type ()")]
556 public class WindowProperties : GLib.Object {572 public class WindowProperties : GLib.Object {
557 [CCode (has_construct_function = false)]573 [CCode (has_construct_function = false)]
558574
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2013-12-13 17:34:40 +0000
+++ po/POTFILES.in 2014-02-20 20:31:17 +0000
@@ -36,7 +36,6 @@
36katze/katze-preferences.c36katze/katze-preferences.c
37katze/midori-uri.vala37katze/midori-uri.vala
38katze/midori-paths.vala38katze/midori-paths.vala
39extensions/adblock.c
40extensions/addons.c39extensions/addons.c
41extensions/colorful-tabs.c40extensions/colorful-tabs.c
42extensions/cookie-manager/cookie-manager.c41extensions/cookie-manager/cookie-manager.c
@@ -92,3 +91,13 @@
92extensions/tabby.vala91extensions/tabby.vala
93extensions/flummi.vala92extensions/flummi.vala
94extensions/notes.vala93extensions/notes.vala
94extensions/adblock/extension.vala
95extensions/adblock/filter.vala
96extensions/adblock/keys.vala
97extensions/adblock/pattern.vala
98extensions/adblock/options.vala
99extensions/adblock/whitelist.vala
100extensions/adblock/subscriptions.vala
101extensions/adblock/config.vala
102extensions/adblock/updater.vala
103extensions/adblock/element.vala

Subscribers

People subscribed via source and target branches

to all changes: