summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorportix <none@none>2012-07-19 00:09:18 +0200
committerportix <none@none>2012-07-19 00:09:18 +0200
commit2c2c2ac95fa9863919e58a3dff87c08306d3eb7b (patch)
tree8ea53a2801dcba8a1ed161ca6c70a260ea642d90
parent5ef8d6b90327cea0b5188b1a373497f9a3ce4e3f (diff)
downloaddwb-2c2c2ac95fa9863919e58a3dff87c08306d3eb7b.zip
Adding extensions formfiller, perdomainsettings, requestpolicy and userscripts
-rw-r--r--Makefile11
-rw-r--r--config.mk1
-rw-r--r--extensions/formfiller380
-rw-r--r--extensions/perdomainsettings202
-rw-r--r--extensions/requestpolicy314
-rw-r--r--extensions/userscripts521
6 files changed, 1426 insertions, 3 deletions
diff --git a/Makefile b/Makefile
index 9166b279..0367a974 100644
--- a/Makefile
+++ b/Makefile
@@ -41,14 +41,19 @@ install-data: all
install -m 644 $(SHAREDIR)/dwb.png $(DESTDIR)$(DATADIR)/pixmaps/dwb.png
install -d $(DESTDIR)$(DATADIR)/applications
install -m 644 $(SHAREDIR)/dwb.desktop $(DESTDIR)$(DATADIR)/applications/dwb.desktop
+ @# Hints
+ install -d $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(JSDIR)
+ install -m 644 $(JSDIR)/$(HINT_SCRIPT) $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(JSDIR)/$(HINT_SCRIPT)
@# Libjs
install -d $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(LIBJSDIR)
for file in $(LIBJSDIR)/*; do \
install -m 644 $$file $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$$file; \
done
- @# Hints
- install -d $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(JSDIR)
- install -m 644 $(JSDIR)/$(HINT_SCRIPT) $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(JSDIR)/$(HINT_SCRIPT)
+ @#Extensions
+ install -d $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(EXTENSIONDIR)
+ for file in $(EXTENSIONDIR)/*; do \
+ install -m 644 $$file $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$$file; \
+ done
uninstall: uninstall-man uninstall-data
@echo "Removing executable from $(subst //,/,$(DESTDIR)$(BINDIR))"
diff --git a/config.mk b/config.mk
index 5836451f..8c17e642 100644
--- a/config.mk
+++ b/config.mk
@@ -12,6 +12,7 @@ LIBJSFILES=$(LIBJSDIR)/signals.js $(LIBJSDIR)/enums.js $(LIBJSDIR)/data.js
SHAREDIR=share
UTILDIR=util
SUBDIRS=$(SRCDIR) $(UTILDIR)
+EXTENSIONDIR=extensions
# Version info
HG_VERSION=$(shell hg id -n 2>/dev/null)
diff --git a/extensions/formfiller b/extensions/formfiller
new file mode 100644
index 00000000..ad331230
--- /dev/null
+++ b/extensions/formfiller
@@ -0,0 +1,380 @@
+//
+// Copyright (c) 2012 Stefan Bolte <portix@gmx.net>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+//
+
+/*
+ *Fill forms automatically
+ *
+ * Extension that saves form data and fills forms with previously saved data
+ *
+ * To use this extension save this script as $HOME/.local/share/dwb/extensions/formfiller
+ * and load it with a userscript in $HOME/.config/dwb/userscripts/, e.g.
+ *
+ * ------------------------------------------------------------------------------
+ * |#!javascript |
+ * | |
+ * |extensions.load("formfiller"); |
+ * ------------------------------------------------------------------------------
+ *
+ *
+ * Configuration options:
+ *
+ * formData : A path to a file where formdata will be saved, the default
+ * path is $XDG_CONFIG_HOME/dwb/forms
+ *
+ * scGetForm : Shortcut that gets and saves form data, the default value is
+ * 'efg'
+ *
+ * scFillForm : Shortcut that fills a form, the default value is 'eff'
+ *
+ * useGPG : Whether to use gpg2 to encrypt the formData file with a
+ * password.
+ *
+ * GPGOptEncrypt : Additional options that will be passed to gpg2 for
+ * encryption, the default gpg2 options are:
+ * --passphrase <password> --batch --no-tty --yes -c --output <formData>
+ * default value: ""
+ *
+ * GPGOptDecrypt : Additional options that will be passed to gpg2 for
+ * decryption, the default gpg2 options are
+ * --passphrase <password> --batch --no-tty --yes -d <formData>
+ * default value: ""
+ *
+ * keepPassword : Whether to save the gpg password in memory, if set to false the
+ * user will be prompted for the password every time a form
+ * is filled or new data is saved, default value: true
+ *
+ * keepFormdata : If useGPG is enabled and this value is set to true the
+ * complete formdata will be kept in memory, if set to false
+ * gpg2 will be called every time a form is filled, default
+ * value: false.
+ *
+ *
+ * Example (loading config with extensions.load())
+ *
+ * ------------------------------------------------------------------------------
+ * |extensions.load("formfiller", { |
+ * | formData : system.getEnv("HOME") + "/data/forms", |
+ * | scGetForm : "Control f", |
+ * | useGPG : true |
+ * |}); |
+ * ------------------------------------------------------------------------------
+ *
+ * Example extensionrc:
+ *
+ * ------------------------------------------------------------------------------
+ * |return { |
+ * | foo : { ... }, |
+ * | |
+ * | formfiller : { |
+ * | scGetForm : "efg", |
+ * | scFillForm : "eff", |
+ * | formData : "/path/to/data" |
+ * | }, |
+ * | bar : { ... } |
+ * |} |
+ * ------------------------------------------------------------------------------
+ *
+ * */
+
+var me = "formfiller";
+var defaultConfig = {
+ scGetForm : "efg",
+ scFillForm : "eff",
+ formData : data.configDir + "/forms",
+ useGPG : false,
+ GPGOptEncrypt : "",
+ GPGOptDecrypt : "",
+ keepPassword : true,
+ keepFormdata : false
+
+};
+var config = {};
+var passWord = null;
+var formData = null;
+
+var injectGetForm = function () {//{{{
+ var ret = null;
+ var forms = document.forms;
+
+ function objectifyForm(f) {
+ var query = "descendant::input[not(@type='hidden') and (@type='text' or @type='password' or @type='checkbox' or not(@type) or @type='email')]";
+ var input, data;
+ var r = document.evaluate(query, f, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
+ var o = {};
+ o.id = f.id || null;
+ o.form = {};
+ var hasValue = false;
+ var hasIds = true;
+
+ while ((input = r.iterateNext()) !== null) {
+ if (input.value && !(/^\s*$/.test(input.value))) {
+ if (/^\**$/.test(input.value) )
+ return null;
+ if (!input.type || input.type.toLowerCase() === "text" || input.type.toLowerCase() === "password")
+ hasValue = true;
+ data = {};
+ if (input.id)
+ data.id = input.id;
+ else
+ hasIds = false;
+ data.value = input.value;
+ o.form[input.name] = data;
+ }
+ }
+ if (hasValue) {
+ var ret = {};
+ o.hasIds = hasIds;
+ ret[window.location.host] = o;
+ return ret;
+ }
+ return null;
+ }
+
+ for (var i=0; i<forms.length; i++) {
+ if ((ret = objectifyForm(forms[i])) !== null) {
+ return ret;
+ }
+ }
+ return ret;
+};//}}}
+
+var injectFillForm = function (data) {//{{{
+ var key, i, forms, form = null, input;
+
+ function fillInput(input, key) {
+ var value = data.form[key].value;
+ if(input.type=="checkbox" || input.type=="radio")
+ input.checked=(value.toLowerCase() !== "false" && value !== "0");
+ else {
+ input.value = value;
+ }
+ }
+ function setValues(form) {
+ var input, value;
+ for (key in data.form) {
+ if (!form[key])
+ return null;
+ }
+ for (key in data.form) {
+ fillInput(form[key], key);
+ }
+ return form;
+ }
+
+ function fillElementsById() {
+ var input;
+ for (key in data.form) {
+ input = document.getElementById(data.form[key].id);
+ if (input === null || input === undefined) {
+ return null;
+ }
+ fillInput(input, key);
+ }
+ return input.form || null;
+ }
+
+ function fillFormById() {
+ var form = document.getElementById(data.id);
+ if (form === null)
+ return null;
+ return setValues(form);
+ }
+
+ if (data.hasIds) {
+ form = fillElementsById();
+ }
+ if (form === null && data.id) {
+ form = fillFormById();
+ }
+ if (form === null) {
+ forms = document.forms;
+ for (i=0; i<forms.length; i++) {
+ if ((form = setValues(forms[i])) !== null)
+ break;
+ }
+ }
+ if (form !== null && data.autosubmit)
+ form.submit();
+ return form !== null;
+};//}}}
+
+function getFormData(callback) {//{{{
+ var stat, ret;
+ if (config.useGPG) {
+ if (formData !== null) {
+ return formData;
+ }
+ getPassWord();
+ stat = system.spawnSync("gpg2 " + config.GPGOptDecrypt + " --passphrase " + passWord + " --batch --no-tty --yes -d " + config.formData);
+ if (stat.status == 512) {
+ io.error("Wrong password");
+ passWord = null;
+ return null;
+ }
+ try {
+ ret = JSON.parse(stat.stdout.replace(/\\"/g, '"'));
+ if (config.keepFormdata) {
+ formData = ret;
+ }
+ return ret;
+ }
+ catch(e) {
+ io.debug(e);
+ io.error("Getting form data failed : " + e.message);
+ }
+ }
+ else {
+ try {
+ return JSON.parse(io.read(config.formData));
+ }
+ catch(e) {
+ io.debug(e);
+ io.error("Getting form data failed : " + e.message);
+ }
+ }
+ return null;
+}//}}}
+
+function getPassWord() {//{{{
+ if (passWord === null) {
+ passWord = io.prompt("Password :", false);
+ }
+}//}}}
+
+function writeFormData(object) {//{{{
+ var written = true, ret;
+ if (config.useGPG) {
+ getPassWord();
+ if (passWord === null)
+ return false;
+
+ ret = system.spawnSync("sh -c \"echo '" + JSON.stringify(object).replace(/"/g, "\\\"") +
+ "' | gpg2 " + config.GPGOptEncrypt + " --passphrase " + passWord + " --batch --no-tty --yes -c --output " + config.formData + "\"");
+ if (ret.status == 512) {
+ io.error("Wrong password");
+ password = null;
+ return false;
+ }
+ written = ret.status === 0;
+ }
+ else {
+ written = io.write(config.formData, "w", JSON.stringify(object, null, 2));
+ }
+ return written;
+}//}}}
+
+function saveForm(form) {//{{{
+ var key, object, data, written = false;
+ var autosubmit = io.prompt("Autosubmit (y/n)?").toLowerCase() == "y" ? true : false;
+ var saved = false;
+ if (! system.fileTest(config.formData, FileTest.regular | FileTest.symlink)) {
+ object = JSON.parse(form);
+ for (key in object)
+ break;
+ object[key].autosubmit = autosubmit;
+ written = writeFormData(object);
+ }
+ else {
+ object = JSON.parse(form);
+ data = getFormData();
+ if (data) {
+ for (key in object)
+ break;
+ data[key] = object[key];
+ data[key].autosubmit = autosubmit;
+ }
+ else if (data === null) {
+ return false;
+ }
+ else {
+ data = object;
+ }
+ written = writeFormData(data);
+ }
+ return written;
+}//}}}
+
+function getForm() {//{{{
+ var frames = tabs.current.allFrames;
+ var form, i, formFound = false;
+ for (i=0; i<frames.length; i++) {
+ form = frames[i].inject(util.getBody(injectGetForm));
+ if (form != "null") {
+ if (saveForm(form)) {
+ io.notify("Form saved");
+ }
+ else {
+ io.notify("An error occured saving formdata");
+ }
+ formFound = true;
+ break;
+ }
+ }
+ if (!config.keepPassword)
+ passWord = null;
+ if (!formFound)
+ io.error("No storable form found");
+}//}}}
+
+function fillForm() {//{{{
+ var data, frames, host, i, ret = false;
+ if (! system.fileTest(config.formData, FileTest.regular | FileTest.symlink)) {
+ io.error("No formdata found");
+ return;
+ }
+ data = getFormData();
+
+ if (data === null)
+ return;
+ frames = tabs.current.allFrames;
+ for (i=0; i<frames.length; i++) {
+ host = frames[i].host;
+ if (data[host]) {
+ frames[i].inject("(" + String(injectFillForm) + ")(" + JSON.stringify(data[host]) + ")");
+ }
+ }
+ if (!config.keepPassword) {
+ passWord = null;
+ }
+ io.notify("Executed formfiller");
+}//}}}
+
+// init {{{
+return {
+ init : function (c) {
+ var key;
+ if (c === null || c === undefined) {
+ config = defaultConfig;
+ }
+ else {
+ for (key in defaultConfig) {
+ config[key] = typeof c[key] == typeof defaultConfig[key] ? c[key] : defaultConfig[key];
+ }
+ }
+ bind(config.scGetForm, getForm, "formfillerGet");
+ bind(config.scFillForm, fillForm, "formfillerFill");
+ return true;
+ },
+ end : function () {
+ unbind(getForm);
+ unbind(fillForm);
+ return true;
+ }
+}//}}}
+// vim: set ft=javascript:
diff --git a/extensions/perdomainsettings b/extensions/perdomainsettings
new file mode 100644
index 00000000..abca8517
--- /dev/null
+++ b/extensions/perdomainsettings
@@ -0,0 +1,202 @@
+//
+// Copyright (c) 2012 Stefan Bolte <portix@gmx.net>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+
+/*
+ * Per domain settings extension
+ *
+ * This extensions can be used for per-domain-settings. Valid settings are
+ * the properties of WebKitWebSettings but in camelcase, see
+ * http://webkitgtk.org/reference/webkitgtk/unstable/WebKitWebSettings.html
+ * for details.
+ *
+ * To use this extension save this script as * $HOME/.local/share/dwb/extensions/perdomainsettings
+ * and load it with a userscript in $HOME/.config/dwb/userscripts/, e.g.
+ *
+ * ------------------------------------------------------------------------------
+ * |#!javascript |
+ * | |
+ * |extensions.load("perdomainsettings"); |
+ * ------------------------------------------------------------------------------
+ *
+ * The config can consist of four objects,
+ *
+ * domains: Settings applied based on the second level domain
+ *
+ * hosts: Settings applied based on the hostname
+ *
+ * uris: Settings applied based on the uri
+ *
+ * defaults: Default settings, for each setting in domains, hosts and uris a
+ * default-value should be specified
+ *
+ * Example extensionrc:
+ *
+ * ------------------------------------------------------------------------------
+ * |return { |
+ * | foo : { ... }, |
+ * | |
+ * | perdomainsettings : { |
+ * | domains : { |
+ * | "example.com" : { |
+ * | "enablePlugins" : true |
+ * | }, |
+ * | "example.uk.com" : { |
+ * | "enablePlugins" : true, |
+ * | "enableScripts" : false |
+ * | } |
+ * | }, |
+ * | hosts : { |
+ * | "www.example1.com" : { |
+ * | "autoLoadImages" : true |
+ * | } |
+ * | }, |
+ * | uris : { |
+ * | "http://www.example2.com/login.php" : { |
+ * | "autoLoadImages" : false |
+ * | } |
+ * | }, |
+ * | defaults : { |
+ * | "enablePlugins" : false, |
+ * | "autoLoadImages" : false, |
+ * | "enableScripts" : true |
+ * | } |
+ * | }, |
+ * | |
+ * | bar : { ... } |
+ * |} |
+ * ------------------------------------------------------------------------------
+ *
+ * Example using extensions.load:
+ *
+ * ------------------------------------------------------------------------------
+ * |extensions.load("perdomainsettings", { |
+ * | domains : { "example.com" : { "enablePlugins" : true } }, |
+ * | defaults : { "enablePlugins" : false } |
+ * |}); |
+ * ------------------------------------------------------------------------------
+ *
+ * */
+
+var me = "perdomainsettings";
+var domains = null;
+var hosts = null;
+var uris = null;
+var defaults = null;
+var webviews = [];
+var sigNavigation = -1;
+var sigCloseTab = -1;
+
+function apply(o, settings) {
+ var key;
+ var defaults = true;
+ var websettings = o.settings;
+ for (key in settings) {
+ if (!o.set[key]) {
+ websettings[key] = settings[key];
+ o.set[key] = true;
+ }
+ else {
+ defaults = false;
+ }
+ }
+ return defaults;
+}
+
+function onNavigation(wv, frame, request, action) {
+ var length = webviews.length;
+ var i;
+ var found = false;
+ var o = null;
+ for (i=0; i<length; i++) {
+ if (webviews[i].webview === wv) {
+ o = webviews[i];
+ break;
+ }
+ }
+ if (o === null) {
+ o = { webview : wv, defaults : false, settings : wv.settings };
+ webviews.push(o);
+ }
+ o.set = {};
+ if (frame === wv.mainFrame) {
+ try {
+ var uri = request.uri;
+ var host = uri.split(/:\/\/|:|\//, 2)[1];
+ var domain = util.domainFromHost(host);
+ if (domains[domain]) {
+ apply(o, domains[domain]);
+ o.defaults = false;
+ }
+ if (hosts[host]) {
+ apply(o, hosts[host]);
+ o.defaults = false;
+ }
+ if (uris[uri]) {
+ apply(o, uris[uri]);
+ o.defaults = false;
+ }
+ if (o.defaults === false && apply(o, defaults)) {
+ o.defaults = true;
+ }
+ }
+ catch (e) {
+ extensions.error(me, e);
+ }
+ }
+}
+function onCloseTab(wv) {
+ var i;
+ for (i=0; i<webviews.length; i++) {
+ if (webviews[i].webview === wv) {
+ webviews.splice(i, 1);
+ return;
+ }
+ }
+}
+function initImpl(config) {
+ domains = config.domains || {};
+ hosts = config.hosts || {};
+ uris = config.uris || {};
+ defaults = config.defaults || {};
+ sigNavigation = signals.connect("navigation", onNavigation);
+ sigCloseTab = signals.connect("closeTab", onCloseTab);
+}
+
+return {
+ init : function (config) {
+ if (!config) {
+ extensions.error(me, "Missing config");
+ return false;
+ }
+ initImpl(config);
+ return true;
+ },
+ end : function () {
+ if (sigNavigation >= 0) {
+ signals.disconnect(sigNavigation);
+ sigNavigation = -1;
+ }
+ if (sigCloseTab >= 0) {
+ signals.disconnect(sigCloseTab);
+ sigCloseTab = -1;
+ }
+ }
+};
+
+// vim: set ft=javascript:
diff --git a/extensions/requestpolicy b/extensions/requestpolicy
new file mode 100644
index 00000000..1ed66ca2
--- /dev/null
+++ b/extensions/requestpolicy
@@ -0,0 +1,314 @@
+//
+// Copyright (c) 2012 Stefan Bolte <portix@gmx.net>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+//
+/*
+ * Block requests from thirdparty domains
+ *
+ * Extension that blocks requests from thirdparty domains with whitelisting
+ * support, either permanently or just for the session.
+ *
+ * To use this extension save this script as $HOME/.local/share/dwb/extensions/requestpolicy
+ * and load it with a userscript in $HOME/.config/dwb/userscripts/, e.g.
+ *
+ * ------------------------------------------------------------------------------
+ * |#!javascript |
+ * | |
+ * |extensions.load("requestpolicy"); |
+ * ------------------------------------------------------------------------------
+ *
+ *
+ * Configuration options:
+ *
+ * shortcut : Shortcut to block / allow requests, default "erp"
+ *
+ * whitelist : A path to the whitelisting file, default is
+ * $XDG_CONFIG_HOME/dwb/<profile>/requestpolicy.json
+ *
+ * autoreload : Whether to automatically reload the website after the
+ * whitelist has changed, default false
+ *
+ * notify : Whether to notify about blocked request, default true
+ *
+ *
+ * Example (loading config with extensions.load())
+ *
+ * ------------------------------------------------------------------------------
+ * |extensions.load("requestpolicy", { |
+ * | whitelist : system.getEnv("HOME") + "/.dwb_request_policy", |
+ * | autoreload : true |
+ * |}); |
+ * ------------------------------------------------------------------------------
+ *
+ * Example extensionrc:
+ *
+ * ------------------------------------------------------------------------------
+ * |return { |
+ * | foo : { ... }, |
+ * | |
+ * | requestpolicy : { |
+ * | autoreload : true |
+ * | shortcut : "rp", |
+ * | notify : false |
+ * | }, |
+ * | bar : { ... } |
+ * |} |
+ * ------------------------------------------------------------------------------
+ *
+ * */
+
+var me = "requestpolicy";
+var priv = "_requestPolicy" + parseInt(Math.random() * 9999999999, 10);
+
+var defaultConfig = {
+ whitelist : data.configDir + "/" + data.profile + "/requestpolicy.json",
+ shortcut : "erp",
+ autoreload : false,
+ notify : true
+};
+var config = {};
+
+var sigs = {
+ resource : -1,
+ navigation : -1,
+ loadFinished : -1
+};
+
+var whiteList = null;
+var tmpWhiteList = {};
+
+var regex = {
+ empty : /^\s*$/
+};
+
+function getPrivate(wv) {
+ var o = wv[priv];
+ if (o === undefined) {
+ o = { domains : [], blocked : 0 };
+ Object.defineProperty(wv, priv, {
+ value : o,
+ writable : true
+ });
+ }
+ return o;
+}
+
+// WHITELIST {{{
+function doWhiteList(o, key, value) {
+ if (!o[key])
+ o[key] = [];
+ if (o[key].indexOf(value) == -1)
+ o[key].push(value);
+}
+function tmpWhiteListAction() {
+ doWhiteList(tmpWhiteList, tabs.current.mainFrame.domain, this.domain);
+}
+function whiteListAction() {
+ doWhiteList(whiteList, tabs.current.mainFrame.domain, this.domain);
+ io.write(config.whitelist, "w", JSON.stringify(whiteList));
+}
+
+function whiteListAllAction() {
+ doWhiteList(whiteList, "_all", tabs.current.mainFrame.domain);
+ io.write(config.whitelist, "w", JSON.stringify(whiteList));
+}
+function tmpWhiteListAllAction() {
+ doWhiteList(tmpWhiteList, "_all", tabs.current.mainFrame.domain);
+}//}}}
+
+// BLACKLIST {{{
+function doBlackList(o, firstParty, domain) {
+ var idx;
+ if (o[firstParty] && (idx = o[firstParty].indexOf(domain)) != -1) {
+ o[firstParty].splice(idx, 1);
+ if (o[firstParty].length === 0)
+ delete o[firstParty];
+ return true;
+ }
+ return false;
+}
+function blackListAction() {
+ var firstParty = tabs.current.mainFrame.domain;
+ if (doBlackList(whiteList, firstParty, this.domain))
+ io.write(config.whitelist, "w", JSON.stringify(whiteList));
+ doBlackList(tmpWhiteList, firstParty, this.domain);
+
+}
+function blackListAll() {
+ var i;
+ var blackListed = false;
+ var firstParty = tabs.current.mainFrame.domain;
+ var domains = getPrivate(tabs.current).domains;
+ for (i=0; i<domains.length; i++)
+ blackListed = doBlackList(whiteList, firstParty, domains[i]) || blackListed;
+ blackListed = doBlackList(whiteList, "_all", firstParty) || blackListed;
+ if (blackListed)
+ io.write(config.whitelist, "w", JSON.stringify(whiteList));
+ for (i=0; i<domains.length; i++)
+ doBlackList(tmpWhiteList, firstParty, priv[i]);
+ doBlackList(tmpWhiteList, "_all", firstParty);
+}//}}}
+
+// MENU {{{
+function showMenu() {
+ var tmpWhiteListed;
+ var whiteListed;
+ var isWhiteListed = false;
+ var domain = tabs.current.mainFrame.domain;
+ var domains = getPrivate(tabs.current).domains;
+ var labels = [];
+ for (var i=0; i<domains.length; i++) {
+ whiteListed = whiteList[domain] && whiteList[domain].indexOf(domains[i]) != -1;
+ tmpWhiteListed = tmpWhiteList[domain] && tmpWhiteList[domain].indexOf(domains[i]) != -1;
+ if (!whiteListed && !tmpWhiteListed) {
+ labels.push({
+ left : "Allow " + domains[i],
+ action : whiteListAction,
+ domain : domains[i]
+ });
+ labels.push({
+ left : "Temporarily allow " + domains[i],
+ action : tmpWhiteListAction,
+ domain : domains[i]
+ });
+ }
+ else {
+ labels.push({
+ left : "Block " + domains[i],
+ action : blackListAction,
+ domain : domains[i]
+ });
+ }
+ isWhiteListed = isWhiteListed || whiteListed || tmpWhiteListed;
+ }
+ if (isWhiteListed ||
+ (whiteList._all && whiteList._all.indexOf(domain) != -1 ) ||
+ (tmpWhiteList._all && tmpWhiteList._all.indexOf(domain) != -1 )) {
+ labels.unshift({
+ left : "Block all requests on " + domain,
+ action : blackListAll
+ });
+ }
+ labels.unshift({
+ left : "Temporarily allow all requests on " + domain,
+ action : tmpWhiteListAllAction
+ });
+ labels.unshift({
+ left : "Allow all requests on " + domain,
+ action : whiteListAllAction
+ });
+
+ tabComplete("Requestpolicy: ", labels, function (response) {
+ var l;
+ for (var i=0; i<labels.length; i++) {
+ l = labels[i];
+ if (l.left == response) {
+ l.action();
+ if (config.autoreload) {
+ tabs.current.reload();
+ }
+ }
+ }
+ }, true);
+}//}}}
+// SIGNALS {{{
+function resourceCB(wv, frame, request, response) {
+ var o, message, domain, firstParty;
+ if (regex.empty.test(request.uri))
+ return false;
+ message = request.message;
+ if (!message)
+ return false;
+ firstParty = util.domainFromHost(message.firstParty.host);
+ domain = util.domainFromHost(message.uri.host);
+ if (firstParty == domain)
+ return false;
+ o = getPrivate(wv);
+ if (o.domains.indexOf(domain) == -1) {
+ o.domains.push(domain);
+ }
+ if ((whiteList._all && whiteList._all.indexOf(firstParty) != -1) ||
+ (tmpWhiteList._all && tmpWhiteList._all.indexOf(firstParty) != -1))
+ return false;
+ if ( (!whiteList[firstParty] || whiteList[firstParty].indexOf(domain) == -1) &&
+ (!tmpWhiteList[firstParty] || tmpWhiteList[firstParty].indexOf(domain) == -1)) {
+ request.uri = 'about:blank';
+ o.blocked++;
+ if (config.notify && wv == tabs.current)
+ io.notify("RP: blocked " + domain);
+ return true;
+ }
+}
+function navigationCB(wv, frame) {
+ if (frame == wv.mainFrame) {
+ var o = getPrivate(wv);
+ o.domains = [];
+ o.blocked = 0;
+ }
+}
+function loadFinishedCB(wv) {
+ if (wv != tabs.current)
+ return;
+
+ var blocked = getPrivate(wv).blocked;
+ if (blocked > 0)
+ io.notify("RP: blocked " + blocked + " requests");
+}
+
+function connect() {
+ sigs.resource = signals.connect("resource", resourceCB);
+ sigs.navigation = signals.connect("navigation", navigationCB);
+ if (config.notify)
+ sigs.loadFinished = signals.connect("loadFinished", loadFinishedCB);
+}
+function disconnect() {
+ sigs.forEach(function (key, value) {
+ if (value != -1) {
+ signals.disconnect(value);
+ sigs[key] = -1;
+ }
+ });
+}//}}}
+return {
+ init : function(c) {
+ if (c === null || c === undefined)
+ config = defaultConfig;
+ else {
+ defaultConfig.forEach(function (key, value, obj) {
+ config[key] = typeof c[key] == typeof defaultConfig[key] ? c[key] : defaultConfig[key];
+ });
+ }
+ if (system.fileTest(config.whitelist, FileTest.regular | FileTest.symlink)) {
+ var rawWhiteList = io.read(config.whitelist);
+ try {
+ whiteList = JSON.parse(rawWhiteList);
+ }
+ catch (e) {
+ extensions.debug(me, e, "Error parsing whitelist");
+ }
+ }
+ whiteList = whiteList || {};
+ connect();
+ bind(config.shortcut, showMenu, "requestpolicy");
+ return true;
+ },
+ end : function () {
+ disconnect();
+ unbind("requestpolicy");
+ }
+}
+// vim: set ft=javascript:
diff --git a/extensions/userscripts b/extensions/userscripts
new file mode 100644
index 00000000..8038d353
--- /dev/null
+++ b/extensions/userscripts
@@ -0,0 +1,521 @@
+//
+// Copyright (c) 2012 Stefan Bolte <portix@gmx.net>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+
+/*
+ * TODO not finished yet
+ *
+ * userscripts extension, load userscripts and injects them into websites, this
+ * extension is mostly greasemonkey compatible. Scripts can be loaded by either
+ * specifying a path with using the configuration or putting them into
+ * $XDG_CONFIG_HOME/dwb/scripts/.
+ *
+ *
+ * To use this extension save this script as $HOME/.local/share/dwb/extensions/userscripts
+ * and load it with a userscript in $HOME/.config/dwb/userscripts/, e.g.
+ *
+ * ------------------------------------------------------------------------------
+ * |#!javascript |
+ * | |
+ * |extensions.load("userscripts"); |
+ * ------------------------------------------------------------------------------
+ *
+ *
+ * Config: An array of paths to userscripts
+ *
+ * Example (loading with extensions load):
+ *
+ * ------------------------------------------------------------------------------
+ * |extensions.load("userscripts", [ "/path/to/script1", "/path/to/script2" ]); | |
+ * ------------------------------------------------------------------------------
+ *
+ * Example (extensionsrc):
+ *
+ * ------------------------------------------------------------------------------
+ * |return { |
+ * | ... : { ... }, // other config objects |
+ * | |
+ * | userscripts : [ "/path/to/script1", "/path/to/script2" ], |
+ * | |
+ * | ... : { ... } // other config objects |
+ * |} |
+ * ------------------------------------------------------------------------------
+ *
+ * */
+
+var me = "userscripts";
+var onStart = [];
+var onEnd = [];
+var sigDocument = -1;
+var sigCommitted = -1;
+
+var metaData = {};
+
+const DATA_DIR = data.userDataDir + "/extension_data/userscripts/resources";
+const META_DATA = DATA_DIR + "/.metadata";
+
+UserScript.prototype = new Function();
+UserScript.prototype.constructor = UserScript;
+function UserScript()
+{
+ this.description = null;
+ this.downloadURL = null;
+ this.exclude = [];
+ this.icon = null;
+ this.include = [];
+ this.match = [];
+ this.name = null;
+ this.namespace = null;
+ this.require = [];
+ this.resource = [];
+ this.runAt = "document-end";
+ this.unwrap = false;
+ this.updateURL = null;
+ this.version = null;
+ this.script = null;
+ this.scriptId = null;
+ // scriptish
+ this.delay = 0;
+ this.noframes = false;
+ this.priority = 0;
+}
+
+// Reused regular expressions
+var regexes = {
+ isTld : /\.tld(?:\/|:|$)/,
+ isRegExp : /^\/.*\/$/
+};
+
+var GM_compatability = function () {
+ var DWB_scriptPrefix = "dwb_userscript_";
+ if (DWB_scriptId !== undefined) {
+ DWB_scriptPrefix = DWB_scriptPrefix + DWB_scriptId + "_";
+ }
+ var GM_addStyle = function (styles)
+ {
+ var style = document.createElement("style");
+ style.setAttribute("type", "text/css");
+ style.appendChild(document.createTextNode(styles));
+ document.getElementsByTagName("head")[0].appendChild(style);
+ };
+ var GM_log = function (text) {
+ console.log(text);
+ };
+ var GM_setValue = function (key, value) {
+ if (localStorage !== null && (typeof value === "string" || typeof value === "number" || typeof value == "boolean") )
+ localStorage.setItem(DWB_scriptPrefix + key, value);
+ else
+ GM_log("GM_setValue only works with enabled localStorage and only for strings, numbers and booleans");
+ };
+ var GM_getValue = function (key, def) {
+ if (localStorage !== null)
+ return localStorage.getItem(DWB_scriptPrefix + key) || def;
+ else
+ GM_log("GM_getValue only works with enabled localStorage");
+ return undefined;
+ };
+ var GM_deleteValue = function (key) {
+ if (localStorage !== null)
+ localStorage.removeItem(DWB_scriptPrefix + key);
+ else
+ GM_log("GM_deleteValue only works with enabled localStorage");
+ };
+ var GM_listValues = function () {
+ var i;
+ var a = [];
+ for (i=0; i<localStorage.length; i++) {
+ a.push(localStorage.key(i).replace(DWB_scriptPrefix, ""));
+ }
+ return a;
+ };
+ var GM_info = function () { return undefined; };
+ var GM_registerMenuCommand = function () { return undefined; };
+ var GM_openInTab = function (url) { return null; };
+ var GM_getResourceText = function (name) {
+ // TODO implemnt
+ return "";
+ };
+ var GM_getResourceURL = function (name) {
+ // TODO implemnt
+ return "";
+ };
+ var GM_xmlHttpRequest = function (details) {
+ // TODO implemnt
+ return null;
+ };
+};
+
+function matchIncludeExclude(frame, items) //{{{
+{
+ var uri = frame.uri;
+ var domain = frame.domain;
+ var i;
+ for (i=0; i<items.length; i++) {
+ try {
+ if (items[i].isTld && domain !== null) {
+ var reg = new RegExp("(?=.)" + domain + "($|/|:)");
+ var newDomain = domain.substring(0, domain.indexOf(".")) + ".tld$1";
+ uri = uri.replace(reg, newDomain);
+ }
+ if (items[i].regExp.test(uri)) {
+ return true;
+ }
+ }
+ catch(e) {
+ extensions.error(me, e);
+ }
+ }
+ return false;
+}//}}}
+
+function matchMatches(frame, items) //{{{
+{
+ var i, item;
+ var o = uriSplit(frame.uri);
+ if (o === null)
+ return false;
+ for (i=0; i<items.length; i++) {
+ item = items[i];
+ if (item.allUrls ||
+ (item.scheme.test(o.scheme) &&
+ item.host.test(o.host) &&
+ (o.path === null || item.path.test(o.path)))) {
+ return true;
+ }
+
+ }
+ return false;
+}//}}}
+
+function doInject(frame, item) {
+ if (item.delay > 0) {
+ timerStart(1000, function() {
+ frame.inject(item.script, item.unwrap);
+ return false;
+ });
+ }
+ else {
+ frame.inject(item.script, item.unwrap);
+ }
+}
+
+function handle(frame, array, isMainFrame) //{{{
+{
+ var i, item;
+ for (i=0; i<array.length; i++) {
+ item = array[i];
+ if (item.noframes && !isMainFrame)
+ continue;
+ try {
+ if (matchIncludeExclude(frame, item.exclude))
+ continue;
+
+ if (matchIncludeExclude(frame, item.include)) {
+ doInject(frame, item, isMainFrame);
+ }
+ else if (matchMatches(frame, item.match)) {
+ doInject(frame, item, isMainFrame);
+ }
+ }
+ catch (e) {
+ extensions.debug(me);
+ }
+ }
+}//}}}
+
+function loadFinishedCallback(wv, frame) //{{{
+{
+ handle(frame, onEnd, wv.mainFrame === frame);
+}//}}}
+
+function loadCommittedCallback(wv) //{{{
+{
+ handle(wv.mainFrame, onStart, true);
+}//}}}
+
+function parseIncludeExclude(array) //{{{
+{
+ var i, rule;
+ for (i=0; i<array.length; i++) {
+ rule = array[i];
+ if (regexes.isRegExp.test(rule)) {
+ array[i] = {
+ regExp : new RegExp(rule.substring(1, rule.length-1)),
+ isTld : regexes.isTld.test(rule)
+ };
+ }
+ else {
+ array[i] = {
+ regExp : new RegExp(rule.replace(/\*/g, ".*")),
+ isTld : regexes.isTld.test(rule)
+ };
+ }
+ }
+}//}}}
+
+function uriSplit(uri)
+{
+ var parts, scheme, host, path, idx;
+ parts = uri.split("://");
+ if (parts[0] === uri)
+ return null;
+ scheme = parts[0];
+ idx = parts[1].indexOf("/");
+ if (idx == -1) {
+ host = parts[1];
+ path = null;
+ }
+ else {
+ host = parts[1].substring(0, idx);
+ path = parts[1].substring(idx);
+ }
+ return { path : path, host: host, scheme : scheme };
+}
+
+function parseMatch(m)
+{
+ var i, scheme, host, path, parts, j;
+ if (m === "<all_urls>") {
+ return { allUrls : true };
+ }
+
+ var o = uriSplit(m);
+ if (o === null) {
+ extensions.warning(me, "Invalid or unsupported match rule: " + m);
+ return null;
+ }
+ if (!(/\*|http|https|file/.test(o.scheme))) {
+ extensions.warning(me, "Invalid scheme pattern: " + m);
+ return null;
+ }
+ else {
+ o.scheme = new RegExp(o.scheme.replace("*", ".*"));
+ }
+ if (! (/^(?:\*\.[^*\/]*|[^*]*|\*)$/.test(o.host))) {
+ extensions.warning(me, "Invalid host pattern: " + m);
+ return null;
+ }
+ else {
+ o.host = new RegExp(o.host.replace(/([.?+^$[\]\\(){}|-])/g, "\\$1").replace("*", ".*"));
+ }
+ if (! (/^\/.*/.test(o.path))) {
+ extensions.warning(me, "Invalid path pattern: " + m);
+ return null;
+ }
+ else if (o.path !== null) {
+ o.path = new RegExp(o.path.replace(/([.?+^$[\]\\(){}|-])/g, "\\$1").replace("*", ".*"));
+ }
+ return { host : o.host, scheme : o.scheme, path : o.path, allUrls : false };
+}
+
+function getRequirements(userscript) {//{{{
+ var i;
+ for (i=0; i<userscript.require.length; i++) {
+ //io.print(userscript.require[i]);
+ //sendRequest(userscript.require[0], function (response, message) {
+ // ;
+ // //io.print(response);
+ //});
+ //sendRequest(userscripts.require[i], function (response) {
+ // io.print(response);
+ //});
+ }
+}//}}}
+
+function parseScript(path) //{{{
+{
+ var userscript;
+ var i, items, key, value, idx, m, matches = [], numVal;
+
+ var script = io.read(path);
+ if (!script || (/^\s*$/).test(script))
+ return;
+
+ var metaStart = script.search(/(^|\n)\/\/\s*==UserScript==/);
+ var metaEnd = script.search(/\n\/\/\s*==\/UserScript==/);
+
+ userscript = new UserScript();
+
+ if (metaStart == -1 || metaEnd == -1) {
+ userscript.script = script;
+ userscript.include = [ { regExp : /.*/, isTld : false} ];
+ onEnd.push(userscript);
+ return;
+ }
+
+ var meta = script.substring(metaStart, metaEnd).split("\n");
+ var scriptStart = script.substring(metaEnd+1).indexOf("\n") + metaEnd + 1;
+
+ var regValue = /\s[^\/@]\S?/;
+ var regIsRule = /^\s*\/\/\s*@/;
+ for (i=1; i<meta.length; i++) {
+ if (! (regIsRule.test(meta[i])) )
+ continue;
+ try {
+ items = meta[i].split(/\s+/, 2);
+ key = items[1].substring(1).trim();
+ idx = meta[i].search(regValue);
+ value = idx >= 0 ? meta[i].substring(idx+1).trim() : null;
+ if (key == "description" ||
+ key == "downloadURL" ||
+ key == "icon" ||
+ key == "name" ||
+ key == "namespace" ||
+ key == "updateURL" ||
+ key == "version")
+ {
+ userscript[key] = value;
+ }
+ else if (typeof userscript[key] == "number") {
+ try {
+ numVal = parseInt(value, 10);
+ if (!isNaN(numVal))
+ userscript[key] = numVal;
+ }
+ catch (e) {
+ extensions.debug(me, e);
+ }
+ }
+ else if (key == "unwrap")
+ userscript.unwrap = true;
+ else if (key == "noframes")
+ userscript.noframes = true;
+ else if (key == "run-at")
+ userscript.runAt = value;
+ else if (userscript[key] instanceof Array) {
+ userscript[key] = userscript[key].concat(value.match(/\S+/g));
+ }
+ }
+ catch(e) {
+ extensions.debug(me, e);
+ }
+
+ }
+ if (userscript.include.length === 0)
+ userscript.include.push({regExp : /.*/, isTld : false});
+ else
+ parseIncludeExclude(userscript.include);
+ parseIncludeExclude(userscript.exclude);
+ // TODO resources
+
+ var scriptId = new String();
+ if (userscript.namespace === null || userscript.name === null)
+ userscript.scriptId = path;
+ else
+ userscript.scriptId = userscript.namespace + "::" + userscript.name;
+ userscript.scriptId = userscript.scriptId.replace(/\s+/g, "_");
+ userscript.script = "var DWB_scriptId = '" + userscript.scriptId + "';" +
+ util.getBody(GM_compatability) + script.substring(0, metaStart) +
+ script.substring(scriptStart);
+
+ getRequirements(userscript);
+
+ for (i=0; i<userscript.match.length; i++) {
+ m = parseMatch(userscript.match[i]);
+ if (m !== null) {
+ matches.push(m);
+ }
+ }
+ userscript.match = matches;
+ if (userscript.runAt == "document-start")
+ onStart.push(userscript);
+ else
+ onEnd.push(userscript);
+}//}}}
+
+function userscriptsStart() {
+ var ret = false;
+ if (onStart.length > 0) {
+ onStart.sort(function(a, b) { return b.priority - a.priority; });
+ sigCommitted = signals.connect("loadCommitted", loadCommittedCallback);
+ ret = true;
+ }
+ if (onEnd.length > 0) {
+ onEnd.sort(function(a, b) { return b.priority - a.priority; });
+ sigDocument = signals.connect("documentLoaded", loadFinishedCallback);
+ ret = true;
+ }
+ //metaData = {};
+ //onStart.concat(onEnd).forEach(function (v, k, obj) {
+ // var o = {
+ // require : obj[k].require,
+ // downloadURL : obj[k].downloadURL,
+ // updateURL : obj[k].updateURL,
+ // version : obj[k].version,
+ // resource : obj[k].resource
+ // };
+ // metaData[obj[k].scriptId] = o;
+ //});
+ //io.write(META_DATA, "w", JSON.stringify(metaData));
+ return ret;
+}
+function parseScripts(scripts) //{{{
+{
+ var i, path;
+ var scriptDir = data.configDir + "/scripts";
+ for (i=0; i<scripts.length; i++) {
+ if (system.fileTest(scripts[i], FileTest.regular | FileTest.symlink)) {
+ parseScript(scripts[i]);
+ }
+ }
+ if (system.fileTest(scriptDir, FileTest.dir)) {
+ var lines = io.dirNames(scriptDir);
+ for (i=0; i<lines.length; i++) {
+ if (lines[i].charAt(0) == ".")
+ continue;
+ path = scriptDir + "/" + lines[i];
+ if (!(/^\s*$/.test(lines[i])) && system.fileTest(path, FileTest.regular | FileTest.symlink)) {
+ parseScript(path);
+ }
+ }
+ }
+ return userscriptsStart();
+}//}}}
+
+return {
+ init : function (c) {
+ //var meta;
+ //if (! system.fileTest(DATA_DIR, FileTest.dir)) {
+ // system.mkdir(DATA_DIR, 0700);
+ //}
+ //if (system.fileTest(META_DATA, FileTest.exists)) {
+ // meta = io.read(META_DATA);
+ // try {
+ // metaData = JSON.parse(meta);
+ // }
+ // catch (e) {
+ // io.debug(e);
+ // metaData = {};
+ // }
+ //}
+ return parseScripts(c ? c.scripts || [] : []);
+ },
+ end : function () {
+ if (sigDocument >= 0) {
+ signals.disconnect(sigDocument);
+ sigDocument = -1;
+ }
+ if (sigCommitted >= 0) {
+ signals.disconnect(sigCommitted);
+ sigCommitted = -1;
+ }
+
+ }
+};
+
+// vim: set ft=javascript: