From e43c8e0f36a8795cb5c70c483013a79548eeda97 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Sun, 28 Oct 2012 10:56:41 -0500 Subject: [PATCH] Adding luakit scripts, creating a mutt folder instead of just .muttrc, and adding urlview config --- luakit/multisession.lua | 216 ++++++++ luakit/nscript_widget.lua | 66 +++ luakit/policy.lua | 969 ++++++++++++++++++++++++++++++++++++ luakit/private_browsing.lua | 89 ++++ luakit/rc.lua | 5 + luakit/useragent.lua | 343 +++++++++++++ luakit/useragentmenu | 10 + mutt/hcache | Bin 0 -> 114688 bytes mutt/macros | 1 + mutt/mailcap | 1 + mutt/muttrc | 39 ++ urlview | 2 + 12 files changed, 1741 insertions(+) create mode 100644 luakit/multisession.lua create mode 100644 luakit/nscript_widget.lua create mode 100644 luakit/policy.lua create mode 100644 luakit/private_browsing.lua create mode 100644 luakit/useragent.lua create mode 100644 luakit/useragentmenu create mode 100644 mutt/hcache create mode 100644 mutt/macros create mode 100644 mutt/mailcap create mode 100644 mutt/muttrc create mode 100644 urlview diff --git a/luakit/multisession.lua b/luakit/multisession.lua new file mode 100644 index 0000000..9ac989f --- /dev/null +++ b/luakit/multisession.lua @@ -0,0 +1,216 @@ +---------------------------------------------------------------- +-- Multi-Session save/restore Plugin -- +---------------------------------------------------------------- + +local ipairs = ipairs +local tostring = tostring +local lfs = lfs +local os = os +local io = io +local table = table +local string = string +local unpack = unpack +local lousy = require "lousy" +local util = lousy.util +local luakit = luakit +local window = window +local add_binds, add_cmds = add_binds, add_cmds +local new_mode, menu_binds = new_mode, menu_binds +local cmd = lousy.bind.cmd +local buf = lousy.bind.buf + +module("plugins.multisession") + +local sessions_dir = luakit.data_dir .. "/sessions" + +local rm = function (file) + luakit.spawn(string.format("rm %q", file)) +end + +local session_save = function (w, name) + if not name then return end + local fpath = sessions_dir .. "/" .. (name or "") + -- Save all given windows uris to file. + local lines = {} + -- Save tabs from all the given window, keep the window-index to keep + -- comptability with default sessions format + local current = w.tabs:current() + for ti, tab in ipairs(w.tabs.children) do + table.insert(lines, string.format("%d\t%d\t%s\t%s", 1, ti, + tostring(current == ti), tab.uri)) + end + -- Only save a non-empty session + if #lines > 0 then + local fh = io.open(fpath, "w") + fh:write(table.concat(lines, "\n")) + io.close(fh) + else + rm(session.file) + end +end + +local session_load = function (name) + local fpath = sessions_dir .. "/" .. (name or "") + if not name or not os.exists(fpath) then return end + local ret = {} + + -- Read file + local lines = {} + local fh = io.open(fpath, "r") + for line in fh:lines() do table.insert(lines, line) end + io.close(fh) + + -- Parse session file, again, ignore the window-index, keeping for + -- compatibility + for _, line in ipairs(lines) do + local wi, ti, current, uri = unpack(util.string.split(line, "\t")) + current = (current == "true") + table.insert(ret, {uri = uri, current = current}) + end + + return (#ret > 0 and ret) or nil +end + +-- Create a new window and open all tabs in the session in it +local session_restore = function (name) + win = session_load(name) + if not win or #win == 0 then return end + + -- Spawn windows + local w + for _, item in ipairs(win) do + if not w then + w = window.new({item.uri}) + else + w:new_tab(item.uri, item.current) + end + end + return w +end + +-- Opens all tabs in named session in current window +local session_append = function (w, name) + ses = session_load(name) + if not ses or #ses == 0 then return false end + for _, item in ipairs(ses) do + w:new_tab(item.uri, item.current) + end + return true +end + +local session_del = function (name) + if not name then return end + local fpath = sessions_dir .. "/" .. (name or "") + if not os.exists(fpath) then return false end + rm(fpath) + return true +end + +local load = function () + local curdir = lfs.currentdir() + if not lfs.chdir(sessions_dir) then + lfs.mkdir(sessions_dir) + else + lfs.chdir(curdir) + end +end + +add_cmds({ + cmd("session", "view list of sessions", function (w) w:set_mode("sessionmenu") end), + cmd({"session-write", "sw"}, "save a session to file", + function (w, a) + local name = util.string.strip(a) or "default" + session_save(w, name) + w:notify("Saved " .. tostring(w.tabs:count()) .. " tabs to session " .. name .. ".") + end), + cmd({"session-delete", "sd"}, "delete a saved session", + function(w, a) + local name = util.string.strip(a) + if session_del(name) then + w:notify("Deleted session " .. name .. ".") + else + w:error("No saved session named " .. name .."!") + end + end), + cmd({"session-restore", "sr"}, "load a saved session in a new window", + function(w, a) + local name = util.string.strip(a) or "default" + if not session_restore(name) then + w:error("Unable to restore session " .. name .. "!") + end + end), + cmd({"session-open", "so"}, "open a saved session in new tabs in current window", + function(w, a) + local name = util.string.strip(a) or "default" + if not session_append(w, name) then + w:error("Unable to open session " .. name .. "!") + else + w:notify("Appended session " .. name .. " to tabs.") + end + end), +}) + +add_binds("normal", { + buf("^sm$", "open list of saved sessions", function(w) w:set_mode("sessionmenu") end), +}) + + + +new_mode("sessionmenu", { + enter = function (w) + local rows = {{"Saved Sessions (# of tabs)", title = true}} + + for filename in lfs.dir(sessions_dir) do + if not string.match(filename, "^%.-$") then + local fh = io.open(sessions_dir .. "/" .. filename) + local tabcnt = 0 + for _ in fh:lines() do tabcnt = tabcnt + 1 end + io.close(fh) + table.insert(rows, {string.format(" %s (%d)", filename, tabcnt), name = filename}) + end + end + if #rows == 1 then + table.insert(rows, {"No saved sessions!"}) + end + w.menu:build(rows) + w:notify("Use j/k to move, d to delete a session, t to append session to current window, Enter to open session (in new window)", false) + end, + + leave = function (w) + w.menu:hide() + end, +}) + +local key = lousy.bind.key +add_binds("sessionmenu", lousy.util.table.join({ + key({}, "Return", function (w) + local row = w.menu:get() + if row and row.name then + w:set_mode() + session_restore(row.name) + end + end), + + key({}, "t", function (w) + local row = w.menu:get() + if row and row.name then + w:set_mode() + session_append(w, row.name) + end + end), + + key({}, "d", function (w) + local row = w.menu:get() + if row and row.name then + w.menu:del() + session_del(row.name) + end + end), + + -- Exit menu + key({}, "q", function (w) w:set_mode() end), + +}, menu_binds)) + +load() +-- vim: et:sw=4:ts=8:sts=4:tw=80 diff --git a/luakit/nscript_widget.lua b/luakit/nscript_widget.lua new file mode 100644 index 0000000..218eb61 --- /dev/null +++ b/luakit/nscript_widget.lua @@ -0,0 +1,66 @@ +------------------------------------------------------------------------------- +-- -- +-- No-Scipt Status Widget -- +-- Adds a simple, skinnable, widget to the status bar to indicate the -- +-- current status of scripts and plugins -- +-- -- +------------------------------------------------------------------------------- + +local string = string +local window = window +local webview = webview +local widget = widget +local theme = theme +local luakit = luakit +local domain_props = domain_props +local lousy = require("lousy") +local history = require("history") + +module("plugins.nscript_widget") + +-- Strings widget uses +local script_str = { enabled = "s", disabled = "!s" } +local plugin_str = { enabled = "p", disabled = "!p" } + + +-- Load themes, if undefined fallback +theme.nsw_enabled = theme.nsw_enabled or "#0f0" +theme.nsw_disabled = theme.nsw_disabled or "#f00" + +-- Create the indictor widget on window creation +window.init_funcs.build_ns_indicator = function(w) + local i = w.sbar.r + i.noscr = widget{type="label"} + i.layout:pack(w.sbar.r.noscr) + i.layout:reorder(w.sbar.r.noscr, 2) + i.noscr.font = theme.buf_sbar_font + w:update_noscr() + -- Update indicator on tab change + w.tabs:add_signal("switch-page", function (_,view) + luakit.idle_add(function() w:update_noscr(w) return false end) + end) +end + +-- Update indicator on page navigation +webview.init_funcs.noscr_update = function(view, w) + view:add_signal("load-status", function (v, status) + if status == "committed" or status == "failed" or status == "finished" then + w:update_noscr() + end + end) +end + +-- Method wrapper +window.methods.update_noscr = function(w) + if not w.view then return end + local scripts = w.view.enable_scripts + local plugins = w.view.enable_plugins + local noscr = w.sbar.r.noscr + local tmpl = '%s %s' + noscr.text = string.format(tmpl, + ( scripts and theme.nsw_enabled ) or theme.nsw_disabled, + ( scripts and script_str.enabled ) or script_str.disabled, + ( plugins and theme.nsw_enabled ) or theme.nsw_disabled, + ( plugins and plugin_str.enabled ) or plugin_str.disabled) + noscr:show() +end diff --git a/luakit/policy.lua b/luakit/policy.lua new file mode 100644 index 0000000..1bf57b2 --- /dev/null +++ b/luakit/policy.lua @@ -0,0 +1,969 @@ +------------------------------------------------------------------------------- +-- Advanced URI-based content filter v0.1.1a -- +-- Combines functionality of AdBlock via pattern-match based blocking, -- +-- domain/sub-domain matching white and black listing, -- +-- a RequestPolicy-like rule system to default-deny 3rd-party requests -- +-- and rough per-domain file-type filtering based on URI patterns -- +-- -- +-- Disclaimer: while this is intended to increase browser security and -- +-- help protect privacy, there is no guarantee. Rely on it at your own risk! -- +------------------------------------------------------------------------------- + +local info = info +local pairs = pairs +local ipairs = ipairs +local assert = assert +local unpack = unpack +local type = type +local io = io +local os = os +local string = string +local table = table +local tostring = tostring +local tonumber = tonumber +local webview = webview +local window = window +local lousy = require("lousy") +local theme = theme +local widget = widget +local util = lousy.util +local chrome = require("chrome") +local capi = { luakit = luakit , sqlite3 = sqlite3 } +local sql_escape = lousy.util.sql_escape +local add_binds, add_cmds = add_binds, add_cmds +local new_mode, menu_binds = new_mode, menu_binds +local lfs = require("lfs") +local setmetatable = setmetatable + +-- Public Suffix Lib +local tld = require("tld") +local getdomain = tld.getdomain + +-- Calls modifed adblock +--local adblock = require("plugins.adblock") + +module("plugins.policy") + +pdebug = function (...) io.stdout:write(string.format(...) .. "\n") end + +-- Settings Flags ============================================================= +filtering = { + -- Flag to accept all requests (disabled all blocking) + acceptall = false, + -- Flag to enable/disable AdBlock / Pattern-Based Blocking + adblock = true, + -- Flag to enable/disable additional scrutiny of 3rd-party requests + requestpolicy = true, + -- Flag to enable/disable file-type blocking policy + typepolicy = true, + -- Flag for whether a subdomain is treated as a 3rd party relative to other subdomains or master domain + strictsubdomain = false, + -- Flas for whether to show status bar widget + widget = true +} + +local policy_dir = capi.luakit.data_dir + +-- Below is the actual internals of the plugin, here be dragons --------------- +------------------------------------------------------------------------------- + +-- Cache to reduce sql calls +local cache = {} +setmetatable(cache, { __mode = "k" }) + +-- A table to store data for navigation and resource requests +-- it has per-view instances, indexed via navto[v].fields +local navto = {} +setmetatable(navto, { __mode = "k" }) + +-- Exception listing +local exlist = { white = {}, third = {white = {} } } + +-- Makes the following more clear... eg: return codes and internal constants +-- allowing is zero so that reasons for denial can be communicated +local ALLOW = 0 +local DENIED = { BLACKLIST = 1, ADBLOCK = 2, NO_WHITELIST = 3, BLACKLISTED_TP = 4 , BLOCKED_TYPE = 5 } +local ANY_PARTY, THIRD_PARTY = 1, 2 +local reasonstring = {"Blacklisted", "ABP", "CDR", "Blacklisted-CDR", "F-TYPE", "Other"} + +-- sqlite command stings +local create_tables = [[ +PRAGMA synchronous = OFF; +PRAGMA secure_delete = 1; +CREATE TABLE IF NOT EXISTS whitelist ( + id INTEGER PRIMARY KEY, + domain TEXT UNIQUE); +CREATE TABLE IF NOT EXISTS blacklist ( + id INTEGER PRIMARY KEY, + domain TEXT UNIQUE); +CREATE TABLE IF NOT EXISTS tp_whitelist ( + id INTEGER PRIMARY KEY, + domain TEXT, + rdomain TEXT); +CREATE TABLE IF NOT EXISTS tp_blacklist ( + id INTEGER PRIMARY KEY, + domain TEXT, + rdomain TEXT); +]] + +local sql_format = { + match_list = "SELECT * FROM %slist WHERE domain == %s;", + add_list = "INSERT INTO %slist VALUES (NULL, %s);", + remove_list = "DELETE FROM %slist WHERE domain == %s;", + match_tp_list = "SELECT * FROM tp_%slist WHERE domain == %s and rdomain == %s;", + add_tp_list = "INSERT INTO tp_%slist VALUES (NULL, %s, %s);", + remove_tp_list_exact = "DELETE FROM tp_%slist WHERE domain == %s and rdomain == %s;", + + remove_tp_list_domain = "DELETE FROM tp_%slist WHERE domain == %s;", + remove_tp_list_rdomain = "DELETE FROM tp_%slist WHERE rdomain == %s;", +} + +-- Open or create & initiaze dbi +initalize_rpdb = function() + if rpdb then return end + rpdb = capi.sqlite3{ filename = policy_dir .. "/policy.db" } + rpdb:exec(create_tables) +end + +-- Helper functions that perform various utility functions ======================== + +-- Attempt to parse a uri for the extension of the requested file +-- return file extension in lower case or "NONE" if no extension +local getextension = function (uri) + if uri then + local puri = lousy.uri.parse(uri) + local ext = string.match(puri and puri.path or "", "%.([a-zA-Z0-9]-)$") + return ext and string.lower(ext) or "NONE" + end + return "NONE" +end + +-- Check if query is domain or a subdomain of host +local subdomainmatch = function(host, query) + if host == query then + return true + else + local abits = util.string.split(string.reverse(host or ""), "%.") + local bbits = util.string.split(string.reverse(query or ""), "%.") + -- If host is an IP abort, eg: 10.8.4.1 is not a subdomain of 8.4.1 + if host and string.match(host, "^%d%.%d%.%d%.%d$") then return false end + -- TODO ipv6 match + for i,s in ipairs(abits) do + if s ~= bbits[i] then + return false + end + end + return true + end +end + +-- Checks domains for a match +local domainmatch = function (a, b) + -- If main uri (a) is nil, then it is always "first party" + -- If a == b then they are the same domain + -- Otherwise do a match score and make a fuzzy match + if not a or a == b then + return true + elseif not filtering.strictsubdomain then + local abits = util.string.split(string.reverse(a) or "", "%.") + local bbits = util.string.split(string.reverse(b) or "", "%.") + local matching = 0 + -- If an IP, don't do partial matches, TODO ipv6 match + if string.match(a, "^%d%.%d%.%d%.%d$") then return false end + -- Count matching bits + for i,s in ipairs(abits) do + if s == bbits[i] then + matching = matching + 1 + else + break + end + end + -- Check the effective tlds of a and b and use that + 1 as the minimum matching requirement + local ab = util.string.split(getdomain(a), "%.") + local bb = util.string.split(getdomain(b), "%.") + local needed_match = ( (#ab > #bb) and #ab) or #bb + if matching >= needed_match then + return true + end + end + return false +end + +-- Returns whether or not rhost is whitelisted +local islisted = function (host, rhost, typ, party) + if party == THIRD_PARTY then + local host_bits = util.table.reverse(util.string.split(host or "", "%.") or {}) + -- Get base domain, we do not want to match vs. just public suffixes + local n = 1 + local phost = getdomain(host) + if not string.match(phost, "^[0-9%.]$") then + local pbits = util.string.split(phost, "%.") + n = #pbits + 1 + else + n = #host_bits + end + local list, tlist + -- Make list to match rhost against, use cache if valid + if cache[host] and cache[host][typ] then + list = cache[host][typ] + if typ == "white" then + tlist = exlist.third.white["all"] + repeat + tlist = util.table.join(tlist, exlist.third.white[phost] or {}) + phost = (host_bits[n] or "").. "." .. phost + n = n + 1 + until n > #host_bits + 1 + end + else + list = rpdb:exec(string.format("SELECT * FROM tp_%slist WHERE domain = %s;", typ, sql_escape("all"))) + tlist = exlist.third.white["all"] + repeat + local rows = rpdb:exec(string.format("SELECT * FROM tp_%slist WHERE domain = %s;", typ, sql_escape(phost))) + list = util.table.join(list, rows or {}) + if typ == "white" then + tlist = util.table.join(tlist, exlist.third.white[phost] or {}) + end + phost = (host_bits[n] or "").. "." .. phost + n = n + 1 + until n > #host_bits + 1 + -- Save list in cache + if not cache[host] then cache[host] = {} end + cache[host][typ] = list + end + -- Match vs lists + for _,v in pairs(list) do + if subdomainmatch(v.rdomain, rhost) or v.rdomain == "all" then + return true + end + end + -- Only check exceptions if checking a whitelist + if typ == "white" then + for k,v in pairs(tlist) do + if subdomainmatch(v, rhost) or v == "all" then + return true + end + end + end + return false + else + local list + if cache[typ] then + list = cache[typ] + else + list = rpdb:exec(string.format("SELECT * FROM %slist;", typ)) + cache[typ] = list + end + + for _,v in pairs(list) do + if subdomainmatch(v.domain, rhost) then + return true; + end + end + -- Only check exceptions if checking a whitelist + if typ == "white" then + for k,v in pairs(exlist.white) do + if subdomainmatch(k, rhost) then + return true; + end + end + end + end + -- No match was found + return false +end + +-- Check Pattern list for matches to rules (aka AdBlocking) +local patternMatch = function (req) + -- This checks that the AdBlock Module is loaded, and if so it calls the matching function + -- I originally intended to re-implement this as part of this plugin, but I decided that + -- it was better to just use a minimally modified version of the upstream AdBlock plugin + if filtering.adblock and adblock and adblock.match then + return not adblock.match(req, "none") + end + return false +end + +-- Main logic checking if a request should be allowed or denied +local checkpolicy = function (host, requested, nav, firstnav) + -- A request for a nil uri is denied (should never happen) + if not requested then + return DENIED.BLACKLIST + end + -- Should always accept these + if string.match(requested, "about:blank") or filtering.acceptall then + return ACCEPT + end + -- Get host from requested uri and file-type extension + local rpuri = lousy.uri.parse(requested) + local req = { host = string.lower(rpuri and rpuri.host or ""), ftype = getextension(requested) } + -- webview.uri is nil for the first requests when loading a page (they are always first party) + local puri = lousy.uri.parse(host or "") + host = puri and puri.host or req.host + + local wlisted = false + -- Skip checks for data: uris - they would be covered by the policies of whatever they were embedded in + if not string.match(requested, "^data:") then + -- Blacklisting overrides whitelist + if islisted(nil, req.host, "black", ANY_PARTY) then + return DENIED.BLACKLIST + end + + -- Check if requested domain is whitelisted + wlisted = islisted(nil, req.host, "white", ANY_PARTY) + + -- Whitelisting overrides AdBlocking/Pattern Block + if not wlisted and filtering.adblock then + -- If AdBlock / Pattern matching is enabled, check if requested uri matches and should be blocked + if patternMatch(requested) then + return DENIED.ADBLOCK + end + end + end + + -- If RequestPolicy is disabled or this is a first party request only type-blocking is checked + -- If this is a a navigation request or the first request post-navigation, relax CDR, we likely clicked on a link + -- data: uri's are not cross-domain, however we might want to check those with file filtering + if nav or firstnav or not filtering.requestpolicy or domainmatch(host, req.host) or string.match(requested, "^data:")then + -- TODO: file-type blocking + return ACCEPT + else + -- Check if this domain is blacklised for ALL 3rd-party requests -OR- + -- blacklisted for 3rd party requests for the main domain + if islisted(host, req.host, "black", THIRD_PARTY) then + return DENIED.BLACKLISTED_TP + end + + -- If requested host is whitelisted universally or for this host for this third party, set to accept + local wlistedtp = islisted(host, req.host, "white", THIRD_PARTY) + if not wlistedtp then + return DENIED.NO_WHITELIST + end + -- TODO: file-type blocking that overrides whitelisted 3rd party requrests + return ACCEPT + end +end + +-- Make a table of request's hosts and keep a count of accepted and denied requests +local concatRes = function (l, uri, r) + if uri then + local puri = lousy.uri.parse(uri) + if puri and puri.host then + -- If the host doesn't have a table yet, add it + if not l[puri.host] then + l[puri.host] = {accept = 0, deny = 0, reasons = {}} + end + -- Increment counter (s) + if not r then + l[puri.host].accept = 1 + l[puri.host].accept + else + l[puri.host].deny = 1 + l[puri.host].deny + -- TODO get make this not so hacky ?? + l[puri.host].reasons[reasonstring[r]] = (l[puri.host].reasons[reasonstring[r]] or 0) + 1 + end + end + end +end + +-- Connect signals to all webview widgets on creation +-- TODO check robustness of the firstnav system +webview.init_funcs.policu_signals = function (view, w) + view:add_signal("navigation-request", + function (v, uri) + -- Check if request should be accepted or denied + local r = checkpolicy(v.uri, uri, true , false) + + -- if navto[v] does not exist set to empty table + navto[v] = navto[v] or {} + + -- Only do the following if request was accepted (r = 0) + if not r then + -- Set temp navigation uri to the requested uri + navto[v].uri = uri + -- Make an empty request table if one does not exist + navto[v].res = navto[v].res or {} + -- Add request to the resource request table + --concatRes(navto[v].res, uri, r) -- TODO I don't think this is needed? + -- XXX "bug-fix" ensures uri bar gets updated on clicking intra-page links + w:update_uri() + elseif r == DENIED.BLACKLIST then + w:error("Policy: Blacklisted domain '" .. uri .. "'") + end + -- Hack to get luakit:// pages to load + local puri = lousy.uri.parse(uri or "") + if puri and puri.scheme == "luakit" then + else + return not r + end + end) + + view:add_signal("resource-request-starting", + function (v, uri) + -- Check if request should be accepted or denied + local r = checkpolicy(navto[v].uri or v.uri, uri, false, (navto[v] or {}).first) + + -- if w.navto[v] does not exist set to empty table + navto[v] = navto[v] or {} + -- Clear temp navigation uri + navto[v].uri = nil + -- Clear first flag + navto[v].first = false + -- Add the request to the request table + concatRes(navto[v].res, uri, r) + + -- Hack to get luakit;// pages to load + local puri = lousy.uri.parse(uri or "") + if puri and puri.scheme == "luakit" then + else + return not r + end + end) + + view:add_signal("load-status", + function (v, status) + --pdebug(("policy: load-status signal uri = " .. (v.uri or "") .. "status = " .. status) or "policy: load-status ???") + navto[v] = navto[v] or {} + if status == "provisional" then + -- Resets Resource requests + navto[v].res = {} + navto[v].first = true + elseif status == "committed" then + navto[v].first = false + end + end) +end + +-- TODO - All the chrome stuff... +-- luakit://policy/help help page, lists commands and explains how to use plugin +-- luakit://policy displys settings status and links to other pages +-- luakit://policy/whitelist list of whitelisted domains (button to remove, search, and add new entries) +-- luakit://policy/blacklist list of blacklisted domains (button to remove, search, and add new entries) +-- luakit://policy/cdr/whitelist list of CDR whitelist entries (") +-- luakit://policy/cdr/blacklist list of CDR blacklist entries (") +-- luakit://policy/adblock redirects to luakit://adblock + +load = function () + -- Load the db with the white/black lists + initalize_rpdb() +end + +-- Functions called by user commands ========================================== +local togglestrings = { + acceptall = "unconditional accepting of all requests.", + adblock = "pattern-based blocking (aka AdBlock).", + requestpolicy = "cross-domain request policy blocking.", + typepolicy = "cross-domain file-type blocking", + strictsubdomain = "strict matching for subdomains.", + widget = "visibility of status widget.", +} + +local rp_setting = function (field, value, w) + -- Check that field is a valid settings field + if togglestrings[field] then + -- Check is toggling or setting value + if value == "toggle" then + filtering[field] = not filtering[field] + else + filtering[field] = value + end + -- User feedback on what setting was changed and what it was changed to + w:notify("Policy: " .. (value and "en" or "dis") .. "abled " .. togglestrings[field]) + end + -- return validity of the setting + return togglestrings[field] and true +end + +-- Clears Excption lists and returns the number of items they contained +local clear_exlist = function () + local listlen = #exlist.white + for _,v in pairs(exlist.third.white) do + for _,_ in pairs(v) do + listlen = listlen + 1 + end + end + exlist.white = {} + exlist.third.white = {} + return listlen or 0 +end + +-- Returns true if entery exists +local checkList = function (typ, hosts) + local host = hosts[1] + local rhost = hosts[2] + if rhost then + if typ == "ex" then + return exlist.third.white[host] and util.table.hasitem(exlist.third.white[host], rhost) and true + elseif typ == "white" or typ == "black" then + local row = rpdb:exec(string.format(sql_format.match_tp_list, typ, sql_escape(host), sql_escape(rhost))) + return row and row[1] and true + end + else + if typ == "ex" then + return util.table.hasitem(exlist.white, host) and true + elseif typ == "white" or typ == "black" then + local row = rpdb:exec(string.format(sql_format.match_list, typ, sql_escape(host))) + return row and row[1] and true + end + end +end + + +-- Return Strings, makes localization easier +local retStr = { + sucess = { + add = { + one = { + ex = "Exception added for %s", + white = "Added %s to whitelist", + black = "Added %s to blacklist"}, + mul = { + ex = "Exception added for requests from %s to %s", + white = "Whitelisted requests from %s made to %s", + black = "Blacklisted requests from %s made to %s"}, + }, + del = { + one = { + ex = "Removed exception for %s", + white = "Removed %s from whitelist", + black = "Removed %s from blacklist"}, + mul = { + ex = "Exception removed for requests from %s to %s", + white = "Removed whitelisting of requests from %s to %s", + black = "Removed blacklisting of requests from %s to %s"}, + }, + }, + failure = { + add = { + one = { + ex = "Exception had already been granted to %s!", + white = "%s was already whitelisted!", + black = "%s was already blacklisted!"}, + mul = { + ex = "Exception had already been granted to requests from %s to %s!", + white = "Requests from %s to %s were already whitelisted!", + black = "Requests from %s to %s were already blacklisted!"}, + }, + del = { + one = { + ex = "Exception had not been granted to %s!", + white = "%s was not whitelisted!", + black = "%s was not blacklsited!"}, + mul = { + ex = "Requests from %s to %s had not been granted an exception!", + white = "Requests from %s to %s were not whitelisted!", + black = "Requests from %s to %s were not blacklisted!"}, + }, +}} -- end retStr + +local modList = function (cmd, typ, hosts, w) + local host = hosts[1] + local rhost = hosts[2] + local listed = checkList(typ, hosts) + local num = (rhost and "mul") or "one" + local suc = "failure" + if cmd == "add" then + if not listed then + if rhost then + if typ == "white" or typ == "black" then + rpdb:exec(string.format(sql_format.add_tp_list, + typ, sql_escape(host), sql_escape(rhost))) + else + if not exlist.third.white[host] then + exlist.third.white[host] = {} + end + table.insert(exlist.third.white[host], rhost) + end + else + if typ == "white" or typ == "black" then + rpdb:exec(string.format(sql_format.add_list, typ, sql_escape(host))) + else + table.insert(exlist.white, host) + end + end + suc = "sucess" + end + elseif cmd == "del" then + if listed then + if rhost then + if typ == "white" or typ == "black" then + rpdb:exec(string.format(sql_format.remove_tp_list_exact, + typ, sql_escape(host), sql_escape(rhost))) + else + local i = util.table.hasitem(exlist.third.white[host] or {}, rhost) + if i then table.remove(exlist.third.white[host], i) end + end + else + if typ == "white" or typ == "black" then + rpdb:exec(string.format(sql_format.remove_list, typ, sql_escape(host))) + else + local i = util.table.hasitem(exlist.white, host) + if i then table.remove(exlist.white, i) end + end + end + suc = "sucess" + end + end + -- Feedback on sucess/failure + if suc == "sucess" then + -- Changed [typ]-list, clear cache + cache = {} + w:notify(string.format(retStr[suc][cmd][num][typ], host or "", rhost or "")) + else + w:error(string.format(retStr[suc][cmd][num][typ], host or "", rhost or "")) + end +end + +-- Master user commands parser +local rp_command = function(command, w, a) + a = string.lower(a or "") + local args = util.string.split(util.string.strip(a or ""), " ") + + if command == "set" then + -- Set request policy behaviors + local val = not string.match(args[1], "!$") + local set = string.match(args[1], "(.*)!$") or args[1] + if not rp_setting(set, val, w) then + w:error("Policy: set '" .. args[1] .. "' is not a valid setting. (adblock[!], requestpolicy[!], typepolicy[!], acceptall[!], strictsubdomain[!])") + end + elseif command == "clear" then + w:notify(string.format("Removed %d policy exceptions.", clear_exlist())) + else + -- else it's wl/bl/ex and args will be hosts and require special parsing + if #args == 0 or #args > 2 then + w:error("Policy: Wrong number of arguments!") + return + end + -- Attempt to get a host/rhost out of args + local host = args[1] and ((args[1] == "all" and "all") or (lousy.uri.parse(args[1]) or {}).host) + local rhost = args[2] and ((args[2] == "all" and "all") or (lousy.uri.parse(args[2]) or {}).host) + + -- Convert empty rhost to nil + if rhost == "" then rhost = nil end + --TODO add host/rhost cheking vs public suffixes with getdomain() + if #args == 1 then + if not host then + w:error("Bad argument error. (" .. host .. ")") + return + end + else + if not host or not rhost then + w:error("Bad argument error. (" .. host .. ", " .. rhost ..")") + return + end + end + + if command == "wl" or command == "whitelist" then + modList("add", "white", {host, rhost}, w) + elseif command == "bl" or comamnd == "blacklist" then + modList("add", "black", {host, rhost}, w) + elseif command == "ex" or command == "exception" then + modList("add", "ex", {host, rhost}, w) + elseif command == "wl!" or command == "whitelist!" then + modList("del", "white", {host, rhost}, w) + elseif command == "bl!" or command == "blacklist!" then + modList("del", "black", {host, rhost}, w) + elseif command == "ex!" or command == "exception!" then + modList("del", "ex", {host, rhost}, w) + else + w:error("Policy: '" .. command .. "' is not a valid request policy command!") + end + end +end + +-- Add commands =============================================================== +new_mode("policymenu", { + enter = function (w) + local afg = theme.rpolicy_active_menu_fg or theme.proxy_active_menu_fg + local ifg = theme.rpolicy_inactive_menu_fg or theme.proxy_inactive_menu_fg + local abg = theme.rpolicy_active_menu_bg or theme.proxy_active_menu_bg + local ibg = theme.rpolicy_inactive_menu_bg or theme.proxy_inactive_menu_bg + + local template = ' Allowed: %2d Blocked: %2d %s' + local reason = { start = "[Reason(s):", ends = "]"} + + local main = (lousy.uri.parse(w.view.uri) or {}).host or "about:config" + local rows = {} + local main_row + for host,pol in pairs(navto[w.view].res) do + -- Generate a list of reason why requests were blocked + local reasons = "" + for k,_ in pairs(pol.reasons) do + reasons = reasons .. (k and " ") .. k + end + local notcdr = domainmatch(main, host) + -- Underline the main domain of the host + local domain = string.gsub(getdomain(host), "[%.%-]", "%%%1") + local formhost = string.gsub(host, domain, "" .. domain .. "") + if host == main then + main_row = { + " " .. formhost .. "", string.format(template, pol.accept ,pol.deny, ((reasons ~= "") and reason.start .. reasons .. reason.ends) or ""), + host = host, pol = pol, + fg = (notcdr and afg) or ifg, + bg = ibg} + else + table.insert(rows, (notcdr and 1) or #rows +1, + { " " .. formhost .. "", string.format(template, pol.accept ,pol.deny, ((reasons ~= "") and reason.start .. reasons .. reason.ends) or ""), + host = host, pol = pol, + fg = (notcdr and afg) or ifg, + bg = ibg, + }) + end + end + -- Add main host to top of the list + if main_row then + table.insert(rows, 1, main_row) + end + -- Add title row entry + table.insert(rows, 1, { "Domains requested by " .. main, "Actions Taken", title = true }) + w.menu:build(rows) + w:notify("Use j/k to move, Enter to select a host and open actions submenu, or h to open policy help (and full list of actions).", false) + end, + + leave = function (w) + w.menu:hide() + end, +}) + +local genLine = function ( fmt, cFmt, cm, h, rh, ifg, ibg) + return {string.format(fmt, h, rh), + string.format(cFmt, cm, h, rh), + host = h, rhost = rh, cmd = cm, + fg = ifg, bg = ibg} +end + +new_mode("policysubmenu", { + enter = function (w) + local ifg = theme.rpolicy_inactive_menu_fg or theme.inactive_menu_fg + local ibg = theme.rpolicy_inactive_menu_bg or theme.inactive_menu_bg + local host = navto[w.view].selectedhost or "REQ_HOST" + local main = (lousy.uri.parse(w.view.uri or "") or {}).host or "HOST" + local rows = {{ "Actions for " .. host , "Command", title = true }} + -- Any host + if checkList("white", {host, nil}) then + table.insert(rows, genLine("Remove %s from whitelist", " :rp %s %s", "wl!", host, nil, ifg, ibg)) + else + table.insert(rows, genLine("Add %s to whitelist", " :rp %s %s", "wl", host, nil, ifg, ibg)) + end + + if checkList("black", {host, nil}) then + table.insert(rows, genLine("Remove %s from blacklist", " :rp %s %s", "bl!", host, nil, ifg, ibg)) + else + table.insert(rows, genLine("Add %s to blacklist", " :rp %s %s", "bl", host, nil, ifg, ibg)) + end + + if checkList("ex", {host, nil}) then + table.insert(rows, genLine("Revoke exception for %s", " :rp %s %s", "ex!", host, nil, ifg, ibg)) + else + table.insert(rows, genLine("Grant an exception for %s", " :rp %s %s", "ex", host, nil, ifg, ibg)) + end + + -- For main host + if host == main then + if checkList("white", {host, "all"}) then + table.insert(rows, genLine("Revoke complete CDR whitelisting from %s", " :rp %s %s %s", "wl!", host, "ALL", ifg, ibg)) + else + table.insert(rows, genLine("Allow all CDRs from %s", " :rp %s %s %s", "wl", host, "ALL", ifg, ibg)) + end + + if checkList("black", {host, "all"}) then + table.insert(rows, genLine("Revoke complete CDR blacklisting from %s", " :rp %s %s %s", "bl!", host, "ALL", ifg, ibg)) + else + table.insert(rows, genLine("Deny all CDRs from %s", " :rp %s %s %s", "bl", host, "ALL", ifg, ibg)) + end + + if checkList("ex", {host, "all"}) then + table.insert(rows, genLine("Revoke complete CDR exception from %s", " :rp %s %s %s", "ex!", host, "ALL", ifg, ibg)) + else + table.insert(rows, genLine("Grant an excpetion for all CDRs from %s", " :rp %s %s %s", "ex", host, "ALL", ifg, ibg)) + end + + -- For other hosts + else + if checkList("white", {main, host}) then + table.insert(rows, genLine("Revoke whitelisting of CDRs from %s to %s", " :rp %s %s %s", "wl!", main, host, ifg, ibg)) + else + table.insert(rows, genLine("Allow CDRs from %s to %s", " :rp %s %s %s", "wl", main, host, ifg, ibg)) + end + + if checkList("black", {main, host}) then + table.insert(rows, genLine("Revoke whitelisting of CDRs from %s to %s", " :rp %s %s %s", "bl!", main, host, ifg, ibg)) + else + table.insert(rows, genLine("Deny CDRs from %s to %s", " :rp %s %s %s", "bl", main, host, ifg, ibg)) + end + + if checkList("white", {"all", host}) then + table.insert(rows, genLine("Revoke whitelisting of CDRs from %s to %s", " :rp %s %s %s", "wl!", "ALL", host, ifg, ibg)) + else + table.insert(rows, genLine("Allow CDRs from %s to %s", " :rp %s %s %s", "wl", "ALL", host, ifg, ibg)) + end + + if checkList("black", {"all", host}) then + table.insert(rows, genLine("Revoke whitelisting of CDRs from %s to %s", " :rp %s %s %s", "bl!", "ALL", host, ifg, ibg)) + else + table.insert(rows, genLine("Deny CDRs from %s to %s", " :rp %s %s %s", "bl", "ALL", host, ifg, ibg)) + end + + if checkList("ex", {main, host}) then + table.insert(rows, genLine("Revoke exception for CDRs from %s to %s", " :rp %s %s %s", "ex!", main, host, ifg, ibg)) + else + table.insert(rows, genLine("Grant an excpetion for CDRs from %s to %s", " :rp %s %s %s", "ex", main, host, ifg, ibg)) + end + + if checkList("ex", {"all", host}) then + table.insert(rows, genLine("Revoke exception for CDRs from %s to %s", " :rp %s %s %s", "ex!", "ALL", host, ifg, ibg)) + else + table.insert(rows, genLine("Grant an excpetion for CDRs from %s to %s", " :rp %s %s %s", "ex", "ALL", host, ifg, ibg)) + end + end + + w.menu:build(rows) + w:notify("Use j/k to move, Enter to execute action, S-Enter to edit command, or q to exit.", false) + end, + + leave = function (w) + w.menu:hide() + end, +}) + +-- Adds keybindings for the policy menus +local key = lousy.bind.key +add_binds("policymenu", lousy.util.table.join({ + -- Select user agent + key({}, "Return", + function (w) + local row = w.menu:get() + if row then + navto[w.view].selectedhost = row.host + w:set_mode("policysubmenu") + end + end), + -- Exit menu + key({}, "q", function (w) w:set_mode() end), + +}, menu_binds)) + +add_binds("policysubmenu", lousy.util.table.join({ + key({}, "Return", + function (w) + local row = w.menu:get() + if row and row.cmd then + w:set_mode() + rp_command(row.cmd, w, (row.host or "") .. " " .. (row.rhost or "")) + end + end), + key({"Shift"}, "Return", + function (w) + local row = w.menu:get() + if row and row[2] then + w:enter_cmd(util.string.strip(row[2])) + end + end), + -- Exit menu + key({}, "q", function (w) w:set_mode() end), + +}, menu_binds)) + +-- Add ex-mode commands +local cmd = lousy.bind.cmd +add_cmds({ + cmd({"requestpolicy", "rp"}, "view current page requests", + function (w, a) + if not a then + w:set_mode("policymenu") + else + -- if called with an argument, try to interprate the command + local args = util.string.split(util.string.strip(a or ""), " ") + local aa = string.match(a, ".- (.*)") + rp_command(args[1], w, aa) + end + end), + cmd({"rp-whitelist", "rp-wl"}, "add a whitelist entry", + function(w, a, o) + rp_command((o.bang and "wl!") or "wl", w, a) + end), + cmd({"rp-blacklist", "rp-bl"}, "add a blacklist entry", + function(w, a, o) + rp_command((o.bang and "bl!") or "bl", w, a) + end), + cmd({"rp-exception", "rp-ex"}, "grant temporary exceptions to request polices", + function(w, a, o) + rp_command((o.bang and "ex!") or "ex", w, a) + end), + cmd({"rp-set"}, "set request policy plugin behaviors", + function(w, a) + rp_command("set", w, a) + end), + cmd({"rp-clear"}, "clear request policy exceptions", + function(w, a) + rp_command("clear", w, a) + end), +}) + +-- Adds buffer command to invoke the policy menu and temp-allow all +-- ,pp = open policy menu +-- ,pa = allow all CDR from current host for this session +-- ,pt = toggle CDR filtering +local buf = lousy.bind.buf +add_binds("normal", { + buf("^,pp$", function (w) w:set_mode("policymenu") end), + buf("^,pa$", function(w) + if w.view and w.view.uri ~= "about:blank" then + rp_command("ex", w, w.view.uri .. " all") + end end), + buf("^,pt$", function (w) rp_setting("requestpolicy", "toggle" ,w) end), +}) + +-- Status Bar Widget ========================================================== +-- Create the indictor widget on window creation +window.init_funcs.build_policy_indicator = function(w) + local i = w.sbar.r + i.policy = widget{type="label"} + i.layout:pack(w.sbar.r.policy) + i.layout:reorder(w.sbar.r.policy, 1) + i.policy.fg = theme.buf_sbar_fg + i.policy.font = theme.buf_sbar_font + w:update_policy() + -- Update indicator on tab change + w.tabs:add_signal("switch-page", function (_,view) + capi.luakit.idle_add(function() w:update_policy(w) return false end) + end) +end + +-- Update indicator on page navigation +webview.init_funcs.policy_update = function(view, w) + view:add_signal("load-status", function (v, status) + if status == "committed" or status == "provisional" then + w:update_policy(false) + elseif status == "failed" or status == "finished" then + w:update_policy(true) + end + end) +end + +-- Update contents of request policy widget +window.methods.update_policy = function(w, fin) + if not w.view then return end + local pw = w.sbar.r.policy + if filtering.widget then + local text + if not fin then + text = "[Loading...]" + else + text = "[" + if navto[w.view].res then + local acc, rej = 0, 0 + for _,pol in pairs(navto[w.view].res) do + acc = acc + pol.accept + rej = rej + pol.deny + end + text = text .. string.format("A%d B%d", acc, rej) + end + text = text .. "]" + end + pw.text = text + pw:show() + else + pw:hide() + end +end +-- Call load() +load() diff --git a/luakit/private_browsing.lua b/luakit/private_browsing.lua new file mode 100644 index 0000000..a6cd850 --- /dev/null +++ b/luakit/private_browsing.lua @@ -0,0 +1,89 @@ +------------------------------------------------------------------------------- +-- -- +-- Blocks Adding "Private Browsing" Hosts to History DB -- +-- -- +------------------------------------------------------------------------------- + +local string = string +local window = window +local webview = webview +local widget = widget +local theme = theme +local luakit = luakit +local domain_props = domain_props +local lousy = require("lousy") +local util = lousy.util +local history = require("history") +local nohist = globals.history_blacklist or {} + +local print = print +module("plugins.private_browsing") + +local indicator = { private = "!hist", notprivate = "" } + +-- Load themes, if undefined fallback +theme.pbm_font = theme.pbm_font or theme.buf_sbar_font +theme.pbm_fg = theme.pbm_fg or theme.buf_sbar_fg +theme.pbm_bg = theme.pbm_bg or theme.buf_sbar_bg + +-- Create the indictor widget on window creation +window.init_funcs.build_pindicator = function(w) + local i = w.sbar.r + i.prvt = widget{type="label"} + i.layout:pack(w.sbar.r.prvt) + i.layout:reorder(w.sbar.r.prvt, 2) + i.prvt.font = theme.buf_sbar_font + i.prvt.fg = theme.buf_sbar_fg + w:update_prvt() + -- Update indicator on tab change + w.tabs:add_signal("switch-page", function (_,view) + luakit.idle_add(function() w:update_prvt(w) return false end) + end) +end + +-- Update indicator on page navigation +webview.init_funcs.prvt_update = function(view, w) + view:add_signal("load-status", function (v, status) + if status == "comitted" or status == "failed" or status == "finished" then + w:update_prvt() + end + end) +end + +-- Updates widget based on status +window.methods.update_prvt = function(w) + if not w.view then return end + local private_br = w.view.enable_private_browsing + + local domain = (lousy.uri.parse(w.view.uri) or {}).host or "" + domain = string.match(domain, "^www%.(.+)") or domain + repeat + private_br = private_br or util.table.hasitem(nohist, domain) + domain = string.match(domain, "%.(.+)") + until not domain + + local prvt = w.sbar.r.prvt + -- Set widget text based on privacy setting + prvt.text = (private_br and indicator.private) or indicator.notprivate + -- Hide blank widget + if string.len(prvt.text) then + prvt:show() + else + prvt:hide() + end +end + +-- Hook to intercept history additions +history.add_signal("add", function (uri, title) + local domain = lousy.uri.parse(uri).host + domain = string.match(domain or "", "^www%.(.+)") or domain or "all" + -- Build list of domains and subdomains, pulling private browsing. + -- I.e. for luakit.org load .luakit.org, luakit.org, .org + local no_hist = domain_props.all.enable_private_browsing or (domain_props[domain] or {}).enable_private_browsing + repeat + no_hist = no_hist or (domain_props["."..domain] or {}).enable_private_browsing + no_hist = no_hist or util.table.hasitem(nohist, domain) + domain = string.match(domain, "%.(.+)") + until not domain + return not no_hist +end) diff --git a/luakit/rc.lua b/luakit/rc.lua index c03a6bd..a53aabe 100644 --- a/luakit/rc.lua +++ b/luakit/rc.lua @@ -178,4 +178,9 @@ if unique then end) end +-- Some plugins I found +-- require "nscript_widget" +-- require "private_browsing" +require "useragent" + -- vim: et:sw=4:ts=8:sts=4:tw=80 diff --git a/luakit/useragent.lua b/luakit/useragent.lua new file mode 100644 index 0000000..e714c10 --- /dev/null +++ b/luakit/useragent.lua @@ -0,0 +1,343 @@ +------------------------------------------------------------ +-- Dynamic useragent settings -- +-- Written by lokichaos based upon proxy.lua by: -- +-- +-- Copyright © Piotr Husiatyński -- +------------------------------------------------------------ + +-- TODO +-- make per-tab widget & add hooks to update it +-- save ua in table +-- clean table for GC pruning +-- set behavior: set-current, set-all, set-new + +-- Grab environment we need +local io = io +local os = os +local pairs = pairs +local ipairs = ipairs +local error = error +local string = string +local lousy = require "lousy" +local theme = theme +local unpack = unpack +local table = table +local capi = { luakit = luakit, soup = soup } +local webview = webview +local widget = widget +local window = window +local util = lousy.util +local globals = globals +-- Check for mode/bind functions +local add_binds, add_cmds = add_binds, add_cmds +local new_mode, menu_binds = new_mode, menu_binds +local setmetatable = setmetatable + +module("plugin.useragent") + + +-- Store each webview's ua-name +local uas = {} +setmetatable(uas, { __mode = "k" }) + +--- Module global variables +local agents_file = capi.luakit.data_dir .. '/useragentmenu' + +local useragents = {} +local realagent = { name = 0, uastring = globals.useragent } +local active = realagent +local default_name = nil + +-- Return ordered list of user agent names +function get_names() + return lousy.util.table.keys(useragents) +end + +-- Return ua string of user agent given by name +function get(name) + return useragents[name] +end + +--- Load user agents list from file +-- @param fd_name custom user agent storage of nil to use default +function load(fd_name) + local fd_name = fd_name or agents_file + if not os.exists(fd_name) then return end + local strip = lousy.util.string.strip + + for line in io.lines(fd_name) do + local status, name, uastring = string.match(line, "^(.)%s(.+)%s\"(.+)\"$") + if uastring then + name, uastring = strip(name), strip(uastring) + if status == '*' then + active = { uastring = uastring, name = name } + end + useragents[name] = uastring + end + end + -- Set saved default ua string + globals.useragent = active.uastring + default_name = active.name +end + +--- Save user agents list to file +-- @param fd_name custom user agent storage of nil to use default +function save(fd_name) + local fd = io.open(fd_name or agents_file, "w") + for name, uastring in pairs(useragents) do + if uastrings ~= "" then + local status = (active.name == name and '*') or ' ' + fd:write(string.format("%s %s \"%s\"\n", status, name, uastring)) + end + end + io.close(fd) +end + +--- Add new user agent to current list +-- @param name user agent configuration name +-- @param uastring user agent string +-- @param save_file do not save configuration if false +function set(name, uastring, save_file) + local name = lousy.util.string.strip(name) + if not string.match(name, "^([%w%p]+)$") then + error("Invalid user agent name: " .. name) + end + useragents[name] = lousy.util.string.strip(uastring) + if save_file ~= false then save() end +end + +--- Delete selected user agent from list +-- @param name user agent name +-- TODO check all tabs +function del(name, w) + local name = lousy.util.string.strip(name) + if useragents[name] then + -- if deleted user agent was the active one, use real/default uastring + if name == active.name then + active = realagent + end + useragents[name] = nil + save() + end +end + +--- Set given user agent to active. Return true on success, else false +-- @param name user agents configuration name or nil to unset user agent. +function set_active(name) + if name ~= 0 then + local name = lousy.util.string.strip(name) + if not useragents[name] then + error("Unknown user agent: " .. name) + end + active = { name = name, uastring = useragents[name] } + else + active = realagent + end + save() + return true +end + +-- Create a user agent indicator widget and add it to the status bar +window.init_funcs.build_ua_indicator = function (w) + local r = w.sbar.r + r.uai = widget{type="label"} + r.layout:pack(r.uai) + r.layout:reorder(r.uai, 2) + r.uai.fg = theme.useragenti_sbar_fg or theme.sbar_fg + r.uai.font = theme.useragenti_sbar_font or theme.sbar_font + w.tabs:add_signal("switch-page", function (nbook, view, idx) + capi.luakit.idle_add(function() w:update_ua_indicator() return false end) + end) + w:update_ua_indicator() +end + +-- Helper function to update text in user agent indicator +window.methods.update_ua_indicator = function (w) + local name = uas[w.view] or (uas[w.view] == 0 and 0) or default_name + local uai = w.sbar.r.uai + if name ~= 0 then + local text = string.format("(%s)", name) + if uai.text ~= text then uai.text = text end + uai:show() + else + uai:hide() + end +end + +new_mode("uagentmenu", { + enter = function (w) + -- Set all tab's uas entires + for index = 1, w.tabs:count() do + uas[w.tabs[index]] = uas[w.tabs[index]] or default_name + end + + local afg = theme.useragent_active_menu_fg or theme.proxy_active_menu_fg + local ifg = theme.useragent_inactive_menu_fg or theme.proxy_inactive_menu_fg + local abg = theme.useragent_active_menu_bg or theme.proxy_active_menu_bg + local ibg = theme.useragent_inactive_menu_bg or theme.proxy_inactive_menu_bg + + -- "Active" user agent, this is the one that is saved as a default between sessions + local act_ua = uas[w.view] + -- Add titlebar, and "Default" UA entry + local dname = string.format(" %s %s%s", "Default Luakit User Agent", + (active.name == 0 and "[Default]") or "", + (default_name == 0 and "[Session]") or "") + local rows = {{ "User Agent Name", " User Agent String", title = true }, + { dname, " " .. realagent.uastring or "", name = 0, uastring = '', + fg = (act_ua == 0 and afg) or ifg, + bg = (act_ua == 0 and abg) or ibg},} + + for _, name in ipairs(get_names()) do + local uastring = get(name) + local uaname = string.format(" %s %s%s", name, + (active.name == name and "[Default]") or "", + (default_name == name and "[Session]") or "") + table.insert(rows, { + uaname, " " .. uastring, + name = name, uastring = lousy.util.escape(uastring), + fg = (act_ua == name and afg) or ifg, + bg = (act_ua == name and abg) or ibg, + }) + end + w.menu:build(rows) + w:notify("Use j/k to move, [d]elete, [e]dit, [a]dd, [S]et default, [s]et for all tabs, [n] set for new tabs, return to set for current tab.", false) + end, + + leave = function (w) + w.menu:hide() + end, +}) + +local cmd = lousy.bind.cmd +add_cmds({ + cmd({"agent", "ua"}, "add new or select useragent", + function (w, a) + if not a then + w:set_mode("uagentmenu") + else + local name, uastring = string.match(a, "(.+)%s\"(.+)\"") + if name and uastring then + set(name, uastring) + else + w:error("Bad usage. Correct format :agent \"\"") + end + end + end), + }) + + +local ua_set = function(mode, ua, w) + if mode == "current" then + w.view.user_agent = ua.uastring or realagent.uastring + uas[w.view] = ua.name + if ua.name ~= 0 then + w:notify(string.format("Set user agent on this tab to: %s (%s).", ua.name, ua.uastring)) + else + w:notify("Set user agent for current tab to default.") + end + + elseif mode == "newtabs" then + globals.useragent = ua.uastring + default_name = ua.name + if ua.name ~= 0 then + w:notify(string.format("Set user agent for all new tabs to: %s (%s).", ua.name, ua.uastring)) + else + w:notify("Set user agent for all new tabs to default Luakit.") + end + + elseif mode == "all" then + -- New Tabs will use this + globals.useragent = ua.uastring + default_name = ua.name + -- Set user agent for all existing tabs + for index = 1, w.tabs:count() do + w.tabs[index].user_agent = ua.uastring + uas[w.tabs[index]] = ua.name + end + if ua.name ~= 0 then + w:notify(string.format("Set user agent for all tabs to: %s (%s).", ua.name, ua.uastring)) + else + w:notify("Set user agent for all tabs to default.") + end + + elseif mode == "default" then + globals.useragent = ua.uastring + default_name = ua.name + set_active(ua.name) + if ua.name ~= 0 then + w:notify(string.format("Set %s as default useragent (%s)", ua.name, ua.uastring)) + else + w:notify("Unset default useragent, will default to real Luakit one.") + end + + end + -- Update useragent statusbar widget + w:update_ua_indicator() +end + +local key = lousy.bind.key +add_binds("uagentmenu", lousy.util.table.join({ + -- Select user agent + key({}, "Return", + function (w) + local row = w.menu:get() + if row then + w:set_mode() + ua_set("current", row, w) + end + end), + key({}, "S", + function (w) + local row = w.menu:get() + if row then + w:set_mode() + ua_set("default", row, w) + end + end), + key({}, "s", + function (w) + local row = w.menu:get() + if row then + w:set_mode() + ua_set("all", row, w) + end + end), + key({}, "n", + function (w) + local row = w.menu:get() + if row then + w:set_mode() + ua_set("newtabs", row, w) + end + end), + -- Delete user agent + key({}, "d", + function (w) + local row = w.menu:get() + if row and row.name then + del(row.name) + w.menu:del() + end + end), + + -- Edit user agent + key({}, "e", + function (w) + local row = w.menu:get() + if row and row.name then + w:enter_cmd(string.format(":agent %s \"%s\"", row.name, row.uastring)) + end + end), + + -- New user agent + key({}, "a", function (w) w:enter_cmd(":agent ") end), + + -- Exit menu + key({}, "q", function (w) w:set_mode() end), + +}, menu_binds)) + +-- Initialize module +load() + +-- vim: et:sw=4:ts=8:sts=4 diff --git a/luakit/useragentmenu b/luakit/useragentmenu new file mode 100644 index 0000000..4723638 --- /dev/null +++ b/luakit/useragentmenu @@ -0,0 +1,10 @@ + camino "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.6; en; rv:1.9.2.14pre) Gecko/20101212 Camino/2.1a1pre (like Firefox/3.6.14pre)" + googlebot "Googlebot/2.1 ( http://www.googlebot.com/bot.html)" + firefox15 "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120427 Firefox/15.0a1" + safari "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10" + inferfox "Mozilla/6.0 (compatible; AppleWebKit/latest; like Gecko/20120405; };-> infernal_edition:goto-hell) Firefox/666" + firefox11 "Mozilla/5.0 (Windows NT 6.1; rv:11.0) Gecko Firefox/11.0" + firefox13 "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/12.0" + ie10 "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)" + firefox14 "Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20120405 Firefox/14.0a1" + opera10 "Opera/9.80 (Macintosh; Intel Mac OS X; U; en) Presto/2.2.15 Version/10.10" diff --git a/mutt/hcache b/mutt/hcache new file mode 100644 index 0000000000000000000000000000000000000000..727a02074f21e5cfd7581c75a4a4f216c64db286 GIT binary patch literal 114688 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%0D<){br%-YMJXRV4tASs|ptTdE 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'urlview' 'Follow links with urlview' diff --git a/mutt/mailcap b/mutt/mailcap new file mode 100644 index 0000000..8603537 --- /dev/null +++ b/mutt/mailcap @@ -0,0 +1 @@ +text/html; w3m -I %{charset} -T text/html; copiousoutput; diff --git a/mutt/muttrc b/mutt/muttrc new file mode 100644 index 0000000..c2053df --- /dev/null +++ b/mutt/muttrc @@ -0,0 +1,39 @@ +source "gpg -dq ~/.my-pwds.gpg |" + +set realname = "Dustin Swan" +set imap_user = "dustinswan@gmail.com" +set imap_pass = $my_pw_personal +set folder = "imaps://imap.gmail.com/" +set spoolfile = +INBOX +set imap_check_subscribed +set header_cache = ~/.mutt/hcache +set record = "+[Gmail]/Sent Mail" +set postponed = "+[Gmail]/Drafts" +unset imap_passive +set imap_keepalive = 300 +set mail_check = 60 +set smtp_url = "smtp://dustinswan@smtp.gmail.com:587/" +set smtp_pass = $my_pw_personal + +# Sorting +set sort = threads +set sort_aux = reverse-last-date-received + +# Sidebar +set sidebar_delim = ' │' +set sidebar_visible = yes +set sidebar_width = 24 +#color sidebar_new color221 color233 + +bind index sidebar-next +bind index sidebar-prev +bind index sidebar-open + +# Status Bar +#set status_chars = " *&A" +#set status_format = "---[ Folder: %f ]---[%r%n messages%?n? (%n new)?%?d? (%d to delete)?%?t? (%t tagged)? ]---%>-%?p?( %p postponed )?---"" + +# Fucking html mail +auto_view text/html +alternative_order text/plain text/enriched text/html + diff --git a/urlview b/urlview new file mode 100644 index 0000000..6da1012 --- /dev/null +++ b/urlview @@ -0,0 +1,2 @@ +REGEXP (((http|https|ftp|gopher)|mailto)[.:][^ >"\t]*|www\.[-a-z0-9.]+)[^ .,;\t>">\):] +COMMAND luakit -n %s 2>/dev/null