changeset 697:0179a1a1fd0c

merge with main
author Johannes Hofmann <Johannes.Hofmann@gmx.de>
date Sat, 20 Dec 2008 23:46:06 +0100
parents 40cdd7a90a06 (current diff) b01e80ee7c88 (diff)
children 602ab87f6cd2 04a873b19301
files src/Makefile.am src/form.cc src/html.cc src/html_common.hh
diffstat 21 files changed, 840 insertions(+), 143 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Dec 20 10:11:06 2008 +0100
+++ b/ChangeLog	Sat Dec 20 23:46:06 2008 +0100
@@ -5,7 +5,7 @@
 dillo-2.1
 
 +- Added ipv6 addresses iteration and ipv4 fallback.
-   Patch: James Turner, Jorge Arellano
+   Patch: James Turner, Jorge Arellano Cid
 +- Added support for numeric IPv6 addresses entered into the url bar.
  - Used the URL authority part instead of stripped default port in HTTP query.
    Patches: Justus Winter
@@ -18,12 +18,16 @@
  - Fix: recover page focus when clicking-out of a widget.
  - Fixed a segfault bug in the test/ directory.
  - Set middle click to submit in a new TAB. (Helps to keep form data!)
+ - Added support for the Q element. BUG#343
+ - Cleaned up Html_pop_tag().
    Patches: place (AKA corvid)
 +- Switched SSL-enabled to configure.in (./configure --enable-ssl).
  - Standardised the installation of dpid/dpidrc with auto* tools.
  - Set the ScrollGroup as the resizable widget in downloads dpi.
  - Cleaned up and normalized D_SUN_LEN usage.
    Patches: Jeremy Henty
++- Implemented Basic authentication!
+   Patch: Jeremy Henty, Jorge Arellano Cid
 +- Allowed compilation with older machines by removing a few C99isms.
  - Added use of inttypes.h when stdint.h isn't found.
    Patches: Dan Fandrich
@@ -34,15 +38,17 @@
 +- Made the parser recognize "[^ ]/>"-terminated XML elements.
    Patch: Johannes Hofmann
 +- Added the "middle_click_drags_page" dillorc option.
-   Patch: Jorge Arellano, Thomas Orgis
+   Patch: Jorge Arellano Cid, Thomas Orgis
 +- Set the File menu label to hide when the File menu-button is shown.
  ? Trying a new iconv() test in configure.in.
  - Allowed the rc parser to skip whitespace around the equal sign.
  - Fixed the parser not to call Html_tag_close_* functions twice.
-   Patches: Jorge Arellano
+   Patches: Jorge Arellano Cid
 
 dw
 
++- Moved clicked from ButtonResource to Resource.
+   Patch: place (AKA corvid)
 +- Cleaned up unused code in fltkviewbase.
    Patch: Johannes Hofmann
 
--- a/dpid/Makefile.am	Sat Dec 20 10:11:06 2008 +0100
+++ b/dpid/Makefile.am	Sat Dec 20 23:46:06 2008 +0100
@@ -24,6 +24,6 @@
 sysconf_DATA = dpidrc
 CLEANFILES = $(sysconf_DATA)
 
-dpidrc: dpidrc.in Makefile
-	sed -e 's|[@]libdir[@]|$(libdir)|' dpidrc.in > dpidrc
+dpidrc: $(srcdir)/dpidrc.in Makefile
+	sed -e 's|[@]libdir[@]|$(libdir)|' $(srcdir)/dpidrc.in > dpidrc
 
--- a/dw/fltkcomplexbutton.cc	Sat Dec 20 10:11:06 2008 +0100
+++ b/dw/fltkcomplexbutton.cc	Sat Dec 20 23:46:06 2008 +0100
@@ -87,7 +87,7 @@
     if (pushed()) return 1; // ignore extra pushes on currently-pushed button
     initial_state = state();
     clear_flag(PUSHED);
-    do_callback();
+    /* do_callback(); */
   case DRAG: {
     bool inside = event_inside(rectangle);
     if (inside) {
--- a/dw/fltkviewbase.cc	Sat Dec 20 10:11:06 2008 +0100
+++ b/dw/fltkviewbase.cc	Sat Dec 20 23:46:06 2008 +0100
@@ -205,8 +205,8 @@
                                  getDwButtonState (), event_button ());
       //printf ("PUSH => %s\n", processed ? "true" : "false");
       if (processed) {
-         /* pressed dw content; fltk widgets should no longer have focus */
-         ::fltk::focus(NULL);
+         /* pressed dw content; give focus to the view */
+         ::fltk::focus(this);
       }
       return processed ? true : Group::handle (event);
 
--- a/dw/ui.cc	Sat Dec 20 10:11:06 2008 +0100
+++ b/dw/ui.cc	Sat Dec 20 23:46:06 2008 +0100
@@ -69,6 +69,19 @@
    resource->emitLeave();
 }
 
+bool Embed::buttonPressImpl (core::EventButton *event)
+{
+   bool handled;
+
+   if (event->button == 3) {
+      resource->emitClicked(event);
+      handled = true;
+   } else {
+      handled = false;
+   }
+   return handled;
+}
+
 void Embed::setWidth (int width)
 {
    resource->setWidth (width);
@@ -201,22 +214,18 @@
    activateEmitter.emitLeave(this);
 }
 
-// ----------------------------------------------------------------------
-
-bool ButtonResource::ClickedEmitter::emitToReceiver (lout::signal::Receiver
-                                                     *receiver,
-                                                     int signalNo,
-                                                     int argc,
-                                                     Object **argv)
+bool Resource::ClickedEmitter::emitToReceiver(lout::signal::Receiver *receiver,
+                                              int signalNo, int argc,
+                                              Object **argv)
 {
    ((ClickedReceiver*)receiver)
-      ->clicked ((ButtonResource*)((Pointer*)argv[0])->getValue (),
+      ->clicked ((Resource*)((Pointer*)argv[0])->getValue (),
                  (EventButton*)((Pointer*)argv[1])->getValue());
    return false;
 }
 
-void ButtonResource::ClickedEmitter::emitClicked (ButtonResource *resource,
-                                                  EventButton *event)
+void Resource::ClickedEmitter::emitClicked (Resource *resource,
+                                            EventButton *event)
 {
    Pointer p1 (resource);
    Pointer p2 (event);
--- a/dw/ui.hh	Sat Dec 20 10:11:06 2008 +0100
+++ b/dw/ui.hh	Sat Dec 20 23:46:06 2008 +0100
@@ -232,6 +232,7 @@
    void sizeAllocateImpl (Allocation *allocation);
    void enterNotifyImpl (core::EventCrossing *event);
    void leaveNotifyImpl (core::EventCrossing *event);
+   bool buttonPressImpl (core::EventButton *event);
 
 public:
    static int CLASS_ID;
@@ -271,6 +272,14 @@
       virtual void enter (Resource *resource) = 0;
       virtual void leave (Resource *resource) = 0;
    };
+   /**
+    * \brief Receiver interface for the "clicked" signal.
+    */
+   class ClickedReceiver: public lout::signal::Receiver
+   {
+   public:
+      virtual void clicked (Resource *resource, EventButton *event) = 0;
+   };
 
 private:
    class ActivateEmitter: public lout::signal::Emitter
@@ -286,8 +295,20 @@
       void emitLeave (Resource *resource);
    };
 
+   class ClickedEmitter: public lout::signal::Emitter
+   {
+   protected:
+      bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+                           int argc, Object **argv);
+   public:
+      inline void connectClicked (ClickedReceiver *receiver) {
+         connect (receiver); }
+      void emitClicked (Resource *resource, EventButton *event);
+   };
+
    Embed *embed;
    ActivateEmitter activateEmitter;
+   ClickedEmitter clickedEmitter;
 
    void emitEnter ();
    void emitLeave ();
@@ -301,6 +322,8 @@
 
    inline void emitActivate () {
       return activateEmitter.emitActivate (this); }
+   inline void emitClicked (EventButton *event) {
+      clickedEmitter.emitClicked (this, event); }
 
 public:
    inline Resource () { embed = NULL; }
@@ -322,43 +345,13 @@
 
    inline void connectActivate (ActivateReceiver *receiver) {
       activateEmitter.connectActivate (receiver); }
+   inline void connectClicked (ClickedReceiver *receiver) {
+      clickedEmitter.connectClicked (receiver); }
 };
 
 
 class ButtonResource: public Resource
-{
-public:
-   /**
-    * \brief Receiver interface for the "clicked" signal.
-    */
-   class ClickedReceiver: public lout::signal::Receiver
-   {
-   public:
-      virtual void clicked (ButtonResource *resource, EventButton *event) = 0;
-   };
-
-private:
-   class ClickedEmitter: public lout::signal::Emitter
-   {
-   protected:
-      bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
-                           int argc, Object **argv);
-   public:
-      inline void connectClicked (ClickedReceiver *receiver) {
-         connect (receiver); }
-      void emitClicked (ButtonResource *resource, EventButton *event);
-   };
-
-   ClickedEmitter clickedEmitter;
-
-protected:
-   inline void emitClicked (EventButton *event) {
-      clickedEmitter.emitClicked (this, event); }
-
-public:
-   inline void connectClicked (ClickedReceiver *receiver) {
-      clickedEmitter.connectClicked (receiver); }
-};
+{};
 
 /**
  * \brief Interface for labelled buttons resources.
--- a/src/IO/http.c	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/IO/http.c	Sat Dec 20 23:46:06 2008 +0100
@@ -33,6 +33,7 @@
 #include "../dns.h"
 #include "../web.hh"
 #include "../cookies.h"
+#include "../auth.h"
 #include "../prefs.h"
 #include "../misc.h"
 
@@ -196,7 +197,7 @@
  */
 Dstr *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy)
 {
-   char *ptr, *cookies, *referer;
+   char *ptr, *cookies, *auth, *referer;
    Dstr *query      = dStr_new(""),
         *full_path  = dStr_new(""),
         *proxy_auth = dStr_new("");
@@ -219,6 +220,7 @@
    }
 
    cookies = a_Cookies_get_query(url);
+   auth = a_Auth_get_auth_str(url);
    referer = Http_get_referer(url);
    if (URL_FLAGS(url) & URL_Post) {
       Dstr *content_type = Http_make_content_type(url);
@@ -228,15 +230,16 @@
          "Connection: close\r\n"
          "Accept-Charset: utf-8,*;q=0.8\r\n"
          "Accept-Encoding: gzip\r\n"
+         "%s" /* auth */
          "Host: %s\r\n"
          "%s"
          "%s"
          "User-Agent: Dillo/%s\r\n"
          "Content-Length: %ld\r\n"
          "Content-Type: %s\r\n"
-         "%s"
+         "%s" /* cookies */
          "\r\n",
-         full_path->str, URL_AUTHORITY(url),
+         full_path->str, auth ? auth : "", URL_AUTHORITY(url),
          proxy_auth->str, referer, VERSION,
          URL_DATA(url)->len, content_type->str,
          cookies);
@@ -250,16 +253,17 @@
          "Connection: close\r\n"
          "Accept-Charset: utf-8,*;q=0.8\r\n"
          "Accept-Encoding: gzip\r\n"
+         "%s" /* auth */
          "Host: %s\r\n"
          "%s"
          "%s"
          "User-Agent: Dillo/%s\r\n"
-         "%s"
+         "%s" /* cookies */
          "\r\n",
          full_path->str,
          (URL_FLAGS(url) & URL_E2EQuery) ?
             "Cache-Control: no-cache\r\nPragma: no-cache\r\n" : "",
-         URL_AUTHORITY(url),
+         auth ? auth : "", URL_AUTHORITY(url),
          proxy_auth->str, referer, VERSION, cookies);
    }
    dFree(referer);
--- a/src/Makefile.am	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/Makefile.am	Sat Dec 20 23:46:06 2008 +0100
@@ -28,6 +28,8 @@
 	bw.c \
 	cookies.c \
 	cookies.h \
+	auth.c \
+	auth.h \
 	colors.c \
 	colors.h \
 	binaryconst.h \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth.c	Sat Dec 20 23:46:06 2008 +0100
@@ -0,0 +1,514 @@
+/*
+ * File: auth.c
+ *
+ * Copyright 2008 Jeremy Henty   <onepoint@starurchin.org>
+ *
+ * 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.
+ */
+
+/* Handling of HTTP AUTH takes place here.
+ * This implementation aims to follow RFC 2617:
+ * http://www.ietf.org/rfc/rfc2617.txt
+ */
+
+
+#include <ctype.h> /* for parsing */
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "auth.h"
+#include "msg.h"
+#include "misc.h"
+#include "dialog.hh"
+#include "IO/Url.h"
+#include "../dlib/dlib.h"
+
+
+typedef struct {
+   int ok;
+   const char *realm;
+} AuthParse_t;
+
+typedef struct {
+   char *name;
+   Dlist *paths; /* stripped of any trailing '/', so the root path is "" */
+   char *authorization; /* the authorization request header */
+} AuthRealm_t;
+
+typedef struct {
+   char *scheme;
+   char *authority;
+   Dlist *realms;
+} AuthHost_t;
+
+typedef struct {
+   const char *realm_name;
+   const DilloUrl *url;
+} AuthDialogData_t;
+
+/*
+ *  Local data
+ */
+static Dlist *auth_hosts;
+
+/*
+ * Initialize the auth module.
+ */
+void a_Auth_init(void)
+{
+   auth_hosts = dList_new(1);
+}
+
+static AuthParse_t *Auth_parse_new()
+{
+   AuthParse_t *auth_parse = dNew(AuthParse_t, 1);
+   auth_parse->ok = 0;
+   auth_parse->realm = NULL;
+   return auth_parse;
+}
+
+static void Auth_parse_free(AuthParse_t *auth_parse)
+{
+   if (auth_parse) {
+      dFree((void *) auth_parse->realm);
+   }
+}
+
+static int Auth_path_is_inside(const char *path1, const char *path2, int len)
+{
+   /*
+    * path2 is effectively truncated to length len.  Typically len will be
+    * strlen(path2), or 1 less when we want to ignore a trailing '/'.
+    */
+   return
+      strncmp(path1, path2, len) == 0 &&
+      (path1[len] == '\0' || path1[len] == '/');
+}
+
+/*
+ * Check valid chars.
+ * Return: 0 if invalid, 1 otherwise.
+ */
+static int Auth_is_token_char(char c)
+{
+   const char *invalid = "\"()<>@,;:\\[]?=/{} \t";
+   return (strchr(invalid, c) || iscntrl(c)) ? 0 : 1;
+}
+
+static void Auth_parse_auth_basic(AuthParse_t *auth_parse, char *auth)
+{
+   int token_value_pairs_found;
+   char *realm = NULL;
+   static const char realm_token[] = "realm";
+
+   /* parse comma-separated token-value pairs */
+   token_value_pairs_found = 0;
+   while (1) {
+      char *token, *value;
+      int token_size, value_size;
+
+      /* skip host and comma characters */
+      while (*auth == ' ' || *auth == ',')
+         auth++;
+
+      /* end of string? */
+      if (!*auth)
+         goto end_parse;
+
+      /* parse a token */
+      token = auth;
+      token_size = 0;
+      while (Auth_is_token_char(*auth)) {
+         auth++;
+         token_size++;
+      }
+      if (token_size == 0) {
+         MSG("auth.c: Auth_parse_auth_basic: "
+             "missing Basic auth token\n");
+         goto end_parse;
+      }
+
+      /* skip space characters */
+      while (*auth == ' ')
+         auth++;
+
+      /* parse the '=' */
+      switch (*auth++) {
+      case '=':
+         break;
+      case '\0':
+      case ',':
+         MSG("auth.c: Auth_parse_auth_basic: "
+             "missing Basic auth token value\n");
+         goto end_parse;
+         break;
+      default:
+         MSG("auth.c: Auth_parse_auth_basic: "
+             "garbage after Basic auth token\n");
+         goto end_parse;
+         break;
+      }
+
+      /* skip space characters */
+      while (*auth == ' ')
+         auth++;
+
+      /* parse a quoted string */
+
+      /* parse a '"' */
+      switch (*auth++) {
+      case '"':
+         break;
+      case '\0':
+      case ',':
+         MSG("auth.c: Auth_parse_auth_basic: "
+             "missing Basic auth token value after '='\n");
+         goto end_parse;
+         break;
+      default:
+         MSG("auth.c: Auth_parse_auth_basic: "
+             "garbage in Basic auth after '='\n");
+         goto end_parse;
+         break;
+      }
+
+      /* parse the rest of a quoted string */
+      value = auth;
+      value_size = 0;
+      while (1) {
+         switch (*auth++) {
+         case '"':
+            goto end_quoted_string;
+            break;
+         case '\0':
+            MSG("auth.c: Auth_parse_auth_basic: "
+                "auth string ended inside quoted string value\n");
+            goto end_parse;
+            break;
+         case '\\':
+            /* end of string? */
+            if (!*auth++) {
+               MSG("auth.c: Auth_parse_auth_basic: "
+                   "auth string ended inside quoted string value "
+                   "immediately after \\\n");
+               goto end_parse;
+            }
+            /* fall through to the next case */
+         default:
+            value_size++;
+            break;
+         }
+      } /* parse quoted string */
+   end_quoted_string:
+
+      token_value_pairs_found = 1;
+
+      if (realm == NULL &&
+          strncasecmp(realm_token,token,token_size) == 0 &&
+          strlen(realm_token) == token_size) {
+         /* unquote and save the value */
+         char c, *value_ptr, *realm_ptr;
+         realm = dNew(char, value_size + 1);
+         value_ptr = value;
+         realm_ptr = realm;
+         while ((c = *value_ptr++) != '"')
+            *realm_ptr++ = (c == '\\') ? *value_ptr++ : c;
+         *realm_ptr = '\0';
+         auth_parse->ok = 1;
+         auth_parse->realm = realm;
+         _MSG("auth.c: Auth_parse_auth_basic: realm: '%s'\n", realm);
+         return;
+      }
+   }
+ end_parse:
+
+   if (!token_value_pairs_found) {
+      MSG("auth.c: Auth_parse_auth_basic: "
+          "missing Basic auth token-value pairs\n");
+      return;
+   }
+
+   if (!realm) {
+      MSG("auth.c: Auth_parse_auth_basic: "
+          "missing Basic auth realm\n");
+      return;
+   }
+}
+
+static void Auth_parse_auth(AuthParse_t *auth_parse, char *auth)
+{
+   _MSG("auth.c: Auth_parse_auth: auth = '%s'\n", auth);
+   if (strncasecmp(auth, "Basic ", 6) == 0) {
+      Auth_parse_auth_basic(auth_parse, auth + 6);
+   } else {
+      MSG("auth.c: Auth_parse_auth: "
+          "unknown authorization scheme: auth = {%s}\n",
+          auth);
+   }
+}
+
+/*
+ * Return the host that contains a URL, or NULL if there is no such host.
+ */
+static AuthHost_t *Auth_host_by_url(const DilloUrl *url)
+{
+   AuthHost_t *host;
+   int i;
+
+   for (i = 0; (host = dList_nth_data(auth_hosts, i)); i++)
+      if (((dStrcasecmp(URL_SCHEME(url), host->scheme) == 0) &&
+           (dStrcasecmp(URL_AUTHORITY(url), host->authority) == 0)))
+         return host;
+
+   return NULL;
+}
+
+/*
+ * Search all realms for the one with the given name.
+ */
+static AuthRealm_t *Auth_realm_by_name(const AuthHost_t *host,
+                                           const char *name)
+{
+   AuthRealm_t *realm;
+   int i;
+
+   for (i = 0; (realm = dList_nth_data(host->realms, i)); i++)
+      if (strcmp(realm->name,name) == 0)
+         return realm;
+
+   return NULL;
+}
+
+/*
+ * Search all realms for the one with the best-matching path.
+ */
+static AuthRealm_t *Auth_realm_by_path(const AuthHost_t *host,
+                                       const char *path)
+{
+   AuthRealm_t *realm_best, *realm;
+   int i, j;
+   int match_length;
+
+   realm_best = NULL;
+   for (i = 0; (realm = dList_nth_data(host->realms, i)); i++) {
+      char *realm_path;
+
+      for (j = 0; (realm_path = dList_nth_data(realm->paths, j)); j++) {
+         int realm_path_length;
+
+         realm_path_length = strlen(realm_path);
+         if (Auth_path_is_inside(path, realm_path, realm_path_length) &&
+             !(realm_best && match_length >= realm_path_length)) {
+            realm_best = realm;
+            match_length = realm_path_length;
+         }
+      } /* for (j = 0; (path = ... */
+   } /* for (i = 0; (realm = ... */
+
+   return realm_best;
+}
+
+static int Auth_realm_includes_path(const AuthRealm_t *realm, const char *path)
+{
+   int i;
+   char *realm_path;
+
+   for (i = 0; (realm_path = dList_nth_data(realm->paths, i)); i++)
+      if (Auth_path_is_inside(path, realm_path, strlen(realm_path)))
+         return 1;
+
+   return 0;
+}
+
+static void Auth_realm_add_path(AuthRealm_t *realm, const char *path)
+{
+   int len, i;
+   char *realm_path, *n_path;
+
+   n_path = strdup(path);
+   len = strlen(n_path);
+
+   /* remove trailing '/' */
+   if (len && n_path[len - 1] == '/')
+      n_path[--len] = 0;
+
+   /* delete existing paths that are inside the new one */
+   for (i = 0; (realm_path = dList_nth_data(realm->paths, i)); i++) {
+      if (Auth_path_is_inside(realm_path, path, len)) {
+         dList_remove_fast(realm->paths, realm_path);
+         dFree(realm_path);
+         i--; /* reconsider this slot */
+      }
+   }
+
+   dList_append(realm->paths, n_path);
+}
+
+/*
+ * Return the authorization header for an HTTP query.
+ */
+char *a_Auth_get_auth_str(const DilloUrl *url)
+{
+   AuthHost_t *host;
+   AuthRealm_t *realm;
+
+   return
+      ((host = Auth_host_by_url(url)) &&
+       (realm = Auth_realm_by_path(host, URL_PATH(url)))) ?
+      realm->authorization : NULL;
+}
+
+/*
+ * Determine whether the user needs to authenticate.
+ */
+static int Auth_do_auth_required(const char *realm_name, const DilloUrl *url)
+{
+   /*
+    * TO DO: I dislike the way that this code must decide whether we
+    * sent authentication during the request and trust us to resend it
+    * after the reload.  Could it be more robust if every DilloUrl
+    * recorded its authentication, and whether it was accepted?  (JCH)
+    */
+
+   AuthHost_t *host;
+   AuthRealm_t *realm;
+
+   /*
+    * The size of the following comments reflects the concerns in the
+    * TO DO at the top of this function.  It should not be so hard to
+    * explain why code is correct! (JCH)
+    */
+
+   /*
+    * If we have authentication but did not send it (because we did
+    * not know this path was in the realm) then we update the realm.
+    * We do not re-authenticate because our authentication is probably
+    * OK.  Thanks to the updated realm the forthcoming reload will
+    * make us send the authentication.  If our authentication is not
+    * OK the server will challenge us again after the reload and then
+    * we will re-authenticate.
+    */
+   if ((host = Auth_host_by_url(url)) &&
+       (realm = Auth_realm_by_name(host, realm_name)) &&
+       (!Auth_realm_includes_path(realm, URL_PATH(url)))) {
+      _MSG("Auth_do_auth_required: updating realm '%s' with URL '%s'\n",
+           realm_name, URL_STR(url));
+      Auth_realm_add_path(realm, URL_PATH(url));
+      return 0;
+   }
+
+   /*
+    * Either we had no authentication or we sent it and the server
+    * rejected it, so we must re-authenticate.
+    */
+   return 1;
+}
+
+static void Auth_do_auth_dialog_cb(const char *user, const char *password,
+                                   void *vData)
+{
+   AuthDialogData_t *data;
+   AuthHost_t *host;
+   AuthRealm_t *realm;
+   char *user_password, *response, *authorization, *authorization_old;
+
+   data = (AuthDialogData_t *)vData;
+
+   /* find or create the host */
+   if (!(host = Auth_host_by_url(data->url))) {
+      /* create a new host */
+      host = dNew(AuthHost_t, 1);
+      host->scheme = dStrdup(URL_SCHEME(data->url));
+      host->authority = dStrdup(URL_AUTHORITY(data->url));
+      host->realms = dList_new(1);
+      dList_append(auth_hosts, host);
+   }
+
+   /* find or create the realm */
+   if (!(realm = Auth_realm_by_name(host, data->realm_name))) {
+      /* create a new realm */
+      realm = dNew(AuthRealm_t, 1);
+      realm->name = dStrdup(data->realm_name);
+      realm->paths = dList_new(1);
+      realm->authorization = NULL;
+      dList_append(host->realms, realm);
+   }
+
+   Auth_realm_add_path(realm, URL_PATH(data->url));
+
+   /* create and set the authorization */
+   user_password = dStrconcat(user, ":", password, NULL);
+   response = a_Misc_encode_base64(user_password);
+   authorization =
+      dStrconcat("Authorization: Basic ", response, "\r\n", NULL);
+   authorization_old = realm->authorization;
+   realm->authorization = authorization;
+   dFree(authorization_old);
+   dFree(user_password);
+   dFree(response);
+}
+
+static int Auth_do_auth_dialog(const char *realm, const DilloUrl *url)
+{
+   int ret;
+   char *message;
+   AuthDialogData_t *data;
+
+   _MSG("auth.c: Auth_do_auth_dialog: realm = '%s'\n", realm);
+   message = dStrconcat("Enter a user and password for \"",
+                        realm, "\".", NULL);
+   data = dNew(AuthDialogData_t, 1);
+   data->realm_name = dStrdup(realm);
+   data->url = a_Url_dup(url);
+   ret = a_Dialog_user_password(message, Auth_do_auth_dialog_cb, data);
+   dFree(message);
+   dFree((void*)data->realm_name);
+   a_Url_free((void*)data->url);
+   dFree(data);
+   return ret;
+}
+
+/*
+ * Do authorization for an auth string.
+ */
+static int Auth_do_auth(char *auth, const DilloUrl *url)
+{
+   int reload;
+   AuthParse_t *auth_parse;
+
+   _MSG("auth.c: Auth_do_auth: auth={%s}\n", auth);
+   reload = 0;
+   auth_parse = Auth_parse_new();
+   Auth_parse_auth(auth_parse, auth);
+   if (auth_parse->ok)
+      reload =
+         Auth_do_auth_required(auth_parse->realm, url) ?
+         Auth_do_auth_dialog(auth_parse->realm, url)
+         : 1;
+   Auth_parse_free(auth_parse);
+
+   return reload;
+}
+
+/*
+ * Do authorization for a set of auth strings.
+ */
+int a_Auth_do_auth(Dlist *auths, const DilloUrl *url)
+{
+   int reload, i;
+   char *auth;
+
+   reload = 0;
+   for (i = 0; (auth = dList_nth_data(auths, i)); ++i)
+      if (Auth_do_auth(auth, url))
+         reload = 1;
+
+   return reload;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth.h	Sat Dec 20 23:46:06 2008 +0100
@@ -0,0 +1,19 @@
+#ifndef __AUTH_H__
+#define __AUTH_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "url.h"
+
+
+char *a_Auth_get_auth_str(const DilloUrl *request_url);
+int a_Auth_do_auth(Dlist *auth_string, const DilloUrl *url);
+void a_Auth_init(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* !__AUTH_H__ */
--- a/src/bw.h	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/bw.h	Sat Dec 20 23:46:06 2008 +0100
@@ -84,6 +84,7 @@
 void a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url);
 void a_Bw_cleanup(BrowserWindow *bw);
 
+typedef void (*BwCallback_t)(BrowserWindow *bw, const void *data);
 
 #ifdef __cplusplus
 }
--- a/src/cache.c	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/cache.c	Sat Dec 20 23:46:06 2008 +0100
@@ -32,6 +32,7 @@
 #include "misc.h"
 #include "capi.h"
 #include "decode.h"
+#include "auth.h"
 
 #include "timeout.hh"
 #include "uicmd.hh"
@@ -54,6 +55,7 @@
    char *TypeMeta;           /* MIME type string from META HTTP-EQUIV */
    Dstr *Header;             /* HTTP header */
    const DilloUrl *Location; /* New URI for redirects */
+   Dlist *Auth;              /* Authentication fields */
    Dstr *Data;               /* Pointer to raw data */
    Dstr *UTF8Data;           /* Data after charset translation */
    int DataRefcount;         /* Reference count */
@@ -87,6 +89,7 @@
  */
 static void Cache_process_queue(CacheEntry_t *entry);
 static void Cache_delayed_process_queue(CacheEntry_t *entry);
+static void Cache_auth_entry(CacheEntry_t *entry, BrowserWindow *bw);
 
 
 /*
@@ -206,6 +209,7 @@
    NewEntry->TypeMeta = NULL;
    NewEntry->Header = dStr_new("");
    NewEntry->Location = NULL;
+   NewEntry->Auth = NULL;
    NewEntry->Data = dStr_sized_new(8*1024);
    NewEntry->UTF8Data = NULL;
    NewEntry->DataRefcount = 0;
@@ -288,6 +292,18 @@
 }
 
 /*
+ *  Free Authentication fields.
+ */
+static void Cache_auth_free(Dlist *auth)
+{
+   int i;
+   void *auth_field;
+   for (i = 0; (auth_field = dList_nth_data(auth, i)); ++i)
+      dFree(auth_field);
+   dList_free(auth);
+}
+
+/*
  *  Free the components of a CacheEntry_t struct.
  */
 static void Cache_entry_free(CacheEntry_t *entry)
@@ -298,6 +314,7 @@
    dFree(entry->TypeMeta);
    dStr_free(entry->Header, TRUE);
    a_Url_free((DilloUrl *)entry->Location);
+   Cache_auth_free(entry->Auth);
    dStr_free(entry->Data, 1);
    dStr_free(entry->UTF8Data, 1);
    if (entry->CharsetDecoder)
@@ -655,6 +672,9 @@
          }
          dFree(location_str);
 
+      } else if (strncmp(header + 9, "401", 3) == 0) {
+         entry->Auth =
+            Cache_parse_multiple_fields(header, "WWW-Authenticate");
       } else if (strncmp(header + 9, "404", 3) == 0) {
          entry->Flags |= CA_NotFound;
       }
@@ -927,6 +947,50 @@
    return 0;
 }
 
+typedef struct {
+   Dlist *auth;
+   DilloUrl *url;
+   BrowserWindow *bw;
+} CacheAuthData_t;
+
+/*
+ * Ask for user/password and reload the page.
+ */
+static void Cache_auth_callback(void *vdata)
+{
+   CacheAuthData_t *data = (CacheAuthData_t *)vdata;
+   if (a_Auth_do_auth(data->auth, data->url))
+      a_Nav_reload(data->bw);
+   Cache_auth_free(data->auth);
+   a_Url_free(data->url);
+   dFree(data);
+   Cache_auth_entry(NULL, NULL);
+   a_Timeout_remove();
+}
+
+/*
+ * Set a timeout function to ask for user/password.
+ */ 
+static void Cache_auth_entry(CacheEntry_t *entry, BrowserWindow *bw)
+{
+   static int busy = 0;
+   CacheAuthData_t *data;
+
+   if (!entry) {
+      busy = 0;
+   } else if (busy) {
+      MSG_WARN("Cache_auth_entry: caught busy!\n");
+   } else if (entry->Auth) {
+      busy = 1;
+      data = dNew(CacheAuthData_t, 1);
+      data->auth = entry->Auth;
+      data->url = a_Url_dup(entry->Url);
+      data->bw = bw;
+      entry->Auth = NULL;
+      a_Timeout_add(0.0, Cache_auth_callback, data);
+   }
+}
+
 /*
  * Check whether a URL scheme is downloadable.
  * Return: 1 enabled, 0 disabled.
@@ -1114,6 +1178,9 @@
          a_UIcmd_save_link(Client_bw, url);
       }
       a_Url_free(url);
+
+   } else if (entry->Auth && (entry->Flags & CA_GotData)) {
+      Cache_auth_entry(entry, Client_bw);
    }
 
    /* Trigger cleanup when there are no cache clients */
--- a/src/dialog.cc	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/dialog.cc	Sat Dec 20 23:46:06 2008 +0100
@@ -20,6 +20,9 @@
 #include <fltk/ReturnButton.h>
 #include <fltk/TextDisplay.h>
 #include <fltk/HighlightButton.h>
+#include <fltk/WordwrapOutput.h>
+#include <fltk/Input.h>
+#include <fltk/SecretInput.h>
 
 #include "msg.h"
 #include "dialog.hh"
@@ -227,3 +230,79 @@
    return choice5_answer;
 }
 
+
+/*--------------------------------------------------------------------------*/
+static int ok_answer = 1, cancel_answer = 0;
+static void Dialog_user_password_cb(Widget *button, void *vIntPtr)
+{
+   int ok = VOIDP2INT(vIntPtr);
+  _MSG("Dialog_user_password_cb: %d\n", ok);
+  button->window()->make_exec_return(ok);
+}
+
+/*
+ * Make a user/password dialog.
+ * Call the callback with the result (OK or not) and the given user and
+ *   password if OK.
+ */
+int a_Dialog_user_password(const char *message, UserPasswordCB cb, void *vp)
+{
+   int ok,
+      window_w = 300, window_h = 280,
+      input_x = 80, input_w = 200, input_h = 30,
+      button_y = 230, button_h = 30;
+
+   Window *window =
+      new Window(window_w,window_h,"User/Password");
+   window->begin();
+
+   /* message */
+   WordwrapOutput *message_output =
+      new WordwrapOutput(20,20,window_w-40,100);
+   message_output->box(DOWN_BOX);
+   message_output->text(message);
+   message_output->textfont(HELVETICA_BOLD_ITALIC);
+   message_output->textsize(14);
+
+   /* inputs */
+   Input *user_input =
+      new Input(input_x,140,input_w,input_h,"User");
+   user_input->labelsize(14);
+   user_input->textsize(14);
+   SecretInput *password_input =
+      new SecretInput(input_x,180,input_w,input_h,"Password");
+   password_input->labelsize(14);
+   password_input->textsize(14);
+
+   /* "OK" button */
+   Button *ok_button =
+      new Button(200,button_y,50,button_h,"OK");
+   ok_button->labelsize(14);
+   ok_button->callback(Dialog_user_password_cb);
+   ok_button->user_data(&ok_answer);
+
+   /* "Cancel" button */
+   Button *cancel_button =
+      new Button(50,button_y,100,button_h,"Cancel");
+   cancel_button->labelsize(14);
+   cancel_button->callback(Dialog_user_password_cb);
+   cancel_button->user_data(&cancel_answer);
+
+   window->end();
+   window->size_range(window_w,window_h,window_w,window_h);
+   window->resizable(window);
+
+   if ((ok = window->exec())) {
+      /* call the callback */
+      const char *user, *password;
+      user = user_input->value();
+      password = password_input->value();
+      _MSG("a_Dialog_user_passwd: ok = %d\n", ok);
+      (*cb)(user, password, vp);
+   }
+
+   delete window;
+
+   return ok;
+}
+
--- a/src/dialog.hh	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/dialog.hh	Sat Dec 20 23:46:06 2008 +0100
@@ -5,12 +5,16 @@
 extern "C" {
 #endif /* __cplusplus */
 
+typedef void (*UserPasswordCB)(const char *user, const char *password,
+                               void *vp);
+
 void a_Dialog_msg(const char *msg);
 int a_Dialog_choice3(const char *msg,
                      const char *b0, const char *b1, const char *b2);
 int a_Dialog_choice5(const char *QuestionTxt,
                      const char *alt1, const char *alt2, const char *alt3,
                      const char *alt4, const char *alt5);
+int a_Dialog_user_password(const char *message, UserPasswordCB cb, void *vp);
 const char *a_Dialog_input(const char *msg);
 const char *a_Dialog_passwd(const char *msg);
 const char *a_Dialog_save_file(const char *msg,
--- a/src/dillo.cc	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/dillo.cc	Sat Dec 20 23:46:06 2008 +0100
@@ -45,6 +45,7 @@
 #include "capi.h"
 #include "dicache.h"
 #include "cookies.h"
+#include "auth.h"
 
 
 /*
@@ -102,6 +103,7 @@
    a_Dicache_init();
    a_Bw_init();
    a_Cookies_init();
+   a_Auth_init();
 
    // Sets WM_CLASS hint on X11
    fltk::Window::xclass("dillo");
--- a/src/form.cc	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/form.cc	Sat Dec 20 23:46:06 2008 +0100
@@ -119,7 +119,7 @@
 
 class DilloHtmlReceiver:
    public Resource::ActivateReceiver,
-   public ButtonResource::ClickedReceiver
+   public Resource::ClickedReceiver
 {
    friend class DilloHtmlForm;
    DilloHtmlForm* form;
@@ -128,7 +128,7 @@
    void activate (Resource *resource);
    void enter (Resource *resource);
    void leave (Resource *resource);
-   void clicked (ButtonResource *resource, EventButton *event);
+   void clicked (Resource *resource, EventButton *event);
 };
 
 class DilloHtmlInput {
@@ -372,8 +372,6 @@
    html->InFlags &= ~IN_SELECT;
    html->InFlags &= ~IN_OPTION;
    html->InFlags &= ~IN_TEXTAREA;
-
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -698,7 +696,6 @@
 
       html->InFlags &= ~IN_TEXTAREA;
    }
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -787,8 +784,6 @@
       SelectionResource *res = (SelectionResource*)input->embed->getResource();
       select->addOptionsTo (res);
    }
-
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -902,7 +897,6 @@
 void Html_tag_close_button(DilloHtml *html, int TagIdx)
 {
    html->InFlags &= ~IN_BUTTON;
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -950,11 +944,15 @@
 void DilloHtmlForm::eventHandler(Resource *resource, EventButton *event)
 {
    MSG("DilloHtmlForm::eventHandler\n");
-   DilloHtmlInput *input = getInput(resource);
-   if (input) {
-      input->activate (this, num_entry_fields, event);
+   if (event && (event->button == 3)) {
+      MSG("Form menu\n");
    } else {
-      MSG("DilloHtmlForm::eventHandler: ERROR, input not found!\n");
+      DilloHtmlInput *input = getInput(resource);
+      if (input) {
+         input->activate (this, num_entry_fields, event);
+      } else {
+        MSG("DilloHtmlForm::eventHandler: ERROR, input not found!\n");
+      }
    }
 }
 
@@ -1522,7 +1520,7 @@
    a_UIcmd_set_msg(html->bw, "");
 }
 
-void DilloHtmlReceiver::clicked (ButtonResource *resource,
+void DilloHtmlReceiver::clicked (Resource *resource,
                                  EventButton *event)
 {
    form->eventHandler(resource, event);
@@ -1576,30 +1574,17 @@
 {
    Resource *resource;
    if (embed && (resource = embed->getResource())) {
-      switch (type) {
-         case DILLO_HTML_INPUT_UNKNOWN:
-         case DILLO_HTML_INPUT_HIDDEN:
-         case DILLO_HTML_INPUT_CHECKBOX:
-         case DILLO_HTML_INPUT_RADIO:
-         case DILLO_HTML_INPUT_BUTTON:
-         case DILLO_HTML_INPUT_TEXTAREA:
-         case DILLO_HTML_INPUT_SELECT:
-         case DILLO_HTML_INPUT_SEL_LIST:
-            // do nothing
-            break;
-         case DILLO_HTML_INPUT_SUBMIT:
-         case DILLO_HTML_INPUT_RESET:
-         case DILLO_HTML_INPUT_BUTTON_SUBMIT:
-         case DILLO_HTML_INPUT_BUTTON_RESET:
-         case DILLO_HTML_INPUT_IMAGE:
-         case DILLO_HTML_INPUT_FILE:
-            ((ButtonResource *)resource)->connectClicked (form_receiver);
-         case DILLO_HTML_INPUT_TEXT:
-         case DILLO_HTML_INPUT_PASSWORD:
-         case DILLO_HTML_INPUT_INDEX:
-            resource->connectActivate (form_receiver);
-            break;
-         break;
+      resource->connectClicked (form_receiver);
+      if (type == DILLO_HTML_INPUT_SUBMIT ||
+          type == DILLO_HTML_INPUT_RESET ||
+          type == DILLO_HTML_INPUT_BUTTON_SUBMIT ||
+          type == DILLO_HTML_INPUT_BUTTON_RESET ||
+          type == DILLO_HTML_INPUT_IMAGE ||
+          type == DILLO_HTML_INPUT_FILE ||
+          type == DILLO_HTML_INPUT_TEXT ||
+          type == DILLO_HTML_INPUT_PASSWORD ||
+          type == DILLO_HTML_INPUT_INDEX) {
+         resource->connectActivate (form_receiver);
       }
    }
 }
@@ -1610,16 +1595,29 @@
 void DilloHtmlInput::activate(DilloHtmlForm *form, int num_entry_fields,
                               EventButton *event)
 {
-   if (type == DILLO_HTML_INPUT_FILE) {
+   switch (type) {
+   case DILLO_HTML_INPUT_FILE:
       readFile (form->html->bw);
-   } else if (type == DILLO_HTML_INPUT_RESET ||
-              type == DILLO_HTML_INPUT_BUTTON_RESET) {
+      break;
+   case DILLO_HTML_INPUT_RESET:
+   case DILLO_HTML_INPUT_BUTTON_RESET:
       form->reset();
-   } else if ((type != DILLO_HTML_INPUT_TEXT &&
-               type != DILLO_HTML_INPUT_PASSWORD) ||
-              prefs.enterpress_forces_submit ||
-              num_entry_fields == 1) {
+      break;
+   case DILLO_HTML_INPUT_TEXT:
+   case DILLO_HTML_INPUT_PASSWORD:
+      if (!(prefs.enterpress_forces_submit || num_entry_fields == 1)) {
+         break;
+      } else {
+         /* fall through */
+      }
+   case DILLO_HTML_INPUT_SUBMIT:
+   case DILLO_HTML_INPUT_BUTTON_SUBMIT:
+   case DILLO_HTML_INPUT_IMAGE:
+   case DILLO_HTML_INPUT_INDEX:
       form->submit(this, event);
+      break;
+   default:
+      break;
    }
 }
 
--- a/src/html.cc	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/html.cc	Sat Dec 20 23:46:06 2008 +0100
@@ -467,7 +467,6 @@
    PrevWasSPC = false;
    InVisitedLink = false;
    ReqTagClose = false;
-   CloseOneTag = false;
    TagSoup = true;
    NameVal = NULL;
 
@@ -1277,12 +1276,6 @@
    int stack_idx, cmp = 1;
    int new_idx = TagIdx;
 
-   if (html->CloseOneTag) {
-      Html_real_pop_tag(html);
-      html->CloseOneTag = false;
-      return;
-   }
-
    /* Look for the candidate tag to close */
    stack_idx = html->stack->size() - 1;
    while (stack_idx &&
@@ -1308,9 +1301,9 @@
                     Tags[toptag_idx].name);
 
          /* Close this and only this tag */
-         html->CloseOneTag = true;
          _MSG("Close: %*s%s\n", html->stack->size()," ",Tags[toptag_idx].name);
          Tags[toptag_idx].close (html, toptag_idx);
+         Html_real_pop_tag(html);
       }
 
    } else {
@@ -1325,14 +1318,6 @@
 }
 
 /*
- * Cleanup (conditional), and Pop the tag (if it matches)
- */
-void a_Html_pop_tag(DilloHtml *html, int TagIdx)
-{
-   Html_tag_cleanup_at_close(html, TagIdx);
-}
-
-/*
  * Some parsing routines.
  */
 
@@ -1546,7 +1531,6 @@
       /* beware of pages with multiple HTML close tags... :-P */
       html->InFlags &= ~IN_HTML;
    }
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -1590,7 +1574,6 @@
       if (html->repush_after_head)
          a_Nav_repush(html->bw);
    }
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -1616,7 +1599,6 @@
    } else {
       BUG_MSG("the TITLE element must be inside the HEAD section\n");
    }
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -1636,7 +1618,6 @@
 static void Html_tag_close_script(DilloHtml *html, int TagIdx)
 {
    /* eventually the stash will be sent to an interpreter for parsing */
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -1673,7 +1654,6 @@
 {
    html->styleEngine->parse(html->Stash->str, html->Stash->len,
                             0, CSS_ORIGIN_AUTHOR);
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -1744,7 +1724,6 @@
       /* some tag soup pages use multiple BODY tags... */
       html->InFlags &= ~IN_BODY;
    }
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -1877,7 +1856,6 @@
 {
    a_Menu_pagemarks_set_text(html->bw, html->Stash->str);
    DW2TB(html->dw)->addParbreak (9, html->styleEngine->wordStyle ());
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -2223,7 +2201,6 @@
 static void Html_tag_close_map(DilloHtml *html, int TagIdx)
 {
    html->InFlags &= ~IN_MAP;
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -2485,7 +2462,6 @@
 static void Html_tag_close_a(DilloHtml *html, int TagIdx)
 {
    html->InVisitedLink = false;
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -2513,6 +2489,31 @@
 }
 
 /*
+ * <Q>
+ */
+static void Html_tag_open_q(DilloHtml *html, const char *tag, int tagsize)
+{
+    /*
+     * Left Double Quotation Mark, which is wrong in many cases, but
+     * should at least be widely recognized.
+     */
+    const char *U201C = "\xe2\x80\x9c";
+
+    DW2TB(html->dw)->addText (U201C, html->styleEngine->wordStyle ());
+}
+
+/*
+ * </Q>
+ */
+static void Html_tag_close_q(DilloHtml *html, int TagIdx)
+{
+   /* Right Double Quotation Mark */
+   const char *U201D = "\xe2\x80\x9d";
+
+   DW2TB(html->dw)->addText (U201D, html->styleEngine->wordStyle ());
+}
+
+/*
  * Handle the <UL> tag.
  */
 static void Html_tag_open_ul(DilloHtml *html, const char *tag, int tagsize)
@@ -2700,7 +2701,6 @@
    html->InFlags &= ~IN_LI;
    html->WordAfterLI = false;
    ((ListItem *)html->dw)->flush ();
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -2803,7 +2803,6 @@
 {
    html->InFlags &= ~IN_PRE;
    DW2TB(html->dw)->addParbreak (9, html->styleEngine->wordStyle ());
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -3105,15 +3104,13 @@
 static void Html_tag_close_div(DilloHtml *html, int TagIdx)
 {
    DW2TB(html->dw)->addParbreak (0, html->styleEngine->wordStyle ());
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
- * Default close for most tags - just pop the stack.
+ * Default close for most tags.
  */
 static void Html_tag_close_default(DilloHtml *html, int TagIdx)
 {
-   a_Html_pop_tag(html, TagIdx);
 }
 
 /*
@@ -3122,7 +3119,6 @@
 static void Html_tag_close_par(DilloHtml *html, int TagIdx)
 {
    DW2TB(html->dw)->addParbreak (9, html->styleEngine->wordStyle ());
-   a_Html_pop_tag(html, TagIdx);
 }
 
 
@@ -3223,7 +3219,7 @@
  {"p", B8(010110),'O',1, Html_tag_open_p, Html_tag_close_par},
  /* param 010001 'F' */
  {"pre", B8(010110),'R',2, Html_tag_open_pre, Html_tag_close_pre},
- /* q 010101 */
+ {"q", B8(010101),'R',2, Html_tag_open_q, Html_tag_close_q},
  {"s", B8(010101),'R',2, Html_tag_open_strike, Html_tag_close_default},
  {"samp", B8(010101),'R',2, Html_tag_open_samp, Html_tag_close_default},
  {"script", B8(111001),'R',2, Html_tag_open_script, Html_tag_close_script},
--- a/src/html_common.hh	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/html_common.hh	Sat Dec 20 23:46:06 2008 +0100
@@ -197,7 +197,6 @@
    bool PrevWasSPC;       /* Flag to help handling collapsing white space */
    bool InVisitedLink;    /* used to 'contrast_visited_colors' */
    bool ReqTagClose;      /* Flag to help handling bad-formed HTML */
-   bool CloseOneTag;      /* Flag to help Html_tag_cleanup_at_close() */
    bool WordAfterLI;      /* Flag to help ignoring the 1st <P> after <LI> */
    bool TagSoup;          /* Flag to enable the parser's cleanup functions */
    char *NameVal;         /* used for validation of "NAME" and "ID" in <A> */
--- a/src/menu.cc	Sat Dec 20 10:11:06 2008 +0100
+++ b/src/menu.cc	Sat Dec 20 23:46:06 2008 +0100
@@ -175,7 +175,7 @@
 }
 
 /* 
- * Save current page
+ * View current page source
  */
 static void Menu_view_page_source_cb(Widget* )
 {
@@ -192,18 +192,22 @@
 
 /*
  * Load images on current page that match URL pattern
- *
- * BUG: assumes that the document is a DilloHtml.
  */
 static void Menu_load_images_cb(Widget*, void *user_data)
 {
    DilloUrl *pattern = (DilloUrl *) user_data ;
 
    if (popup_bw && popup_bw->Docs) {
-      int i, n;
-      n = dList_length(popup_bw->Docs);
-      for (i = 0; i < n; i++) {
-         a_Html_load_images(dList_nth_data(popup_bw->Docs, i), pattern);
+      if (dList_find_custom(popup_bw->PageUrls, popup_url,
+                            (dCompareFunc)a_Url_cmp)){
+         /* HTML page is still there */
+         int n = dList_length(popup_bw->Docs);
+         if (n == 1) {
+            a_Html_load_images(dList_nth_data(popup_bw->Docs, 0), pattern);
+         } else if (n > 1) {
+            /* e.g. frames implemented and not all containing html */
+            MSG("Menu_load_images_cb multiple Docs not handled\n");
+         }
       }
    }
 }
--- a/test/form.cc	Sat Dec 20 10:11:06 2008 +0100
+++ b/test/form.cc	Sat Dec 20 23:46:06 2008 +0100
@@ -164,7 +164,7 @@
    delete[] value;
 }
       
-void Form::FormClickedReceiver::clicked (ButtonResource *resource,
+void Form::FormClickedReceiver::clicked (Resource *resource,
                                          dw::core::EventButton *event)
 {
    form->send (name, value, event->xCanvas, event->yCanvas);
--- a/test/form.hh	Sat Dec 20 10:11:06 2008 +0100
+++ b/test/form.hh	Sat Dec 20 23:46:06 2008 +0100
@@ -120,7 +120,7 @@
    };
 
    class FormClickedReceiver:
-      public dw::core::ui::ButtonResource::ClickedReceiver
+      public dw::core::ui::Resource::ClickedReceiver
    {
    private:
       Form *form;
@@ -130,7 +130,7 @@
       FormClickedReceiver (Form *form, const char *name, const char *value);
       ~FormClickedReceiver ();
       
-      void clicked(dw::core::ui::ButtonResource *resource,
+      void clicked(dw::core::ui::Resource *resource,
                    dw::core::EventButton *event);
    };