]> sjero.net Git - wget/blob - src/html-parse.c
[svn] Added undefs for FITS and SKIP_SEMI macros.
[wget] / src / html-parse.c
1 /* HTML parser for Wget.
2    Copyright (C) 1998, 2000, 2003 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at
9 your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 In addition, as a special exception, the Free Software Foundation
21 gives permission to link the code of its release of Wget with the
22 OpenSSL project's "OpenSSL" library (or with modified versions of it
23 that use the same license as the "OpenSSL" library), and distribute
24 the linked executables.  You must obey the GNU General Public License
25 in all respects for all of the code used other than "OpenSSL".  If you
26 modify this file, you may extend this exception to your version of the
27 file, but you are not obligated to do so.  If you do not wish to do
28 so, delete this exception statement from your version.  */
29
30 /* The only entry point to this module is map_html_tags(), which see.  */
31
32 /* TODO:
33
34    - Allow hooks for callers to process contents outside tags.  This
35      is needed to implement handling <style> and <script>.  The
36      taginfo structure already carries the information about where the
37      tags are, but this is not enough, because one would also want to
38      skip the comments.  (The funny thing is that for <style> and
39      <script> you *don't* want to skip comments!)
40
41    - Create a test suite for regression testing. */
42
43 /* HISTORY:
44
45    This is the third HTML parser written for Wget.  The first one was
46    written some time during the Geturl 1.0 beta cycle, and was very
47    inefficient and buggy.  It also contained some very complex code to
48    remember a list of parser states, because it was supposed to be
49    reentrant.
50
51    The second HTML parser was written for Wget 1.4 (the first version
52    by the name `Wget'), and was a complete rewrite.  Although the new
53    parser behaved much better and made no claims of reentrancy, it
54    still shared many of the fundamental flaws of the old version -- it
55    only regarded HTML in terms tag-attribute pairs, where the
56    attribute's value was a URL to be returned.  Any other property of
57    HTML, such as <base href=...>, or strange way to specify a URL,
58    such as <meta http-equiv=Refresh content="0; URL=..."> had to be
59    crudely hacked in -- and the caller had to be aware of these hacks.
60    Like its predecessor, this parser did not support HTML comments.
61
62    After Wget 1.5.1 was released, I set out to write a third HTML
63    parser.  The objectives of the new parser were to: (1) provide a
64    clean way to analyze HTML lexically, (2) separate interpretation of
65    the markup from the parsing process, (3) be as correct as possible,
66    e.g. correctly skipping comments and other SGML declarations, (4)
67    understand the most common errors in markup and skip them or be
68    relaxed towrds them, and (5) be reasonably efficient (no regexps,
69    minimum copying and minimum or no heap allocation).
70
71    I believe this parser meets all of the above goals.  It is
72    reasonably well structured, and could be relatively easily
73    separated from Wget and used elsewhere.  While some of its
74    intrinsic properties limit its value as a general-purpose HTML
75    parser, I believe that, with minimum modifications, it could serve
76    as a backend for one.
77
78    Due to time and other constraints, this parser was not integrated
79    into Wget until the version 1.7. */
80
81 /* DESCRIPTION:
82
83    The single entry point of this parser is map_html_tags(), which
84    works by calling a function you specify for each tag.  The function
85    gets called with the pointer to a structure describing the tag and
86    its attributes.  */
87
88 /* To test as standalone, compile with `-DSTANDALONE -I.'.  You'll
89    still need Wget headers to compile.  */
90
91 #include <config.h>
92
93 #ifdef STANDALONE
94 # define I_REALLY_WANT_CTYPE_MACROS
95 #endif
96
97 #include <stdio.h>
98 #include <stdlib.h>
99 #ifdef HAVE_STRING_H
100 # include <string.h>
101 #else
102 # include <strings.h>
103 #endif
104 #include <assert.h>
105
106 #include "wget.h"
107 #include "html-parse.h"
108
109 #ifdef STANDALONE
110 # undef xmalloc
111 # undef xrealloc
112 # undef xfree
113 # define xmalloc malloc
114 # define xrealloc realloc
115 # define xfree free
116
117 # undef ISSPACE
118 # undef ISDIGIT
119 # undef ISXDIGIT
120 # undef ISALPHA
121 # undef ISALNUM
122 # undef TOLOWER
123 # undef TOUPPER
124
125 # define ISSPACE(x) isspace (x)
126 # define ISDIGIT(x) isdigit (x)
127 # define ISXDIGIT(x) isxdigit (x)
128 # define ISALPHA(x) isalpha (x)
129 # define ISALNUM(x) isalnum (x)
130 # define TOLOWER(x) tolower (x)
131 # define TOUPPER(x) toupper (x)
132
133 struct hash_table {
134   int dummy;
135 };
136 static void *
137 hash_table_get (const struct hash_table *ht, void *ptr)
138 {
139   return ptr;
140 }
141 #else  /* not STANDALONE */
142 # include "hash.h"
143 #endif
144
145 /* Pool support.  A pool is a resizable chunk of memory.  It is first
146    allocated on the stack, and moved to the heap if it needs to be
147    larger than originally expected.  map_html_tags() uses it to store
148    the zero-terminated names and values of tags and attributes.
149
150    Thus taginfo->name, and attr->name and attr->value for each
151    attribute, do not point into separately allocated areas, but into
152    different parts of the pool, separated only by terminating zeros.
153    This ensures minimum amount of allocation and, for most tags, no
154    allocation because the entire pool is kept on the stack.  */
155
156 struct pool {
157   char *contents;               /* pointer to the contents. */
158   int size;                     /* size of the pool. */
159   int tail;                     /* next available position index. */
160   int resized;                  /* whether the pool has been resized
161                                    using malloc. */
162
163   char *orig_contents;          /* original pool contents, usually
164                                    stack-allocated.  used by POOL_FREE
165                                    to restore the pool to the initial
166                                    state. */
167   int orig_size;
168 };
169
170 /* Initialize the pool to hold INITIAL_SIZE bytes of storage. */
171
172 #define POOL_INIT(p, initial_storage, initial_size) do {        \
173   struct pool *P = (p);                                         \
174   P->contents = (initial_storage);                              \
175   P->size = (initial_size);                                     \
176   P->tail = 0;                                                  \
177   P->resized = 0;                                               \
178   P->orig_contents = P->contents;                               \
179   P->orig_size = P->size;                                       \
180 } while (0)
181
182 /* Grow the pool to accomodate at least SIZE new bytes.  If the pool
183    already has room to accomodate SIZE bytes of data, this is a no-op.  */
184
185 #define POOL_GROW(p, increase)                                  \
186   GROW_ARRAY ((p)->contents, (p)->size, (p)->tail + (increase), \
187               (p)->resized, char)
188
189 /* Append text in the range [beg, end) to POOL.  No zero-termination
190    is done.  */
191
192 #define POOL_APPEND(p, beg, end) do {                   \
193   const char *PA_beg = (beg);                           \
194   int PA_size = (end) - PA_beg;                         \
195   POOL_GROW (p, PA_size);                               \
196   memcpy ((p)->contents + (p)->tail, PA_beg, PA_size);  \
197   (p)->tail += PA_size;                                 \
198 } while (0)
199
200 /* Append one character to the pool.  Can be used to zero-terminate
201    pool strings.  */
202
203 #define POOL_APPEND_CHR(p, ch) do {             \
204   char PAC_char = (ch);                         \
205   POOL_GROW (p, 1);                             \
206   (p)->contents[(p)->tail++] = PAC_char;        \
207 } while (0)
208
209 /* Forget old pool contents.  The allocated memory is not freed. */
210 #define POOL_REWIND(p) (p)->tail = 0
211
212 /* Free heap-allocated memory for contents of POOL.  This calls
213    xfree() if the memory was allocated through malloc.  It also
214    restores `contents' and `size' to their original, pre-malloc
215    values.  That way after POOL_FREE, the pool is fully usable, just
216    as if it were freshly initialized with POOL_INIT.  */
217
218 #define POOL_FREE(p) do {                       \
219   struct pool *P = p;                           \
220   if (P->resized)                               \
221     xfree (P->contents);                        \
222   P->contents = P->orig_contents;               \
223   P->size = P->orig_size;                       \
224   P->tail = 0;                                  \
225   P->resized = 0;                               \
226 } while (0)
227
228 /* Used for small stack-allocated memory chunks that might grow.  Like
229    DO_REALLOC, this macro grows BASEVAR as necessary to take
230    NEEDED_SIZE items of TYPE.
231
232    The difference is that on the first resize, it will use
233    malloc+memcpy rather than realloc.  That way you can stack-allocate
234    the initial chunk, and only resort to heap allocation if you
235    stumble upon large data.
236
237    After the first resize, subsequent ones are performed with realloc,
238    just like DO_REALLOC.  */
239
240 #define GROW_ARRAY(basevar, sizevar, needed_size, resized, type) do {           \
241   long ga_needed_size = (needed_size);                                          \
242   long ga_newsize = (sizevar);                                                  \
243   while (ga_newsize < ga_needed_size)                                           \
244     ga_newsize <<= 1;                                                           \
245   if (ga_newsize != (sizevar))                                                  \
246     {                                                                           \
247       if (resized)                                                              \
248         basevar = (type *)xrealloc (basevar, ga_newsize * sizeof (type));       \
249       else                                                                      \
250         {                                                                       \
251           void *ga_new = xmalloc (ga_newsize * sizeof (type));                  \
252           memcpy (ga_new, basevar, (sizevar) * sizeof (type));                  \
253           (basevar) = ga_new;                                                   \
254           resized = 1;                                                          \
255         }                                                                       \
256       (sizevar) = ga_newsize;                                                   \
257     }                                                                           \
258 } while (0)
259 \f
260 /* Test whether n+1-sized entity name fits in P.  We don't support
261    IE-style non-terminated entities, e.g. "&ltfoo" -> "<foo".
262    However, "&lt;foo" will work, as will "&lt!foo", "&lt", etc.  In
263    other words an entity needs to be terminated by either a
264    non-alphanumeric or the end of string.  */
265 #define FITS(p, n) (p + n == end || (p + n < end && !ISALNUM (p[n])))
266
267 /* Macros that test entity names by returning true if P is followed by
268    the specified characters.  */
269 #define ENT1(p, c0) (FITS (p, 1) && p[0] == c0)
270 #define ENT2(p, c0, c1) (FITS (p, 2) && p[0] == c0 && p[1] == c1)
271 #define ENT3(p, c0, c1, c2) (FITS (p, 3) && p[0]==c0 && p[1]==c1 && p[2]==c2)
272
273 /* Increment P by INC chars.  If P lands at a semicolon, increment it
274    past the semicolon.  This ensures that e.g. "&lt;foo" is converted
275    to "<foo", but "&lt,foo" to "<,foo".  */
276 #define SKIP_SEMI(p, inc) (p += inc, p < end && *p == ';' ? ++p : p)
277
278 /* Decode the HTML character entity at *PTR, considering END to be end
279    of buffer.  It is assumed that the "&" character that marks the
280    beginning of the entity has been seen at *PTR-1.  If a recognized
281    ASCII entity is seen, it is returned, and *PTR is moved to the end
282    of the entity.  Otherwise, -1 is returned and *PTR left unmodified.
283
284    The recognized entities are: &lt, &gt, &amp, &apos, and &quot.  */
285
286 static int
287 decode_entity (const char **ptr, const char *end)
288 {
289   const char *p = *ptr;
290   int value = -1;
291
292   if (++p == end)
293     return -1;
294
295   switch (*p++)
296     {
297     case '#':
298       /* Process numeric entities "&#DDD;" and "&#xHH;".  */
299       {
300         int digits = 0;
301         value = 0;
302         if (*p == 'x')
303           for (++p; value < 256 && p < end && ISXDIGIT (*p); p++, digits++)
304             value = (value << 4) + XDIGIT_TO_NUM (*p);
305         else
306           for (; value < 256 && p < end && ISDIGIT (*p); p++, digits++)
307             value = (value * 10) + (*p - '0');
308         if (!digits)
309           return -1;
310         /* Don't interpret 128+ codes and NUL because we cannot
311            portably reinserted them into HTML.  */
312         if (!value || (value & ~0x7f))
313           return -1;
314         *ptr = SKIP_SEMI (p, 0);
315         return value;
316       }
317     /* Process named ASCII entities.  */
318     case 'g':
319       if (ENT1 (p, 't'))
320         value = '>', *ptr = SKIP_SEMI (p, 1);
321       break;
322     case 'l':
323       if (ENT1 (p, 't'))
324         value = '<', *ptr = SKIP_SEMI (p, 1);
325       break;
326     case 'a':
327       if (ENT2 (p, 'm', 'p'))
328         value = '&', *ptr = SKIP_SEMI (p, 2);
329       else if (ENT3 (p, 'p', 'o', 's'))
330         /* handle &apos for the sake of the XML/XHTML crowd. */
331         value = '\'', *ptr = SKIP_SEMI (p, 3);
332       break;
333     case 'q':
334       if (ENT3 (p, 'u', 'o', 't'))
335         value = '\"', *ptr = SKIP_SEMI (p, 3);
336       break;
337     }
338   return value;
339 }
340 #undef ENT1
341 #undef ENT2
342 #undef ENT3
343 #undef FITS
344 #undef SKIP_SEMI
345
346 enum {
347   AP_DOWNCASE           = 1,
348   AP_DECODE_ENTITIES    = 2,
349   AP_TRIM_BLANKS        = 4
350 };
351
352 /* Copy the text in the range [BEG, END) to POOL, optionally
353    performing operations specified by FLAGS.  FLAGS may be any
354    combination of AP_DOWNCASE, AP_DECODE_ENTITIES and AP_TRIM_BLANKS
355    with the following meaning:
356
357    * AP_DOWNCASE -- downcase all the letters;
358
359    * AP_DECODE_ENTITIES -- decode the named and numeric entities in
360      the ASCII range when copying the string.
361
362    * AP_TRIM_BLANKS -- ignore blanks at the beginning and at the end
363      of text.  */
364
365 static void
366 convert_and_copy (struct pool *pool, const char *beg, const char *end, int flags)
367 {
368   int old_tail = pool->tail;
369   int size;
370
371   /* First, skip blanks if required.  We must do this before entities
372      are processed, so that blanks can still be inserted as, for
373      instance, `&#32;'.  */
374   if (flags & AP_TRIM_BLANKS)
375     {
376       while (beg < end && ISSPACE (*beg))
377         ++beg;
378       while (end > beg && ISSPACE (end[-1]))
379         --end;
380     }
381   size = end - beg;
382
383   if (flags & AP_DECODE_ENTITIES)
384     {
385       /* Grow the pool, then copy the text to the pool character by
386          character, processing the encountered entities as we go
387          along.
388
389          It's safe (and necessary) to grow the pool in advance because
390          processing the entities can only *shorten* the string, it can
391          never lengthen it.  */
392       const char *from = beg;
393       char *to;
394
395       POOL_GROW (pool, end - beg);
396       to = pool->contents + pool->tail;
397
398       while (from < end)
399         {
400           if (*from != '&')
401             *to++ = *from++;
402           else
403             {
404               int entity = decode_entity (&from, end);
405               if (entity != -1)
406                 *to++ = entity;
407               else
408                 *to++ = *from++;
409             }
410         }
411       /* Verify that we haven't exceeded the original size.  (It
412          shouldn't happen, hence the assert.)  */
413       assert (to - (pool->contents + pool->tail) <= end - beg);
414
415       /* Make POOL's tail point to the position following the string
416          we've written.  */
417       pool->tail = to - pool->contents;
418       POOL_APPEND_CHR (pool, '\0');
419     }
420   else
421     {
422       /* Just copy the text to the pool.  */
423       POOL_APPEND (pool, beg, end);
424       POOL_APPEND_CHR (pool, '\0');
425     }
426
427   if (flags & AP_DOWNCASE)
428     {
429       char *p = pool->contents + old_tail;
430       for (; *p; p++)
431         *p = TOLOWER (*p);
432     }
433 }
434 \f
435 /* Originally we used to adhere to rfc 1866 here, and allowed only
436    letters, digits, periods, and hyphens as names (of tags or
437    attributes).  However, this broke too many pages which used
438    proprietary or strange attributes, e.g. <img src="a.gif"
439    v:shapes="whatever">.
440
441    So now we allow any character except:
442      * whitespace
443      * 8-bit and control chars
444      * characters that clearly cannot be part of name:
445        '=', '>', '/'.
446
447    This only affects attribute and tag names; attribute values allow
448    an even greater variety of characters.  */
449
450 #define NAME_CHAR_P(x) ((x) > 32 && (x) < 127                           \
451                         && (x) != '=' && (x) != '>' && (x) != '/')
452
453 #ifdef STANDALONE
454 static int comment_backout_count;
455 #endif
456
457 /* Advance over an SGML declaration, such as <!DOCTYPE ...>.  In
458    strict comments mode, this is used for skipping over comments as
459    well.
460
461    To recap: any SGML declaration may have comments associated with
462    it, e.g.
463        <!MY-DECL -- isn't this fun? -- foo bar>
464
465    An HTML comment is merely an empty declaration (<!>) with a comment
466    attached, like this:
467        <!-- some stuff here -->
468
469    Several comments may be embedded in one comment declaration:
470        <!-- have -- -- fun -->
471
472    Whitespace is allowed between and after the comments, but not
473    before the first comment.  Additionally, this function attempts to
474    handle double quotes in SGML declarations correctly.  */
475
476 static const char *
477 advance_declaration (const char *beg, const char *end)
478 {
479   const char *p = beg;
480   char quote_char = '\0';       /* shut up, gcc! */
481   char ch;
482
483   enum {
484     AC_S_DONE,
485     AC_S_BACKOUT,
486     AC_S_BANG,
487     AC_S_DEFAULT,
488     AC_S_DCLNAME,
489     AC_S_DASH1,
490     AC_S_DASH2,
491     AC_S_COMMENT,
492     AC_S_DASH3,
493     AC_S_DASH4,
494     AC_S_QUOTE1,
495     AC_S_IN_QUOTE,
496     AC_S_QUOTE2
497   } state = AC_S_BANG;
498
499   if (beg == end)
500     return beg;
501   ch = *p++;
502
503   /* It looked like a good idea to write this as a state machine, but
504      now I wonder...  */
505
506   while (state != AC_S_DONE && state != AC_S_BACKOUT)
507     {
508       if (p == end)
509         state = AC_S_BACKOUT;
510       switch (state)
511         {
512         case AC_S_DONE:
513         case AC_S_BACKOUT:
514           break;
515         case AC_S_BANG:
516           if (ch == '!')
517             {
518               ch = *p++;
519               state = AC_S_DEFAULT;
520             }
521           else
522             state = AC_S_BACKOUT;
523           break;
524         case AC_S_DEFAULT:
525           switch (ch)
526             {
527             case '-':
528               state = AC_S_DASH1;
529               break;
530             case ' ':
531             case '\t':
532             case '\r':
533             case '\n':
534               ch = *p++;
535               break;
536             case '>':
537               state = AC_S_DONE;
538               break;
539             case '\'':
540             case '\"':
541               state = AC_S_QUOTE1;
542               break;
543             default:
544               if (NAME_CHAR_P (ch))
545                 state = AC_S_DCLNAME;
546               else
547                 state = AC_S_BACKOUT;
548               break;
549             }
550           break;
551         case AC_S_DCLNAME:
552           if (ch == '-')
553             state = AC_S_DASH1;
554           else if (NAME_CHAR_P (ch))
555             ch = *p++;
556           else
557             state = AC_S_DEFAULT;
558           break;
559         case AC_S_QUOTE1:
560           /* We must use 0x22 because broken assert macros choke on
561              '"' and '\"'.  */
562           assert (ch == '\'' || ch == 0x22);
563           quote_char = ch;      /* cheating -- I really don't feel like
564                                    introducing more different states for
565                                    different quote characters. */
566           ch = *p++;
567           state = AC_S_IN_QUOTE;
568           break;
569         case AC_S_IN_QUOTE:
570           if (ch == quote_char)
571             state = AC_S_QUOTE2;
572           else
573             ch = *p++;
574           break;
575         case AC_S_QUOTE2:
576           assert (ch == quote_char);
577           ch = *p++;
578           state = AC_S_DEFAULT;
579           break;
580         case AC_S_DASH1:
581           assert (ch == '-');
582           ch = *p++;
583           state = AC_S_DASH2;
584           break;
585         case AC_S_DASH2:
586           switch (ch)
587             {
588             case '-':
589               ch = *p++;
590               state = AC_S_COMMENT;
591               break;
592             default:
593               state = AC_S_BACKOUT;
594             }
595           break;
596         case AC_S_COMMENT:
597           switch (ch)
598             {
599             case '-':
600               state = AC_S_DASH3;
601               break;
602             default:
603               ch = *p++;
604               break;
605             }
606           break;
607         case AC_S_DASH3:
608           assert (ch == '-');
609           ch = *p++;
610           state = AC_S_DASH4;
611           break;
612         case AC_S_DASH4:
613           switch (ch)
614             {
615             case '-':
616               ch = *p++;
617               state = AC_S_DEFAULT;
618               break;
619             default:
620               state = AC_S_COMMENT;
621               break;
622             }
623           break;
624         }
625     }
626
627   if (state == AC_S_BACKOUT)
628     {
629 #ifdef STANDALONE
630       ++comment_backout_count;
631 #endif
632       return beg + 1;
633     }
634   return p;
635 }
636
637 /* Find the first occurrence of the substring "-->" in [BEG, END) and
638    return the pointer to the character after the substring.  If the
639    substring is not found, return NULL.  */
640
641 static const char *
642 find_comment_end (const char *beg, const char *end)
643 {
644   /* Open-coded Boyer-Moore search for "-->".  Examine the third char;
645      if it's not '>' or '-', advance by three characters.  Otherwise,
646      look at the preceding characters and try to find a match.  */
647
648   const char *p = beg - 1;
649
650   while ((p += 3) < end)
651     switch (p[0])
652       {
653       case '>':
654         if (p[-1] == '-' && p[-2] == '-')
655           return p + 1;
656         break;
657       case '-':
658       at_dash:
659         if (p[-1] == '-')
660           {
661           at_dash_dash:
662             if (++p == end) return NULL;
663             switch (p[0])
664               {
665               case '>': return p + 1;
666               case '-': goto at_dash_dash;
667               }
668           }
669         else
670           {
671             if ((p += 2) >= end) return NULL;
672             switch (p[0])
673               {
674               case '>':
675                 if (p[-1] == '-')
676                   return p + 1;
677                 break;
678               case '-':
679                 goto at_dash;
680               }
681           }
682       }
683   return NULL;
684 }
685 \f
686 /* Return non-zero of the string inside [b, e) are present in hash
687    table HT.  */
688
689 static int
690 name_allowed (const struct hash_table *ht, const char *b, const char *e)
691 {
692   char *copy;
693   if (!ht)
694     return 1;
695   BOUNDED_TO_ALLOCA (b, e, copy);
696   return hash_table_get (ht, copy) != NULL;
697 }
698
699 /* Advance P (a char pointer), with the explicit intent of being able
700    to read the next character.  If this is not possible, go to finish.  */
701
702 #define ADVANCE(p) do {                         \
703   ++p;                                          \
704   if (p >= end)                                 \
705     goto finish;                                \
706 } while (0)
707
708 /* Skip whitespace, if any. */
709
710 #define SKIP_WS(p) do {                         \
711   while (ISSPACE (*p)) {                        \
712     ADVANCE (p);                                \
713   }                                             \
714 } while (0)
715
716 /* Skip non-whitespace, if any. */
717
718 #define SKIP_NON_WS(p) do {                     \
719   while (!ISSPACE (*p)) {                       \
720     ADVANCE (p);                                \
721   }                                             \
722 } while (0)
723
724 #ifdef STANDALONE
725 static int tag_backout_count;
726 #endif
727
728 /* Map MAPFUN over HTML tags in TEXT, which is SIZE characters long.
729    MAPFUN will be called with two arguments: pointer to an initialized
730    struct taginfo, and MAPARG.
731
732    ALLOWED_TAG_NAMES should be a NULL-terminated array of tag names to
733    be processed by this function.  If it is NULL, all the tags are
734    allowed.  The same goes for attributes and ALLOWED_ATTRIBUTE_NAMES.
735
736    (Obviously, the caller can filter out unwanted tags and attributes
737    just as well, but this is just an optimization designed to avoid
738    unnecessary copying for tags/attributes which the caller doesn't
739    want to know about.  These lists are searched linearly; therefore,
740    if you're interested in a large number of tags or attributes, you'd
741    better set these to NULL and filter them out yourself with a
742    hashing process most appropriate for your application.)  */
743
744 void
745 map_html_tags (const char *text, int size,
746                void (*mapfun) (struct taginfo *, void *), void *maparg,
747                int flags,
748                const struct hash_table *allowed_tags,
749                const struct hash_table *allowed_attributes)
750 {
751   /* storage for strings passed to MAPFUN callback; if 256 bytes is
752      too little, POOL_APPEND allocates more with malloc. */
753   char pool_initial_storage[256];
754   struct pool pool;
755
756   const char *p = text;
757   const char *end = text + size;
758
759   struct attr_pair attr_pair_initial_storage[8];
760   int attr_pair_size = countof (attr_pair_initial_storage);
761   int attr_pair_resized = 0;
762   struct attr_pair *pairs = attr_pair_initial_storage;
763
764   if (!size)
765     return;
766
767   POOL_INIT (&pool, pool_initial_storage, countof (pool_initial_storage));
768
769   {
770     int nattrs, end_tag;
771     const char *tag_name_begin, *tag_name_end;
772     const char *tag_start_position;
773     int uninteresting_tag;
774
775   look_for_tag:
776     POOL_REWIND (&pool);
777
778     nattrs = 0;
779     end_tag = 0;
780
781     /* Find beginning of tag.  We use memchr() instead of the usual
782        looping with ADVANCE() for speed. */
783     p = memchr (p, '<', end - p);
784     if (!p)
785       goto finish;
786
787     tag_start_position = p;
788     ADVANCE (p);
789
790     /* Establish the type of the tag (start-tag, end-tag or
791        declaration).  */
792     if (*p == '!')
793       {
794         if (!(flags & MHT_STRICT_COMMENTS)
795             && p < end + 3 && p[1] == '-' && p[2] == '-')
796           {
797             /* If strict comments are not enforced and if we know
798                we're looking at a comment, simply look for the
799                terminating "-->".  Non-strict is the default because
800                it works in other browsers and most HTML writers can't
801                be bothered with getting the comments right.  */
802             const char *comment_end = find_comment_end (p + 3, end);
803             if (comment_end)
804               p = comment_end;
805           }
806         else
807           {
808             /* Either in strict comment mode or looking at a non-empty
809                declaration.  Real declarations are much less likely to
810                be misused the way comments are, so advance over them
811                properly regardless of strictness.  */
812             p = advance_declaration (p, end);
813           }
814         if (p == end)
815           goto finish;
816         goto look_for_tag;
817       }
818     else if (*p == '/')
819       {
820         end_tag = 1;
821         ADVANCE (p);
822       }
823     tag_name_begin = p;
824     while (NAME_CHAR_P (*p))
825       ADVANCE (p);
826     if (p == tag_name_begin)
827       goto look_for_tag;
828     tag_name_end = p;
829     SKIP_WS (p);
830     if (end_tag && *p != '>')
831       goto backout_tag;
832
833     if (!name_allowed (allowed_tags, tag_name_begin, tag_name_end))
834       /* We can't just say "goto look_for_tag" here because we need
835          the loop below to properly advance over the tag's attributes.  */
836       uninteresting_tag = 1;
837     else
838       {
839         uninteresting_tag = 0;
840         convert_and_copy (&pool, tag_name_begin, tag_name_end, AP_DOWNCASE);
841       }
842
843     /* Find the attributes. */
844     while (1)
845       {
846         const char *attr_name_begin, *attr_name_end;
847         const char *attr_value_begin, *attr_value_end;
848         const char *attr_raw_value_begin, *attr_raw_value_end;
849         int operation = AP_DOWNCASE; /* stupid compiler. */
850
851         SKIP_WS (p);
852
853         if (*p == '/')
854           {
855             /* A slash at this point means the tag is about to be
856                closed.  This is legal in XML and has been popularized
857                in HTML via XHTML.  */
858             /* <foo a=b c=d /> */
859             /*              ^  */
860             ADVANCE (p);
861             SKIP_WS (p);
862             if (*p != '>')
863               goto backout_tag;
864           }
865
866         /* Check for end of tag definition. */
867         if (*p == '>')
868           break;
869
870         /* Establish bounds of attribute name. */
871         attr_name_begin = p;    /* <foo bar ...> */
872                                 /*      ^        */
873         while (NAME_CHAR_P (*p))
874           ADVANCE (p);
875         attr_name_end = p;      /* <foo bar ...> */
876                                 /*         ^     */
877         if (attr_name_begin == attr_name_end)
878           goto backout_tag;
879
880         /* Establish bounds of attribute value. */
881         SKIP_WS (p);
882         if (NAME_CHAR_P (*p) || *p == '/' || *p == '>')
883           {
884             /* Minimized attribute syntax allows `=' to be omitted.
885                For example, <UL COMPACT> is a valid shorthand for <UL
886                COMPACT="compact">.  Even if such attributes are not
887                useful to Wget, we need to support them, so that the
888                tags containing them can be parsed correctly. */
889             attr_raw_value_begin = attr_value_begin = attr_name_begin;
890             attr_raw_value_end = attr_value_end = attr_name_end;
891           }
892         else if (*p == '=')
893           {
894             ADVANCE (p);
895             SKIP_WS (p);
896             if (*p == '\"' || *p == '\'')
897               {
898                 int newline_seen = 0;
899                 char quote_char = *p;
900                 attr_raw_value_begin = p;
901                 ADVANCE (p);
902                 attr_value_begin = p; /* <foo bar="baz"> */
903                                       /*           ^     */
904                 while (*p != quote_char)
905                   {
906                     if (!newline_seen && *p == '\n')
907                       {
908                         /* If a newline is seen within the quotes, it
909                            is most likely that someone forgot to close
910                            the quote.  In that case, we back out to
911                            the value beginning, and terminate the tag
912                            at either `>' or the delimiter, whichever
913                            comes first.  Such a tag terminated at `>'
914                            is discarded.  */
915                         p = attr_value_begin;
916                         newline_seen = 1;
917                         continue;
918                       }
919                     else if (newline_seen && *p == '>')
920                       break;
921                     ADVANCE (p);
922                   }
923                 attr_value_end = p; /* <foo bar="baz"> */
924                                     /*              ^  */
925                 if (*p == quote_char)
926                   ADVANCE (p);
927                 else
928                   goto look_for_tag;
929                 attr_raw_value_end = p; /* <foo bar="baz"> */
930                                         /*               ^ */
931                 operation = AP_DECODE_ENTITIES;
932                 if (flags & MHT_TRIM_VALUES)
933                   operation |= AP_TRIM_BLANKS;
934               }
935             else
936               {
937                 attr_value_begin = p; /* <foo bar=baz> */
938                                       /*          ^    */
939                 /* According to SGML, a name token should consist only
940                    of alphanumerics, . and -.  However, this is often
941                    violated by, for instance, `%' in `width=75%'.
942                    We'll be liberal and allow just about anything as
943                    an attribute value.  */
944                 while (!ISSPACE (*p) && *p != '>')
945                   ADVANCE (p);
946                 attr_value_end = p; /* <foo bar=baz qux=quix> */
947                                     /*             ^          */
948                 if (attr_value_begin == attr_value_end)
949                   /* <foo bar=> */
950                   /*          ^ */
951                   goto backout_tag;
952                 attr_raw_value_begin = attr_value_begin;
953                 attr_raw_value_end = attr_value_end;
954                 operation = AP_DECODE_ENTITIES;
955               }
956           }
957         else
958           {
959             /* We skipped the whitespace and found something that is
960                neither `=' nor the beginning of the next attribute's
961                name.  Back out.  */
962             goto backout_tag;   /* <foo bar [... */
963                                 /*          ^    */
964           }
965
966         /* If we're not interested in the tag, don't bother with any
967            of the attributes.  */
968         if (uninteresting_tag)
969           continue;
970
971         /* If we aren't interested in the attribute, skip it.  We
972            cannot do this test any sooner, because our text pointer
973            needs to correctly advance over the attribute.  */
974         if (!name_allowed (allowed_attributes, attr_name_begin, attr_name_end))
975           continue;
976
977         GROW_ARRAY (pairs, attr_pair_size, nattrs + 1, attr_pair_resized,
978                     struct attr_pair);
979
980         pairs[nattrs].name_pool_index = pool.tail;
981         convert_and_copy (&pool, attr_name_begin, attr_name_end, AP_DOWNCASE);
982
983         pairs[nattrs].value_pool_index = pool.tail;
984         convert_and_copy (&pool, attr_value_begin, attr_value_end, operation);
985         pairs[nattrs].value_raw_beginning = attr_raw_value_begin;
986         pairs[nattrs].value_raw_size = (attr_raw_value_end
987                                         - attr_raw_value_begin);
988         ++nattrs;
989       }
990
991     if (uninteresting_tag)
992       {
993         ADVANCE (p);
994         goto look_for_tag;
995       }
996
997     /* By now, we have a valid tag with a name and zero or more
998        attributes.  Fill in the data and call the mapper function.  */
999     {
1000       int i;
1001       struct taginfo taginfo;
1002
1003       taginfo.name      = pool.contents;
1004       taginfo.end_tag_p = end_tag;
1005       taginfo.nattrs    = nattrs;
1006       /* We fill in the char pointers only now, when pool can no
1007          longer get realloc'ed.  If we did that above, we could get
1008          hosed by reallocation.  Obviously, after this point, the pool
1009          may no longer be grown.  */
1010       for (i = 0; i < nattrs; i++)
1011         {
1012           pairs[i].name = pool.contents + pairs[i].name_pool_index;
1013           pairs[i].value = pool.contents + pairs[i].value_pool_index;
1014         }
1015       taginfo.attrs = pairs;
1016       taginfo.start_position = tag_start_position;
1017       taginfo.end_position   = p + 1;
1018       /* Ta-dam! */
1019       (*mapfun) (&taginfo, maparg);
1020       ADVANCE (p);
1021     }
1022     goto look_for_tag;
1023
1024   backout_tag:
1025 #ifdef STANDALONE
1026     ++tag_backout_count;
1027 #endif
1028     /* The tag wasn't really a tag.  Treat its contents as ordinary
1029        data characters. */
1030     p = tag_start_position + 1;
1031     goto look_for_tag;
1032   }
1033
1034  finish:
1035   POOL_FREE (&pool);
1036   if (attr_pair_resized)
1037     xfree (pairs);
1038 }
1039
1040 #undef ADVANCE
1041 #undef SKIP_WS
1042 #undef SKIP_NON_WS
1043 \f
1044 #ifdef STANDALONE
1045 static void
1046 test_mapper (struct taginfo *taginfo, void *arg)
1047 {
1048   int i;
1049
1050   printf ("%s%s", taginfo->end_tag_p ? "/" : "", taginfo->name);
1051   for (i = 0; i < taginfo->nattrs; i++)
1052     printf (" %s=%s", taginfo->attrs[i].name, taginfo->attrs[i].value);
1053   putchar ('\n');
1054   ++*(int *)arg;
1055 }
1056
1057 int main ()
1058 {
1059   int size = 256;
1060   char *x = (char *)xmalloc (size);
1061   int length = 0;
1062   int read_count;
1063   int tag_counter = 0;
1064
1065   while ((read_count = fread (x + length, 1, size - length, stdin)))
1066     {
1067       length += read_count;
1068       size <<= 1;
1069       x = (char *)xrealloc (x, size);
1070     }
1071
1072   map_html_tags (x, length, test_mapper, &tag_counter, 0, NULL, NULL);
1073   printf ("TAGS: %d\n", tag_counter);
1074   printf ("Tag backouts:     %d\n", tag_backout_count);
1075   printf ("Comment backouts: %d\n", comment_backout_count);
1076   return 0;
1077 }
1078 #endif /* STANDALONE */