diff options
author | Brendan Taylor <whateley@gmail.com> | 2011-03-13 21:17:43 -0600 |
---|---|---|
committer | Brendan Taylor <whateley@gmail.com> | 2011-03-13 21:17:43 -0600 |
commit | 5ec505fdb007d22bcfb5631e88e67038b06d778a (patch) | |
tree | 9b467f9fce2266114bcfe12cf7c1db0596b1f50b /examples | |
parent | 6b2a973e17a0cf4d3cdc88e71973624929018c78 (diff) | |
parent | 0d9073aa31d7592ae3e7b321ac71c4b480750e08 (diff) |
Merge branch 'experimental' into dev/fix-shell-scripts
Conflicts:
examples/config/config
examples/data/scripts/follow.sh
examples/data/scripts/formfiller.sh
Diffstat (limited to 'examples')
-rw-r--r-- | examples/config/config | 29 | ||||
-rw-r--r-- | examples/data/per-site-settings | 2 | ||||
-rw-r--r-- | examples/data/plugins/bind.py | 13 | ||||
-rw-r--r-- | examples/data/plugins/config.py | 7 | ||||
-rw-r--r-- | examples/data/plugins/cookies.py | 60 | ||||
-rw-r--r-- | examples/data/plugins/downloads.py | 18 | ||||
-rw-r--r-- | examples/data/plugins/history.py | 129 | ||||
-rw-r--r-- | examples/data/plugins/keycmd.py | 5 | ||||
-rw-r--r-- | examples/data/plugins/on_event.py | 6 | ||||
-rw-r--r-- | examples/data/scripts/follow.js | 416 | ||||
-rwxr-xr-x | examples/data/scripts/follow.sh | 30 | ||||
-rw-r--r-- | examples/data/scripts/formfiller.js | 67 | ||||
-rwxr-xr-x | examples/data/scripts/formfiller.sh | 320 | ||||
-rwxr-xr-x | examples/data/scripts/uzbl-cookie-daemon | 677 | ||||
-rwxr-xr-x | examples/data/scripts/uzbl-event-manager | 22 | ||||
-rwxr-xr-x | examples/data/scripts/uzbl-tabbed | 7 | ||||
-rw-r--r-- | examples/data/style.css | 38 | ||||
-rw-r--r-- | examples/uzbl-cookie-manager.c | 381 |
18 files changed, 677 insertions, 1550 deletions
diff --git a/examples/config/config b/examples/config/config index b5421da..bcd6d3e 100644 --- a/examples/config/config +++ b/examples/config/config @@ -69,6 +69,11 @@ set download_handler = sync_spawn @scripts_dir/download.sh # Load commit handlers @on_event LOAD_COMMIT @set_status <span foreground="green">recv</span> + # add some javascript to the page for other 'js' and 'script' commands to access later. +@on_event LOAD_COMMIT js uzbl = {}; +@on_event LOAD_COMMIT script @scripts_dir/formfiller.js +@on_event LOAD_COMMIT script @scripts_dir/follow.js + # Userscripts/per-site-settings. See the script and the example configuration for details #@on_event LOAD_COMMIT spawn @scripts_dir/per-site-settings.py @data_home/uzbl/per-site-settings @@ -85,10 +90,13 @@ set download_handler = sync_spawn @scripts_dir/download.sh #@on_event CONFIG_CHANGED print Config changed: %1 = %2 # Scroll percentage calculation -@on_event SCROLL_VERT set scroll_message = \@<(function(){var a='%1'.split(' ');var p='--';if(a[2]!=a[1]){p=(a[0]/(a[2]-a[3]));p=Math.round(10000*p)/100;};return p+'%';})()>\@ +@on_event SCROLL_VERT set scroll_message = \@<(function(){var p='--';if(%3!=%2){p=(%1/(%3-%4));p=Math.round(10000*p)/100;};return p+'%';})()>\@ # === Behaviour and appearance =============================================== +# Custom CSS can be defined here, including link follower hint styles +set stylesheet_uri = file://@data_home/uzbl/style.css + set show_status = 1 set status_top = 0 set status_background = #303030 @@ -129,7 +137,11 @@ set progress.pending = set useragent = Uzbl (Webkit @{WEBKIT_MAJOR}.@{WEBKIT_MINOR}) (@(+uname -sm)@ [@ARCH_UZBL]) # === Configure cookie blacklist ======================================================== -# Drop google analytics tracking cookies + +# Accept 'session cookies' from uzbl.org (when you have a whitelist all other cookies are dropped) +#request WHITELIST_COOKIE domain 'uzbl.org$' expires '^$' + +# Drop google analytics tracking cookies (applied after whitelists if any) #request BLACKLIST_COOKIE name '^__utm.$' # === Key binding configuration ============================================== @@ -172,6 +184,7 @@ set ebind = @mode_bind global,-insert # Resets keycmd and returns to default mode. @on_event ESCAPE @set_mode +@on_event ESCAPE event KEYCMD_CLEAR @bind <Escape> = event ESCAPE @bind <Ctrl>[ = event ESCAPE @@ -190,6 +203,15 @@ set ebind = @mode_bind global,-insert @ebind <Ctrl>a = event SET_CURSOR_POS 0 @ebind <Ctrl>e = event SET_CURSOR_POS -1 +@ebind <Up> = event HISTORY_PREV +@ebind <Down> = event HISTORY_NEXT +@ebind <Ctrl>r<search:>_ = event HISTORY_SEARCH %s +# Keycmd injection/append examples. +#@ebind <Ctrl>su = event INJECT_KEYCMD \@uri +#@ebind <Ctrl>st = event INJECT_KEYCMD \@title +#@ebind <Ctrl>du = event APPEND_KEYCMD \@uri +#@ebind <Ctrl>dt = event APPEND_KEYCMD \@title + # --- Mouse bindings --------------------------------------------------------- # Middle click open in new window @@ -309,7 +331,8 @@ set follow_hint_keys = 0123456789 #set follow_hint_keys = qwerty #set follow_hint_keys = asdfghjkl; #set follow_hint_keys = thsnd-rcgmvwb/;789aefijkopquxyz234 -@cbind fl* = spawn @scripts_dir/follow.sh follow_hint_keys "%s" +@cbind fl* = spawn @scripts_dir/follow.sh \@< uzbl.follow("\@follow_hint_keys", "%s", 0) >\@ +@cbind Fl* = spawn @scripts_dir/follow.sh \@< uzbl.follow("\@follow_hint_keys", "%s", 1) >\@ @cbind gi = spawn @scripts_dir/go_input.sh # Form filler binds diff --git a/examples/data/per-site-settings b/examples/data/per-site-settings index 78bade4..d02d7a7 100644 --- a/examples/data/per-site-settings +++ b/examples/data/per-site-settings @@ -1,3 +1,3 @@ .* .*/\d+-\w+/(thread|subject|author|date).html - script @data_home/uzbl/scripts/pipermail.js + script @scripts_dir/pipermail.js diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py index 5b13476..69fd863 100644 --- a/examples/data/plugins/bind.py +++ b/examples/data/plugins/bind.py @@ -164,15 +164,6 @@ def split_glob(glob): return (mods, glob) -def unquote(str): - '''Remove quotation marks around string.''' - - if str and str[0] == str[-1] and str[0] in ['"', "'"]: - str = str[1:-1] - - return str - - class Bind(object): # Class attribute to hold the number of Bind classes created. @@ -379,7 +370,6 @@ def mode_changed(uzbl, mode): if mode != 'stack': uzbl.bindlet.reset() - uzbl.clear_keycmd() def match_and_exec(uzbl, bind, depth, keylet, bindlet): @@ -421,7 +411,6 @@ def match_and_exec(uzbl, bind, depth, keylet, bindlet): if not has_args or on_exec: del uzbl.config['mode'] bindlet.reset() - uzbl.clear_current() return True @@ -469,3 +458,5 @@ def init(uzbl): 'mode_bind': mode_bind, 'bindlet': Bindlet(uzbl), }) + +# vi: set et ts=4: diff --git a/examples/data/plugins/config.py b/examples/data/plugins/config.py index ed2d761..c9bdf67 100644 --- a/examples/data/plugins/config.py +++ b/examples/data/plugins/config.py @@ -3,8 +3,6 @@ from types import BooleanType from UserDict import DictMixin valid_key = compile('^[A-Za-z0-9_\.]+$').match -types = {'int': int, 'float': float, 'str': unicode} -escape = lambda s: unicode(s).replace('\n', '\\n') class Config(DictMixin): def __init__(self, uzbl): @@ -49,7 +47,8 @@ class Config(DictMixin): value = int(value) else: - value = escape(value) + value = unicode(value) + assert '\n' not in value if not force and key in self and self[key] == value: return @@ -82,6 +81,8 @@ def parse_set_event(uzbl, args): # plugin init hook def init(uzbl): + global types + types = {'int': int, 'float': float, 'str': unquote} export(uzbl, 'config', Config(uzbl)) connect(uzbl, 'VARIABLE_SET', parse_set_event) diff --git a/examples/data/plugins/cookies.py b/examples/data/plugins/cookies.py index c9fe2c3..e29ee36 100644 --- a/examples/data/plugins/cookies.py +++ b/examples/data/plugins/cookies.py @@ -7,10 +7,6 @@ import os, re # these are symbolic names for the components of the cookie tuple symbolic = {'domain': 0, 'path':1, 'name':2, 'value':3, 'scheme':4, 'expires':5} -_splitquoted = re.compile("( |\\\".*?\\\"|'.*?')") -def splitquoted(text): - return [str(p.strip('\'"')) for p in _splitquoted.split(text) if p.strip()] - # allows for partial cookies # ? allow wildcard in key def match(key, cookie): @@ -38,24 +34,38 @@ class TextStore(object): self.filename = filename def as_event(self, cookie): + """Convert cookie.txt row to uzbls cookie event format""" + scheme = { + 'TRUE' : 'https', + 'FALSE' : 'http' + } if cookie[0].startswith("#HttpOnly_"): domain = cookie[0][len("#HttpOnly_"):] elif cookie[0].startswith('#'): return None else: domain = cookie[0] - return (domain, - cookie[2], - cookie[5], - cookie[6], - 'https' if cookie[3] == 'TRUE' else 'http', - cookie[4]) + try: + return (domain, + cookie[2], + cookie[5], + cookie[6], + scheme[cookie[3]], + cookie[4]) + except (KeyError,IndexError): + # Let malformed rows pass through like comments + return None def as_file(self, cookie): + """Convert cookie event to cookie.txt row""" + secure = { + 'https' : 'TRUE', + 'http' : 'FALSE' + } return (cookie[0], 'TRUE' if cookie[0].startswith('.') else 'FALSE', cookie[1], - 'TRUE' if cookie[4] == 'https' else 'FALSE', + secure[cookie[4]], cookie[5], cookie[2], cookie[3]) @@ -92,8 +102,11 @@ DefaultStore = TextStore(os.path.join(xdg_data_home, 'uzbl/cookies.txt')) SessionStore = TextStore(os.path.join(xdg_data_home, 'uzbl/session-cookies.txt')) def match_list(_list, cookie): - for component, match in _list: - if match(cookie[component]) is not None: + for matcher in _list: + for component, match in matcher: + if match(cookie[component]) is None: + break + else: return True return False @@ -144,18 +157,21 @@ def delete_cookie(uzbl, cookie): store.delete_cookie(cookie, splitted) # add a cookie matcher to a whitelist or a blacklist. -# a matcher is a (component, re) tuple that matches a cookie when the +# a matcher is a list of (component, re) tuples that matches a cookie when the # "component" part of the cookie matches the regular expression "re". # "component" is one of the keys defined in the variable "symbolic" above, # or the index of a component of a cookie tuple. def add_cookie_matcher(_list, arg): - component, regexp = splitquoted(arg) - try: - component = symbolic[component] - except KeyError: - component = int(component) - assert component <= 5 - _list.append((component, re.compile(regexp).search)) + args = splitquoted(arg) + mlist = [] + for (component, regexp) in zip(args[0::2], args[1::2]): + try: + component = symbolic[component] + except KeyError: + component = int(component) + assert component <= 5 + mlist.append((component, re.compile(regexp).search)) + _list.append(mlist) def blacklist(uzbl, arg): add_cookie_matcher(uzbl.cookie_blacklist, arg) @@ -174,3 +190,5 @@ def init(uzbl): 'cookie_blacklist' : [], 'cookie_whitelist' : [] }) + +# vi: set et ts=4: diff --git a/examples/data/plugins/downloads.py b/examples/data/plugins/downloads.py index 7bf32d7..8d796ce 100644 --- a/examples/data/plugins/downloads.py +++ b/examples/data/plugins/downloads.py @@ -31,7 +31,11 @@ def update_download_section(uzbl): if uzbl.config.get('downloads', '') != result: uzbl.config['downloads'] = result -def download_started(uzbl, destination_path): +def download_started(uzbl, args): + # parse the arguments + args = splitquoted(args) + destination_path = args[0] + # add to the list of active downloads global ACTIVE_DOWNLOADS ACTIVE_DOWNLOADS[destination_path] = (0.0,) @@ -41,9 +45,9 @@ def download_started(uzbl, destination_path): def download_progress(uzbl, args): # parse the arguments - s = args.rindex(' ') - destination_path = args[:s] - progress = float(args[s+1:]) + args = splitquoted(args) + destination_path = args[0] + progress = float(args[1]) # update the progress global ACTIVE_DOWNLOADS @@ -52,7 +56,11 @@ def download_progress(uzbl, args): # update the status bar variable update_download_section(uzbl) -def download_complete(uzbl, destination_path): +def download_complete(uzbl, args): + # parse the arguments + args = splitquoted(args) + destination_path = args[0] + # remove from the list of active downloads global ACTIVE_DOWNLOADS del ACTIVE_DOWNLOADS[destination_path] diff --git a/examples/data/plugins/history.py b/examples/data/plugins/history.py new file mode 100644 index 0000000..5e9e4e1 --- /dev/null +++ b/examples/data/plugins/history.py @@ -0,0 +1,129 @@ +import random + +shared_history = {'':[]} + +class History(object): + def __init__(self, uzbl): + self.uzbl = uzbl + self._temporary = [] + self.prompt = '' + self.cursor = None + self.__temp_tail = False + self.search_key = None + + def prev(self): + if self.cursor is None: + self.cursor = len(self) - 1 + else: + self.cursor -= 1 + + if self.search_key: + while self.cursor >= 0 and self.search_key not in self[self.cursor]: + self.cursor -= 1 + + if self.cursor < 0 or len(self) == 0: + self.cursor = -1 + return random.choice(end_messages) + + return self[self.cursor] + + def next(self): + if self.cursor is None: + return '' + + self.cursor += 1 + + if self.search_key: + while self.cursor < len(self) and self.search_key not in self[self.cursor]: + self.cursor += 1 + + if self.cursor >= len(shared_history[self.prompt]): + self.cursor = None + self.search_key = None + + if self._temporary: + return self._temporary.pop() + return '' + + return self[self.cursor] + + def change_prompt(self, prompt): + self.prompt = prompt + self._temporary = [] + self.__temp_tail = False + if prompt not in shared_history: + shared_history[prompt] = [] + + def search(self, key): + self.search_key = key + self.cursor = None + + def add(self, cmd): + if self._temporary: + self._temporary.pop() + + shared_history[self.prompt].append(cmd) + self.cursor = None + self.search_key = None + + def add_temporary(self, cmd): + assert not self._temporary + + self._temporary.append(cmd) + self.cursor = len(self) - 1 + + def __getitem__(self, i): + if i < len(shared_history[self.prompt]): + return shared_history[self.prompt][i] + return self._temporary[i-len(shared_history)+1] + + def __len__(self): + return len(shared_history[self.prompt]) + len(self._temporary) + + def __str__(self): + return "(History %s, %s)" % (self.cursor, self.prompt) + +def keycmd_exec(uzbl, keylet): + cmd = keylet.get_keycmd() + if cmd: + uzbl.history.add(cmd) + +def history_prev(uzbl, _x): + cmd = uzbl.keylet.get_keycmd() + if uzbl.history.cursor is None and cmd: + uzbl.history.add_temporary(cmd) + + uzbl.set_keycmd(uzbl.history.prev()) + uzbl.logger.debug('PREV %s' % uzbl.history) + +def history_next(uzbl, _x): + cmd = uzbl.keylet.get_keycmd() + + uzbl.set_keycmd(uzbl.history.next()) + uzbl.logger.debug('NEXT %s' % uzbl.history) + +def history_search(uzbl, key): + uzbl.history.search(key) + uzbl.send('event HISTORY_PREV') + uzbl.logger.debug('SEARCH %s %s' % (key, uzbl.history)) + +end_messages = ('Look behind you, A three-headed monkey!', 'error #4: static from nylon underwear.', 'error #5: static from plastic slide rules.', 'error #6: global warming.', 'error #9: doppler effect.', 'error #16: somebody was calculating pi on the server.', 'error #19: floating point processor overflow.', 'error #21: POSIX compliance problem.', 'error #25: Decreasing electron flux.', 'error #26: first Saturday after first full moon in Winter.', 'error #64: CPU needs recalibration.', 'error #116: the real ttys became pseudo ttys and vice-versa.', 'error #229: wrong polarity of neutron flow.', 'error #330: quantum decoherence.', 'error #388: Bad user karma.', 'error #407: Route flapping at the NAP.', 'error #435: Internet shut down due to maintenance.') + +# plugin init hook +def init(uzbl): + connect_dict(uzbl, { + 'KEYCMD_EXEC': keycmd_exec, + 'HISTORY_PREV': history_prev, + 'HISTORY_NEXT': history_next, + 'HISTORY_SEARCH': history_search + }) + + export_dict(uzbl, { + 'history' : History(uzbl) + }) + +# plugin after hook +def after(uzbl): + uzbl.on_set('keycmd_prompt', lambda uzbl, k, v: uzbl.history.change_prompt(v)) + +# vi: set et ts=4: diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py index 2fb2283..928c597 100644 --- a/examples/data/plugins/keycmd.py +++ b/examples/data/plugins/keycmd.py @@ -210,7 +210,7 @@ def modkey_addition_parse(uzbl, modkeys): add_modkey_addition(uzbl, keys[:-1], keys[-1]) -def clear_keycmd(uzbl): +def clear_keycmd(uzbl, *args): '''Clear the keycmd for this uzbl instance.''' k = uzbl.keylet @@ -493,6 +493,7 @@ def init(uzbl): 'KEYCMD_DELETE': keycmd_delete, 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, 'KEYCMD_STRIP_WORD': keycmd_strip_word, + 'KEYCMD_CLEAR': clear_keycmd, 'KEY_PRESS': key_press, 'KEY_RELEASE': key_release, 'MODKEY_ADDITION': modkey_addition_parse, @@ -514,3 +515,5 @@ def init(uzbl): 'set_cursor_pos': set_cursor_pos, 'set_keycmd': set_keycmd, }) + +# vi: set et ts=4: diff --git a/examples/data/plugins/on_event.py b/examples/data/plugins/on_event.py index 5142275..32f09e2 100644 --- a/examples/data/plugins/on_event.py +++ b/examples/data/plugins/on_event.py @@ -24,6 +24,10 @@ def event_handler(uzbl, *args, **kargs): '''This function handles all the events being watched by various on_event definitions and responds accordingly.''' + # Could be connected to a EM internal event that can use anything as args + if len(args) == 1 and isinstance(args[0], basestring): + args = splitquoted(args[0]) + events = uzbl.on_events event = kargs['on_event'] if event not in events: @@ -80,3 +84,5 @@ def cleanup(uzbl): del handlers[:] uzbl.on_events.clear() + +# vi: set et ts=4: diff --git a/examples/data/scripts/follow.js b/examples/data/scripts/follow.js index d995696..536256b 100644 --- a/examples/data/scripts/follow.js +++ b/examples/data/scripts/follow.js @@ -1,219 +1,192 @@ /* This is the basic linkfollowing script. - * Its pretty stable, and you configure which keys to use for hinting * - * TODO: Some pages mess around a lot with the zIndex which - * lets some hints in the background. - * TODO: Some positions are not calculated correctly (mostly - * because of uber-fancy-designed-webpages. Basic HTML and CSS - * works good - * TODO: Still some links can't be followed/unexpected things - * happen. Blame some freaky webdesigners ;) + * TODO: + * Some pages mess around a lot with the zIndex which + * lets some hints in the background. + * Some positions are not calculated correctly (mostly + * because of uber-fancy-designed-webpages. Basic HTML and CSS + * works good + * Still some links can't be followed/unexpected things + * happen. Blame some freaky webdesigners ;) */ -//Just some shortcuts and globals -var uzblid = 'uzbl_link_hint'; -var uzbldivid = uzblid + '_div_container'; -var doc = document; -var win = window; -var links = document.links; -var forms = document.forms; -//Make onlick-links "clickable" -try { - HTMLElement.prototype.click = function() { - if (typeof this.onclick == 'function') { - this.onclick({ - type: 'click' - }); - } - }; -} catch(e) {} -//Catch the ESC keypress to stop linkfollowing -function keyPressHandler(e) { - var kC = window.event ? event.keyCode: e.keyCode; - var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; - if (kC == Esc) { - removeAllHints(); - } +// Globals +uzbldivid = 'uzbl_link_hints'; + +uzbl.follow = function() { + // Export + charset = arguments[0]; + newwindow = arguments[2]; + + var keypress = arguments[1]; + return arguments.callee.followLinks(keypress); +} + +uzbl.follow.isFrame = function(el) { + return (el.tagName == "FRAME" || el.tagName == "IFRAME"); +} + +// find the document that the given element belongs to +uzbl.follow.getDocument = function(el) { + if (this.isFrame(el)) + return el.contentDocument; + + var doc = el; + while (doc.parentNode !== null) + doc = doc.parentNode; + return doc; +} + +// find all documents in the display, searching frames recursively +uzbl.follow.documents = function() { + return this.windows().map(function(w) { return w.document; }).filter(function(d) { return d != null; }); +} + +// find all windows in the display, searching for frames recursively +uzbl.follow.windows = function(w) { + w = (typeof w == 'undefined') ? window.top : w; + + var wins = [w]; + var frames = w.frames; + for(var i = 0; i < frames.length; i++) + wins = wins.concat(uzbl.follow.windows(frames[i])); + return wins; +} + +// search all frames for elements matching the given CSS selector +uzbl.follow.query = function(selector) { + var res = []; + this.documents().forEach(function (doc) { + var set = doc.body.querySelectorAll(selector); + // convert the NodeList to an Array + set = Array.prototype.slice.call(set); + res = res.concat(set); + }); + return res; } -//Calculate element position to draw the hint -//Pretty accurate but on fails in some very fancy cases -function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; - var width = el.offsetWidth; + +// Calculate element position to draw the hint +uzbl.follow.elementPosition = function(el) { + // el.getBoundingClientRect is another way to do this, but when a link is + // line-wrapped we want our hint at the left end of the link, not its + // bounding rectangle + var up = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; var height = el.offsetHeight; + while (el.offsetParent) { el = el.offsetParent; up += el.offsetTop; left += el.offsetLeft; } + return [up, left, width, height]; } -//Calculate if an element is visible -function isVisible(el) { - if (el == doc) { - return true; - } - if (!el) { - return false; - } - if (!el.parentNode) { - return false; - } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); -} -//Calculate if an element is on the viewport. -function elementInViewport(el) { - offset = elementPosition(el); - var up = offset[0]; - var left = offset[1]; - var width = offset[2]; + +// Calculate if an element is on the viewport. +uzbl.follow.elementInViewport = function(el) { + offset = uzbl.follow.elementPosition(el); + var up = offset[0]; + var left = offset[1]; + var width = offset[2]; var height = offset[3]; - return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; + return up < window.pageYOffset + window.innerHeight && + left < window.pageXOffset + window.innerWidth && + (up + height) > window.pageYOffset && + (left + width) > window.pageXOffset; } -//Removes all hints/leftovers that might be generated -//by this script. -function removeAllHints() { + +// Removes all hints/leftovers that might be generated +// by this script. +uzbl.follow.removeAllHints = function(doc) { var elements = doc.getElementById(uzbldivid); - if (elements) { - elements.parentNode.removeChild(elements); - } + if (elements) elements.parentNode.removeChild(elements); } -//Generate a hint for an element with the given label -//Here you can play around with the style of the hints! -function generateHint(el, label) { - var pos = elementPosition(el); - var hint = doc.createElement('div'); - hint.setAttribute('name', uzblid); + +// Generate a hint for an element with the given label +// Here you can play around with the style of the hints! +uzbl.follow.generateHint = function(doc, el, label, top, left) { + var hint = doc.createElement('span'); hint.innerText = label; - hint.style.display = 'inline'; - hint.style.backgroundColor = '#B9FF00'; - hint.style.border = '2px solid #4A6600'; - hint.style.color = 'black'; - hint.style.fontSize = '9px'; - hint.style.fontWeight = 'bold'; - hint.style.lineHeight = '9px'; - hint.style.margin = '0px'; - hint.style.width = 'auto'; // fix broken rendering on w3schools.com - hint.style.padding = '1px'; hint.style.position = 'absolute'; - hint.style.zIndex = '1000'; - // hint.style.textTransform = 'uppercase'; - hint.style.left = pos[1] + 'px'; - hint.style.top = pos[0] + 'px'; - // var img = el.getElementsByTagName('img'); - // if (img.length > 0) { - // hint.style.top = pos[1] + img[0].height / 2 - 6 + 'px'; - // } - hint.style.textDecoration = 'none'; - // hint.style.webkitBorderRadius = '6px'; // slow - // Play around with this, pretty funny things to do :) - // hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; + hint.style.top = top + 'px'; + hint.style.left = left + 'px'; return hint; } -// Here we choose what to do with an element that the user has selected. -// Form elements get selected and/or focussed, and links and buttons are -// clicked. This function returns "XXXRESET_MODEXXX" to indicate that uzbl -// should be reset to command mode with an empty keycmd, or -// "XXX_EMIT_FORM_ACTIVEXXX" to indicate that uzbl should be set to insert mode. -function clickElem(item) { - removeAllHints(); - if (item) { - var name = item.tagName; - if (name == 'BUTTON') { - item.click(); - return "XXXRESET_MODEXXX"; - } else if (name == 'INPUT') { - var type = item.type.toUpperCase(); - if (type == 'TEXT' || type == 'SEARCH' || type == 'PASSWORD') { - item.focus(); - item.select(); - return "XXXEMIT_FORM_ACTIVEXXX"; - } else { - item.click(); - return "XXXRESET_MODEXXX"; - } - } else if (name == 'TEXTAREA' || name == 'SELECT') { +// Here we choose what to do with an element if we +// want to "follow" it. On form elements we "select" +// or pass the focus, on links we try to perform a click, +// but at least set the href of the link. (needs some improvements) +uzbl.follow.clickElem = function(item) { + if(!item) return; + var name = item.tagName; + + if (name == 'INPUT') { + var type = item.getAttribute('type').toUpperCase(); + if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { item.focus(); item.select(); return "XXXEMIT_FORM_ACTIVEXXX"; - } else { - item.click(); - window.location = item.href; - return "XXXRESET_MODEXXX"; } + // otherwise fall through to a simulated mouseclick. + } else if (name == 'TEXTAREA' || name == 'SELECT') { + item.focus(); + item.select(); + return "XXXEMIT_FORM_ACTIVEXXX"; } + + // simulate a mouseclick to activate the element + var mouseEvent = document.createEvent("MouseEvent"); + mouseEvent.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + item.dispatchEvent(mouseEvent); + return "XXXRESET_MODEXXX"; } -//Returns a list of all links (in this version -//just the elements itself, but in other versions, we -//add the label here. -function addLinks() { - res = [[], []]; - for (var l = 0; l < links.length; l++) { - var li = links[l]; - if (isVisible(li) && elementInViewport(li)) { - res[0].push(li); - } - } - return res; -} -//Same as above, just for the form elements -function addFormElems() { - res = [[], []]; - for (var f = 0; f < forms.length; f++) { - for (var e = 0; e < forms[f].elements.length; e++) { - var el = forms[f].elements[e]; - if (el && ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { - res[0].push(el); - } - } - } - return res; -} -//Draw all hints for all elements passed. "len" is for -//the number of chars we should use to avoid collisions -function reDrawHints(elems, chars) { - removeAllHints(); - var hintdiv = doc.createElement('div'); - hintdiv.setAttribute('id', uzbldivid); - for (var i = 0; i < elems[0].length; i++) { - if (elems[0][i]) { - var label = elems[1][i].substring(chars); - var h = generateHint(elems[0][i], label); - hintdiv.appendChild(h); - } - } - if (document.body) { - document.body.appendChild(hintdiv); - } + +// Draw all hints for all elements passed. +uzbl.follow.reDrawHints = function(elems, chars) { + var elements = elems.map(function(pair) { return pair[0] }); + var labels = elems.map(function(pair) { return pair[1].substring(chars) }); + // we have to calculate element positions before we modify the DOM + // otherwise the elementPosition call slows way down. + var positions = elements.map(uzbl.follow.elementPosition); + + this.documents().forEach(function(doc) { + uzbl.follow.removeAllHints(doc); + if (!doc.body) return; + doc.hintdiv = doc.createElement('div'); + doc.hintdiv.id = uzbldivid; + if(newwindow) doc.hintdiv.className = "new-window"; + doc.body.appendChild(doc.hintdiv); + }); + + elements.forEach(function(el, i) { + var label = labels[i]; + var pos = positions[i]; + var doc = uzbl.follow.getDocument(el); + var h = uzbl.follow.generateHint(doc, el, label, pos[0], pos[1]); + doc.hintdiv.appendChild(h); + }); } + // pass: number of keys // returns: key length -function labelLength(n) { +uzbl.follow.labelLength = function(n) { var oldn = n; var keylen = 0; - if(n < 2) { - return 1; - } - n -= 1; // our highest key will be n-1 + if(n < 2) return 1; + n -= 1; // Our highest key will be n-1 while(n) { keylen += 1; n = Math.floor(n / charset.length); } return keylen; } + // pass: number // returns: label -function intToLabel(n) { +uzbl.follow.intToLabel = function(n) { var label = ''; do { label = charset.charAt(n % charset.length) + label; @@ -221,55 +194,68 @@ function intToLabel(n) { } while(n); return label; } + // pass: label // returns: number -function labelToInt(label) { +uzbl.follow.labelToInt = function(label) { var n = 0; - var i; - for(i = 0; i < label.length; ++i) { + for(var i = 0; i < label.length; ++i) { n *= charset.length; n += charset.indexOf(label[i]); } return n; } -//Put it all together -function followLinks(follow) { - // if(follow.charAt(0) == 'l') { - // follow = follow.substr(1); - // charset = 'thsnlrcgfdbmwvz-/'; - // } + +// Put it all together +uzbl.follow.followLinks = function(follow) { var s = follow.split(''); - var linknr = labelToInt(follow); - if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); - var linkelems = addLinks(); - var formelems = addFormElems(); - var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; - var len = labelLength(elems[0].length); - var oldDiv = doc.getElementById(uzbldivid); - var leftover = [[], []]; - if (s.length == len && linknr < elems[0].length && linknr >= 0) { - return clickElem(elems[0][linknr]); - } else { - for (var j = 0; j < elems[0].length; j++) { - var b = true; - var label = intToLabel(j); - var n = label.length; - for (n; n < len; n++) { - label = charset.charAt(0) + label; - } - for (var k = 0; k < s.length; k++) { - b = b && label.charAt(k) == s[k]; - } - if (b) { - leftover[0].push(elems[0][j]); - leftover[1].push(label); - } + var linknr = this.labelToInt(follow); + + var followable = 'a, area, textarea, select, input:not([type=hidden]), button'; + var uri = 'a, area, frame, iframe'; + //var focusable = 'a, area, textarea, select, input:not([type=hidden]), button, frame, iframe, applet, object'; + //var desc = '*[title], img[alt], applet[alt], area[alt], input[alt]'; + //var image = 'img, input[type=image]'; + + if(newwindow) + var res = this.query(uri); + else + var res = this.query(followable); + + var elems = res.filter(uzbl.follow.elementInViewport); + var len = this.labelLength(elems.length); + + if (s.length == len && linknr < elems.length && linknr >= 0) { + // an element has been selected! + var el = elems[linknr]; + + // clear all of our hints + this.documents().forEach(uzbl.follow.removeAllHints); + + if (newwindow) { + // we're opening a new window using the URL attached to this element + var uri = el.src || el.href; + if(uri.match(/javascript:/)) return; + window.open(uri); + return "XXXRESET_MODEXXX" } - reDrawHints(leftover, s.length); + + // we're just going to click the element + return this.clickElem(el); } -} -//Parse input: first argument is follow keys, second is user input. -var args = '%s'.split(' '); -var charset = args[0]; -followLinks(args[1]); + var leftover = []; + for (var j = 0; j < elems.length; j++) { + var b = true; + var label = this.intToLabel(j); + var n = label.length; + for (n; n < len; n++) + label = charset.charAt(0) + label; + for (var k = 0; k < s.length; k++) + b = b && label.charAt(k) == s[k]; + if (b) + leftover.push([elems[j], label]); + } + + this.reDrawHints(leftover, s.length); +} diff --git a/examples/data/scripts/follow.sh b/examples/data/scripts/follow.sh index 6401188..014793e 100755 --- a/examples/data/scripts/follow.sh +++ b/examples/data/scripts/follow.sh @@ -1,31 +1,13 @@ #!/bin/sh +# This scripts acts on the return value of followLinks in follow.js -# This script is just a wrapper around follow.js that lets us change uzbl's mode -# after a link is selected. - -. "$UZBL_UTIL_DIR/uzbl-util.sh" - -key_variable="$1" -shift - -keys="$1" -shift - -# if socat is installed then we can change Uzbl's input mode once a link is -# selected; otherwise we just select a link. -if ! which socat >/dev/null 2>&1; then - print "script @scripts_dir/follow.js \"@{$key_variable} $keys\"\n" > "$UZBL_FIFO" - exit 0 -fi - -result="$( print "script @scripts_dir/follow.js \"@{$key_variable} $keys\"\n" | socat - "unix-connect:$UZBL_SOCKET" )" -case $result in - *XXXEMIT_FORM_ACTIVEXXX*) +case "$1" in + XXXEMIT_FORM_ACTIVEXXX) # a form element was selected - print "event FORM_ACTIVE\n" > "$UZBL_FIFO" + printf 'event FORM_ACTIVE\nevent KEYCMD_CLEAR\n' > "$UZBL_FIFO" ;; - *XXXRESET_MODEXXX*) + XXXRESET_MODEXXX) # a link was selected, reset uzbl's input mode - print "set mode=\n" > "$UZBL_FIFO" + printf 'set mode=\nevent KEYCMD_CLEAR\n' > "$UZBL_FIFO" ;; esac diff --git a/examples/data/scripts/formfiller.js b/examples/data/scripts/formfiller.js new file mode 100644 index 0000000..abf0162 --- /dev/null +++ b/examples/data/scripts/formfiller.js @@ -0,0 +1,67 @@ +uzbl.formfiller = { + + dump: function() { + var rv = ''; + var allFrames = new Array(window); + for ( f=0; f<window.frames.length; ++f ) { + allFrames.push(window.frames[f]); + } + for ( j=0; j<allFrames.length; ++j ) { + try { + var xp_res = allFrames[j].document.evaluate( + '//input', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null + ); + var input; + while ( input = xp_res.iterateNext() ) { + var type = (input.type?input.type:text); + if ( type == 'text' || type == 'password' || type == 'search' ) { + rv += '%' + escape(input.name) + '(' + type + '):' + input.value + '\n'; + } + else if ( type == 'checkbox' || type == 'radio' ) { + rv += '%' + escape(input.name) + '(' + type + '){' + escape(input.value) + '}:' + (input.checked?'1':'0') + '\n'; + } + } + xp_res = allFrames[j].document.evaluate( + '//textarea', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null + ); + var input; + while ( input = xp_res.iterateNext() ) { + rv += '%' + escape(input.name) + '(textarea):\n' + input.value.replace(/\n%/g,"\n\\%") + '\n%\n'; + } + } + catch (err) { } + } + return 'formfillerstart\n' + rv + '%!end'; + } + + , + + insert: function(fname, ftype, fvalue, fchecked) { + fname = unescape(fname); + var allFrames = new Array(window); + for ( f=0; f<window.frames.length; ++f ) { + allFrames.push(window.frames[f]); + } + for ( j=0; j<allFrames.length; ++j ) { + try { + if ( ftype == 'text' || ftype == 'password' || ftype == 'search' || ftype == 'textarea' ) { + allFrames[j].document.getElementsByName(fname)[0].value = fvalue; + } + else if ( ftype == 'checkbox' ) { + allFrames[j].document.getElementsByName(fname)[0].checked = fchecked; + } + else if ( ftype == 'radio' ) { + fvalue = unescape(fvalue); + var radios = allFrames[j].document.getElementsByName(fname); + for ( r=0; r<radios.length; ++r ) { + if ( radios[r].value == fvalue ) { + radios[r].checked = fchecked; + } + } + } + } + catch (err) { } + } + } + +} diff --git a/examples/data/scripts/formfiller.sh b/examples/data/scripts/formfiller.sh index 17634e2..3dc9dc4 100755 --- a/examples/data/scripts/formfiller.sh +++ b/examples/data/scripts/formfiller.sh @@ -1,198 +1,146 @@ #!/bin/sh # -# Enhanced html form (eg for logins) filler (and manager) for uzbl. -# -# uses settings files like: $UZBL_FORMS_DIR/<domain> -# files contain lines like: !profile=<profile_name> -# <fieldname>(fieldtype): <value> -# profile_name should be replaced with a name that will tell sth about that -# profile -# fieldtype can be checkbox, text or password, textarea - only for information -# pupropse (auto-generated) - don't change that -# -# Texteares: for textareas edited text can be now splitted into more lines. -# If there will be text, that doesn't match key line: -# <fieldname>(fieldtype):<value> -# then it will be considered as a multiline for the first field above it -# Keep in mind, that if you make more than one line for fileds like input -# text fields, then all lines will be inserted into as one line -# -# Checkboxes/radio-buttons: to uncheck it type on of the following after the -# colon: -# no -# off -# 0 -# unchecked -# false -# or leave it blank, even without spaces -# otherwise it will be considered as checked -# -# user arg 1: -# edit: force editing the file (falls back to new if not found) -# new: start with a new file. -# load: try to load from file into form -# add: try to add another profile to an existing file -# once: edit form using external editor -# -# something else (or empty): if file not available: new, otherwise load. +# action +# new: add new profile template (creates file if not found), then edit +# edit: edit file (fall back to 'new' if file not found) +# load: load from file +# once: use temporary file to edit form once +# (empty): if file not available, new; otherwise, load # -DMENU_ARGS="-i" -DMENU_SCHEMA="formfiller" -DMENU_LINES="3" -DMENU_PROMPT="Choose profile" -DMENU_OPTIONS="vertical resize" +action=$1 -. "$UZBL_UTIL_DIR/dmenu.sh" -. "$UZBL_UTIL_DIR/editor.sh" . "$UZBL_UTIL_DIR/uzbl-dir.sh" +. "$UZBL_UTIL_DIR/editor.sh" + +mkdir -p "$UZBL_FORMS_DIR" || exit + +domain=${UZBL_URI#*://} +domain=${domain%%/*} -RAND="$( dd if=/dev/urandom count=1 2>/dev/null | cksum | cut -c 1-5 )" -MODELINE="> vim:ft=formfiller" - -[ -d "$( dirname "$UZBL_FORMS_DIR" )" ] || exit 1 -[ -d "$UZBL_FORMS_DIR" ] || mkdir "$UZBL_FORMS_DIR" || exit 1 - -action="$1" -shift - -domain="$( print "$UZBL_URI\n" | sed -e 's/\(http\|https\):\/\/\([^\/]\+\)\/.*/\2/' )" -form_file="$UZBL_FORMS_DIR/$domain" - -if [ "$action" != 'edit' ] && [ "$action" != 'new' ] && [ "$action" != 'load' ] && [ "$action" != 'add' ] && [ "$action" != 'once' ]; then - action="new" - [ -e "$form_file" ] && action="load" -elif [ "$action" = 'edit' ] && [ ! -e "$form_file" ]; then - action="new" -fi - -dumpFunction="function dump() { \ - var rv=''; \ - var allFrames = new Array(window); \ - for(f=0;f<window.frames.length;f=f+1) { \ - allFrames.push(window.frames[f]); \ - } \ - for(j=0;j<allFrames.length;j=j+1) { \ - try { \ - var xp_res=allFrames[j].document.evaluate('//input', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null); \ - var input; \ - while(input=xp_res.iterateNext()) { \ - var type=(input.type?input.type:text); \ - if(type == 'text' || type == 'password' || type == 'search') { \ - rv += input.name + '(' + type + '):' + input.value + '\\\\n'; \ - } \ - else if(type == 'checkbox' || type == 'radio') { \ - rv += input.name + '{' + input.value + '}(' + type + '):' + (input.checked?'ON':'OFF') + '\\\\n'; \ - } \ - } \ - xp_res=allFrames[j].document.evaluate('//textarea', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null); \ - var input; \ - while(input=xp_res.iterateNext()) { \ - rv += input.name + '(textarea):' + input.value + '\\\\n'; \ - } \ - } \ - catch(err) { } \ - } \ - return rv; \ -};" - -insertFunction="function insert(fname, ftype, fvalue, fchecked) { \ - var allFrames = new Array(window); \ - for(f=0;f<window.frames.length;f=f+1) { \ - allFrames.push(window.frames[f]); \ - } \ - for(j=0;j<allFrames.length;j=j+1) { \ - try { \ - if(ftype == 'text' || ftype == 'password' || ftype == 'search' || ftype == 'textarea') { \ - allFrames[j].document.getElementsByName(fname)[0].value = fvalue; \ - } \ - else if(ftype == 'checkbox') { \ - allFrames[j].document.getElementsByName(fname)[0].checked = fchecked;\ - } \ - else if(ftype == 'radio') { \ - var radios = allFrames[j].document.getElementsByName(fname); \ - for(r=0;r<radios.length;r+=1) { \ - if(radios[r].value == fvalue) { \ - radios[r].checked = fchecked; \ - } \ - } \ - } \ - } \ - catch(err) { } \ - } \ -};" - -if [ "$action" = 'load' ]; then - [ -e "$form_file" ] || exit 2 - if [ "$( grep "!profile" "$form_file" | wc -l )" -gt 1 ]; then - menu="$( sed -n -e 's/^!profile=\([^[:blank:]]\+\)/\1/p' "$form_file" )" - option="$( print "$menu" | $DMENU )" +test "$domain" || exit + +file=$UZBL_FORMS_DIR/$domain + +GenForm () +{ + echo 'js uzbl.formfiller.dump();' \ + | socat - unix-connect:"$UZBL_SOCKET" \ + | awk ' + /^formfillerstart$/ { + while (getline) { + if ( /^%!end/ ) exit + print + } + } + ' +} + +GetOption () +{ + DMENU_SCHEME=formfiller + DMENU_PROMPT="choose profile" + DMENU_LINES=4 + + . "$UZBL_UTIL_DIR/dmenu.sh" + + if [ $(grep -c '^!profile' "$1") -gt 1 ] + then sed -n 's/^!profile=//p' "$1" | $DMENU + else sed -n 's/^!profile=//p' "$1" fi +} - sed -i -e 's/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):\(off\|no\|false\|unchecked\|0\|$\)/\1{\2}(\3):0/I;s/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[^0]\+/\1{\2}(\3):1/I' "$form_file" - fields="$( sed -n -e "/^!profile=${option}/,/^!profile=/p" "$form_file" | \ - sed -e '/^!profile=/d' | \ - sed -e 's/^\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):/%{>\1\2):<}%/' | \ - sed -e 's/^\(.\+\)$/<{br}>\1/' | \ - tr -d '\n' | \ - sed -e 's/<{br}>%{>\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):<}%/\\n\1\2):/g' )" - printf "%s\n" "${fields}" | \ - sed -n -e "s/\([^(]\+\)(\(password\|text\|search\|textarea\)\+):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\2', '\3', 0);/p" | \ - sed -e 's/@/\\@/g;s/<{br}>/\\\\n/g' | socat - "unix-connect:$UZBL_SOCKET" - printf "%s\n" "${fields}" | \ - sed -n -e "s/\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\3', '\2', \4);/p" | \ - sed -e 's/@/\\@/g' | socat - "unix-connect:$UZBL_SOCKET" -elif [ "$action" = "once" ]; then - tmpfile="$( mktemp )" - printf "js %s dump();\n" "$dumpFunction" | \ - socat - "unix-connect:$UZBL_SOCKET" | \ - sed -n -e '/^[^(]\+([^)]\+):/p' > "$tmpfile" - printf "$MODELINE\n" >> "$tmpfile" - $UZBL_EDITOR "$tmpfile" +ParseProfile () +{ + sed "/^>/d; /^!profile=$1$/,/^!/!d; /^!/d" +} + +ParseFields () +{ + awk '/^%/ { + + sub ( /%/, "" ) - [ -e "$tmpfile" ] || exit 2 - - # Remove comments - sed -i -e '/^>/d' "$tmpfile" - - sed -i -e 's/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):\(off\|no\|false\|unchecked\|0\|$\)/\1{\2}(\3):0/I;s/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[^0]\+/\1{\2}(\3):1/I' "$tmpfile" - fields="$( sed -e 's/^\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):/%{>\1\2):<}%/' "$tmpfile" | \ - sed -e 's/^\(.\+\)$/<{br}>\1/' | \ - tr -d '\n' | \ - sed -e 's/<{br}>%{>\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):<}%/\\n\1\2):/g' )" - printf "%s\n" "${fields}" | \ - sed -n -e "s/\([^(]\+\)(\(password\|text\|search\|textarea\)\+):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\2', '\3', 0);/p" | \ - sed -e 's/@/\\@/g;s/<{br}>/\\\\n/g' | socat - "unix-connect:$UZBL_SOCKET" - printf "%s\n" "${fields}" | \ - sed -n -e "s/\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\3', '\2', \4);/p" | \ - sed -e 's/@/\\@/g' | socat - "unix-connect:$UZBL_SOCKET" - rm -f "$tmpfile" -else - if [ "$action" = 'new' -o "$action" = 'add' ]; then - [ "$action" = 'new' ] && echo "$MODELINE" > "$form_file" - print "!profile=NAME_THIS_PROFILE$RAND\n" >> "$form_file" - # - # 2. and 3. line (tr -d and sed) are because, on gmail login for example, - # <input > tag is splited into lines - # ex: - # <input name="Email" - # type="text" - # value=""> - # So, tr removes all new lines, and sed inserts new line after each > - # Next sed selects only <input> tags and only with type = "text" or = "password" - # If type is first and name is second, then another sed will change their order - # so the last sed will make output - # text_from_the_name_attr(text or password): - # - # login(text): - # passwd(password): - # - printf "js %s dump();\n" "$dumpFunction" | \ - socat - "unix-connect:$UZBL_SOCKET" | \ - sed -n -e '/^[^(]\+([^)]\+):/p' >> "$form_file" + split( $0, parts, /\(|\)|\{|\}/ ) + + field = $0 + sub ( /[^:]*:/, "", field ) + + if ( parts[2] ~ /(text|password|search)/ ) + printf( "js uzbl.formfiller.insert(\"%s\",\"%s\",\"%s\",0);\n", + parts[1], parts[2], field ) + + else if ( parts[2] ~ /(checkbox|radio)/ ) + printf( "js uzbl.formfiller.insert(\"%s\",\"%s\",\"%s\",%s);\n", + parts[1], parts[2], parts[3], field ) + + else if ( parts[2] == "textarea" ) { + field = "" + while (getline) { + if ( /^%/ ) break + sub ( /^\\/, "" ) + gsub ( /"/, "\\\"" ) + gsub ( /\\/, "\\\\" ) + field = field $0 "\\n" + } + printf( "js uzbl.formfiller.insert(\"%s\",\"%s\",\"%s\",0);\n", + parts[1], parts[2], field ) + } + + }' +} + +New () +{ + { echo '!profile=NAME_THIS_PROFILE' + GenForm | sed 's/^!/\\!/' + echo '!' + } >> "$file" + chmod 600 "$file" + $UZBL_EDITOR "$file" +} + +Edit () + if [ -e "$file" ] + then $UZBL_EDITOR "$file" + else New fi - [ -e "$form_file" ] || exit 3 #this should never happen, but you never know. - $UZBL_EDITOR "$form_file" #TODO: if user aborts save in editor, the file is already overwritten -fi -# vim:fileencoding=utf-8:sw=4 +Load () +{ + test -e "$file" || exit + + option=$(GetOption "$file") + + case $option in *[!a-zA-Z0-9_-]*) exit 1; esac + + ParseProfile $option < "$file" \ + | ParseFields \ + | sed 's/@/\\@/' \ + > "$UZBL_FIFO" +} + +Once () +{ + tmpfile=/tmp/${0##*/}-$$-tmpfile + trap 'rm -f "$tmpfile"' EXIT + + GenForm > "$tmpfile" + chmod 600 "$tmpfile" + + $UZBL_EDITOR "$tmpfile" + + test -e "$tmpfile" && + ParseFields < "$tmpfile" \ + | sed 's/@/\\@' \ + > "$UZBL_FIFO" +} + +case $action in + new) New; Load ;; + edit) Edit; Load ;; + load) Load ;; + once) Once ;; + '') if [ -e "$file" ]; then Load; else New; Load; fi ;; + *) exit 1 +esac diff --git a/examples/data/scripts/uzbl-cookie-daemon b/examples/data/scripts/uzbl-cookie-daemon deleted file mode 100755 index 0b9bef9..0000000 --- a/examples/data/scripts/uzbl-cookie-daemon +++ /dev/null @@ -1,677 +0,0 @@ -#!/usr/bin/env python - -# The Python Cookie Daemon for Uzbl. -# Copyright (c) 2009, Tom Adams <tom@holizz.com> -# Copyright (c) 2009, Dieter Plaetinck <dieter@plaetinck.be> -# Copyright (c) 2009, Mason Larobina <mason.larobina@gmail.com> -# Copyright (c) 2009, Michael Fiano <axionix@gmail.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -''' -The Python Cookie Daemon -======================== - -This daemon is a re-write of the original cookies.py script found in uzbl's -master branch. This script provides more functionality than the original -cookies.py by adding numerous command line options to specify different cookie -jar locations, socket locations, verbose output, etc. This functionality is -very useful as it allows you to run multiple daemons at once serving cookies -to different groups of uzbl instances as required. - -Keeping up to date -================== - -Check the cookie daemon uzbl-wiki page for more information on where to -find the latest version of the cookie_daemon.py - - http://www.uzbl.org/wiki/cookie_daemon.py - -Command line options -==================== - -Use the following command to get a full list of the cookie_daemon.py command -line options: - - ./cookie_daemon.py --help - -Talking with uzbl -================= - -In order to get uzbl to talk to a running cookie daemon you add the following -to your uzbl config: - - set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket - -Or if you prefer using the $HOME variable: - - set cookie_handler = talk_to_socket $HOME/.cache/uzbl/cookie_daemon_socket - -Todo list -========= - - - Use a pid file to make force killing a running daemon possible. - -Reporting bugs / getting help -============================= - -The best way to report bugs and or get help with the cookie daemon is to -contact the maintainers it the #uzbl irc channel found on the Freenode IRC -network (irc.freenode.org). -''' - -import cookielib -import os -import sys -import urllib2 -import select -import socket -import time -import atexit -from traceback import print_exc -from signal import signal, SIGTERM -from optparse import OptionParser -from os.path import join - -try: - import cStringIO as StringIO - -except ImportError: - import StringIO - - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return join(os.environ['HOME'], default) - -# Setup xdg paths. -CACHE_DIR = join(xdghome('CACHE', '.cache/'), 'uzbl/') -DATA_DIR = join(xdghome('DATA', '.local/share/'), 'uzbl/') -CONFIG_DIR = join(xdghome('CONFIG', '.config/'), 'uzbl/') - -# Ensure data paths exist. -for path in [CACHE_DIR, DATA_DIR, CONFIG_DIR]: - if not os.path.exists(path): - os.makedirs(path) - -# Default config -config = { - - # Default cookie jar, whitelist, and daemon socket locations. - 'cookie_jar': join(DATA_DIR, 'cookies.txt'), - 'cookie_whitelist': join(CONFIG_DIR, 'cookie_whitelist'), - 'cookie_socket': join(CACHE_DIR, 'cookie_daemon_socket'), - - # Don't use a cookie whitelist policy by default. - 'use_whitelist': False, - - # Time out after x seconds of inactivity (set to 0 for never time out). - # WARNING: Do not use this option if you are manually launching the daemon. - 'daemon_timeout': 0, - - # Daemonise by default. - 'daemon_mode': True, - - # Optionally print helpful debugging messages to the terminal. - 'verbose': False, - -} # End of config dictionary. - - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - - -_SCRIPTNAME = os.path.basename(sys.argv[0]) -def echo(msg): - '''Prints only if the verbose flag has been set.''' - - if config['verbose']: - sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) - - -def error(msg): - '''Prints error message and exits.''' - - sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) - sys.exit(1) - - -def mkbasedir(filepath): - '''Create the base directories of the file in the file-path if the dirs - don't exist.''' - - dirname = os.path.dirname(filepath) - if not os.path.exists(dirname): - echo("creating dirs: %r" % dirname) - os.makedirs(dirname) - - -def daemon_running(cookie_socket): - '''Check if another process (hopefully a cookie_daemon.py) is listening - on the cookie daemon socket. If another process is found to be - listening on the socket exit the daemon immediately and leave the - socket alone. If the connect fails assume the socket has been abandoned - and delete it (to be re-created in the create socket function).''' - - if not os.path.exists(cookie_socket): - return False - - if os.path.isfile(cookie_socket): - raise Exception("regular file at %r is not a socket" % cookie_socket) - - - if os.path.isdir(cookie_socket): - raise Exception("directory at %r is not a socket" % cookie_socket) - - try: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) - sock.connect(cookie_socket) - sock.close() - echo("detected daemon listening on %r" % cookie_socket) - return True - - except socket.error: - # Failed to connect to cookie_socket so assume it has been - # abandoned by another cookie daemon process. - if os.path.exists(cookie_socket): - echo("deleting abandoned socket at %r" % cookie_socket) - os.remove(cookie_socket) - - return False - - -def send_command(cookie_socket, cmd): - '''Send a command to a running cookie daemon.''' - - if not daemon_running(cookie_socket): - return False - - try: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) - sock.connect(cookie_socket) - sock.send(cmd) - sock.close() - echo("sent command %r to %r" % (cmd, cookie_socket)) - return True - - except socket.error: - print_exc() - error("failed to send message %r to %r" % (cmd, cookie_socket)) - return False - - -def kill_daemon(cookie_socket): - '''Send the "EXIT" command to running cookie_daemon.''' - - if send_command(cookie_socket, "EXIT"): - # Now ensure the cookie_socket is cleaned up. - start = time.time() - while os.path.exists(cookie_socket): - time.sleep(0.1) - if (time.time() - start) > 5: - error("force deleting socket %r" % cookie_socket) - os.remove(cookie_socket) - return - - echo("stopped daemon listening on %r"% cookie_socket) - - else: - if os.path.exists(cookie_socket): - os.remove(cookie_socket) - echo("removed abandoned/broken socket %r" % cookie_socket) - - -def daemonize(): - '''Daemonize the process using the Stevens' double-fork magic.''' - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #1 failed") - sys.exit(1) - - os.chdir('/') - os.setsid() - os.umask(0) - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #2 failed") - sys.exit(1) - - if sys.stdout.isatty(): - sys.stdout.flush() - sys.stderr.flush() - - devnull = '/dev/null' - stdin = file(devnull, 'r') - stdout = file(devnull, 'a+') - stderr = file(devnull, 'a+', 0) - - os.dup2(stdin.fileno(), sys.stdin.fileno()) - os.dup2(stdout.fileno(), sys.stdout.fileno()) - os.dup2(stderr.fileno(), sys.stderr.fileno()) - - -class CookieMonster: - '''The uzbl cookie daemon class.''' - - def __init__(self): - '''Initialise class variables.''' - - self.server_socket = None - self.jar = None - self.last_request = time.time() - self._running = False - - - def run(self): - '''Start the daemon.''' - - # The check healthy function will exit if another daemon is detected - # listening on the cookie socket and remove the abandoned socket if - # there isnt. - if os.path.exists(config['cookie_socket']): - if daemon_running(config['cookie_socket']): - sys.exit(1) - - # Create cookie daemon socket. - self.create_socket() - - # Daemonize process. - if config['daemon_mode']: - echo("entering daemon mode") - daemonize() - - # Register a function to cleanup on exit. - atexit.register(self.quit) - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) - - # Create cookie jar object from file. - self.open_cookie_jar() - - # Create a way to exit nested loops by setting a running flag. - self._running = True - - while self._running: - try: - # Enter main listen loop. - self.listen() - - except KeyboardInterrupt: - self._running = False - print - - except socket.error: - print_exc() - - except: - # Clean up - self.del_socket() - - # Raise exception - raise - - # Always delete the socket before calling create again. - self.del_socket() - # Create cookie daemon socket. - self.create_socket() - - - def load_whitelist(self): - '''Load the cookie jar whitelist policy.''' - - cookie_whitelist = config['cookie_whitelist'] - - if cookie_whitelist: - mkbasedir(cookie_whitelist) - - # Create cookie whitelist file if it does not exist. - if not os.path.exists(cookie_whitelist): - open(cookie_whitelist, 'w').close() - - # Read cookie whitelist file into list. - file = open(cookie_whitelist,'r') - domain_list = [line.rstrip('\n') for line in file] - file.close() - - # Define policy of allowed domains - policy = cookielib.DefaultCookiePolicy(allowed_domains=domain_list) - self.jar.set_policy(policy) - - # Save the last modified time of the whitelist. - self._whitelistmtime = os.stat(cookie_whitelist).st_mtime - - - def open_cookie_jar(self): - '''Open the cookie jar.''' - - cookie_jar = config['cookie_jar'] - cookie_whitelist = config['cookie_whitelist'] - - if cookie_jar: - mkbasedir(cookie_jar) - - # Create cookie jar object from file. - self.jar = cookielib.MozillaCookieJar(cookie_jar) - - # Load cookie whitelist policy. - if config['use_whitelist']: - self.load_whitelist() - - if cookie_jar: - try: - # Attempt to load cookies from the cookie jar. - self.jar.load(ignore_discard=True) - - # Ensure restrictive permissions are set on the cookie jar - # to prevent other users on the system from hi-jacking your - # authenticated sessions simply by copying your cookie jar. - os.chmod(cookie_jar, 0600) - - except: - pass - - - def reload_whitelist(self): - '''Reload the cookie whitelist.''' - - cookie_whitelist = config['cookie_whitelist'] - if os.path.exists(cookie_whitelist): - echo("reloading whitelist %r" % cookie_whitelist) - self.open_cookie_jar() - - - def create_socket(self): - '''Create AF_UNIX socket for communication with uzbl instances.''' - - cookie_socket = config['cookie_socket'] - mkbasedir(cookie_socket) - - self.server_socket = socket.socket(socket.AF_UNIX, - socket.SOCK_SEQPACKET) - - self.server_socket.bind(cookie_socket) - - # Set restrictive permissions on the cookie socket to prevent other - # users on the system from data-mining your cookies. - os.chmod(cookie_socket, 0600) - - - def listen(self): - '''Listen for incoming cookie PUT and GET requests.''' - - daemon_timeout = config['daemon_timeout'] - echo("listening on %r" % config['cookie_socket']) - - connections = [] - - while self._running: - # This line tells the socket how many pending incoming connections - # to enqueue at once. Raising this number may or may not increase - # performance. - self.server_socket.listen(1) - - r, w, x = select.select([self.server_socket]+connections, [], [], 1) - - for socket in r: - if self.server_socket == socket: - client_socket, _ = socket.accept() - connections.append(client_socket) - else: - if not self.handle_request(socket): - # connection was closed, forget about the client socket - connections.remove(socket) - - self.last_request = time.time() - - if daemon_timeout: - # Checks if the daemon has been idling for too long. - idle = time.time() - self.last_request - if idle > daemon_timeout: - self._running = False - - - def handle_request(self, client_socket): - '''Connection made, now to serve a cookie PUT or GET request.''' - - # Receive cookie request from client. - data = client_socket.recv(8192) - if not data: - return False - - # Cookie argument list in packet is null separated. - argv = data.split("\0") - action = argv[0].upper().strip() - - # Catch the EXIT command sent to kill running daemons. - if action == "EXIT": - self._running = False - return False - - # Catch whitelist RELOAD command. - elif action == "RELOAD": - self.reload_whitelist() - return True - - # Return if command unknown. - elif action not in ['GET', 'PUT']: - error("unknown command %r." % argv) - return True - - # Determine whether or not to print cookie data to terminal. - print_cookie = (config['verbose'] and not config['daemon_mode']) - if print_cookie: - print ' '.join(argv[:4]) - - uri = urllib2.urlparse.ParseResult( - scheme=argv[1], - netloc=argv[2], - path=argv[3], - params='', - query='', - fragment='').geturl() - - req = urllib2.Request(uri) - - if action == "GET": - self.jar._policy._now = self._now = int(time.time()) - cookies = self.jar._cookies_for_request(req) - attrs = self.jar._cookie_attrs(cookies) - if attrs: - cookie = "; ".join(attrs) - client_socket.send(cookie) - if print_cookie: - print cookie - else: - client_socket.send("\0") - - elif action == "PUT": - cookie = argv[4] if len(argv) > 3 else None - if print_cookie: - print cookie - - self.put_cookie(req, cookie) - client_socket.send("\0") - - if print_cookie: - print - - return True - - - def put_cookie(self, req, cookie=None): - '''Put a cookie in the cookie jar.''' - - hdr = urllib2.httplib.HTTPMessage(\ - StringIO.StringIO('Set-Cookie: %s' % cookie)) - res = urllib2.addinfourl(StringIO.StringIO(), hdr, - req.get_full_url()) - self.jar.extract_cookies(res, req) - if config['cookie_jar']: - self.jar.save(ignore_discard=True) - - - def del_socket(self): - '''Remove the cookie_socket file on exit. In a way the cookie_socket - is the daemons pid file equivalent.''' - - if self.server_socket: - try: - self.server_socket.close() - - except: - pass - - self.server_socket = None - - cookie_socket = config['cookie_socket'] - if os.path.exists(cookie_socket): - echo("deleting socket %r" % cookie_socket) - os.remove(cookie_socket) - - - def quit(self): - '''Called on exit to make sure all loose ends are tied up.''' - - self.del_socket() - sys.exit(0) - - -def main(): - '''Main function.''' - - # Define command line parameters. - usage = "usage: %prog [options] {start|stop|restart|reload}" - parser = OptionParser(usage=usage) - parser.add_option('-n', '--no-daemon', dest='no_daemon', - action='store_true', help="don't daemonise the process.") - - parser.add_option('-v', '--verbose', dest="verbose", - action='store_true', help="print verbose output.") - - parser.add_option('-t', '--daemon-timeout', dest='daemon_timeout', - action="store", metavar="SECONDS", help="shutdown the daemon after x "\ - "seconds inactivity. WARNING: Do not use this when launching the "\ - "cookie daemon manually.") - - parser.add_option('-s', '--cookie-socket', dest="cookie_socket", - metavar="SOCKET", help="manually specify the socket location.") - - parser.add_option('-j', '--cookie-jar', dest='cookie_jar', - metavar="FILE", help="manually specify the cookie jar location.") - - parser.add_option('-m', '--memory', dest='memory', action='store_true', - help="store cookies in memory only - do not write to disk") - - parser.add_option('-u', '--use-whitelist', dest='usewhitelist', - action='store_true', help="use cookie whitelist policy") - - parser.add_option('-w', '--cookie-whitelist', dest='whitelist', - action='store', help="manually specify whitelist location", - metavar='FILE') - - # Parse the command line arguments. - (options, args) = parser.parse_args() - - expand = lambda p: os.path.realpath(os.path.expandvars(p)) - - initcommands = ['start', 'stop', 'restart', 'reload'] - for arg in args: - if arg not in initcommands: - error("unknown argument %r" % args[0]) - sys.exit(1) - - if len(args) > 1: - error("the daemon only accepts one {%s} action at a time." - % '|'.join(initcommands)) - sys.exit(1) - - if len(args): - action = args[0] - - else: - action = "start" - - if options.no_daemon: - config['daemon_mode'] = False - - if options.cookie_socket: - config['cookie_socket'] = expand(options.cookie_socket) - - if options.cookie_jar: - config['cookie_jar'] = expand(options.cookie_jar) - - if options.memory: - config['cookie_jar'] = None - - if options.whitelist: - config['cookie_whitelist'] = expand(options.whitelist) - - if options.whitelist or options.usewhitelist: - config['use_whitelist'] = True - - if options.daemon_timeout: - try: - config['daemon_timeout'] = int(options.daemon_timeout) - - except ValueError: - error("expected int argument for -t, --daemon-timeout") - - # Expand $VAR's in config keys that relate to paths. - for key in ['cookie_socket', 'cookie_jar', 'cookie_whitelist']: - if config[key]: - config[key] = os.path.expandvars(config[key]) - - if options.verbose: - config['verbose'] = True - import pprint - sys.stderr.write("%s\n" % pprint.pformat(config)) - - # It would be better if we didn't need to start this python process just - # to send a command to the socket, but unfortunately socat doesn't seem - # to support SEQPACKET. - if action == "reload": - send_command(config['cookie_socket'], "RELOAD") - - if action in ['stop', 'restart']: - kill_daemon(config['cookie_socket']) - - if action in ['start', 'restart']: - CookieMonster().run() - - -if __name__ == "__main__": - main() diff --git a/examples/data/scripts/uzbl-event-manager b/examples/data/scripts/uzbl-event-manager index 8ad3af7..cb462c7 100755 --- a/examples/data/scripts/uzbl-event-manager +++ b/examples/data/scripts/uzbl-event-manager @@ -34,6 +34,7 @@ import socket import sys import time import weakref +import re from collections import defaultdict from functools import partial from glob import glob @@ -169,13 +170,16 @@ class EventHandler(object): self.callback(uzbl, *args, **kwargs) + + + class Plugin(object): '''Plugin module wrapper object.''' # Special functions exported from the Plugin instance to the # plugin namespace. special_functions = ['require', 'export', 'export_dict', 'connect', - 'connect_dict', 'logger'] + 'connect_dict', 'logger', 'unquote', 'splitquoted'] def __init__(self, parent, name, path, plugin): @@ -291,6 +295,20 @@ class Plugin(object): assert plugin in self.parent.plugins, self.logger.critical( 'plugin %r required by plugin %r' (plugin, self.name)) + @classmethod + def unquote(cls, s): + '''Removes quotation marks around strings if any and interprets + \\-escape sequences using `string_escape`''' + if s and s[0] == s[-1] and s[0] in ['"', "'"]: + s = s[1:-1] + return s.encode('utf-8').decode('string_escape').decode('utf-8') + + _splitquoted = re.compile("( |\"(?:\\\\.|[^\"])*?\"|'(?:\\\\.|[^'])*?')") + @classmethod + def splitquoted(cls, text): + '''Splits string on whitespace while respecting quotations''' + return [cls.unquote(p) for p in cls._splitquoted.split(text) if p.strip()] + class Uzbl(object): def __init__(self, parent, child_socket): @@ -969,3 +987,5 @@ if __name__ == "__main__": daemon_actions[action]() logger.debug('process CPU time: %f' % time.clock()) + +# vi: set et ts=4: diff --git a/examples/data/scripts/uzbl-tabbed b/examples/data/scripts/uzbl-tabbed index 1d64436..de71c2c 100755 --- a/examples/data/scripts/uzbl-tabbed +++ b/examples/data/scripts/uzbl-tabbed @@ -472,8 +472,11 @@ class UzblInstance: self.parent.update_gtk_tab_pos() elif var == "status_background": if config['status_background'].strip(): - col = gtk.gdk.color_parse(config['status_background']) - self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) + try: + col = gtk.gdk.color_parse(config['status_background']) + self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) + except ValueError: + pass # got an invalid colour, just ignore it elif var == "tab_titles" or var == "tab_indexes": for tab in self.parent.notebook: self.parent.tabs[tab].title_changed(True) diff --git a/examples/data/style.css b/examples/data/style.css index f9b111e..ff055d1 100644 --- a/examples/data/style.css +++ b/examples/data/style.css @@ -1,25 +1,25 @@ -.uzbl_highlight { background-color: yellow;} -.uzbl_h_first { background-color: lightgreen;} +#uzbl_link_hints > span { + z-index: 1000 !important; -.uzbl_follow { border-style: dotted; - border-width: thin; + background-color: #aaff00 !important; + border: 2px solid #556600 !important; + margin: 0 !important; + padding: 1px !important; + + color: black !important; + font-size: 9px !important; + line-height: 9px !important; + font-weight: bold !important; + font-variant: normal !important; + text-decoration: none !important; + + -webkit-transform: translate(-5px,-5px); + /* opacity: 0.7; */ } -#uzbl_hint > div { - display: inline; - border: 2px solid #4a6600; - background-color: #b9ff00; - color: black; - font-size: 9px; - font-weight: bold; - line-height: 9px; - margin: 0px; - padding: 0px; - position: absolute; - z-index: 1000; - -webkit-border-radius: 6px; - text-decoration: none; - -wekit-transform: scale(1) rotate(0deg) translate(-6px,-5px); +/* we can have different colours for different types of hints! */ +#uzbl_link_hints.new-window > span { + background-color: #ffff00 !important; } /* vim:set et ts=4: */ diff --git a/examples/uzbl-cookie-manager.c b/examples/uzbl-cookie-manager.c deleted file mode 100644 index 70addf3..0000000 --- a/examples/uzbl-cookie-manager.c +++ /dev/null @@ -1,381 +0,0 @@ -#define _POSIX_SOURCE - -#include <stdio.h> -#include <errno.h> -#include <string.h> -#include <ctype.h> -#include <signal.h> - -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <sys/select.h> -#include <sys/unistd.h> - -#include <sys/stat.h> -#include <sys/file.h> -#include <fcntl.h> -#include <stdlib.h> - -#include <libsoup/soup-cookie.h> -#include <libsoup/soup-cookie-jar-text.h> -#include <libsoup/soup-uri.h> - -#include "../src/util.h" - -extern const XDG_Var XDG[]; - -int verbose = 0; - -#define SOCK_BACKLOG 10 -#define MAX_COOKIE_LENGTH 4096 - -char cookie_buffer[MAX_COOKIE_LENGTH]; - -int setup_socket(const char *cookied_socket_path) { - /* delete the cookie socket if it was left behind on a previous run */ - unlink(cookied_socket_path); - - int socket_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); - - if(socket_fd < 0) { - fprintf(stderr, "socket failed (%s)\n", strerror(errno)); - return -1; - } - - struct sockaddr_un sa; - sa.sun_family = AF_UNIX; - strcpy(sa.sun_path, cookied_socket_path); - - if(bind(socket_fd, (struct sockaddr*)&sa, sizeof(sa)) < 0) { - fprintf(stderr, "bind failed (%s)\n", strerror(errno)); - return -1; - } - - if(listen(socket_fd, SOCK_BACKLOG) < 0) { - fprintf(stderr, "listen failed (%s)\n", strerror(errno)); - return -1; - } - - return socket_fd; -} - -const char *whitelist_path = NULL; -GPtrArray *whitelisted_hosts = NULL; -time_t whitelist_update_time = 0; - -void whitelist_line_cb(const gchar* line, void *user_data) { - (void) user_data; - - gchar *norm_host; - - const gchar *p = line; - while(isspace(*p)) - p++; - - if(p[0] == '#' || !p[0]) /* ignore comments and blank lines */ - return; - - if(p[0] == '.') - norm_host = g_strdup(p); - else - norm_host = g_strconcat(".", p, NULL); - - g_ptr_array_add(whitelisted_hosts, g_strchomp(norm_host)); -} - -gboolean load_whitelist(const char *whitelist_path) { - if(!file_exists(whitelist_path)) - return FALSE; - - /* check if the whitelist file was updated */ - struct stat f; - if(stat(whitelist_path, &f) < 0) - return FALSE; - - if(whitelisted_hosts == NULL) - whitelisted_hosts = g_ptr_array_new(); - - if(f.st_mtime > whitelist_update_time) { - /* the file was updated, reload the whitelist */ - if(verbose) puts("reloading whitelist"); - while(whitelisted_hosts->len > 0) { - g_free(g_ptr_array_index(whitelisted_hosts, 0)); - g_ptr_array_remove_index_fast(whitelisted_hosts, 0); - } - for_each_line_in_file(whitelist_path, whitelist_line_cb, NULL); - whitelist_update_time = f.st_mtime; - } - - return TRUE; -} - -gboolean should_save_cookie(const char *host) { - if(!load_whitelist(whitelist_path)) - return TRUE; /* some error with the file, assume no whitelist */ - - /* we normalize the hostname so it has a . in front like the whitelist entries */ - gchar *test_host = (host[0] == '.') ? g_strdup(host) : g_strconcat(".", host, NULL); - int hl = strlen(test_host); - - /* test against each entry in the whitelist */ - gboolean result = FALSE; - guint i; - for(i = 0; i < whitelisted_hosts->len; i++) { - /* a match means the host ends with (or is equal to) the whitelist entry */ - const gchar *entry = g_ptr_array_index(whitelisted_hosts, i); - int el = strlen(entry); - result = (el <= hl) && !strcmp(test_host + (hl - el), entry); - - if(result) - break; - } - - g_free(test_host); - - return result; -} - -void handle_request(SoupCookieJar *j, const char *buff, int len, int fd) { - const char *command = buff; - - const char *scheme = command + strlen(command) + 1; - if((scheme - buff) > len) { - fprintf(stderr, "got malformed or partial request\n"); - return; - } - - const char *host = scheme + strlen(scheme) + 1; - if((host - buff) > len) { - fprintf(stderr, "got malformed or partial request\n"); - return; - } - - const char *path = host + strlen(host) + 1; - if((path - buff) > len) { - fprintf(stderr, "got malformed or partial request\n"); - return; - } - - /* glue the parts back together into a SoupURI */ - char *u = g_strconcat(scheme, "://", host, path, NULL); - if(verbose) printf("%s %s\n", command, u); - SoupURI *uri = soup_uri_new(u); - g_free(u); - - if(!strcmp(command, "GET")) { - char *result = soup_cookie_jar_get_cookies(j, uri, TRUE); - if(result) { - if(verbose) puts(result); - if(write(fd, result, strlen(result)+1) < 0) - fprintf(stderr, "write failed (%s)", strerror(errno)); - - g_free(result); - } else { - if(verbose) puts("-"); - if(write(fd, "", 1) < 0) - fprintf(stderr, "write failed (%s)", strerror(errno)); - } - } else if(!strcmp(command, "PUT")) { - const char *name_and_val = path + strlen(path) + 1; - if((name_and_val - buff) > len) { - fprintf(stderr, "got malformed or partial request\n"); - return; - } - - if(verbose) puts(name_and_val); - - if(should_save_cookie(host)) { - char *eql = strchr(name_and_val, '='); - eql[0] = 0; - - const char *name = name_and_val; - const char *value = eql + 1; - - SoupCookie *cookie = soup_cookie_new(name, value, host, path, SOUP_COOKIE_MAX_AGE_ONE_YEAR); - - soup_cookie_jar_add_cookie(j, cookie); - } else if(verbose) - puts("no, blacklisted."); - - if(write(fd, "", 1) < 0) - fprintf(stderr, "write failed (%s)", strerror(errno)); - } - - soup_uri_free(uri); -} - -void -wait_for_things_to_happen_and_then_do_things(SoupCookieJar* j, int cookie_socket) { - GArray *connections = g_array_new (FALSE, FALSE, sizeof (int)); - - while(1) { - unsigned int i; - int r; - fd_set fs; - - int maxfd = cookie_socket; - FD_ZERO(&fs); - FD_SET(maxfd, &fs); - - for(i = 0; i < connections->len; i++) { - int fd = g_array_index(connections, int, i); - if(fd > maxfd) maxfd = fd; - FD_SET(fd, &fs); - } - - r = select(maxfd+1, &fs, NULL, NULL, NULL); - if(r < 0) { - fprintf(stderr, "select failed (%s)\n", strerror(errno)); - continue; - } - - if(FD_ISSET(cookie_socket, &fs)) { - /* handle new connection */ - int fd = accept(cookie_socket, NULL, NULL); - g_array_append_val(connections, fd); - if(verbose) puts("got connection."); - } - - for(i = 0; i < connections->len; i++) { - /* handle activity on a connection */ - int fd = g_array_index(connections, int, i); - if(FD_ISSET(fd, &fs)) { - r = read(fd, cookie_buffer, MAX_COOKIE_LENGTH); - if(r < 0) { - fprintf(stderr, "read failed (%s)\n", strerror(errno)); - continue; - } else if(r == 0) { - if(verbose) puts("client hung up."); - g_array_remove_index(connections, i); - i--; /* other elements in the array are moved down to fill the gap */ - continue; - } - cookie_buffer[r] = 0; - - handle_request(j, cookie_buffer, r, fd); - } - } - } -} - -void usage(const char *progname) { - printf("%s [-s socket-path] [-f cookies.txt] [-w whitelist-file] [-n] [-v]\n", progname); - puts("\t-n\tdon't daemonise the process"); - puts("\t-v\tbe verbose"); -} - -void daemonise() { - int r = fork(); - - if(r < 0) { - fprintf(stderr, "fork failed (%s)", strerror(errno)); - exit(1); - } else if (r > 0) { - /* this is the parent, which has done its job */ - exit(0); - } - - if(setsid() < 0) { - fprintf(stderr, "setsid failed (%s)", strerror(errno)); - exit(1); - } -} - -const char *pid_file_path = NULL; -const char *cookied_socket_path = NULL; - -void cleanup_after_signal(int signal) { - (void) signal; - unlink(pid_file_path); - unlink(cookied_socket_path); - exit(0); -} - -int main(int argc, char *argv[]) { - int i; - - const char *cookies_txt_path = NULL; - gboolean foreground = FALSE; - - for(i = 1; i < argc && argv[i][0] == '-'; i++) { - switch(argv[i][1]) { - case 's': - cookied_socket_path = argv[++i]; - break; - case 'f': - cookies_txt_path = argv[++i]; - break; - case 'w': - whitelist_path = argv[++i]; - break; - case 'n': - foreground = TRUE; - break; - case 'v': - verbose = 1; - break; - default: - usage(argv[0]); - return 1; - } - } - - if(!foreground) - daemonise(); - - if(!cookies_txt_path) - cookies_txt_path = g_strconcat(get_xdg_var(XDG[1]), "/uzbl/cookies.txt", NULL); - - if(!cookied_socket_path) - cookied_socket_path = g_strconcat(get_xdg_var(XDG[2]), "/uzbl/cookie_daemon_socket", NULL); - - if(!whitelist_path) - whitelist_path = g_strconcat(get_xdg_var(XDG[0]), "/uzbl/cookie_whitelist", NULL); - - /* write out and lock the pid file. - * this ensures that only one uzbl-cookie-manager is running per-socket. - * (we should probably also lock the cookies.txt to prevent accidents...) */ - pid_file_path = g_strconcat(cookied_socket_path, ".pid", NULL); - int lockfd = open(pid_file_path, O_RDWR|O_CREAT, 0600); - if(lockfd < 0) { - fprintf(stderr, "couldn't open pid file %s (%s)\n", pid_file_path, strerror(errno)); - return 1; - } - - if(flock(lockfd, LOCK_EX|LOCK_NB) < 0) { - fprintf(stderr, "couldn't lock pid file %s (%s)\n", pid_file_path, strerror(errno)); - fprintf(stderr, "uzbl-cookie-manager is probably already running\n"); - return 1; - } - - gchar* pids = g_strdup_printf("%d\n", getpid()); - write(lockfd, pids, strlen(pids)); - g_free(pids); - - struct sigaction sa; - sa.sa_handler = cleanup_after_signal; - if(sigaction(SIGINT, &sa, NULL) || sigaction(SIGTERM, &sa, NULL)) { - fprintf(stderr, "sigaction failed (%s)\n", strerror(errno)); - return 1; - } - - if(!foreground) { - /* close STDIO */ - close(0); - close(1); - close(2); - } - - g_type_init(); - - SoupCookieJar *j = soup_cookie_jar_text_new(cookies_txt_path, FALSE); - - int cookie_socket = setup_socket(cookied_socket_path); - if(cookie_socket < 0) - return 1; - - wait_for_things_to_happen_and_then_do_things(j, cookie_socket); - - return 0; -} |