/* HTML parser for Wget.
- Copyright (C) 1998, 2000, 2003 Free Software Foundation, Inc.
+ Copyright (C) 1998-2005 Free Software Foundation, Inc.
This file is part of GNU Wget.
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.
+along with Wget; if not, write to the Free Software Foundation, Inc.,
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
In addition, as a special exception, the Free Software Foundation
gives permission to link the code of its release of Wget with the
#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"
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
+ bool resized; /* whether the pool has been resized
using malloc. */
char *orig_contents; /* original pool contents, usually
P->contents = (initial_storage); \
P->size = (initial_size); \
P->tail = 0; \
- P->resized = 0; \
+ P->resized = false; \
P->orig_contents = P->contents; \
P->orig_size = P->size; \
} while (0)
P->contents = P->orig_contents; \
P->size = P->orig_size; \
P->tail = 0; \
- P->resized = 0; \
+ P->resized = false; \
} while (0)
/* Used for small stack-allocated memory chunks that might grow. Like
if (ga_newsize != (sizevar)) \
{ \
if (resized) \
- basevar = (type *)xrealloc (basevar, ga_newsize * sizeof (type)); \
+ 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 = 1; \
+ 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. "<foo" -> "<foo".
+ However, "<foo" will work, as will "<!foo", "<", 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 && !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. "<foo" is converted
+ to "<foo", but "<,foo" to "<,foo". */
+#define SKIP_SEMI(p, inc) (p += inc, p < end && *p == ';' ? ++p : p)
+
+/* 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: <, >, &, &apos, and ". */
+
+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 && ISXDIGIT (*p); p++, digits++)
+ value = (value << 4) + XDIGIT_TO_NUM (*p);
+ else
+ for (; value < 256 && p < end && 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 <, >, &, ",
-   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, ` '. */
+ /* Skip blanks if required. We must do this before entities are
+ processed, so that blanks can still be inserted as, for instance,
+ ` '. */
if (flags & AP_TRIM_BLANKS)
{
while (beg < end && ISSPACE (*beg))
while (end > beg && 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
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
+ if (*from == '&')
{
- 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;
+ int entity = decode_entity (&from, end);
+ if (entity != -1)
+ *to++ = entity;
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++;
+ *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.) */
AC_S_DASH4,
AC_S_QUOTE1,
AC_S_IN_QUOTE,
- AC_S_QUOTE2,
+ AC_S_QUOTE2
} state = AC_S_BANG;
if (beg == end)
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;
}
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,
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;
if (!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);
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;
+ uninteresting_tag = false;
convert_and_copy (&pool, tag_name_begin, tag_name_end, AP_DOWNCASE);
}
SKIP_WS (p);
if (*p == '\"' || *p == '\'')
{
- int newline_seen = 0;
+ bool newline_seen = false;
char quote_char = *p;
attr_raw_value_begin = p;
ADVANCE (p);
comes first. Such a tag terminated at `>'
is discarded. */
p = attr_value_begin;
- newline_seen = 1;
+ newline_seen = true;
continue;
}
else if (newline_seen && *p == '>')
goto look_for_tag;
attr_raw_value_end = p; /* <foo bar="baz"> */
/* ^ */
- operation = AP_PROCESS_ENTITIES;
+ operation = AP_DECODE_ENTITIES;
if (flags & MHT_TRIM_VALUES)
operation |= AP_TRIM_BLANKS;
}
goto backout_tag;
attr_raw_value_begin = attr_value_begin;
attr_raw_value_end = attr_value_end;
- operation = AP_PROCESS_ENTITIES;
+ operation = AP_DECODE_ENTITIES;
}
}
else
taginfo.attrs = pairs;
taginfo.start_position = tag_start_position;
taginfo.end_position = p + 1;
- /* Ta-dam! */
- (*mapfun) (&taginfo, maparg);
+ mapfun (&taginfo, maparg);
ADVANCE (p);
}
goto look_for_tag;
int main ()
{
int size = 256;
- char *x = (char *)xmalloc (size);
+ char *x = xmalloc (size);
int length = 0;
int read_count;
int tag_counter = 0;
{
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);