diff options
-rw-r--r-- | AUTHORS | 3 | ||||
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | README | 50 | ||||
-rw-r--r-- | examples/config/config | 3 | ||||
-rwxr-xr-x | examples/data/scripts/auth.py | 53 | ||||
-rw-r--r-- | examples/data/scripts/follow.js | 7 | ||||
-rw-r--r-- | examples/data/scripts/follower.js | 420 | ||||
-rw-r--r-- | src/callbacks.c | 32 | ||||
-rw-r--r-- | src/callbacks.h | 6 | ||||
-rw-r--r-- | src/events.c | 3 | ||||
-rw-r--r-- | src/events.h | 1 | ||||
-rw-r--r-- | src/uzbl-core.c | 72 | ||||
-rw-r--r-- | src/uzbl-core.h | 9 | ||||
-rw-r--r-- | tests/test-expand.c | 6 |
14 files changed, 657 insertions, 16 deletions
@@ -37,6 +37,7 @@ In alphabetical order: Evgeny Grablyk - libsoup settings Helmut Grohne (helmut) - move void **ptr to union, various fixes Henri Kemppainen (DuClare) <email is akarinotengoku AT THE DOMAIN OF gmail.com> - many contributions, mostly old handler code + Igor Bogomazov - mouse ptr events Jake Probst <jake.probst@gmail.com> - uzbl_tabbed: multiline tablist James S Wheaton (uranther) - zoom level, test framework Jan Kolkmeier (jouz) - scrolling, link following @@ -50,7 +51,7 @@ In alphabetical order: Moritz Lenz - small doc fix Nicolas Pouillard - refactored scroll command Olivier Schwander - auto file:// prepend - Paweł Zuzelski (pawelz) - download handler proxy patch + Paweł Zuzelski (pawelz) - http auth handler, misc patches Peter Suschlik - backwards searching Přemysl Hrubý (anydot) <email is dfenze AT gmail.com> - several C contributions and cleanups Robert Manea (robm) <email is rob DOT manea AT gmail DOT com> - C code all over the place @@ -34,9 +34,6 @@ uzbl-core: ${TOBJ} # why doesn't ${OBJ} work? @echo LINKING object files @${CC} -o $@ ${OBJ} ${LDFLAGS} @echo ... done. - @echo Stripping binary - @strip $@ - @echo ... done. uzbl-browser: uzbl-core @@ -94,6 +91,11 @@ clean: cd ./tests/; $(MAKE) clean rm -rf ./sandbox/ +strip: + @echo Stripping binary + @strip uzbl-core + @echo ... done. + install: install-uzbl-core install-uzbl-browser install-uzbl-tabbed install-uzbl-core: all @@ -513,9 +513,59 @@ The script specific arguments are: - `$8 URI` of the page to be navigated to +* authentication handler: + + $8 authentication zone unique identifier + $9 domain part of URL that requests authentication + $10 authentication realm + $11 FALSE if this is the first attempt to authenticate, TRUE otherwise + Custom, userdefined scripts (`spawn foo bar`) get first the arguments as specified in the config and then the above 7 are added at the end. +### HTTP/BASIC AUTHENTICATION + +You can use the authentication_handler variable to denote how http +authentication should be handled. +If this variable is: + +* not set or empty: use webkit internal auth dialog +* a valid handler (i.e. {sh,sync}_spawn correct_script), use this handler +* innvalid handler (spawn, some other command, uses script that does not + print anything): skip authentication. + +Example: + + set authentication_handler = sync_spawn /patch/to/your/script + +Script will be executed on each authentication request. +It will receive four auth-related parameters: + + $8 authentication zone unique identifier (may be used as 'key') + $9 domain part of URL that requests authentication + $10 authentication realm + $11 FALSE if this is the first attempt to authenticate, TRUE otherwise + +Script is expected to print exactly two lines of text on stdout (that means +its output must contain exactly two '\n' bytes). +The first line contains username, the second one - password. +If authentication fails, script will be executed again (with $11 = TRUE). +Non-interactive scripts should handle this case and do not try to +authenticate again to avoid loops. If number of '\n' characters in scripts +output does not equal 2, authentication will fail. +That means 401 error will be displayed and uzbl won't try to authenticate anymore. + +The simplest example of authentication handler script is: + +#!/bin/sh +[ "$11" == "TRUE ] && exit +echo alice +echo wonderland + +This script tries to authenticate as user alice with password wonderland once +and never retries authentication. +See examples for more sofisticated, interactive authentication handler. + ### JAVASCRIPT HELPER OBJECT DISABLED BECAUSE OF SECURITY LEAK JavaScript code run from `uzbl` is given a special object in the global diff --git a/examples/config/config b/examples/config/config index eb30cc1..4c63fe7 100644 --- a/examples/config/config +++ b/examples/config/config @@ -43,6 +43,7 @@ set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data:scrip # support events that can wait for a response from a script. set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket set scheme_handler = sync_spawn @scripts_dir/scheme.py +set authentication_handler = sync_spawn @scripts_dir/auth.py # Open in the same window. #set new_window = sh 'echo uri "$8" > $4' @@ -313,7 +314,7 @@ set follow_hint_keys = 0123456789 #set follow_hint_keys = qwerty #set follow_hint_keys = asdfghjkl; #set follow_hint_keys = thsnd-rcgmvwb/;789aefijkopquxyz234 -@cbind fl* = script @scripts_dir/follow.js '%s @{follow_hint_keys}' +@cbind fl* = script @scripts_dir/follow.js '@follow_hint_keys %s' # --- Form filler binds --- # this script allows you to configure (per domain) values to fill in form diff --git a/examples/data/scripts/auth.py b/examples/data/scripts/auth.py new file mode 100755 index 0000000..4feb90b --- /dev/null +++ b/examples/data/scripts/auth.py @@ -0,0 +1,53 @@ +#!/usr/bin/python + +import gtk +import sys + +def responseToDialog(entry, dialog, response): + dialog.response(response) + +def getText(authInfo, authHost, authRealm): + dialog = gtk.MessageDialog( + None, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_QUESTION, + gtk.BUTTONS_OK_CANCEL, + None) + dialog.set_markup('%s at %s' % (authRealm, authHost)) + + login = gtk.Entry() + password = gtk.Entry() + password.set_visibility(False) + + login.connect("activate", responseToDialog, dialog, gtk.RESPONSE_OK) + password.connect("activate", responseToDialog, dialog, gtk.RESPONSE_OK) + + hbox = gtk.HBox(); + + vbox_entries = gtk.VBox(); + vbox_labels = gtk.VBox(); + + vbox_labels.pack_start(gtk.Label("Login:"), False, 5, 5) + vbox_labels.pack_end(gtk.Label("Password:"), False, 5, 5) + + vbox_entries.pack_start(login) + vbox_entries.pack_end(password) + + dialog.format_secondary_markup("Please enter username and password:") + hbox.pack_start(vbox_labels, True, True, 0) + hbox.pack_end(vbox_entries, True, True, 0) + + dialog.vbox.pack_start(hbox) + dialog.show_all() + rv = dialog.run() + + output = login.get_text() + "\n" + password.get_text() + dialog.destroy() + return rv, output + +if __name__ == '__main__': + rv, output = getText(sys.argv[8], sys.argv[9], sys.argv[10]) + if (rv == gtk.RESPONSE_OK): + print output; + else: + exit(1) diff --git a/examples/data/scripts/follow.js b/examples/data/scripts/follow.js index 12b3765..afb327f 100644 --- a/examples/data/scripts/follow.js +++ b/examples/data/scripts/follow.js @@ -263,8 +263,7 @@ function followLinks(follow) { } } -//Parse input: first argument is user input, second is defined hint keys. +//Parse input: first argument is follow keys, second is user input. var args = '%s'.split(' '); -var charset = args[1]; - -followLinks(args[0]); +var charset = args[0]; +followLinks(args[1]); diff --git a/examples/data/scripts/follower.js b/examples/data/scripts/follower.js new file mode 100644 index 0000000..604b779 --- /dev/null +++ b/examples/data/scripts/follower.js @@ -0,0 +1,420 @@ +// A Link Follower for Uzbl. +// P.C. Shyamshankar <sykora@lucentbeing.com> +// +// WARNING: this script depends on the Uzbl object which is now disabled for +// WARNING security reasons. So the script currently doesn't work but it's +// WARNING interesting nonetheless +// +// Based extensively (like copy-paste) on the follow_numbers.js and +// linkfollow.js included with uzbl, but modified to be more customizable and +// extensible. +// +// Usage +// ----- +// +// First, you'll need to make sure the script is loaded on each page. This can +// be done with: +// +// @on_event LOAD_COMMIT script /path/to/follower.js +// +// Then you can bind it to a key: +// +// @bind f* = js follower.follow('%s', matchSpec, handler, hintStyler) +// +// where matchSpec, handler and hintStyler are parameters which control the +// operation of follower. If you don't want to customize any further, you can +// set these to follower.genericMatchSpec, follower.genericHandler and +// follower.genericHintStyler respectively. +// +// For example, +// +// @bind f* = js follower.follow('%s', follower.genericMatchSpec, follower.genericHandler, follower.genericHintStyler) +// @bind F* = js follower.follow('%s', follower.onlyLinksMatchSpec, follower.newPageHandler, follower.newPageHintStyler) +// +// In order to make hints disappear when pressing a key (the Escape key, for +// example), you can do this: +// +// @bind <Escape> = js follower.clearHints() +// +// If your Escape is already bound to something like command mode, chain it. +// +// Alternatively, you can tell your <Escape> key to emit an event, and handle +// that instead. +// +// @bind <Escape> = event ESCAPE +// @on_event ESCAPE js follower.clearHints() +// +// Customization +// ------------- +// +// If however you do want to customize, 3 Aspects of the link follower can be +// customized with minimal pain or alteration to the existing code base: +// +// * What elements are hinted. +// * The style of the hints displayed. +// * How the hints are handled. +// +// In order to customize behavior, write an alternative, and pass that in to +// follower.follow invocation. You _will_ have to modify this script, but only +// locally, it beats having to copy the entire script under a new name and +// modify. +// +// TODO: +// * Whatever all the other TODOs in the file say. +// * Find out how to do default arguments in Javascript. +// * Abstract out the hints into a Hint object, make hintables a list of hint +// objects instead of two lists. + +// Helpers +String.prototype.lpad = function(padding, length) { + var padded = this; + while (padded.length < length) { + padded = padding + padded; + } + + return padded; +} + +function Follower() { + + // Globals + var uzblID = 'uzbl-follow'; // ID to apply to each hint. + var uzblContainerID = 'uzbl-follow-container'; // ID to apply to the div containing hints. + + // Translation table, used to display something other than numbers as hint + // labels. Typically set to the ten keys of the home row. + // + // Must have exactly 10 elements. + // + // I haven't parameterized this, to make it customizable. Should I? Do + // people really use more than one set of keys at a time? + var translation = ["a", "r", "s", "t", "d", "h", "n", "e", "i", "o"]; + + // MatchSpecs + // These are XPath expressions which indicate which elements will be hinted. + // Use multiple expressions for different situations, like hinting only form + // elements, or only links, etc. + // + // TODO: Check that these XPath expressions are correct, and optimize/make + // them more elegant. Preferably by someone who actually knows XPath, unlike + // me. + + // Vimperator default (copy-pasted, I never used vimperator). + this.genericMatchSpec = " //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select"; + + // Matches only links, suitable for opening in a new instance (I think). + this.onlyLinksMatchSpec = " //*[@href] | //a | //area"; + + // Follow Handlers + // These decide how an element should be 'followed'. The handler is passed + // the element in question. + + // Generic Handler, opens links in the same instance, emits the FORM_ACTIVE + // event if a form element was chosen. Also clears the keycmd. + this.genericHandler = function(node) { + if (node) { + if (window.itemClicker != undefined) { + window.itemClicker(node); + } else { + var tag = node.tagName.toLowerCase(); + if (tag == 'a') { + node.click(); + window.location = node.href; + } else if (tag == 'input') { + var inputType = node.getAttribute('type'); + if (inputType == undefined) + inputType = 'text'; + + inputType = inputType.toLowerCase(); + + if (inputType == 'text' || inputType == 'file' || inputType == 'password') { + node.focus(); + node.select(); + } else { + node.click(); + } + Uzbl.run("event FORM_ACTIVE"); + } else if (tag == 'textarea'|| tag == 'select') { + node.focus(); + node.select(); + Uzbl.run("event FORM_ACTIVE"); + } else { + node.click(); + if ((node.href != undefined) && node.href) + window.location = node.href; + } + } + } + Uzbl.run("event SET_KEYCMD"); + } + + // Handler to open links in a new page. The rest is the same as before. + this.newPageHandler = function(node) { + if (node) { + if (window.itemClicker != undefined) { + window.itemClicker(node); + } else { + var tag = node.tagName.toLowerCase(); + if (tag == 'a') { + node.click(); + Uzbl.run("@new_window " + node.href); + } else if (tag == 'input') { + var inputType = node.getAttribute('type'); + if (inputType == undefined) + inputType = 'text'; + + inputType = inputType.toLowerCase(); + + if (inputType == 'text' || inputType == 'file' || inputType == 'password') { + node.focus(); + node.select(); + } else { + node.click(); + } + Uzbl.run("event FORM_ACTIVE"); + } else if (tag == 'textarea'|| tag == 'select') { + node.focus(); + node.select(); + Uzbl.run("event FORM_ACTIVE"); + } else { + node.click(); + if ((node.href != undefined) && node.href) + window.location = node.href; + } + } + } + Uzbl.run("event SET_KEYCMD"); + }; + + // Hint styling. + // Pretty much any attribute of the hint object can be modified here, but it + // was meant to change the styling. Useful to differentiate between hints + // with different handlers. + // + // Hint stylers are applied at the end of hint creation, so that they + // override the defaults. + + this.genericHintStyler = function(hint) { + hint.style.backgroundColor = '#AAAAAA'; + hint.style.border = '2px solid #4A6600'; + hint.style.color = 'black'; + hint.style.fontSize = '10px'; + hint.style.fontWeight = 'bold'; + hint.style.lineHeight = '12px'; + return hint; + }; + + this.newPageHintStyler = function(hint) { + hint.style.backgroundColor = '#FFCC00'; + hint.style.border = '2px solid #4A6600'; + hint.style.color = 'black'; + hint.style.fontSize = '10px'; + hint.style.fontWeight = 'bold'; + hint.style.lineHeight = '12px'; + return hint; + }; + + // Beyond lies a jungle of pasta and verbosity. + + // Translate a numeric label using the translation table. + function translate(digitLabel, translationTable) { + translatedLabel = ''; + for (var i = 0; i < digitLabel.length; i++) { + translatedLabel += translationTable[digitLabel.charAt(i)]; + } + + return translatedLabel; + } + + function computeElementPosition(element) { + var up = element.offsetTop; + var left = element.offsetLeft; + var width = element.offsetWidth; + var height = element.offsetHeight; + + while (element.offsetParent) { + element = element.offsetParent; + up += element.offsetTop; + left += element.offsetLeft; + } + + return {up: up, left: left, width: width, height: height}; + } + + // Pretty much copy-pasted from every other link following script. + function isInViewport(element) { + offset = computeElementPosition(element); + + var up = offset.up; + var left = offset.left; + var width = offset.width; + var height = offset.height; + + return up < window.pageYOffset + window.innerHeight && + left < window.pageXOffset + window.innerWidth && + (up + height) > window.pageYOffset && + (left + width) > window.pageXOffset; + } + + function isVisible(element) { + if (element == document) { + return true; + } + + if (!element){ + return false; + } + + if (element.style) { + if (element.style.display == 'none' || element.style.visibiilty == 'hidden') { + return false; + } + } + + return isVisible(element.parentNode); + } + + function generateHintContainer() { + var container = document.getElementById(uzblContainerID); + if (container) { + container.parentNode.removeChild(container); + } + + container = document.createElement('div'); + container.id = uzblContainerID; + + if (document.body) { + document.body.appendChild(container); + } + return container; + } + + // Generate everything that is to be hinted, as per the given matchSpec. + // hintables[0] refers to the items, hintables[1] to their labels. + function generateHintables(matchSpec) { + var hintables = [[], []]; + + var itemsFromXPath = document.evaluate(matchSpec, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + for (var i = 0; i < itemsFromXPath.snapshotLength; ++i) { + var element = itemsFromXPath.snapshotItem(i); + if (element && isVisible(element) && isInViewport(element)) { + hintables[0].push(element); + } + } + + // Assign labels to each hintable. Can't be combined with the previous + // step, because we didn't know how many there were at that time. + var hintLength = hintables.length; + for (var i = 0; i < hintables[0].length; ++i) { + var code = translate(i.toString(), translation); + hintables[1].push(code.lpad(translation[0], hintLength)); + } + + return hintables; + } + + // Filter the hintables based on input from the user. Makes the screen less + // cluttered after the user has typed some prefix of hint labels. + function filterHintables(hintables, target) { + var filtered = [[], []]; + + var targetPattern = new RegExp("^" + target); + + for (var i = 0; i < hintables[0].length; i++) { + if (hintables[1][i].match(targetPattern)) { + filtered[0].push(hintables[0][i]); + filtered[1].push(hintables[1][i].substring(target.length)); + } + } + + return filtered; + } + + // TODO make this use the container variable from main, instead of searching + // for it? + function clearHints() { + var container = document.getElementById(uzblContainerID); + if (container) { + container.parentNode.removeChild(container); + } + } + + // So that we can offer this as a separate function. + this.clearHints = clearHints; + + function makeHint(node, code, styler) { + var position = computeElementPosition(node); + var hint = document.createElement('div'); + + hint.name = uzblID; + hint.innerText = code; + hint.style.display = 'inline'; + + hint.style.margin = '0px'; + hint.style.padding = '1px'; + hint.style.position = 'absolute'; + hint.style.zIndex = '10000'; + + hint.style.left = position.left + 'px'; + hint.style.top = position.up + 'px'; + + var img = node.getElementsByTagName('img'); + if (img.length > 0) { + hint.style.left = position.left + img[0].width / 2 + 'px'; + } + + hint.style.textDecoration = 'none'; + hint.style.webkitBorderRadius = '6px'; + hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px, -5px)'; + + hint = styler(hint); // So that custom hint stylers can override the above. + return hint; + } + + + function drawHints(container, hintables, styler) { + for (var i = 0; i < hintables[0].length; i++) { + hint = makeHint(hintables[0][i], hintables[1][i], styler); + container.appendChild(hint); + } + + if (document.body) { + document.body.appendChild(container); + } + } + + // The main hinting function. I don't know how to do default values to + // functions, so all arguments must be specified. Use generics if you must. + this.follow = function(target, matchSpec, handler, hintStyler) { + var container = generateHintContainer(); // Get a container to hold all hints. + var allHintables = generateHintables(matchSpec); // Get all items that can be hinted. + hintables = filterHintables(allHintables, target); // Filter them based on current input. + + clearHints(); // Clear existing hints, if any. + + if (hintables[0].length == 0) { + // Nothing was hinted, user pressed an unknown key, maybe? + // Do nothing. + } else if (hintables[0].length == 1) { + handler(hintables[0][0]); // Only one hint remains, handle it. + } else { + drawHints(container, hintables, hintStyler); // Draw whatever hints remain. + } + + return; + }; +} + +// Make on-click links clickable. +try { + HTMLElement.prototype.click = function() { + if (typeof this.onclick == 'function') { + this.onclick({ + type: 'click' + }); + } + }; +} catch(e) {} + +follower = new Follower(); diff --git a/src/callbacks.c b/src/callbacks.c index 9130f5f..6ec48ea 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -27,6 +27,25 @@ set_proxy_url() { } void +set_authentication_handler() { + /* Check if WEBKIT_TYPE_SOUP_AUTH_DIALOG feature is set */ + GSList *flist = soup_session_get_features (uzbl.net.soup_session, (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG); + guint feature_is_set = g_slist_length(flist); + g_slist_free(flist); + + if (uzbl.behave.authentication_handler == NULL || *uzbl.behave.authentication_handler == NULL) { + if (!feature_is_set) + soup_session_add_feature_by_type + (uzbl.net.soup_session, (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG); + } else { + if (feature_is_set) + soup_session_remove_feature_by_type + (uzbl.net.soup_session, (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG); + } + return; +} + +void set_icon() { if(file_exists(uzbl.gui.icon)) { if (uzbl.gui.main_window) @@ -559,6 +578,19 @@ button_release_cb (GtkWidget* window, GdkEventButton* event) { } gboolean +motion_notify_cb(GtkWidget* window, GdkEventMotion* event, gpointer user_data) { + (void) window; + (void) event; + (void) user_data; + + gchar *details; + details = g_strdup_printf("%.0lf %.0lf %u", event->x, event->y, event->state); + send_event(PTR_MOVE, details, NULL); + + return FALSE; +} + +gboolean navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { (void) web_view; (void) frame; diff --git a/src/callbacks.h b/src/callbacks.h index 882ffa4..43a9192 100644 --- a/src/callbacks.h +++ b/src/callbacks.h @@ -13,6 +13,9 @@ void set_proxy_url(); void +set_authentication_handler(); + +void set_icon(); void @@ -167,6 +170,9 @@ gboolean key_release_cb (GtkWidget* window, GdkEventKey* event); gboolean +motion_notify_cb(GtkWidget* window, GdkEventMotion* event, gpointer user_data); + +gboolean navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data); diff --git a/src/events.c b/src/events.c index acb554c..371c8b8 100644 --- a/src/events.c +++ b/src/events.c @@ -43,7 +43,8 @@ const char *event_table[LAST_EVENT] = { "FILE_INCLUDED" , "PLUG_CREATED" , "COMMAND_ERROR" , - "BUILTINS" + "BUILTINS" , + "PTR_MOVE" }; void diff --git a/src/events.h b/src/events.h index 7b8f58b..b02a43c 100644 --- a/src/events.h +++ b/src/events.h @@ -14,6 +14,7 @@ enum event_type { LINK_UNHOVER, FORM_ACTIVE, ROOT_ACTIVE, FOCUS_LOST, FOCUS_GAINED, FILE_INCLUDED, PLUG_CREATED, COMMAND_ERROR, BUILTINS, + PTR_MOVE, /* must be last entry */ LAST_EVENT diff --git a/src/uzbl-core.c b/src/uzbl-core.c index da61093..9168597 100644 --- a/src/uzbl-core.c +++ b/src/uzbl-core.c @@ -101,6 +101,7 @@ const struct var_name_to_ptr_t { { "forward_keys", PTR_V_INT(uzbl.behave.forward_keys, 1, NULL)}, { "download_handler", PTR_V_STR(uzbl.behave.download_handler, 1, NULL)}, { "cookie_handler", PTR_V_STR(uzbl.behave.cookie_handler, 1, NULL)}, + { "authentication_handler", PTR_V_STR(uzbl.behave.authentication_handler, 1, set_authentication_handler)}, { "new_window", PTR_V_STR(uzbl.behave.new_window, 1, NULL)}, { "scheme_handler", PTR_V_STR(uzbl.behave.scheme_handler, 1, NULL)}, { "fifo_dir", PTR_V_STR(uzbl.behave.fifo_dir, 1, cmd_fifo_dir)}, @@ -2041,6 +2042,7 @@ create_browser () { "signal::key-release-event", (GCallback)key_release_cb, NULL, "signal::button-press-event", (GCallback)button_press_cb, NULL, "signal::button-release-event", (GCallback)button_release_cb, NULL, + "signal::motion-notify-event", (GCallback)motion_notify_cb, NULL, "signal::title-changed", (GCallback)title_change_cb, NULL, "signal::selection-changed", (GCallback)selection_changed_cb, NULL, "signal::load-progress-changed", (GCallback)progress_change_cb, NULL, @@ -2317,6 +2319,63 @@ settings_init () { init_connect_socket(); g_signal_connect_after(n->soup_session, "request-started", G_CALLBACK(handle_cookies), NULL); + g_signal_connect(n->soup_session, "authenticate", G_CALLBACK(handle_authentication), NULL); +} + +void handle_authentication (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data) { + + (void) user_data; + + if(uzbl.behave.authentication_handler && *uzbl.behave.authentication_handler != NULL) { + gchar *info, *host, *realm; + gchar *p; + + soup_session_pause_message(session, msg); + + /* Sanitize strings */ + info = g_strdup(soup_auth_get_info(auth)); + host = g_strdup(soup_auth_get_host(auth)); + realm = g_strdup(soup_auth_get_realm(auth)); + for (p = info; *p; p++) if (*p == '\'') *p = '\"'; + for (p = host; *p; p++) if (*p == '\'') *p = '\"'; + for (p = realm; *p; p++) if (*p == '\'') *p = '\"'; + + GString *s = g_string_new (""); + g_string_printf(s, "'%s' '%s' '%s' '%s'", + info, host, realm, retrying?"TRUE":"FALSE"); + + run_handler(uzbl.behave.authentication_handler, s->str); + + if (uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) { + char *username, *password; + int number_of_endls=0; + + username = uzbl.comm.sync_stdout; + + for (p = uzbl.comm.sync_stdout; *p; p++) { + if (*p == '\n') { + *p = '\0'; + if (++number_of_endls == 1) + password = p + 1; + } + } + + /* If stdout was correct (contains exactly two lines of text) do + * authenticate. */ + if (number_of_endls == 2) + soup_auth_authenticate(auth, username, password); + } + + if (uzbl.comm.sync_stdout) + uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + + soup_session_unpause_message(session, msg); + + g_string_free(s, TRUE); + g_free(info); + g_free(host); + g_free(realm); + } } void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){ @@ -2467,9 +2526,9 @@ initialize(int argc, char *argv[]) { } event_buffer_timeout(10); - uzbl.info.webkit_major = WEBKIT_MAJOR_VERSION; - uzbl.info.webkit_minor = WEBKIT_MINOR_VERSION; - uzbl.info.webkit_micro = WEBKIT_MICRO_VERSION; + uzbl.info.webkit_major = webkit_major_version(); + uzbl.info.webkit_minor = webkit_minor_version(); + uzbl.info.webkit_micro = webkit_micro_version(); uzbl.info.arch = ARCH; uzbl.info.commit = COMMIT; @@ -2483,6 +2542,13 @@ initialize(int argc, char *argv[]) { void load_uri_imp(gchar *uri) { GString* newuri; + + /* Strip leading whitespaces */ + while (*uri) { + if (!isspace(*uri)) break; + uri++; + } + if (g_strstr_len (uri, 11, "javascript:") != NULL) { eval_js(uzbl.gui.web_view, uri, NULL); return; diff --git a/src/uzbl-core.h b/src/uzbl-core.h index 70a383c..e05fcdb 100644 --- a/src/uzbl-core.h +++ b/src/uzbl-core.h @@ -28,6 +28,7 @@ #include <stdio.h> #include <string.h> +#include <ctype.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> @@ -123,6 +124,7 @@ typedef struct { gchar* socket_dir; gchar* download_handler; gchar* cookie_handler; + gchar* authentication_handler; gchar* new_window; gchar* default_font_family; gchar* monospace_font_family; @@ -390,6 +392,13 @@ run_external_js (WebKitWebView * web_view, GArray *argv, GString *result); void eval_js(WebKitWebView * web_view, gchar *script, GString *result); +void +handle_authentication (SoupSession *session, + SoupMessage *msg, + SoupAuth *auth, + gboolean retrying, + gpointer user_data); + void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data); diff --git a/tests/test-expand.c b/tests/test-expand.c index 4dcd82d..7ea3d61 100644 --- a/tests/test-expand.c +++ b/tests/test-expand.c @@ -72,11 +72,11 @@ test_useragent (void) { void test_WEBKIT_VERSION (void) { GString* expected = g_string_new(""); - g_string_append(expected, itos(WEBKIT_MAJOR_VERSION)); + g_string_append(expected, itos(webkit_major_version())); g_string_append(expected, " "); - g_string_append(expected, itos(WEBKIT_MINOR_VERSION)); + g_string_append(expected, itos(webkit_minor_version())); g_string_append(expected, " "); - g_string_append(expected, itos(WEBKIT_MICRO_VERSION)); + g_string_append(expected, itos(webkit_micro_version())); g_assert_cmpstr(expand("@WEBKIT_MAJOR @WEBKIT_MINOR @WEBKIT_MICRO", 0), ==, g_string_free(expected, FALSE)); } |