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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'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