/* Copyright (C) 2013 Stephan Haller This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the file COPYING for the full license text. */ #include "nojs.h" #include "nojs-view.h" #include /* Define this class in GObject system */ G_DEFINE_TYPE(NoJS, nojs, G_TYPE_OBJECT) /* Properties */ enum { PROP_0, PROP_EXTENSION, PROP_APPLICATION, PROP_DATABASE, PROP_DATABASE_FILENAME, PROP_ALLOW_LOCAL_PAGES, PROP_ONLY_SECOND_LEVEL, PROP_UNKNOWN_DOMAIN_POLICY, PROP_LAST }; static GParamSpec* NoJSProperties[PROP_LAST]={ 0, }; /* Signals */ enum { URI_LOAD_POLICY_STATUS, POLICY_CHANGED, SIGNAL_LAST }; static guint NoJSSignals[SIGNAL_LAST]={ 0, }; /* Private structure - access only by public API if needed */ #define NOJS_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_NOJS, NoJSPrivate)) struct _NoJSPrivate { /* Extension related */ MidoriExtension *extension; MidoriApp *application; sqlite3 *database; gchar *databaseFilename; gboolean allowLocalPages; gboolean checkOnlySecondLevel; NoJSPolicy unknownDomainPolicy; guint requestStartedSignalID; }; /* Taken from http://www.w3.org/html/wg/drafts/html/master/scripting-1.html#scriptingLanguages * A list of javascript mime types */ static const gchar* javascriptTypes[]= { "application/ecmascript", "application/javascript", "application/x-ecmascript", "application/x-javascript", "text/ecmascript", "text/javascript", "text/javascript1.0", "text/javascript1.1", "text/javascript1.2", "text/javascript1.3", "text/javascript1.4", "text/javascript1.5", "text/jscript", "text/livescript", "text/x-ecmascript", "text/x-javascript", NULL }; /* IMPLEMENTATION: Private variables and methods */ /* Closure for: void (*closure)(NoJS *self, gchar *inURI, NoJSPolicy inPolicy) */ static void _nojs_closure_VOID__STRING_ENUM(GClosure *inClosure, GValue *ioReturnValue G_GNUC_UNUSED, guint inNumberValues, const GValue *inValues, gpointer inInvocationHint G_GNUC_UNUSED, gpointer inMarshalData) { typedef void (*GMarshalFunc_VOID__STRING_ENUM)(gpointer inObject, gpointer inArg1, gint inArg2, gpointer inUserData); register GMarshalFunc_VOID__STRING_ENUM callback; register GCClosure *closure=(GCClosure*)inClosure; register gpointer object, userData; g_return_if_fail(inNumberValues==3); if(G_CCLOSURE_SWAP_DATA(inClosure)) { object=inClosure->data; userData=g_value_peek_pointer(inValues+0); } else { object=g_value_peek_pointer(inValues+0); userData=inClosure->data; } callback=(GMarshalFunc_VOID__STRING_ENUM)(inMarshalData ? inMarshalData : closure->callback); callback(object, (gchar*)g_value_get_string(inValues+1), g_value_get_enum(inValues+2), userData); } /* Show common error dialog */ static void _nojs_error(NoJS *self, const gchar *inReason) { g_return_if_fail(IS_NOJS(self)); g_return_if_fail(inReason); GtkWidget *dialog; /* Show confirmation dialog for undetermined cookies */ dialog=gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("A fatal error occurred which prevents " "the NoJS extension to continue. " "You should disable it.")); gtk_window_set_title(GTK_WINDOW(dialog), _("Error in NoJS extension")); gtk_window_set_icon_name(GTK_WINDOW (dialog), "midori"); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s:\n%s", _("Reason"), inReason); gtk_dialog_run(GTK_DIALOG(dialog)); /* Free up allocated resources */ gtk_widget_destroy(dialog); } /* Open database containing policies for javascript sites. * Create database and setup table structure if it does not exist yet. */ static void _nojs_open_database(NoJS *self) { g_return_if_fail(IS_NOJS(self)); NoJSPrivate *priv=self->priv; const gchar *configDir; gchar *sql; gchar *error=NULL; gint success; /* Close any open database */ if(priv->database) { priv->databaseFilename=NULL; sqlite3_close(priv->database); priv->database=NULL; g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE]); g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE_FILENAME]); } /* Build path to database file */ configDir=midori_extension_get_config_dir(priv->extension); if(!configDir) return; if(katze_mkdir_with_parents(configDir, 0700)) { g_warning(_("Could not create configuration folder for extension: %s"), g_strerror(errno)); _nojs_error(self, _("Could not create configuration folder for extension.")); return; } /* Open database */ priv->databaseFilename=g_build_filename(configDir, NOJS_DATABASE, NULL); success=sqlite3_open(priv->databaseFilename, &priv->database); if(success!=SQLITE_OK) { g_warning(_("Could not open database of extension: %s"), sqlite3_errmsg(priv->database)); g_free(priv->databaseFilename); priv->databaseFilename=NULL; if(priv->database) sqlite3_close(priv->database); priv->database=NULL; _nojs_error(self, _("Could not open database of extension.")); return; } /* Create table structure if it does not exist */ success=sqlite3_exec(priv->database, "CREATE TABLE IF NOT EXISTS " "policies(site text, value integer);", NULL, NULL, &error); if(success==SQLITE_OK) { success=sqlite3_exec(priv->database, "CREATE UNIQUE INDEX IF NOT EXISTS " "site ON policies (site);", NULL, NULL, &error); } if(success==SQLITE_OK) { success=sqlite3_exec(priv->database, "PRAGMA journal_mode=TRUNCATE;", NULL, NULL, &error); } if(success!=SQLITE_OK || error) { _nojs_error(self, _("Could not set up database structure of extension.")); if(error) { g_critical(_("Failed to execute database statement: %s"), error); sqlite3_free(error); } g_free(priv->databaseFilename); priv->databaseFilename=NULL; sqlite3_close(priv->database); priv->database=NULL; return; } /* Delete all temporarily allowed sites */ sql=sqlite3_mprintf("DELETE FROM policies WHERE value=%d;", NOJS_POLICY_ACCEPT_TEMPORARILY); success=sqlite3_exec(priv->database, sql, NULL, NULL, &error); if(success!=SQLITE_OK) g_warning(_("SQL fails: %s"), error); if(error) sqlite3_free(error); sqlite3_free(sql); g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE]); g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_DATABASE_FILENAME]); } /* A request through libsoup is going to start and http headers must be * checked for content type */ static void _nojs_on_got_headers(NoJS *self, gpointer inUserData) { g_return_if_fail(IS_NOJS(self)); g_return_if_fail(SOUP_IS_MESSAGE(inUserData)); NoJSPrivate *priv=self->priv; SoupMessage *message=SOUP_MESSAGE(inUserData); SoupSession *session=webkit_get_default_session(); SoupMessageHeaders *headers; SoupMessageBody *body; const gchar *contentType; SoupURI *uri; gchar *uriText; NoJSPolicy policy; gboolean isJS; const gchar **iter; /* Get headers from message to retrieve content type */ g_object_get(message, "response-headers", &headers, NULL); if(!headers) { g_warning("Could not get headers from message to check for javascript."); return; } /* Get content type of uri and check if it is a javascript resource */ contentType=soup_message_headers_get_content_type(headers, NULL); isJS=FALSE; iter=javascriptTypes; while(*iter && !isJS) { isJS=(g_strcmp0(contentType, *iter)==0); iter++; } if(!isJS) return; /* The document being loaded is javascript so get URI from message, * get policy for domain of URI and emit signal */ uri=soup_message_get_uri(message); policy=nojs_get_policy(self, uri); if(policy==NOJS_POLICY_UNDETERMINED) { g_warning("Got invalid policy. Using default policy for unknown domains."); policy=priv->unknownDomainPolicy; } uriText=soup_uri_to_string(uri, FALSE); g_signal_emit(self, NoJSSignals[URI_LOAD_POLICY_STATUS], 0, uriText, policy==NOJS_POLICY_UNDETERMINED ? NOJS_POLICY_BLOCK : policy); g_free(uriText); /* Return here if policy is any type of accept */ if(policy!=NOJS_POLICY_UNDETERMINED && policy!=NOJS_POLICY_BLOCK) return; /* Cancel this message */ soup_session_cancel_message(session, message, SOUP_STATUS_CANCELLED); /* Discard any load data */ g_object_get(message, "response-body", &body, NULL); if(body) soup_message_body_truncate(body); } static void _nojs_on_request_started(NoJS *self, SoupMessage *inMessage, SoupSocket *inSocket, gpointer inUserData) { g_return_if_fail(IS_NOJS(self)); g_return_if_fail(SOUP_IS_MESSAGE(inMessage)); /* Connect to "got-headers" to cancel loading javascript documents early */ g_signal_connect_swapped(inMessage, "got-headers", G_CALLBACK(_nojs_on_got_headers), self); } /* The icon in statusbar was clicked */ static void _nojs_on_statusbar_icon_clicked(MidoriBrowser *inBrowser, gpointer inUserData) { g_return_if_fail(MIDORI_IS_BROWSER(inBrowser)); MidoriView *activeView; NoJSView *view; GtkMenu *menu; /* Get current active midori view */ activeView=MIDORI_VIEW(midori_browser_get_current_tab(inBrowser)); g_return_if_fail(MIDORI_IS_VIEW(activeView)); /* Get NoJS view of current active midori view */ view=NOJS_VIEW(g_object_get_data(G_OBJECT(activeView), "nojs-view-instance")); g_return_if_fail(NOJS_IS_VIEW(view)); /* Get menu of current view */ menu=nojs_view_get_menu(view); g_return_if_fail(menu); /* Show menu */ gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time()); } gchar* nojs_get_icon_path (const gchar* icon) { gchar* nojs_dir = midori_paths_get_res_filename("nojs"); return g_build_filename (nojs_dir, icon, NULL); } /* Menu icon of a view has changed */ static void _nojs_on_menu_icon_changed(MidoriBrowser *inBrowser, GParamSpec *inSpec, gpointer inUserData) { g_return_if_fail(MIDORI_IS_BROWSER(inBrowser)); g_return_if_fail(NOJS_IS_VIEW(inUserData)); NoJSView *view=NOJS_VIEW(inUserData); NoJSMenuIconState menuIconState; GtkWidget *statusbarIcon; GtkWidget *buttonImage; gchar *imageFilename; /* Get icon in status bar of this browser */ statusbarIcon=GTK_WIDGET(g_object_get_data(G_OBJECT(inBrowser), "nojs-statusicon")); g_return_if_fail(GTK_IS_WIDGET(statusbarIcon)); /* Get menu icon state of view */ menuIconState=nojs_view_get_menu_icon_state(view); /* Create image for statusbar button */ imageFilename=NULL; switch(menuIconState) { case NOJS_MENU_ICON_STATE_ALLOWED: imageFilename=nojs_get_icon_path("nojs-statusicon-allowed.png"); break; case NOJS_MENU_ICON_STATE_MIXED: imageFilename=nojs_get_icon_path("nojs-statusicon-mixed.png"); break; case NOJS_MENU_ICON_STATE_DENIED: case NOJS_MENU_ICON_STATE_UNDETERMINED: imageFilename=nojs_get_icon_path("nojs-statusicon-denied.png"); break; } buttonImage=gtk_image_new_from_file(imageFilename); g_free(imageFilename); /* Set image at statusbar button */ gtk_button_set_image(GTK_BUTTON(statusbarIcon), buttonImage); } /* A tab in browser was activated */ static void _nojs_on_switch_tab(NoJS *self, MidoriView *inOldView, MidoriView *inNewView, gpointer inUserData) { g_return_if_fail(IS_NOJS(self)); g_return_if_fail(MIDORI_IS_BROWSER(inUserData)); MidoriBrowser *browser=MIDORI_BROWSER(inUserData); NoJSView *view; /* Disconnect signal handlers from old view */ if(inOldView) { /* Get NoJS view of old view */ view=(NoJSView*)g_object_get_data(G_OBJECT(inOldView), "nojs-view-instance"); g_return_if_fail(NOJS_IS_VIEW(view)); /* Disconnect signal handlers */ g_signal_handlers_disconnect_by_func(view, G_CALLBACK(_nojs_on_menu_icon_changed), browser); } /* Get NoJS view of new view */ view=(NoJSView*)g_object_get_data(G_OBJECT(inNewView), "nojs-view-instance"); g_return_if_fail(NOJS_IS_VIEW(view)); /* Connect signals */ g_signal_connect_swapped(view, "notify::menu-icon-state", G_CALLBACK(_nojs_on_menu_icon_changed), browser); /* Update menu icon*/ _nojs_on_menu_icon_changed(browser, NULL, view); } /* A tab of a browser was removed */ static void _nojs_on_remove_tab(NoJS *self, MidoriView *inView, gpointer inUserData) { g_return_if_fail(IS_NOJS(self)); NoJSView *view; /* Get NoJS view of current active midori view */ view=NOJS_VIEW(g_object_get_data(G_OBJECT(inView), "nojs-view-instance")); g_return_if_fail(NOJS_IS_VIEW(view)); g_object_unref(view); } /* A tab of a browser was added */ static void _nojs_on_add_tab(NoJS *self, MidoriView *inView, gpointer inUserData) { g_return_if_fail(IS_NOJS(self)); g_return_if_fail(MIDORI_IS_BROWSER(inUserData)); /* Create nojs view and add to tab */ MidoriBrowser *browser=MIDORI_BROWSER(inUserData); nojs_view_new(self, browser, inView); } /* A browser window was added */ static void _nojs_on_add_browser(NoJS *self, MidoriBrowser *inBrowser, gpointer inUserData) { g_return_if_fail(IS_NOJS(self)); g_return_if_fail(MIDORI_IS_BROWSER(inBrowser)); GList *tabs, *iter; GtkWidget *statusbar; GtkWidget *statusbarIcon; MidoriView *view; NoJSView *nojsView; /* Set up all current available tabs in browser */ tabs=midori_browser_get_tabs(inBrowser); for(iter=tabs; iter; iter=g_list_next(iter)) _nojs_on_add_tab(self, iter->data, inBrowser); g_list_free(tabs); /* Add status bar icon to browser */ g_object_get(inBrowser, "statusbar", &statusbar, NULL); if(statusbar) { /* Create and set up status icon */ statusbarIcon=gtk_button_new(); gtk_button_set_relief(GTK_BUTTON(statusbarIcon), GTK_RELIEF_NONE); gtk_widget_show_all(statusbarIcon); gtk_box_pack_end(GTK_BOX(statusbar), statusbarIcon, FALSE, FALSE, 0); g_object_set_data_full(G_OBJECT(inBrowser), "nojs-statusicon", g_object_ref(statusbarIcon), (GDestroyNotify)gtk_widget_destroy); /* Connect signals */ g_signal_connect_swapped(statusbarIcon, "clicked", G_CALLBACK(_nojs_on_statusbar_icon_clicked), inBrowser); /* Release our reference to statusbar and status icon */ g_object_unref(statusbarIcon); g_object_unref(statusbar); /* Update menu icon*/ view=MIDORI_VIEW(midori_browser_get_current_tab(inBrowser)); if(view) { nojsView=(NoJSView*)g_object_get_data(G_OBJECT(view), "nojs-view-instance"); if(nojsView) _nojs_on_menu_icon_changed(inBrowser, NULL, nojsView); } } /* Listen to new tabs opened in browser */ g_signal_connect_swapped(inBrowser, "add-tab", G_CALLBACK(_nojs_on_add_tab), self); g_signal_connect_swapped(inBrowser, "switch-tab", G_CALLBACK(_nojs_on_switch_tab), self); g_signal_connect_swapped(inBrowser, "remove-tab", G_CALLBACK(_nojs_on_remove_tab), self); } /* Application property has changed */ static void _nojs_on_application_changed(NoJS *self) { g_return_if_fail(IS_NOJS(self)); NoJSPrivate *priv=NOJS(self)->priv; GList *browsers, *iter; /* Set up all current open browser windows */ browsers=midori_app_get_browsers(priv->application); for(iter=browsers; iter; iter=g_list_next(iter)) _nojs_on_add_browser(self, MIDORI_BROWSER(iter->data), priv->application); g_list_free(browsers); /* Listen to new browser windows opened */ g_signal_connect_swapped(priv->application, "add-browser", G_CALLBACK(_nojs_on_add_browser), self); /* Notify about property change */ g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_APPLICATION]); } /* IMPLEMENTATION: GObject */ /* Finalize this object */ static void nojs_finalize(GObject *inObject) { NoJS *self=NOJS(inObject); NoJSPrivate *priv=self->priv; GList *browsers, *browser; GList *tabs, *tab; WebKitWebView *webkitView; SoupSession *session; /* Dispose allocated resources */ session=webkit_get_default_session(); g_signal_handlers_disconnect_by_data(session, self); if(priv->databaseFilename) { g_free(priv->databaseFilename); priv->databaseFilename=NULL; } if(priv->database) { sqlite3_close(priv->database); priv->database=NULL; } if(priv->application) { g_signal_handlers_disconnect_by_data(priv->application, self); browsers=midori_app_get_browsers(priv->application); for(browser=browsers; browser; browser=g_list_next(browser)) { g_signal_handlers_disconnect_by_data(browser->data, self); g_object_set_data(G_OBJECT(browser->data), "nojs-statusicon", NULL); tabs=midori_browser_get_tabs(MIDORI_BROWSER(browser->data)); for(tab=tabs; tab; tab=g_list_next(tab)) { g_signal_handlers_disconnect_by_data(tab->data, self); webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(MIDORI_VIEW(tab->data))); g_signal_handlers_disconnect_by_data(webkitView, self); } g_list_free(tabs); } g_list_free(browsers); priv->application=NULL; } /* Call parent's class finalize method */ G_OBJECT_CLASS(nojs_parent_class)->finalize(inObject); } /* Set/get properties */ static void nojs_set_property(GObject *inObject, guint inPropID, const GValue *inValue, GParamSpec *inSpec) { NoJS *self=NOJS(inObject); switch(inPropID) { /* Construct-only properties */ case PROP_EXTENSION: self->priv->extension=g_value_get_object(inValue); _nojs_open_database(self); break; case PROP_APPLICATION: self->priv->application=g_value_get_object(inValue); _nojs_on_application_changed(self); break; case PROP_ALLOW_LOCAL_PAGES: self->priv->allowLocalPages=g_value_get_boolean(inValue); g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ALLOW_LOCAL_PAGES]); break; case PROP_ONLY_SECOND_LEVEL: self->priv->checkOnlySecondLevel=g_value_get_boolean(inValue); g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ONLY_SECOND_LEVEL]); break; case PROP_UNKNOWN_DOMAIN_POLICY: self->priv->unknownDomainPolicy=g_value_get_enum(inValue); g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_UNKNOWN_DOMAIN_POLICY]); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec); break; } } static void nojs_get_property(GObject *inObject, guint inPropID, GValue *outValue, GParamSpec *inSpec) { NoJS *self=NOJS(inObject); switch(inPropID) { case PROP_EXTENSION: g_value_set_object(outValue, self->priv->extension); break; case PROP_APPLICATION: g_value_set_object(outValue, self->priv->application); break; case PROP_DATABASE: g_value_set_pointer(outValue, self->priv->database); break; case PROP_DATABASE_FILENAME: g_value_set_string(outValue, self->priv->databaseFilename); break; case PROP_ALLOW_LOCAL_PAGES: g_value_set_boolean(outValue, self->priv->allowLocalPages); break; case PROP_ONLY_SECOND_LEVEL: g_value_set_boolean(outValue, self->priv->checkOnlySecondLevel); break; case PROP_UNKNOWN_DOMAIN_POLICY: g_value_set_enum(outValue, self->priv->unknownDomainPolicy); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec); break; } } /* Class initialization * Override functions in parent classes and define properties and signals */ static void nojs_class_init(NoJSClass *klass) { GObjectClass *gobjectClass=G_OBJECT_CLASS(klass); /* Override functions */ gobjectClass->finalize=nojs_finalize; gobjectClass->set_property=nojs_set_property; gobjectClass->get_property=nojs_get_property; /* Set up private structure */ g_type_class_add_private(klass, sizeof(NoJSPrivate)); /* Define properties */ NoJSProperties[PROP_EXTENSION]= g_param_spec_object("extension", _("Extension instance"), _("The Midori extension instance for this extension"), MIDORI_TYPE_EXTENSION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); NoJSProperties[PROP_APPLICATION]= g_param_spec_object("application", _("Application instance"), _("The Midori application instance this extension belongs to"), MIDORI_TYPE_APP, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); NoJSProperties[PROP_DATABASE]= g_param_spec_pointer("database", _("Database instance"), _("Pointer to sqlite database instance used by this extension"), G_PARAM_READABLE); NoJSProperties[PROP_DATABASE_FILENAME]= g_param_spec_string("database-filename", _("Database path"), _("Path to sqlite database instance used by this extension"), NULL, G_PARAM_READABLE); NoJSProperties[PROP_ALLOW_LOCAL_PAGES]= g_param_spec_boolean("allow-local-pages", _("Allow local pages"), _("Allow scripts to run on local (file://) pages"), TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); NoJSProperties[PROP_ONLY_SECOND_LEVEL]= g_param_spec_boolean("only-second-level", _("Only second level"), _("Reduce each domain to its second-level (e.g. www.example.org to example.org) for comparison"), TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); NoJSProperties[PROP_UNKNOWN_DOMAIN_POLICY]= g_param_spec_enum("unknown-domain-policy", _("Unknown domain policy"), _("Policy to use for unknown domains"), NOJS_TYPE_POLICY, NOJS_POLICY_BLOCK, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_properties(gobjectClass, PROP_LAST, NoJSProperties); /* Define signals */ /* Why does this signal exist? * * The problem I faced when developing this extension was * that I needed to cancel a SoupMessage as soon as possible * (when http headers were received). * I tried to connect to signal "resource-response-received" * of WebKitWebView but the SoupMessage instance was not * exactly the same which were sent or received by SoupSession. * So I could not cancel the SoupMessage or better: I cancelled * a SoupMessage which is not be handled so it had no effect. * The body of SoupMessage was still being loaded and javascript * was executed. I think the problem is that webkit-gtk creates * a copy of the real SoupMessage which is going to be sent and * received. * * So I decided to connect to signal "got-headers" of every * SoupMessage sent by the default SoupSession which I notice * by connecting to signal "request-started" of SoupSession. Each * NoJSView connects to signal "resource-request-starting" of * WebKitWebView to remember each URI going to be loaded. When * a SoupMessage hits "got-headers" and is a javascript resource * I can cancel the message immediately and clear the body which * causes webkit-gtk to copy a empty body if it does at all as the * SoupMessage was cancelled. Then I emit this signal * "uri-load-policy-status" to notify each view but the cancellation. * (It also notifies all views if it is going to load to keep the * menu in right state.) Each view will check if it _could_ be a * resource itself requested and will update its menu accordingly. * It might happen that a request will match two views because only * the URI will be checked by the view because I cannot determine * to which view the SoupMessage belongs to. But it doesn't matter * because if a javascript resource was denied or allowed in one view * it is likely be denied or allowed in other views too ;) */ NoJSSignals[URI_LOAD_POLICY_STATUS]= g_signal_new("uri-load-policy-status", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(NoJSClass, uri_load_policy_status), NULL, NULL, _nojs_closure_VOID__STRING_ENUM, G_TYPE_NONE, 2, G_TYPE_STRING, NOJS_TYPE_POLICY); NoJSSignals[POLICY_CHANGED]= g_signal_new("policy-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(NoJSClass, policy_changed), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); } /* Object initialization * Create private structure and set up default values */ static void nojs_init(NoJS *self) { NoJSPrivate *priv; SoupSession *session; priv=self->priv=NOJS_GET_PRIVATE(self); /* Set up default values */ priv->database=NULL; priv->databaseFilename=NULL; priv->allowLocalPages=TRUE; priv->checkOnlySecondLevel=TRUE; priv->unknownDomainPolicy=NOJS_POLICY_BLOCK; /* Connect to signals on session to be able to cancel messages * loading javascript documents */ session=webkit_get_default_session(); g_signal_connect_swapped(session, "request-started", G_CALLBACK(_nojs_on_request_started), self); } /* Implementation: Public API */ /* Create new object */ NoJS* nojs_new(MidoriExtension *inExtension, MidoriApp *inApp) { return(g_object_new(TYPE_NOJS, "extension", inExtension, "application", inApp, NULL)); } /* Retrieves domain from uri depending on preferences (e.g. only second level domain) */ gchar* nojs_get_domain(NoJS *self, SoupURI *inURI) { g_return_val_if_fail(IS_NOJS(self), NULL); g_return_val_if_fail(inURI, NULL); NoJSPrivate *priv=self->priv; const gchar *realDomain; gchar *finalDomain; /* Get domain of site to lookup */ realDomain=soup_uri_get_host(inURI); if(priv->checkOnlySecondLevel) finalDomain=midori_uri_get_base_domain(realDomain); else finalDomain=midori_uri_to_ascii(realDomain); /* Return domain */ return(finalDomain); } /* Get/set policy for javascript from site */ gint nojs_get_policy(NoJS *self, SoupURI *inURI) { g_return_val_if_fail(IS_NOJS(self), NOJS_POLICY_UNDETERMINED); g_return_val_if_fail(inURI, NOJS_POLICY_UNDETERMINED); NoJSPrivate *priv=self->priv; sqlite3_stmt *statement=NULL; gint error; gint policy=NOJS_POLICY_UNDETERMINED; gchar *inDomain; /* Check to allow local pages */ if(soup_uri_get_scheme(inURI) == SOUP_URI_SCHEME_FILE) { if(priv->allowLocalPages) return(NOJS_POLICY_ACCEPT); else return(priv->unknownDomainPolicy); } /* Check for open database */ g_return_val_if_fail(priv->database, policy); /* Get domain from URI */ inDomain=nojs_get_domain(self, inURI); /* Lookup policy for site in database */ error=sqlite3_prepare_v2(priv->database, "SELECT site, value FROM policies WHERE site LIKE ? LIMIT 1;", -1, &statement, NULL); if(statement && error==SQLITE_OK) error=sqlite3_bind_text(statement, 1, inDomain, -1, NULL); if(statement && error==SQLITE_OK) { if(sqlite3_step(statement)==SQLITE_ROW) policy=sqlite3_column_int(statement, 1); } else g_warning(_("SQL fails: %s"), sqlite3_errmsg(priv->database)); sqlite3_finalize(statement); /* If we have not found a policy for the domain then it is an unknown domain. * Get default policy for unknown domains. */ if(policy==NOJS_POLICY_UNDETERMINED) policy=priv->unknownDomainPolicy; return(policy); } void nojs_set_policy(NoJS *self, const gchar *inDomain, NoJSPolicy inPolicy) { g_return_if_fail(IS_NOJS(self)); g_return_if_fail(inDomain); g_return_if_fail(inPolicy>=NOJS_POLICY_ACCEPT && inPolicy<=NOJS_POLICY_BLOCK); NoJSPrivate *priv=self->priv; gchar *sql; gchar *error=NULL; gint success; /* Check for open database */ g_return_if_fail(priv->database); /* Update policy in database */ sql=sqlite3_mprintf("INSERT OR REPLACE INTO policies (site, value) VALUES ('%q', %d);", inDomain, inPolicy); success=sqlite3_exec(priv->database, sql, NULL, NULL, &error); if(success!=SQLITE_OK) g_warning(_("SQL fails: %s"), error); if(error) sqlite3_free(error); sqlite3_free(sql); /* Emit signal to notify about policy change */ if(success==SQLITE_OK) g_signal_emit(self, NoJSSignals[POLICY_CHANGED], 0, inDomain); } /* Get/set default policy for unknown domains */ NoJSPolicy nojs_get_policy_for_unknown_domain(NoJS *self) { g_return_val_if_fail(IS_NOJS(self), NOJS_POLICY_UNDETERMINED); return(self->priv->unknownDomainPolicy); } void nojs_set_policy_for_unknown_domain(NoJS *self, NoJSPolicy inPolicy) { g_return_if_fail(IS_NOJS(self)); g_return_if_fail(inPolicy>=NOJS_POLICY_ACCEPT && inPolicy<=NOJS_POLICY_BLOCK); if(self->priv->unknownDomainPolicy!=inPolicy) { self->priv->unknownDomainPolicy=inPolicy; midori_extension_set_integer(self->priv->extension, "unknown-domain-policy", inPolicy); g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_UNKNOWN_DOMAIN_POLICY]); } } /* Get/set flag to allow javascript on local pages */ gboolean nojs_get_allow_local_pages(NoJS *self) { g_return_val_if_fail(IS_NOJS(self), TRUE); return(self->priv->allowLocalPages); } void nojs_set_allow_local_pages(NoJS *self, gboolean inAllow) { g_return_if_fail(IS_NOJS(self)); if(self->priv->allowLocalPages!=inAllow) { self->priv->allowLocalPages=inAllow; midori_extension_set_boolean(self->priv->extension, "allow-local-pages", inAllow); g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ALLOW_LOCAL_PAGES]); } } /* Get/set flag to check for second-level domains only */ gboolean nojs_get_only_second_level_domain(NoJS *self) { g_return_val_if_fail(IS_NOJS(self), TRUE); return(self->priv->checkOnlySecondLevel); } void nojs_set_only_second_level_domain(NoJS *self, gboolean inOnlySecondLevel) { g_return_if_fail(IS_NOJS(self)); if(self->priv->checkOnlySecondLevel!=inOnlySecondLevel) { self->priv->checkOnlySecondLevel=inOnlySecondLevel; midori_extension_set_boolean(self->priv->extension, "only-second-level", inOnlySecondLevel); g_object_notify_by_pspec(G_OBJECT(self), NoJSProperties[PROP_ONLY_SECOND_LEVEL]); } } /************************************************************************************/ /* Implementation: Enumeration */ GType nojs_policy_get_type(void) { static volatile gsize g_define_type_id__volatile=0; if(g_once_init_enter(&g_define_type_id__volatile)) { static const GEnumValue values[]= { { NOJS_POLICY_UNDETERMINED, "NOJS_POLICY_UNDETERMINED", N_("Undetermined") }, { NOJS_POLICY_ACCEPT, "NOJS_POLICY_ACCEPT", N_("Accept") }, { NOJS_POLICY_ACCEPT_TEMPORARILY, "NOJS_POLICY_ACCEPT_TEMPORARILY", N_("Accept temporarily") }, { NOJS_POLICY_BLOCK, "NOJS_POLICY_BLOCK", N_("Block") }, { 0, NULL, NULL } }; GType g_define_type_id=g_enum_register_static(g_intern_static_string("NoJSPolicy"), values); g_once_init_leave(&g_define_type_id__volatile, g_define_type_id); } return(g_define_type_id__volatile); }