]> sjero.net Git - wget/blobdiff - src/html-parse.c
Fix build when libpsl is not available
[wget] / src / html-parse.c
index 88aec75341f7d0553af4b83843eff698817a39ea..20791cd83450272210c17a129ecd6904aec86231 100644 (file)
@@ -1,11 +1,12 @@
 /* HTML parser for Wget.
-   Copyright (C) 1998, 2000, 2003 Free Software Foundation, Inc.
+   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
+   2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
 
 This file is part of GNU Wget.
 
 GNU Wget 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 2 of the License, or (at
+the Free Software Foundation; either version 3 of the License, or (at
 your option) any later version.
 
 GNU Wget is distributed in the hope that it will be useful,
@@ -14,18 +15,18 @@ 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 Wget; if not, write to the Free Software
-Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-In addition, as a special exception, the Free Software Foundation
-gives permission to link the code of its release of Wget with the
-OpenSSL project's "OpenSSL" library (or with modified versions of it
-that use the same license as the "OpenSSL" library), and distribute
-the linked executables.  You must obey the GNU General Public License
-in all respects for all of the code used other than "OpenSSL".  If you
-modify this file, you may extend this exception to your version of the
-file, but you are not obligated to do so.  If you do not wish to do
-so, delete this exception statement from your version.  */
+along with Wget.  If not, see <http://www.gnu.org/licenses/>.
+
+Additional permission under GNU GPL version 3 section 7
+
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work.  */
 
 /* The only entry point to this module is map_html_tags(), which see.  */
 
@@ -88,7 +89,7 @@ so, delete this exception statement from your version.  */
 /* To test as standalone, compile with `-DSTANDALONE -I.'.  You'll
    still need Wget headers to compile.  */
 
-#include <config.h>
+#include "wget.h"
 
 #ifdef STANDALONE
 # define I_REALLY_WANT_CTYPE_MACROS
@@ -96,14 +97,10 @@ so, delete this exception statement from your version.  */
 
 #include <stdio.h>
 #include <stdlib.h>
-#ifdef HAVE_STRING_H
-# include <string.h>
-#else
-# include <strings.h>
-#endif
+#include <string.h>
 #include <assert.h>
 
-#include "wget.h"
+#include "utils.h"
 #include "html-parse.h"
 
 #ifdef STANDALONE
@@ -114,21 +111,21 @@ so, delete this exception statement from your version.  */
 # define xrealloc realloc
 # define xfree free
 
-# undef ISSPACE
-# undef ISDIGIT
-# undef ISXDIGIT
-# undef ISALPHA
-# undef ISALNUM
-# undef TOLOWER
-# undef TOUPPER
-
-# define ISSPACE(x) isspace (x)
-# define ISDIGIT(x) isdigit (x)
-# define ISXDIGIT(x) isxdigit (x)
-# define ISALPHA(x) isalpha (x)
-# define ISALNUM(x) isalnum (x)
-# define TOLOWER(x) tolower (x)
-# define TOUPPER(x) toupper (x)
+# undef c_isspace
+# undef c_isdigit
+# undef c_isxdigit
+# undef c_isalpha
+# undef c_isalnum
+# undef c_tolower
+# undef c_toupper
+
+# define c_isspace(x) isspace (x)
+# define c_isdigit(x) isdigit (x)
+# define c_isxdigit(x) isxdigit (x)
+# define c_isalpha(x) isalpha (x)
+# define c_isalnum(x) isalnum (x)
+# define c_tolower(x) tolower (x)
+# define c_toupper(x) toupper (x)
 
 struct hash_table {
   int dummy;
@@ -154,13 +151,13 @@ hash_table_get (const struct hash_table *ht, void *ptr)
    allocation because the entire pool is kept on the stack.  */
 
 struct pool {
-  char *contents;              /* pointer to the contents. */
-  int size;                    /* size of the pool. */
-  int tail;                    /* next available position index. */
-  int resized;                 /* whether the pool has been resized
-                                  using malloc. */
+  char *contents;               /* pointer to the contents. */
+  int size;                     /* size of the pool. */
+  int tail;                     /* next available position index. */
+  bool resized;                 /* whether the pool has been resized
+                                   using malloc. */
 
-  char *orig_contents;         /* original pool contents, usually
+  char *orig_contents;          /* original pool contents, usually
                                    stack-allocated.  used by POOL_FREE
                                    to restore the pool to the initial
                                    state. */
@@ -169,41 +166,41 @@ struct pool {
 
 /* Initialize the pool to hold INITIAL_SIZE bytes of storage. */
 
-#define POOL_INIT(p, initial_storage, initial_size) do {       \
-  struct pool *P = (p);                                                \
-  P->contents = (initial_storage);                             \
-  P->size = (initial_size);                                    \
-  P->tail = 0;                                                 \
-  P->resized = 0;                                              \
-  P->orig_contents = P->contents;                              \
-  P->orig_size = P->size;                                      \
+#define POOL_INIT(p, initial_storage, initial_size) do {        \
+  struct pool *P = (p);                                         \
+  P->contents = (initial_storage);                              \
+  P->size = (initial_size);                                     \
+  P->tail = 0;                                                  \
+  P->resized = false;                                           \
+  P->orig_contents = P->contents;                               \
+  P->orig_size = P->size;                                       \
 } while (0)
 
 /* Grow the pool to accomodate at least SIZE new bytes.  If the pool
    already has room to accomodate SIZE bytes of data, this is a no-op.  */
 
-#define POOL_GROW(p, increase)                                 \
-  GROW_ARRAY ((p)->contents, (p)->size, (p)->tail + (increase),        \
-             (p)->resized, char)
+#define POOL_GROW(p, increase)                                  \
+  GROW_ARRAY ((p)->contents, (p)->size, (p)->tail + (increase), \
+              (p)->resized, char)
 
 /* Append text in the range [beg, end) to POOL.  No zero-termination
    is done.  */
 
-#define POOL_APPEND(p, beg, end) do {                  \
-  const char *PA_beg = (beg);                          \
-  int PA_size = (end) - PA_beg;                                \
-  POOL_GROW (p, PA_size);                              \
-  memcpy ((p)->contents + (p)->tail, PA_beg, PA_size); \
-  (p)->tail += PA_size;                                        \
+#define POOL_APPEND(p, beg, end) do {                   \
+  const char *PA_beg = (beg);                           \
+  int PA_size = (end) - PA_beg;                         \
+  POOL_GROW (p, PA_size);                               \
+  memcpy ((p)->contents + (p)->tail, PA_beg, PA_size);  \
+  (p)->tail += PA_size;                                 \
 } while (0)
 
 /* Append one character to the pool.  Can be used to zero-terminate
    pool strings.  */
 
-#define POOL_APPEND_CHR(p, ch) do {            \
-  char PAC_char = (ch);                                \
-  POOL_GROW (p, 1);                            \
-  (p)->contents[(p)->tail++] = PAC_char;       \
+#define POOL_APPEND_CHR(p, ch) do {             \
+  char PAC_char = (ch);                         \
+  POOL_GROW (p, 1);                             \
+  (p)->contents[(p)->tail++] = PAC_char;        \
 } while (0)
 
 /* Forget old pool contents.  The allocated memory is not freed. */
@@ -215,14 +212,14 @@ struct pool {
    values.  That way after POOL_FREE, the pool is fully usable, just
    as if it were freshly initialized with POOL_INIT.  */
 
-#define POOL_FREE(p) do {                      \
-  struct pool *P = p;                          \
-  if (P->resized)                              \
-    xfree (P->contents);                       \
-  P->contents = P->orig_contents;              \
-  P->size = P->orig_size;                      \
-  P->tail = 0;                                 \
-  P->resized = 0;                              \
+#define POOL_FREE(p) do {                       \
+  struct pool *P = p;                           \
+  if (P->resized)                               \
+    xfree (P->contents);                        \
+  P->contents = P->orig_contents;               \
+  P->size = P->orig_size;                       \
+  P->tail = 0;                                  \
+  P->resized = false;                           \
 } while (0)
 
 /* Used for small stack-allocated memory chunks that might grow.  Like
@@ -237,152 +234,272 @@ struct pool {
    After the first resize, subsequent ones are performed with realloc,
    just like DO_REALLOC.  */
 
-#define GROW_ARRAY(basevar, sizevar, needed_size, resized, type) do {          \
-  long ga_needed_size = (needed_size);                                         \
-  long ga_newsize = (sizevar);                                                 \
-  while (ga_newsize < ga_needed_size)                                          \
-    ga_newsize <<= 1;                                                          \
-  if (ga_newsize != (sizevar))                                                 \
-    {                                                                          \
-      if (resized)                                                             \
-       basevar = (type *)xrealloc (basevar, ga_newsize * sizeof (type));       \
-      else                                                                     \
-       {                                                                       \
-         void *ga_new = xmalloc (ga_newsize * sizeof (type));                  \
-         memcpy (ga_new, basevar, (sizevar) * sizeof (type));                  \
-         (basevar) = ga_new;                                                   \
-         resized = 1;                                                          \
-       }                                                                       \
-      (sizevar) = ga_newsize;                                                  \
-    }                                                                          \
+#define GROW_ARRAY(basevar, sizevar, needed_size, resized, type) do {           \
+  long ga_needed_size = (needed_size);                                          \
+  long ga_newsize = (sizevar);                                                  \
+  while (ga_newsize < ga_needed_size)                                           \
+    ga_newsize <<= 1;                                                           \
+  if (ga_newsize != (sizevar))                                                  \
+    {                                                                           \
+      if (resized)                                                              \
+        basevar = xrealloc (basevar, ga_newsize * sizeof (type));               \
+      else                                                                      \
+        {                                                                       \
+          void *ga_new = xmalloc (ga_newsize * sizeof (type));                  \
+          memcpy (ga_new, basevar, (sizevar) * sizeof (type));                  \
+          (basevar) = ga_new;                                                   \
+          resized = true;                                                       \
+        }                                                                       \
+      (sizevar) = ga_newsize;                                                   \
+    }                                                                           \
 } while (0)
 \f
-#define AP_DOWNCASE            1
-#define AP_PROCESS_ENTITIES    2
-#define AP_TRIM_BLANKS         4
+/* Test whether n+1-sized entity name fits in P.  We don't support
+   IE-style non-terminated entities, e.g. "&ltfoo" -> "<foo".
+   However, "&lt;foo" will work, as will "&lt!foo", "&lt", etc.  In
+   other words an entity needs to be terminated by either a
+   non-alphanumeric or the end of string.  */
+#define FITS(p, n) (p + n == end || (p + n < end && !c_isalnum (p[n])))
+
+/* Macros that test entity names by returning true if P is followed by
+   the specified characters.  */
+#define ENT1(p, c0) (FITS (p, 1) && p[0] == c0)
+#define ENT2(p, c0, c1) (FITS (p, 2) && p[0] == c0 && p[1] == c1)
+#define ENT3(p, c0, c1, c2) (FITS (p, 3) && p[0]==c0 && p[1]==c1 && p[2]==c2)
+
+/* Increment P by INC chars.  If P lands at a semicolon, increment it
+   past the semicolon.  This ensures that e.g. "&lt;foo" is converted
+   to "<foo", but "&lt,foo" to "<,foo".  */
+#define SKIP_SEMI(p, inc) (p += inc, p < end && *p == ';' ? ++p : p)
+
+struct tagstack_item {
+  const char *tagname_begin;
+  const char *tagname_end;
+  const char *contents_begin;
+  struct tagstack_item *prev;
+  struct tagstack_item *next;
+};
+
+static struct tagstack_item *
+tagstack_push (struct tagstack_item **head, struct tagstack_item **tail)
+{
+  struct tagstack_item *ts = xmalloc(sizeof(struct tagstack_item));
+  if (*head == NULL)
+    {
+      *head = *tail = ts;
+      ts->prev = ts->next = NULL;
+    }
+  else
+    {
+      (*tail)->next = ts;
+      ts->prev = *tail;
+      *tail = ts;
+      ts->next = NULL;
+    }
+
+  return ts;
+}
+
+/* remove ts and everything after it from the stack */
+static void
+tagstack_pop (struct tagstack_item **head, struct tagstack_item **tail,
+              struct tagstack_item *ts)
+{
+  if (*head == NULL)
+    return;
+
+  if (ts == *tail)
+    {
+      if (ts == *head)
+        {
+          xfree (ts);
+          *head = *tail = NULL;
+        }
+      else
+        {
+          ts->prev->next = NULL;
+          *tail = ts->prev;
+          xfree (ts);
+        }
+    }
+  else
+    {
+      if (ts == *head)
+        {
+          *head = NULL;
+        }
+      *tail = ts->prev;
+
+      if (ts->prev)
+        {
+          ts->prev->next = NULL;
+        }
+      while (ts)
+        {
+          struct tagstack_item *p = ts->next;
+          xfree (ts);
+          ts = p;
+        }
+    }
+}
+
+static struct tagstack_item *
+tagstack_find (struct tagstack_item *tail, const char *tagname_begin,
+               const char *tagname_end)
+{
+  int len = tagname_end - tagname_begin;
+  while (tail)
+    {
+      if (len == (tail->tagname_end - tail->tagname_begin))
+        {
+          if (0 == strncasecmp (tail->tagname_begin, tagname_begin, len))
+            return tail;
+        }
+      tail = tail->prev;
+    }
+  return NULL;
+}
+
+/* Decode the HTML character entity at *PTR, considering END to be end
+   of buffer.  It is assumed that the "&" character that marks the
+   beginning of the entity has been seen at *PTR-1.  If a recognized
+   ASCII entity is seen, it is returned, and *PTR is moved to the end
+   of the entity.  Otherwise, -1 is returned and *PTR left unmodified.
+
+   The recognized entities are: &lt, &gt, &amp, &apos, and &quot.  */
+
+static int
+decode_entity (const char **ptr, const char *end)
+{
+  const char *p = *ptr;
+  int value = -1;
+
+  if (++p == end)
+    return -1;
+
+  switch (*p++)
+    {
+    case '#':
+      /* Process numeric entities "&#DDD;" and "&#xHH;".  */
+      {
+        int digits = 0;
+        value = 0;
+        if (*p == 'x')
+          for (++p; value < 256 && p < end && c_isxdigit (*p); p++, digits++)
+            value = (value << 4) + XDIGIT_TO_NUM (*p);
+        else
+          for (; value < 256 && p < end && c_isdigit (*p); p++, digits++)
+            value = (value * 10) + (*p - '0');
+        if (!digits)
+          return -1;
+        /* Don't interpret 128+ codes and NUL because we cannot
+           portably reinserted them into HTML.  */
+        if (!value || (value & ~0x7f))
+          return -1;
+        *ptr = SKIP_SEMI (p, 0);
+        return value;
+      }
+    /* Process named ASCII entities.  */
+    case 'g':
+      if (ENT1 (p, 't'))
+        value = '>', *ptr = SKIP_SEMI (p, 1);
+      break;
+    case 'l':
+      if (ENT1 (p, 't'))
+        value = '<', *ptr = SKIP_SEMI (p, 1);
+      break;
+    case 'a':
+      if (ENT2 (p, 'm', 'p'))
+        value = '&', *ptr = SKIP_SEMI (p, 2);
+      else if (ENT3 (p, 'p', 'o', 's'))
+        /* handle &apos for the sake of the XML/XHTML crowd. */
+        value = '\'', *ptr = SKIP_SEMI (p, 3);
+      break;
+    case 'q':
+      if (ENT3 (p, 'u', 'o', 't'))
+        value = '\"', *ptr = SKIP_SEMI (p, 3);
+      break;
+    }
+  return value;
+}
+#undef ENT1
+#undef ENT2
+#undef ENT3
+#undef FITS
+#undef SKIP_SEMI
+
+enum {
+  AP_DOWNCASE           = 1,
+  AP_DECODE_ENTITIES    = 2,
+  AP_TRIM_BLANKS        = 4
+};
 
 /* Copy the text in the range [BEG, END) to POOL, optionally
    performing operations specified by FLAGS.  FLAGS may be any
-   combination of AP_DOWNCASE, AP_PROCESS_ENTITIES and AP_TRIM_BLANKS
+   combination of AP_DOWNCASE, AP_DECODE_ENTITIES and AP_TRIM_BLANKS
    with the following meaning:
 
    * AP_DOWNCASE -- downcase all the letters;
 
-   * AP_PROCESS_ENTITIES -- process the SGML entities and write out
-   the decoded string.  Recognized entities are &lt, &gt, &amp, &quot,
-   &nbsp and the numerical entities.
+   * AP_DECODE_ENTITIES -- decode the named and numeric entities in
+     the ASCII range when copying the string.
 
    * AP_TRIM_BLANKS -- ignore blanks at the beginning and at the end
-   of text.  */
+     of text, as well as embedded newlines.  */
 
 static void
 convert_and_copy (struct pool *pool, const char *beg, const char *end, int flags)
 {
   int old_tail = pool->tail;
-  int size;
 
-  /* First, skip blanks if required.  We must do this before entities
-     are processed, so that blanks can still be inserted as, for
-     instance, `&#32;'.  */
+  /* Skip blanks if required.  We must do this before entities are
+     processed, so that blanks can still be inserted as, for instance,
+     `&#32;'.  */
   if (flags & AP_TRIM_BLANKS)
     {
-      while (beg < end && ISSPACE (*beg))
-       ++beg;
-      while (end > beg && ISSPACE (end[-1]))
-       --end;
+      while (beg < end && c_isspace (*beg))
+        ++beg;
+      while (end > beg && c_isspace (end[-1]))
+        --end;
     }
-  size = end - beg;
 
-  if (flags & AP_PROCESS_ENTITIES)
+  if (flags & AP_DECODE_ENTITIES)
     {
       /* Grow the pool, then copy the text to the pool character by
-        character, processing the encountered entities as we go
-        along.
+         character, processing the encountered entities as we go
+         along.
 
-        It's safe (and necessary) to grow the pool in advance because
-        processing the entities can only *shorten* the string, it can
-        never lengthen it.  */
+         It's safe (and necessary) to grow the pool in advance because
+         processing the entities can only *shorten* the string, it can
+         never lengthen it.  */
       const char *from = beg;
       char *to;
+      bool squash_newlines = !!(flags & AP_TRIM_BLANKS);
 
       POOL_GROW (pool, end - beg);
       to = pool->contents + pool->tail;
 
       while (from < end)
-       {
-         if (*from != '&')
-           *to++ = *from++;
-         else
-           {
-             const char *save = from;
-             int remain;
-
-             if (++from == end)
-               goto lose;
-             remain = end - from;
-
-             /* Process numeric entities "&#DDD;" and "&#xHH;".  */
-             if (*from == '#')
-               {
-                 int numeric = 0, digits = 0;
-                 ++from;
-                 if (*from == 'x')
-                   {
-                     ++from;
-                     for (; from < end && ISXDIGIT (*from); from++, digits++)
-                       numeric = (numeric << 4) + XDIGIT_TO_NUM (*from);
-                   }
-                 else
-                   {
-                     for (; from < end && ISDIGIT (*from); from++, digits++)
-                       numeric = (numeric * 10) + (*from - '0');
-                   }
-                 if (!digits)
-                   goto lose;
-                 numeric &= 0xff;
-                 *to++ = numeric;
-               }
-#define FROB(x) (remain >= (sizeof (x) - 1)                    \
-                && 0 == memcmp (from, x, sizeof (x) - 1)       \
-                && (*(from + sizeof (x) - 1) == ';'            \
-                    || remain == sizeof (x) - 1                \
-                    || !ISALNUM (*(from + sizeof (x) - 1))))
-             else if (FROB ("lt"))
-               *to++ = '<', from += 2;
-             else if (FROB ("gt"))
-               *to++ = '>', from += 2;
-             else if (FROB ("amp"))
-               *to++ = '&', from += 3;
-             else if (FROB ("quot"))
-               *to++ = '\"', from += 4;
-             /* We don't implement the proposed "Added Latin 1"
-                entities (except for nbsp), because it is unnecessary
-                in the context of Wget, and would require hashing to
-                work efficiently.  */
-             else if (FROB ("nbsp"))
-               *to++ = 160, from += 4;
-             else
-               goto lose;
-#undef FROB
-             /* If the entity was followed by `;', we step over the
-                `;'.  Otherwise, it was followed by either a
-                non-alphanumeric or EOB, in which case we do nothing.  */
-             if (from < end && *from == ';')
-               ++from;
-             continue;
-
-           lose:
-             /* This was not an entity after all.  Back out.  */
-             from = save;
-             *to++ = *from++;
-           }
-       }
+        {
+          if (*from == '&')
+            {
+              int entity = decode_entity (&from, end);
+              if (entity != -1)
+                *to++ = entity;
+              else
+                *to++ = *from++;
+            }
+          else if ((*from == '\n' || *from == '\r') && squash_newlines)
+            ++from;
+          else
+            *to++ = *from++;
+        }
       /* Verify that we haven't exceeded the original size.  (It
-        shouldn't happen, hence the assert.)  */
+         shouldn't happen, hence the assert.)  */
       assert (to - (pool->contents + pool->tail) <= end - beg);
 
       /* Make POOL's tail point to the position following the string
-        we've written.  */
+         we've written.  */
       pool->tail = to - pool->contents;
       POOL_APPEND_CHR (pool, '\0');
     }
@@ -397,7 +514,7 @@ convert_and_copy (struct pool *pool, const char *beg, const char *end, int flags
     {
       char *p = pool->contents + old_tail;
       for (; *p; p++)
-       *p = TOLOWER (*p);
+        *p = c_tolower (*p);
     }
 }
 \f
@@ -411,13 +528,14 @@ convert_and_copy (struct pool *pool, const char *beg, const char *end, int flags
      * whitespace
      * 8-bit and control chars
      * characters that clearly cannot be part of name:
-       '=', '>', '/'.
+       '=', '<', '>', '/'.
 
    This only affects attribute and tag names; attribute values allow
    an even greater variety of characters.  */
 
-#define NAME_CHAR_P(x) ((x) > 32 && (x) < 127                          \
-                       && (x) != '=' && (x) != '>' && (x) != '/')
+#define NAME_CHAR_P(x) ((x) > 32 && (x) < 127                           \
+                        && (x) != '=' && (x) != '<' && (x) != '>'       \
+                        && (x) != '/')
 
 #ifdef STANDALONE
 static int comment_backout_count;
@@ -446,7 +564,7 @@ static const char *
 advance_declaration (const char *beg, const char *end)
 {
   const char *p = beg;
-  char quote_char = '\0';      /* shut up, gcc! */
+  char quote_char = '\0';       /* shut up, gcc! */
   char ch;
 
   enum {
@@ -462,7 +580,7 @@ advance_declaration (const char *beg, const char *end)
     AC_S_DASH4,
     AC_S_QUOTE1,
     AC_S_IN_QUOTE,
-    AC_S_QUOTE2,
+    AC_S_QUOTE2
   } state = AC_S_BANG;
 
   if (beg == end)
@@ -475,122 +593,123 @@ advance_declaration (const char *beg, const char *end)
   while (state != AC_S_DONE && state != AC_S_BACKOUT)
     {
       if (p == end)
-       state = AC_S_BACKOUT;
+        state = AC_S_BACKOUT;
       switch (state)
-       {
-       case AC_S_DONE:
-       case AC_S_BACKOUT:
-         break;
-       case AC_S_BANG:
-         if (ch == '!')
-           {
-             ch = *p++;
-             state = AC_S_DEFAULT;
-           }
-         else
-           state = AC_S_BACKOUT;
-         break;
-       case AC_S_DEFAULT:
-         switch (ch)
-           {
-           case '-':
-             state = AC_S_DASH1;
-             break;
-           case ' ':
-           case '\t':
-           case '\r':
-           case '\n':
-             ch = *p++;
-             break;
-           case '>':
-             state = AC_S_DONE;
-             break;
-           case '\'':
-           case '\"':
-             state = AC_S_QUOTE1;
-             break;
-           default:
-             if (NAME_CHAR_P (ch))
-               state = AC_S_DCLNAME;
-             else
-               state = AC_S_BACKOUT;
-             break;
-           }
-         break;
-       case AC_S_DCLNAME:
-         if (ch == '-')
-           state = AC_S_DASH1;
-         else if (NAME_CHAR_P (ch))
-           ch = *p++;
-         else
-           state = AC_S_DEFAULT;
-         break;
-       case AC_S_QUOTE1:
-         /* We must use 0x22 because broken assert macros choke on
-            '"' and '\"'.  */
-         assert (ch == '\'' || ch == 0x22);
-         quote_char = ch;      /* cheating -- I really don't feel like
-                                  introducing more different states for
-                                  different quote characters. */
-         ch = *p++;
-         state = AC_S_IN_QUOTE;
-         break;
-       case AC_S_IN_QUOTE:
-         if (ch == quote_char)
-           state = AC_S_QUOTE2;
-         else
-           ch = *p++;
-         break;
-       case AC_S_QUOTE2:
-         assert (ch == quote_char);
-         ch = *p++;
-         state = AC_S_DEFAULT;
-         break;
-       case AC_S_DASH1:
-         assert (ch == '-');
-         ch = *p++;
-         state = AC_S_DASH2;
-         break;
-       case AC_S_DASH2:
-         switch (ch)
-           {
-           case '-':
-             ch = *p++;
-             state = AC_S_COMMENT;
-             break;
-           default:
-             state = AC_S_BACKOUT;
-           }
-         break;
-       case AC_S_COMMENT:
-         switch (ch)
-           {
-           case '-':
-             state = AC_S_DASH3;
-             break;
-           default:
-             ch = *p++;
-             break;
-           }
-         break;
-       case AC_S_DASH3:
-         assert (ch == '-');
-         ch = *p++;
-         state = AC_S_DASH4;
-         break;
-       case AC_S_DASH4:
-         switch (ch)
-           {
-           case '-':
-             ch = *p++;
-             state = AC_S_DEFAULT;
-             break;
-           default:
-             state = AC_S_COMMENT;
-             break;
-           }
-         break;
-       }
+        {
+        case AC_S_DONE:
+        case AC_S_BACKOUT:
+          break;
+        case AC_S_BANG:
+          if (ch == '!')
+            {
+              ch = *p++;
+              state = AC_S_DEFAULT;
+            }
+          else
+            state = AC_S_BACKOUT;
+          break;
+        case AC_S_DEFAULT:
+          switch (ch)
+            {
+            case '-':
+              state = AC_S_DASH1;
+              break;
+            case ' ':
+            case '\t':
+            case '\r':
+            case '\n':
+              ch = *p++;
+              break;
+            case '<':
+            case '>':
+              state = AC_S_DONE;
+              break;
+            case '\'':
+            case '\"':
+              state = AC_S_QUOTE1;
+              break;
+            default:
+              if (NAME_CHAR_P (ch))
+                state = AC_S_DCLNAME;
+              else
+                state = AC_S_BACKOUT;
+              break;
+            }
+          break;
+        case AC_S_DCLNAME:
+          if (ch == '-')
+            state = AC_S_DASH1;
+          else if (NAME_CHAR_P (ch))
+            ch = *p++;
+          else
+            state = AC_S_DEFAULT;
+          break;
+        case AC_S_QUOTE1:
+          /* We must use 0x22 because broken assert macros choke on
+             '"' and '\"'.  */
+          assert (ch == '\'' || ch == 0x22);
+          quote_char = ch;      /* cheating -- I really don't feel like
+                                   introducing more different states for
+                                   different quote characters. */
+          ch = *p++;
+          state = AC_S_IN_QUOTE;
+          break;
+        case AC_S_IN_QUOTE:
+          if (ch == quote_char)
+            state = AC_S_QUOTE2;
+          else
+            ch = *p++;
+          break;
+        case AC_S_QUOTE2:
+          assert (ch == quote_char);
+          ch = *p++;
+          state = AC_S_DEFAULT;
+          break;
+        case AC_S_DASH1:
+          assert (ch == '-');
+          ch = *p++;
+          state = AC_S_DASH2;
+          break;
+        case AC_S_DASH2:
+          switch (ch)
+            {
+            case '-':
+              ch = *p++;
+              state = AC_S_COMMENT;
+              break;
+            default:
+              state = AC_S_BACKOUT;
+            }
+          break;
+        case AC_S_COMMENT:
+          switch (ch)
+            {
+            case '-':
+              state = AC_S_DASH3;
+              break;
+            default:
+              ch = *p++;
+              break;
+            }
+          break;
+        case AC_S_DASH3:
+          assert (ch == '-');
+          ch = *p++;
+          state = AC_S_DASH4;
+          break;
+        case AC_S_DASH4:
+          switch (ch)
+            {
+            case '-':
+              ch = *p++;
+              state = AC_S_DEFAULT;
+              break;
+            default:
+              state = AC_S_COMMENT;
+              break;
+            }
+          break;
+        }
     }
 
   if (state == AC_S_BACKOUT)
@@ -620,47 +739,47 @@ find_comment_end (const char *beg, const char *end)
     switch (p[0])
       {
       case '>':
-       if (p[-1] == '-' && p[-2] == '-')
-         return p + 1;
-       break;
+        if (p[-1] == '-' && p[-2] == '-')
+          return p + 1;
+        break;
       case '-':
       at_dash:
-       if (p[-1] == '-')
-         {
-         at_dash_dash:
-           if (++p == end) return NULL;
-           switch (p[0])
-             {
-             case '>': return p + 1;
-             case '-': goto at_dash_dash;
-             }
-         }
-       else
-         {
-           if ((p += 2) >= end) return NULL;
-           switch (p[0])
-             {
-             case '>':
-               if (p[-1] == '-')
-                 return p + 1;
-               break;
-             case '-':
-               goto at_dash;
-             }
-         }
+        if (p[-1] == '-')
+          {
+          at_dash_dash:
+            if (++p == end) return NULL;
+            switch (p[0])
+              {
+              case '>': return p + 1;
+              case '-': goto at_dash_dash;
+              }
+          }
+        else
+          {
+            if ((p += 2) >= end) return NULL;
+            switch (p[0])
+              {
+              case '>':
+                if (p[-1] == '-')
+                  return p + 1;
+                break;
+              case '-':
+                goto at_dash;
+              }
+          }
       }
   return NULL;
 }
 \f
-/* Return non-zero of the string inside [b, e) are present in hash
-   table HT.  */
+/* Return true if the string containing of characters inside [b, e) is
+   present in hash table HT.  */
 
-static int
+static bool
 name_allowed (const struct hash_table *ht, const char *b, const char *e)
 {
   char *copy;
   if (!ht)
-    return 1;
+    return true;
   BOUNDED_TO_ALLOCA (b, e, copy);
   return hash_table_get (ht, copy) != NULL;
 }
@@ -668,26 +787,26 @@ name_allowed (const struct hash_table *ht, const char *b, const char *e)
 /* Advance P (a char pointer), with the explicit intent of being able
    to read the next character.  If this is not possible, go to finish.  */
 
-#define ADVANCE(p) do {                                \
-  ++p;                                         \
-  if (p >= end)                                        \
-    goto finish;                               \
+#define ADVANCE(p) do {                         \
+  ++p;                                          \
+  if (p >= end)                                 \
+    goto finish;                                \
 } while (0)
 
 /* Skip whitespace, if any. */
 
-#define SKIP_WS(p) do {                                \
-  while (ISSPACE (*p)) {                       \
-    ADVANCE (p);                               \
-  }                                            \
+#define SKIP_WS(p) do {                         \
+  while (c_isspace (*p)) {                        \
+    ADVANCE (p);                                \
+  }                                             \
 } while (0)
 
 /* Skip non-whitespace, if any. */
 
-#define SKIP_NON_WS(p) do {                    \
-  while (!ISSPACE (*p)) {                      \
-    ADVANCE (p);                               \
-  }                                            \
+#define SKIP_NON_WS(p) do {                     \
+  while (!c_isspace (*p)) {                       \
+    ADVANCE (p);                                \
+  }                                             \
 } while (0)
 
 #ifdef STANDALONE
@@ -698,24 +817,22 @@ static int tag_backout_count;
    MAPFUN will be called with two arguments: pointer to an initialized
    struct taginfo, and MAPARG.
 
-   ALLOWED_TAG_NAMES should be a NULL-terminated array of tag names to
-   be processed by this function.  If it is NULL, all the tags are
-   allowed.  The same goes for attributes and ALLOWED_ATTRIBUTE_NAMES.
+   ALLOWED_TAGS and ALLOWED_ATTRIBUTES are hash tables the keys of
+   which are the tags and attribute names that this function should
+   use.  If ALLOWED_TAGS is NULL, all tags are processed; if
+   ALLOWED_ATTRIBUTES is NULL, all attributes are returned.
 
    (Obviously, the caller can filter out unwanted tags and attributes
    just as well, but this is just an optimization designed to avoid
-   unnecessary copying for tags/attributes which the caller doesn't
-   want to know about.  These lists are searched linearly; therefore,
-   if you're interested in a large number of tags or attributes, you'd
-   better set these to NULL and filter them out yourself with a
-   hashing process most appropriate for your application.)  */
+   unnecessary copying of tags/attributes which the caller doesn't
+   care about.)  */
 
 void
 map_html_tags (const char *text, int size,
-              void (*mapfun) (struct taginfo *, void *), void *maparg,
-              int flags,
-              const struct hash_table *allowed_tags,
-              const struct hash_table *allowed_attributes)
+               void (*mapfun) (struct taginfo *, void *), void *maparg,
+               int flags,
+               const struct hash_table *allowed_tags,
+               const struct hash_table *allowed_attributes)
 {
   /* storage for strings passed to MAPFUN callback; if 256 bytes is
      too little, POOL_APPEND allocates more with malloc. */
@@ -727,9 +844,12 @@ map_html_tags (const char *text, int size,
 
   struct attr_pair attr_pair_initial_storage[8];
   int attr_pair_size = countof (attr_pair_initial_storage);
-  int attr_pair_resized = 0;
+  bool attr_pair_resized = false;
   struct attr_pair *pairs = attr_pair_initial_storage;
 
+  struct tagstack_item *head = NULL;
+  struct tagstack_item *tail = NULL;
+
   if (!size)
     return;
 
@@ -739,7 +859,7 @@ map_html_tags (const char *text, int size,
     int nattrs, end_tag;
     const char *tag_name_begin, *tag_name_end;
     const char *tag_start_position;
-    int uninteresting_tag;
+    bool uninteresting_tag;
 
   look_for_tag:
     POOL_REWIND (&pool);
@@ -760,34 +880,34 @@ map_html_tags (const char *text, int size,
        declaration).  */
     if (*p == '!')
       {
-       if (!(flags & MHT_STRICT_COMMENTS)
-           && p < end + 3 && p[1] == '-' && p[2] == '-')
-         {
-           /* If strict comments are not enforced and if we know
-              we're looking at a comment, simply look for the
-              terminating "-->".  Non-strict is the default because
-              it works in other browsers and most HTML writers can't
-              be bothered with getting the comments right.  */
-           const char *comment_end = find_comment_end (p + 3, end);
-           if (comment_end)
-             p = comment_end;
-         }
-       else
-         {
-           /* Either in strict comment mode or looking at a non-empty
-              declaration.  Real declarations are much less likely to
-              be misused the way comments are, so advance over them
-              properly regardless of strictness.  */
-           p = advance_declaration (p, end);
-         }
-       if (p == end)
-         goto finish;
-       goto look_for_tag;
+        if (!(flags & MHT_STRICT_COMMENTS)
+            && p < end + 3 && p[1] == '-' && p[2] == '-')
+          {
+            /* If strict comments are not enforced and if we know
+               we're looking at a comment, simply look for the
+               terminating "-->".  Non-strict is the default because
+               it works in other browsers and most HTML writers can't
+               be bothered with getting the comments right.  */
+            const char *comment_end = find_comment_end (p + 3, end);
+            if (comment_end)
+              p = comment_end;
+          }
+        else
+          {
+            /* Either in strict comment mode or looking at a non-empty
+               declaration.  Real declarations are much less likely to
+               be misused the way comments are, so advance over them
+               properly regardless of strictness.  */
+            p = advance_declaration (p, end);
+          }
+        if (p == end)
+          goto finish;
+        goto look_for_tag;
       }
     else if (*p == '/')
       {
-       end_tag = 1;
-       ADVANCE (p);
+        end_tag = 1;
+        ADVANCE (p);
       }
     tag_name_begin = p;
     while (NAME_CHAR_P (*p))
@@ -796,171 +916,189 @@ map_html_tags (const char *text, int size,
       goto look_for_tag;
     tag_name_end = p;
     SKIP_WS (p);
-    if (end_tag && *p != '>')
+
+    if (!end_tag)
+      {
+        struct tagstack_item *ts = tagstack_push (&head, &tail);
+        if (ts)
+          {
+            ts->tagname_begin  = tag_name_begin;
+            ts->tagname_end    = tag_name_end;
+            ts->contents_begin = NULL;
+          }
+      }
+
+    if (end_tag && *p != '>' && *p != '<')
       goto backout_tag;
 
     if (!name_allowed (allowed_tags, tag_name_begin, tag_name_end))
       /* We can't just say "goto look_for_tag" here because we need
          the loop below to properly advance over the tag's attributes.  */
-      uninteresting_tag = 1;
+      uninteresting_tag = true;
     else
       {
-       uninteresting_tag = 0;
-       convert_and_copy (&pool, tag_name_begin, tag_name_end, AP_DOWNCASE);
+        uninteresting_tag = false;
+        convert_and_copy (&pool, tag_name_begin, tag_name_end, AP_DOWNCASE);
       }
 
     /* Find the attributes. */
     while (1)
       {
-       const char *attr_name_begin, *attr_name_end;
-       const char *attr_value_begin, *attr_value_end;
-       const char *attr_raw_value_begin, *attr_raw_value_end;
-       int operation = AP_DOWNCASE; /* stupid compiler. */
-
-       SKIP_WS (p);
-
-       if (*p == '/')
-         {
-           /* A slash at this point means the tag is about to be
-              closed.  This is legal in XML and has been popularized
-              in HTML via XHTML.  */
-           /* <foo a=b c=d /> */
-           /*              ^  */
-           ADVANCE (p);
-           SKIP_WS (p);
-           if (*p != '>')
-             goto backout_tag;
-         }
-
-       /* Check for end of tag definition. */
-       if (*p == '>')
-         break;
-
-       /* Establish bounds of attribute name. */
-       attr_name_begin = p;    /* <foo bar ...> */
-                               /*      ^        */
-       while (NAME_CHAR_P (*p))
-         ADVANCE (p);
-       attr_name_end = p;      /* <foo bar ...> */
-                               /*         ^     */
-       if (attr_name_begin == attr_name_end)
-         goto backout_tag;
-
-       /* Establish bounds of attribute value. */
-       SKIP_WS (p);
-       if (NAME_CHAR_P (*p) || *p == '/' || *p == '>')
-         {
-           /* Minimized attribute syntax allows `=' to be omitted.
+        const char *attr_name_begin, *attr_name_end;
+        const char *attr_value_begin, *attr_value_end;
+        const char *attr_raw_value_begin, *attr_raw_value_end;
+        int operation = AP_DOWNCASE; /* stupid compiler. */
+
+        SKIP_WS (p);
+
+        if (*p == '/')
+          {
+            /* A slash at this point means the tag is about to be
+               closed.  This is legal in XML and has been popularized
+               in HTML via XHTML.  */
+            /* <foo a=b c=d /> */
+            /*              ^  */
+            ADVANCE (p);
+            SKIP_WS (p);
+            if (*p != '<' && *p != '>')
+              goto backout_tag;
+          }
+
+        /* Check for end of tag definition. */
+        if (*p == '<' || *p == '>')
+          break;
+
+        /* Establish bounds of attribute name. */
+        attr_name_begin = p;    /* <foo bar ...> */
+                                /*      ^        */
+        while (NAME_CHAR_P (*p))
+          ADVANCE (p);
+        attr_name_end = p;      /* <foo bar ...> */
+                                /*         ^     */
+        if (attr_name_begin == attr_name_end)
+          goto backout_tag;
+
+        /* Establish bounds of attribute value. */
+        SKIP_WS (p);
+
+        if (NAME_CHAR_P (*p) || *p == '/' || *p == '<' || *p == '>')
+          {
+            /* Minimized attribute syntax allows `=' to be omitted.
                For example, <UL COMPACT> is a valid shorthand for <UL
                COMPACT="compact">.  Even if such attributes are not
                useful to Wget, we need to support them, so that the
                tags containing them can be parsed correctly. */
-           attr_raw_value_begin = attr_value_begin = attr_name_begin;
-           attr_raw_value_end = attr_value_end = attr_name_end;
-         }
-       else if (*p == '=')
-         {
-           ADVANCE (p);
-           SKIP_WS (p);
-           if (*p == '\"' || *p == '\'')
-             {
-               int newline_seen = 0;
-               char quote_char = *p;
-               attr_raw_value_begin = p;
-               ADVANCE (p);
-               attr_value_begin = p; /* <foo bar="baz"> */
-                                     /*           ^     */
-               while (*p != quote_char)
-                 {
-                   if (!newline_seen && *p == '\n')
-                     {
-                       /* If a newline is seen within the quotes, it
-                          is most likely that someone forgot to close
-                          the quote.  In that case, we back out to
-                          the value beginning, and terminate the tag
-                          at either `>' or the delimiter, whichever
-                          comes first.  Such a tag terminated at `>'
-                          is discarded.  */
-                       p = attr_value_begin;
-                       newline_seen = 1;
-                       continue;
-                     }
-                   else if (newline_seen && *p == '>')
-                     break;
-                   ADVANCE (p);
-                 }
-               attr_value_end = p; /* <foo bar="baz"> */
-                                   /*              ^  */
-               if (*p == quote_char)
-                 ADVANCE (p);
-               else
-                 goto look_for_tag;
-               attr_raw_value_end = p; /* <foo bar="baz"> */
-                                       /*               ^ */
-               operation = AP_PROCESS_ENTITIES;
-               if (flags & MHT_TRIM_VALUES)
-                 operation |= AP_TRIM_BLANKS;
-             }
-           else
-             {
-               attr_value_begin = p; /* <foo bar=baz> */
-                                     /*          ^    */
-               /* According to SGML, a name token should consist only
-                  of alphanumerics, . and -.  However, this is often
-                  violated by, for instance, `%' in `width=75%'.
-                  We'll be liberal and allow just about anything as
-                  an attribute value.  */
-               while (!ISSPACE (*p) && *p != '>')
-                 ADVANCE (p);
-               attr_value_end = p; /* <foo bar=baz qux=quix> */
-                                   /*             ^          */
-               if (attr_value_begin == attr_value_end)
-                 /* <foo bar=> */
-                 /*          ^ */
-                 goto backout_tag;
-               attr_raw_value_begin = attr_value_begin;
-               attr_raw_value_end = attr_value_end;
-               operation = AP_PROCESS_ENTITIES;
-             }
-         }
-       else
-         {
-           /* We skipped the whitespace and found something that is
-              neither `=' nor the beginning of the next attribute's
-              name.  Back out.  */
-           goto backout_tag;   /* <foo bar [... */
-                               /*          ^    */
-         }
-
-       /* If we're not interested in the tag, don't bother with any
+            attr_raw_value_begin = attr_value_begin = attr_name_begin;
+            attr_raw_value_end = attr_value_end = attr_name_end;
+          }
+        else if (*p == '=')
+          {
+            ADVANCE (p);
+            SKIP_WS (p);
+            if (*p == '\"' || *p == '\'')
+              {
+                bool newline_seen = false;
+                char quote_char = *p;
+                attr_raw_value_begin = p;
+                ADVANCE (p);
+                attr_value_begin = p; /* <foo bar="baz"> */
+                                      /*           ^     */
+                while (*p != quote_char)
+                  {
+                    if (!newline_seen && *p == '\n')
+                      {
+                        /* If a newline is seen within the quotes, it
+                           is most likely that someone forgot to close
+                           the quote.  In that case, we back out to
+                           the value beginning, and terminate the tag
+                           at either `>' or the delimiter, whichever
+                           comes first.  Such a tag terminated at `>'
+                           is discarded.  */
+                        p = attr_value_begin;
+                        newline_seen = true;
+                        continue;
+                      }
+                    else if (newline_seen && (*p == '<' || *p == '>'))
+                      break;
+                    ADVANCE (p);
+                  }
+                attr_value_end = p; /* <foo bar="baz"> */
+                                    /*              ^  */
+                if (*p == quote_char)
+                  ADVANCE (p);
+                else
+                  goto look_for_tag;
+                attr_raw_value_end = p; /* <foo bar="baz"> */
+                                        /*               ^ */
+                operation = AP_DECODE_ENTITIES;
+                if (flags & MHT_TRIM_VALUES)
+                  operation |= AP_TRIM_BLANKS;
+              }
+            else
+              {
+                attr_value_begin = p; /* <foo bar=baz> */
+                                      /*          ^    */
+                /* According to SGML, a name token should consist only
+                   of alphanumerics, . and -.  However, this is often
+                   violated by, for instance, `%' in `width=75%'.
+                   We'll be liberal and allow just about anything as
+                   an attribute value.  */
+                while (!c_isspace (*p) && *p != '<' && *p != '>')
+                  ADVANCE (p);
+                attr_value_end = p; /* <foo bar=baz qux=quix> */
+                                    /*             ^          */
+                if (attr_value_begin == attr_value_end)
+                  /* <foo bar=> */
+                  /*          ^ */
+                  goto backout_tag;
+                attr_raw_value_begin = attr_value_begin;
+                attr_raw_value_end = attr_value_end;
+                operation = AP_DECODE_ENTITIES;
+              }
+          }
+        else
+          {
+            /* We skipped the whitespace and found something that is
+               neither `=' nor the beginning of the next attribute's
+               name.  Back out.  */
+            goto backout_tag;   /* <foo bar [... */
+                                /*          ^    */
+          }
+
+        /* If we're not interested in the tag, don't bother with any
            of the attributes.  */
-       if (uninteresting_tag)
-         continue;
+        if (uninteresting_tag)
+          continue;
 
-       /* If we aren't interested in the attribute, skip it.  We
+        /* If we aren't interested in the attribute, skip it.  We
            cannot do this test any sooner, because our text pointer
            needs to correctly advance over the attribute.  */
-       if (!name_allowed (allowed_attributes, attr_name_begin, attr_name_end))
-         continue;
+        if (!name_allowed (allowed_attributes, attr_name_begin, attr_name_end))
+          continue;
+
+        GROW_ARRAY (pairs, attr_pair_size, nattrs + 1, attr_pair_resized,
+                    struct attr_pair);
 
-       GROW_ARRAY (pairs, attr_pair_size, nattrs + 1, attr_pair_resized,
-                   struct attr_pair);
+        pairs[nattrs].name_pool_index = pool.tail;
+        convert_and_copy (&pool, attr_name_begin, attr_name_end, AP_DOWNCASE);
 
-       pairs[nattrs].name_pool_index = pool.tail;
-       convert_and_copy (&pool, attr_name_begin, attr_name_end, AP_DOWNCASE);
+        pairs[nattrs].value_pool_index = pool.tail;
+        convert_and_copy (&pool, attr_value_begin, attr_value_end, operation);
+        pairs[nattrs].value_raw_beginning = attr_raw_value_begin;
+        pairs[nattrs].value_raw_size = (attr_raw_value_end
+                                        - attr_raw_value_begin);
+        ++nattrs;
+      }
 
-       pairs[nattrs].value_pool_index = pool.tail;
-       convert_and_copy (&pool, attr_value_begin, attr_value_end, operation);
-       pairs[nattrs].value_raw_beginning = attr_raw_value_begin;
-       pairs[nattrs].value_raw_size = (attr_raw_value_end
-                                       - attr_raw_value_begin);
-       ++nattrs;
+    if (!end_tag && tail && (tail->tagname_begin == tag_name_begin))
+      {
+        tail->contents_begin = p+1;
       }
 
     if (uninteresting_tag)
       {
-       ADVANCE (p);
-       goto look_for_tag;
+        ADVANCE (p);
+        goto look_for_tag;
       }
 
     /* By now, we have a valid tag with a name and zero or more
@@ -968,25 +1106,43 @@ map_html_tags (const char *text, int size,
     {
       int i;
       struct taginfo taginfo;
+      struct tagstack_item *ts = NULL;
 
       taginfo.name      = pool.contents;
       taginfo.end_tag_p = end_tag;
       taginfo.nattrs    = nattrs;
       /* We fill in the char pointers only now, when pool can no
-        longer get realloc'ed.  If we did that above, we could get
-        hosed by reallocation.  Obviously, after this point, the pool
-        may no longer be grown.  */
+         longer get realloc'ed.  If we did that above, we could get
+         hosed by reallocation.  Obviously, after this point, the pool
+         may no longer be grown.  */
       for (i = 0; i < nattrs; i++)
-       {
-         pairs[i].name = pool.contents + pairs[i].name_pool_index;
-         pairs[i].value = pool.contents + pairs[i].value_pool_index;
-       }
+        {
+          pairs[i].name = pool.contents + pairs[i].name_pool_index;
+          pairs[i].value = pool.contents + pairs[i].value_pool_index;
+        }
       taginfo.attrs = pairs;
       taginfo.start_position = tag_start_position;
       taginfo.end_position   = p + 1;
-      /* Ta-dam! */
-      (*mapfun) (&taginfo, maparg);
-      ADVANCE (p);
+      taginfo.contents_begin = NULL;
+      taginfo.contents_end = NULL;
+
+      if (end_tag)
+        {
+          ts = tagstack_find (tail, tag_name_begin, tag_name_end);
+          if (ts)
+            {
+              if (ts->contents_begin)
+                {
+                  taginfo.contents_begin = ts->contents_begin;
+                  taginfo.contents_end   = tag_start_position;
+                }
+              tagstack_pop (&head, &tail, ts);
+            }
+        }
+
+      mapfun (&taginfo, maparg);
+      if (*p != '<')
+        ADVANCE (p);
     }
     goto look_for_tag;
 
@@ -1004,6 +1160,8 @@ map_html_tags (const char *text, int size,
   POOL_FREE (&pool);
   if (attr_pair_resized)
     xfree (pairs);
+  /* pop any tag stack that's left */
+  tagstack_pop (&head, &tail, head);
 }
 
 #undef ADVANCE
@@ -1026,7 +1184,7 @@ test_mapper (struct taginfo *taginfo, void *arg)
 int main ()
 {
   int size = 256;
-  char *x = (char *)xmalloc (size);
+  char *x = xmalloc (size);
   int length = 0;
   int read_count;
   int tag_counter = 0;
@@ -1035,7 +1193,7 @@ int main ()
     {
       length += read_count;
       size <<= 1;
-      x = (char *)xrealloc (x, size);
+      x = xrealloc (x, size);
     }
 
   map_html_tags (x, length, test_mapper, &tag_counter, 0, NULL, NULL);