diff options
author | portix <none@none> | 2012-07-19 00:09:18 +0200 |
---|---|---|
committer | portix <none@none> | 2012-07-19 00:09:18 +0200 |
commit | 2c2c2ac95fa9863919e58a3dff87c08306d3eb7b (patch) | |
tree | 8ea53a2801dcba8a1ed161ca6c70a260ea642d90 /extensions | |
parent | 5ef8d6b90327cea0b5188b1a373497f9a3ce4e3f (diff) | |
download | dwb-2c2c2ac95fa9863919e58a3dff87c08306d3eb7b.zip |
Adding extensions formfiller, perdomainsettings, requestpolicy and userscripts
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/formfiller | 380 | ||||
-rw-r--r-- | extensions/perdomainsettings | 202 | ||||
-rw-r--r-- | extensions/requestpolicy | 314 | ||||
-rw-r--r-- | extensions/userscripts | 521 |
4 files changed, 1417 insertions, 0 deletions
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: |