]> sjero.net Git - wget/blob - src/html-parse.c
[svn] Documentation fix.
[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, as well as embedded newlines.  */
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
370   /* Skip blanks if required.  We must do this before entities are
371      processed, so that blanks can still be inserted as, for instance,
372      `&#32;'.  */
373   if (flags & AP_TRIM_BLANKS)
374     {
375       while (beg < end && ISSPACE (*beg))
376         ++beg;
377       while (end > beg && ISSPACE (end[-1]))
378         --end;
379     }
380
381   if (flags & AP_DECODE_ENTITIES)
382     {
383       /* Grow the pool, then copy the text to the pool character by
384          character, processing the encountered entities as we go
385          along.
386
387          It's safe (and necessary) to grow the pool in advance because
388          processing the entities can only *shorten* the string, it can
389          never lengthen it.  */
390       const char *from = beg;
391       char *to;
392       int squash_newlines = flags & AP_TRIM_BLANKS;
393
394       POOL_GROW (pool, end - beg);
395       to = pool->contents + pool->tail;
396
397       while (from < end)
398         {
399           if (*from == '&')
400             {
401               int entity = decode_entity (&from, end);
402               if (entity != -1)
403                 *to++ = entity;
404               else
405                 *to++ = *from++;
406             }
407           else if ((*from == '\n' || *from == '\r') && squash_newlines)
408             ++from;
409           else
410             *to++ = *from++;
411         }
412       /* Verify that we haven't exceeded the original size.  (It
413          shouldn't happen, hence the assert.)  */
414       assert (to - (pool->contents + pool->tail) <= end - beg);
415
416       /* Make POOL's tail point to the position following the string
417          we've written.  */
418       pool->tail = to - pool->contents;
419       POOL_APPEND_CHR (pool, '\0');
420     }
421   else
422     {
423       /* Just copy the text to the pool.  */
424       POOL_APPEND (pool, beg, end);
425       POOL_APPEND_CHR (pool, '\0');
426     }
427
428   if (flags & AP_DOWNCASE)
429     {
430       char *p = pool->contents + old_tail;
431       for (; *p; p++)
432         *p = TOLOWER (*p);
433     }
434 }
435 \f
436 /* Originally we used to adhere to rfc 1866 here, and allowed only
437    letters, digits, periods, and hyphens as names (of tags or
438    attributes).  However, this broke too many pages which used
439    proprietary or strange attributes, e.g. <img src="a.gif"
440    v:shapes="whatever">.
441
442    So now we allow any character except:
443      * whitespace
444      * 8-bit and control chars
445      * characters that clearly cannot be part of name:
446        '=', '>', '/'.
447
448    This only affects attribute and tag names; attribute values allow
449    an even greater variety of characters.  */
450
451 #define NAME_CHAR_P(x) ((x) > 32 && (x) < 127                           \
452                         && (x) != '=' && (x) != '>' && (x) != '/')
453
454 #ifdef STANDALONE
455 static int comment_backout_count;
456 #endif
457
458 /* Advance over an SGML declaration, such as <!DOCTYPE ...>.  In
459    strict comments mode, this is used for skipping over comments as
460    well.
461
462    To recap: any SGML declaration may have comments associated with
463    it, e.g.
464        <!MY-DECL -- isn't this fun? -- foo bar>
465
466    An HTML comment is merely an empty declaration (<!>) with a comment
467    attached, like this:
468        <!-- some stuff here -->
469
470    Several comments may be embedded in one comment declaration:
471        <!-- have -- -- fun -->
472
473    Whitespace is allowed between and after the comments, but not
474    before the first comment.  Additionally, this function attempts to
475    handle double quotes in SGML declarations correctly.  */
476
477 static const char *
478 advance_declaration (const char *beg, const char *end)
479 {
480   const char *p = beg;
481   char quote_char = '\0';       /* shut up, gcc! */
482   char ch;
483
484   enum {
485     AC_S_DONE,
486     AC_S_BACKOUT,
487     AC_S_BANG,
488     AC_S_DEFAULT,
489     AC_S_DCLNAME,
490     AC_S_DASH1,
491     AC_S_DASH2,
492     AC_S_COMMENT,
493     AC_S_DASH3,
494     AC_S_DASH4,
495     AC_S_QUOTE1,
496     AC_S_IN_QUOTE,
497     AC_S_QUOTE2
498   } state = AC_S_BANG;
499
500   if (beg == end)
501     return beg;
502   ch = *p++;
503
504   /* It looked like a good idea to write this as a state machine, but
505      now I wonder...  */
506
507   while (state != AC_S_DONE && state != AC_S_BACKOUT)
508     {
509       if (p == end)
510         state = AC_S_BACKOUT;
511       switch (state)
512         {
513         case AC_S_DONE:
514         case AC_S_BACKOUT:
515           break;
516         case AC_S_BANG:
517           if (ch == '!')
518             {
519               ch = *p++;
520               state = AC_S_DEFAULT;
521             }
522           else
523             state = AC_S_BACKOUT;
524           break;
525         case AC_S_DEFAULT:
526           switch (ch)
527             {
528             case '-':
529               state = AC_S_DASH1;
530               break;
531             case ' ':
532             case '\t':
533             case '\r':
534             case '\n':
535               ch = *p++;
536               break;
537             case '>':
538               state = AC_S_DONE;
539               break;
540             case '\'':
541             case '\"':
542               state = AC_S_QUOTE1;
543               break;
544             default:
545               if (NAME_CHAR_P (ch))
546                 state = AC_S_DCLNAME;
547               else
548                 state = AC_S_BACKOUT;
549               break;
550             }
551           break;
552         case AC_S_DCLNAME:
553           if (ch == '-')
554             state = AC_S_DASH1;
555           else if (NAME_CHAR_P (ch))
556             ch = *p++;
557           else
558             state = AC_S_DEFAULT;
559           break;
560         case AC_S_QUOTE1:
561           /* We must use 0x22 because broken assert macros choke on
562              '"' and '\"'.  */
563           assert (ch == '\'' || ch == 0x22);
564           quote_char = ch;      /* cheating -- I really don't feel like
565                                    introducing more different states for
566                                    different quote characters. */
567           ch = *p++;
568           state = AC_S_IN_QUOTE;
569           break;
570         case AC_S_IN_QUOTE:
571           if (ch == quote_char)
572             state = AC_S_QUOTE2;
573           else
574             ch = *p++;
575           break;
576         case AC_S_QUOTE2:
577           assert (ch == quote_char);
578           ch = *p++;
579           state = AC_S_DEFAULT;
580           break;
581         case AC_S_DASH1:
582           assert (ch == '-');
583           ch = *p++;
584           state = AC_S_DASH2;
585           break;
586         case AC_S_DASH2:
587           switch (ch)
588             {
589             case '-':
590               ch = *p++;
591               state = AC_S_COMMENT;
592               break;
593             default:
594               state = AC_S_BACKOUT;
595             }
596           break;
597         case AC_S_COMMENT:
598           switch (ch)
599             {
600             case '-':
601               state = AC_S_DASH3;
602               break;
603             default:
604               ch = *p++;
605               break;
606             }
607           break;
608         case AC_S_DASH3:
609           assert (ch == '-');
610           ch = *p++;
611           state = AC_S_DASH4;
612           break;
613         case AC_S_DASH4:
614           switch (ch)
615             {
616             case '-':
617               ch = *p++;
618               state = AC_S_DEFAULT;
619               break;
620             default:
621               state = AC_S_COMMENT;
622               break;
623             }
624           break;
625         }
626     }
627
628   if (state == AC_S_BACKOUT)
629     {
630 #ifdef STANDALONE
631       ++comment_backout_count;
632 #endif
633       return beg + 1;
634     }
635   return p;
636 }
637
638 /* Find the first occurrence of the substring "-->" in [BEG, END) and
639    return the pointer to the character after the substring.  If the
640    substring is not found, return NULL.  */
641
642 static const char *
643 find_comment_end (const char *beg, const char *end)
644 {
645   /* Open-coded Boyer-Moore search for "-->".  Examine the third char;
646      if it's not '>' or '-', advance by three characters.  Otherwise,
647      look at the preceding characters and try to find a match.  */
648
649   const char *p = beg - 1;
650
651   while ((p += 3) < end)
652     switch (p[0])
653       {
654       case '>':
655         if (p[-1] == '-' && p[-2] == '-')
656           return p + 1;
657         break;
658       case '-':
659       at_dash:
660         if (p[-1] == '-')
661           {
662           at_dash_dash:
663             if (++p == end) return NULL;
664             switch (p[0])
665               {
666               case '>': return p + 1;
667               case '-': goto at_dash_dash;
668               }
669           }
670         else
671           {
672             if ((p += 2) >= end) return NULL;
673             switch (p[0])
674               {
675               case '>':
676                 if (p[-1] == '-')
677                   return p + 1;
678                 break;
679               case '-':
680                 goto at_dash;
681               }
682           }
683       }
684   return NULL;
685 }
686 \f
687 /* Return non-zero of the string inside [b, e) are present in hash
688    table HT.  */
689
690 static int
691 name_allowed (const struct hash_table *ht, const char *b, const char *e)
692 {
693   char *copy;
694   if (!ht)
695     return 1;
696   BOUNDED_TO_ALLOCA (b, e, copy);
697   return hash_table_get (ht, copy) != NULL;
698 }
699
700 /* Advance P (a char pointer), with the explicit intent of being able
701    to read the next character.  If this is not possible, go to finish.  */
702
703 #define ADVANCE(p) do {                         \
704   ++p;                                          \
705   if (p >= end)                                 \
706     goto finish;                                \
707 } while (0)
708
709 /* Skip whitespace, if any. */
710
711 #define SKIP_WS(p) do {                         \
712   while (ISSPACE (*p)) {                        \
713     ADVANCE (p);                                \
714   }                                             \
715 } while (0)
716
717 /* Skip non-whitespace, if any. */
718
719 #define SKIP_NON_WS(p) do {                     \
720   while (!ISSPACE (*p)) {                       \
721     ADVANCE (p);                                \
722   }                                             \
723 } while (0)
724
725 #ifdef STANDALONE
726 static int tag_backout_count;
727 #endif
728
729 /* Map MAPFUN over HTML tags in TEXT, which is SIZE characters long.
730    MAPFUN will be called with two arguments: pointer to an initialized
731    struct taginfo, and MAPARG.
732
733    ALLOWED_TAGS and ALLOWED_ATTRIBUTES are hash tables the keys of
734    which are the tags and attribute names that this function should
735    use.  If ALLOWED_TAGS is NULL, all tags are processed; if
736    ALLOWED_ATTRIBUTES is NULL, all attributes are returned.
737
738    (Obviously, the caller can filter out unwanted tags and attributes
739    just as well, but this is just an optimization designed to avoid
740    unnecessary copying of tags/attributes which the caller doesn't
741    care about.)  */
742
743 void
744 map_html_tags (const char *text, int size,
745                void (*mapfun) (struct taginfo *, void *), void *maparg,
746                int flags,
747                const struct hash_table *allowed_tags,
748                const struct hash_table *allowed_attributes)
749 {
750   /* storage for strings passed to MAPFUN callback; if 256 bytes is
751      too little, POOL_APPEND allocates more with malloc. */
752   char pool_initial_storage[256];
753   struct pool pool;
754
755   const char *p = text;
756   const char *end = text + size;
757
758   struct attr_pair attr_pair_initial_storage[8];
759   int attr_pair_size = countof (attr_pair_initial_storage);
760   int attr_pair_resized = 0;
761   struct attr_pair *pairs = attr_pair_initial_storage;
762
763   if (!size)
764     return;
765
766   POOL_INIT (&pool, pool_initial_storage, countof (pool_initial_storage));
767
768   {
769     int nattrs, end_tag;
770     const char *tag_name_begin, *tag_name_end;
771     const char *tag_start_position;
772     int uninteresting_tag;
773
774   look_for_tag:
775     POOL_REWIND (&pool);
776
777     nattrs = 0;
778     end_tag = 0;
779
780     /* Find beginning of tag.  We use memchr() instead of the usual
781        looping with ADVANCE() for speed. */
782     p = memchr (p, '<', end - p);
783     if (!p)
784       goto finish;
785
786     tag_start_position = p;
787     ADVANCE (p);
788
789     /* Establish the type of the tag (start-tag, end-tag or
790        declaration).  */
791     if (*p == '!')
792       {
793         if (!(flags & MHT_STRICT_COMMENTS)
794             && p < end + 3 && p[1] == '-' && p[2] == '-')
795           {
796             /* If strict comments are not enforced and if we know
797                we're looking at a comment, simply look for the
798                terminating "-->".  Non-strict is the default because
799                it works in other browsers and most HTML writers can't
800                be bothered with getting the comments right.  */
801             const char *comment_end = find_comment_end (p + 3, end);
802             if (comment_end)
803               p = comment_end;
804           }
805         else
806           {
807             /* Either in strict comment mode or looking at a non-empty
808                declaration.  Real declarations are much less likely to
809                be misused the way comments are, so advance over them
810                properly regardless of strictness.  */
811             p = advance_declaration (p, end);
812           }
813         if (p == end)
814           goto finish;
815         goto look_for_tag;
816       }
817     else if (*p == '/')
818       {
819         end_tag = 1;
820         ADVANCE (p);
821       }
822     tag_name_begin = p;
823     while (NAME_CHAR_P (*p))
824       ADVANCE (p);
825     if (p == tag_name_begin)
826       goto look_for_tag;
827     tag_name_end = p;
828     SKIP_WS (p);
829     if (end_tag && *p != '>')
830       goto backout_tag;
831
832     if (!name_allowed (allowed_tags, tag_name_begin, tag_name_end))
833       /* We can't just say "goto look_for_tag" here because we need
834          the loop below to properly advance over the tag's attributes.  */
835       uninteresting_tag = 1;
836     else
837       {
838         uninteresting_tag = 0;
839         convert_and_copy (&pool, tag_name_begin, tag_name_end, AP_DOWNCASE);
840       }
841
842     /* Find the attributes. */
843     while (1)
844       {
845         const char *attr_name_begin, *attr_name_end;
846         const char *attr_value_begin, *attr_value_end;
847         const char *attr_raw_value_begin, *attr_raw_value_end;
848         int operation = AP_DOWNCASE; /* stupid compiler. */
849
850         SKIP_WS (p);
851
852         if (*p == '/')
853           {
854             /* A slash at this point means the tag is about to be
855                closed.  This is legal in XML and has been popularized
856                in HTML via XHTML.  */
857             /* <foo a=b c=d /> */
858             /*              ^  */
859             ADVANCE (p);
860             SKIP_WS (p);
861             if (*p != '>')
862               goto backout_tag;
863           }
864
865         /* Check for end of tag definition. */
866         if (*p == '>')
867           break;
868
869         /* Establish bounds of attribute name. */
870         attr_name_begin = p;    /* <foo bar ...> */
871                                 /*      ^        */
872         while (NAME_CHAR_P (*p))
873           ADVANCE (p);
874         attr_name_end = p;      /* <foo bar ...> */
875                                 /*         ^     */
876         if (attr_name_begin == attr_name_end)
877           goto backout_tag;
878
879         /* Establish bounds of attribute value. */
880         SKIP_WS (p);
881         if (NAME_CHAR_P (*p) || *p == '/' || *p == '>')
882           {
883             /* Minimized attribute syntax allows `=' to be omitted.
884                For example, <UL COMPACT> is a valid shorthand for <UL
885                COMPACT="compact">.  Even if such attributes are not
886                useful to Wget, we need to support them, so that the
887                tags containing them can be parsed correctly. */
888             attr_raw_value_begin = attr_value_begin = attr_name_begin;
889             attr_raw_value_end = attr_value_end = attr_name_end;
890           }
891         else if (*p == '=')
892           {
893             ADVANCE (p);
894             SKIP_WS (p);
895             if (*p == '\"' || *p == '\'')
896               {
897                 int newline_seen = 0;
898                 char quote_char = *p;
899                 attr_raw_value_begin = p;
900                 ADVANCE (p);
901                 attr_value_begin = p; /* <foo bar="baz"> */
902                                       /*           ^     */
903                 while (*p != quote_char)
904                   {
905                     if (!newline_seen && *p == '\n')
906                       {
907                         /* If a newline is seen within the quotes, it
908                            is most likely that someone forgot to close
909                            the quote.  In that case, we back out to
910                            the value beginning, and terminate the tag
911                            at either `>' or the delimiter, whichever
912                            comes first.  Such a tag terminated at `>'
913                            is discarded.  */
914                         p = attr_value_begin;
915                         newline_seen = 1;
916                         continue;
917                       }
918                     else if (newline_seen && *p == '>')
919                       break;
920                     ADVANCE (p);
921                   }
922                 attr_value_end = p; /* <foo bar="baz"> */
923                                     /*              ^  */
924                 if (*p == quote_char)
925                   ADVANCE (p);
926                 else
927                   goto look_for_tag;
928                 attr_raw_value_end = p; /* <foo bar="baz"> */
929                                         /*               ^ */
930                 operation = AP_DECODE_ENTITIES;
931                 if (flags & MHT_TRIM_VALUES)
932                   operation |= AP_TRIM_BLANKS;
933               }
934             else
935               {
936                 attr_value_begin = p; /* <foo bar=baz> */
937                                       /*          ^    */
938                 /* According to SGML, a name token should consist only
939                    of alphanumerics, . and -.  However, this is often
940                    violated by, for instance, `%' in `width=75%'.
941                    We'll be liberal and allow just about anything as
942                    an attribute value.  */
943                 while (!ISSPACE (*p) && *p != '>')
944                   ADVANCE (p);
945                 attr_value_end = p; /* <foo bar=baz qux=quix> */
946                                     /*             ^          */
947                 if (attr_value_begin == attr_value_end)
948                   /* <foo bar=> */
949                   /*          ^ */
950                   goto backout_tag;
951                 attr_raw_value_begin = attr_value_begin;
952                 attr_raw_value_end = attr_value_end;
953                 operation = AP_DECODE_ENTITIES;
954               }
955           }
956         else
957           {
958             /* We skipped the whitespace and found something that is
959                neither `=' nor the beginning of the next attribute's
960                name.  Back out.  */
961             goto backout_tag;   /* <foo bar [... */
962                                 /*          ^    */
963           }
964
965         /* If we're not interested in the tag, don't bother with any
966            of the attributes.  */
967         if (uninteresting_tag)
968           continue;
969
970         /* If we aren't interested in the attribute, skip it.  We
971            cannot do this test any sooner, because our text pointer
972            needs to correctly advance over the attribute.  */
973         if (!name_allowed (allowed_attributes, attr_name_begin, attr_name_end))
974           continue;
975
976         GROW_ARRAY (pairs, attr_pair_size, nattrs + 1, attr_pair_resized,
977                     struct attr_pair);
978
979         pairs[nattrs].name_pool_index = pool.tail;
980         convert_and_copy (&pool, attr_name_begin, attr_name_end, AP_DOWNCASE);
981
982         pairs[nattrs].value_pool_index = pool.tail;
983         convert_and_copy (&pool, attr_value_begin, attr_value_end, operation);
984         pairs[nattrs].value_raw_beginning = attr_raw_value_begin;
985         pairs[nattrs].value_raw_size = (attr_raw_value_end
986                                         - attr_raw_value_begin);
987         ++nattrs;
988       }
989
990     if (uninteresting_tag)
991       {
992         ADVANCE (p);
993         goto look_for_tag;
994       }
995
996     /* By now, we have a valid tag with a name and zero or more
997        attributes.  Fill in the data and call the mapper function.  */
998     {
999       int i;
1000       struct taginfo taginfo;
1001
1002       taginfo.name      = pool.contents;
1003       taginfo.end_tag_p = end_tag;
1004       taginfo.nattrs    = nattrs;
1005       /* We fill in the char pointers only now, when pool can no
1006          longer get realloc'ed.  If we did that above, we could get
1007          hosed by reallocation.  Obviously, after this point, the pool
1008          may no longer be grown.  */
1009       for (i = 0; i < nattrs; i++)
1010         {
1011           pairs[i].name = pool.contents + pairs[i].name_pool_index;
1012           pairs[i].value = pool.contents + pairs[i].value_pool_index;
1013         }
1014       taginfo.attrs = pairs;
1015       taginfo.start_position = tag_start_position;
1016       taginfo.end_position   = p + 1;
1017       /* Ta-dam! */
1018       (*mapfun) (&taginfo, maparg);
1019       ADVANCE (p);
1020     }
1021     goto look_for_tag;
1022
1023   backout_tag:
1024 #ifdef STANDALONE
1025     ++tag_backout_count;
1026 #endif
1027     /* The tag wasn't really a tag.  Treat its contents as ordinary
1028        data characters. */
1029     p = tag_start_position + 1;
1030     goto look_for_tag;
1031   }
1032
1033  finish:
1034   POOL_FREE (&pool);
1035   if (attr_pair_resized)
1036     xfree (pairs);
1037 }
1038
1039 #undef ADVANCE
1040 #undef SKIP_WS
1041 #undef SKIP_NON_WS
1042 \f
1043 #ifdef STANDALONE
1044 static void
1045 test_mapper (struct taginfo *taginfo, void *arg)
1046 {
1047   int i;
1048
1049   printf ("%s%s", taginfo->end_tag_p ? "/" : "", taginfo->name);
1050   for (i = 0; i < taginfo->nattrs; i++)
1051     printf (" %s=%s", taginfo->attrs[i].name, taginfo->attrs[i].value);
1052   putchar ('\n');
1053   ++*(int *)arg;
1054 }
1055
1056 int main ()
1057 {
1058   int size = 256;
1059   char *x = (char *)xmalloc (size);
1060   int length = 0;
1061   int read_count;
1062   int tag_counter = 0;
1063
1064   while ((read_count = fread (x + length, 1, size - length, stdin)))
1065     {
1066       length += read_count;
1067       size <<= 1;
1068       x = (char *)xrealloc (x, size);
1069     }
1070
1071   map_html_tags (x, length, test_mapper, &tag_counter, 0, NULL, NULL);
1072   printf ("TAGS: %d\n", tag_counter);
1073   printf ("Tag backouts:     %d\n", tag_backout_count);
1074   printf ("Comment backouts: %d\n", comment_backout_count);
1075   return 0;
1076 }
1077 #endif /* STANDALONE */