]> sjero.net Git - wget/blob - src/html-parse.c
[svn] Squash embedded newlines in links.
[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_TAG_NAMES should be a NULL-terminated array of tag names to
734    be processed by this function.  If it is NULL, all the tags are
735    allowed.  The same goes for attributes and ALLOWED_ATTRIBUTE_NAMES.
736
737    (Obviously, the caller can filter out unwanted tags and attributes
738    just as well, but this is just an optimization designed to avoid
739    unnecessary copying for tags/attributes which the caller doesn't
740    want to know about.  These lists are searched linearly; therefore,
741    if you're interested in a large number of tags or attributes, you'd
742    better set these to NULL and filter them out yourself with a
743    hashing process most appropriate for your application.)  */
744
745 void
746 map_html_tags (const char *text, int size,
747                void (*mapfun) (struct taginfo *, void *), void *maparg,
748                int flags,
749                const struct hash_table *allowed_tags,
750                const struct hash_table *allowed_attributes)
751 {
752   /* storage for strings passed to MAPFUN callback; if 256 bytes is
753      too little, POOL_APPEND allocates more with malloc. */
754   char pool_initial_storage[256];
755   struct pool pool;
756
757   const char *p = text;
758   const char *end = text + size;
759
760   struct attr_pair attr_pair_initial_storage[8];
761   int attr_pair_size = countof (attr_pair_initial_storage);
762   int attr_pair_resized = 0;
763   struct attr_pair *pairs = attr_pair_initial_storage;
764
765   if (!size)
766     return;
767
768   POOL_INIT (&pool, pool_initial_storage, countof (pool_initial_storage));
769
770   {
771     int nattrs, end_tag;
772     const char *tag_name_begin, *tag_name_end;
773     const char *tag_start_position;
774     int uninteresting_tag;
775
776   look_for_tag:
777     POOL_REWIND (&pool);
778
779     nattrs = 0;
780     end_tag = 0;
781
782     /* Find beginning of tag.  We use memchr() instead of the usual
783        looping with ADVANCE() for speed. */
784     p = memchr (p, '<', end - p);
785     if (!p)
786       goto finish;
787
788     tag_start_position = p;
789     ADVANCE (p);
790
791     /* Establish the type of the tag (start-tag, end-tag or
792        declaration).  */
793     if (*p == '!')
794       {
795         if (!(flags & MHT_STRICT_COMMENTS)
796             && p < end + 3 && p[1] == '-' && p[2] == '-')
797           {
798             /* If strict comments are not enforced and if we know
799                we're looking at a comment, simply look for the
800                terminating "-->".  Non-strict is the default because
801                it works in other browsers and most HTML writers can't
802                be bothered with getting the comments right.  */
803             const char *comment_end = find_comment_end (p + 3, end);
804             if (comment_end)
805               p = comment_end;
806           }
807         else
808           {
809             /* Either in strict comment mode or looking at a non-empty
810                declaration.  Real declarations are much less likely to
811                be misused the way comments are, so advance over them
812                properly regardless of strictness.  */
813             p = advance_declaration (p, end);
814           }
815         if (p == end)
816           goto finish;
817         goto look_for_tag;
818       }
819     else if (*p == '/')
820       {
821         end_tag = 1;
822         ADVANCE (p);
823       }
824     tag_name_begin = p;
825     while (NAME_CHAR_P (*p))
826       ADVANCE (p);
827     if (p == tag_name_begin)
828       goto look_for_tag;
829     tag_name_end = p;
830     SKIP_WS (p);
831     if (end_tag && *p != '>')
832       goto backout_tag;
833
834     if (!name_allowed (allowed_tags, tag_name_begin, tag_name_end))
835       /* We can't just say "goto look_for_tag" here because we need
836          the loop below to properly advance over the tag's attributes.  */
837       uninteresting_tag = 1;
838     else
839       {
840         uninteresting_tag = 0;
841         convert_and_copy (&pool, tag_name_begin, tag_name_end, AP_DOWNCASE);
842       }
843
844     /* Find the attributes. */
845     while (1)
846       {
847         const char *attr_name_begin, *attr_name_end;
848         const char *attr_value_begin, *attr_value_end;
849         const char *attr_raw_value_begin, *attr_raw_value_end;
850         int operation = AP_DOWNCASE; /* stupid compiler. */
851
852         SKIP_WS (p);
853
854         if (*p == '/')
855           {
856             /* A slash at this point means the tag is about to be
857                closed.  This is legal in XML and has been popularized
858                in HTML via XHTML.  */
859             /* <foo a=b c=d /> */
860             /*              ^  */
861             ADVANCE (p);
862             SKIP_WS (p);
863             if (*p != '>')
864               goto backout_tag;
865           }
866
867         /* Check for end of tag definition. */
868         if (*p == '>')
869           break;
870
871         /* Establish bounds of attribute name. */
872         attr_name_begin = p;    /* <foo bar ...> */
873                                 /*      ^        */
874         while (NAME_CHAR_P (*p))
875           ADVANCE (p);
876         attr_name_end = p;      /* <foo bar ...> */
877                                 /*         ^     */
878         if (attr_name_begin == attr_name_end)
879           goto backout_tag;
880
881         /* Establish bounds of attribute value. */
882         SKIP_WS (p);
883         if (NAME_CHAR_P (*p) || *p == '/' || *p == '>')
884           {
885             /* Minimized attribute syntax allows `=' to be omitted.
886                For example, <UL COMPACT> is a valid shorthand for <UL
887                COMPACT="compact">.  Even if such attributes are not
888                useful to Wget, we need to support them, so that the
889                tags containing them can be parsed correctly. */
890             attr_raw_value_begin = attr_value_begin = attr_name_begin;
891             attr_raw_value_end = attr_value_end = attr_name_end;
892           }
893         else if (*p == '=')
894           {
895             ADVANCE (p);
896             SKIP_WS (p);
897             if (*p == '\"' || *p == '\'')
898               {
899                 int newline_seen = 0;
900                 char quote_char = *p;
901                 attr_raw_value_begin = p;
902                 ADVANCE (p);
903                 attr_value_begin = p; /* <foo bar="baz"> */
904                                       /*           ^     */
905                 while (*p != quote_char)
906                   {
907                     if (!newline_seen && *p == '\n')
908                       {
909                         /* If a newline is seen within the quotes, it
910                            is most likely that someone forgot to close
911                            the quote.  In that case, we back out to
912                            the value beginning, and terminate the tag
913                            at either `>' or the delimiter, whichever
914                            comes first.  Such a tag terminated at `>'
915                            is discarded.  */
916                         p = attr_value_begin;
917                         newline_seen = 1;
918                         continue;
919                       }
920                     else if (newline_seen && *p == '>')
921                       break;
922                     ADVANCE (p);
923                   }
924                 attr_value_end = p; /* <foo bar="baz"> */
925                                     /*              ^  */
926                 if (*p == quote_char)
927                   ADVANCE (p);
928                 else
929                   goto look_for_tag;
930                 attr_raw_value_end = p; /* <foo bar="baz"> */
931                                         /*               ^ */
932                 operation = AP_DECODE_ENTITIES;
933                 if (flags & MHT_TRIM_VALUES)
934                   operation |= AP_TRIM_BLANKS;
935               }
936             else
937               {
938                 attr_value_begin = p; /* <foo bar=baz> */
939                                       /*          ^    */
940                 /* According to SGML, a name token should consist only
941                    of alphanumerics, . and -.  However, this is often
942                    violated by, for instance, `%' in `width=75%'.
943                    We'll be liberal and allow just about anything as
944                    an attribute value.  */
945                 while (!ISSPACE (*p) && *p != '>')
946                   ADVANCE (p);
947                 attr_value_end = p; /* <foo bar=baz qux=quix> */
948                                     /*             ^          */
949                 if (attr_value_begin == attr_value_end)
950                   /* <foo bar=> */
951                   /*          ^ */
952                   goto backout_tag;
953                 attr_raw_value_begin = attr_value_begin;
954                 attr_raw_value_end = attr_value_end;
955                 operation = AP_DECODE_ENTITIES;
956               }
957           }
958         else
959           {
960             /* We skipped the whitespace and found something that is
961                neither `=' nor the beginning of the next attribute's
962                name.  Back out.  */
963             goto backout_tag;   /* <foo bar [... */
964                                 /*          ^    */
965           }
966
967         /* If we're not interested in the tag, don't bother with any
968            of the attributes.  */
969         if (uninteresting_tag)
970           continue;
971
972         /* If we aren't interested in the attribute, skip it.  We
973            cannot do this test any sooner, because our text pointer
974            needs to correctly advance over the attribute.  */
975         if (!name_allowed (allowed_attributes, attr_name_begin, attr_name_end))
976           continue;
977
978         GROW_ARRAY (pairs, attr_pair_size, nattrs + 1, attr_pair_resized,
979                     struct attr_pair);
980
981         pairs[nattrs].name_pool_index = pool.tail;
982         convert_and_copy (&pool, attr_name_begin, attr_name_end, AP_DOWNCASE);
983
984         pairs[nattrs].value_pool_index = pool.tail;
985         convert_and_copy (&pool, attr_value_begin, attr_value_end, operation);
986         pairs[nattrs].value_raw_beginning = attr_raw_value_begin;
987         pairs[nattrs].value_raw_size = (attr_raw_value_end
988                                         - attr_raw_value_begin);
989         ++nattrs;
990       }
991
992     if (uninteresting_tag)
993       {
994         ADVANCE (p);
995         goto look_for_tag;
996       }
997
998     /* By now, we have a valid tag with a name and zero or more
999        attributes.  Fill in the data and call the mapper function.  */
1000     {
1001       int i;
1002       struct taginfo taginfo;
1003
1004       taginfo.name      = pool.contents;
1005       taginfo.end_tag_p = end_tag;
1006       taginfo.nattrs    = nattrs;
1007       /* We fill in the char pointers only now, when pool can no
1008          longer get realloc'ed.  If we did that above, we could get
1009          hosed by reallocation.  Obviously, after this point, the pool
1010          may no longer be grown.  */
1011       for (i = 0; i < nattrs; i++)
1012         {
1013           pairs[i].name = pool.contents + pairs[i].name_pool_index;
1014           pairs[i].value = pool.contents + pairs[i].value_pool_index;
1015         }
1016       taginfo.attrs = pairs;
1017       taginfo.start_position = tag_start_position;
1018       taginfo.end_position   = p + 1;
1019       /* Ta-dam! */
1020       (*mapfun) (&taginfo, maparg);
1021       ADVANCE (p);
1022     }
1023     goto look_for_tag;
1024
1025   backout_tag:
1026 #ifdef STANDALONE
1027     ++tag_backout_count;
1028 #endif
1029     /* The tag wasn't really a tag.  Treat its contents as ordinary
1030        data characters. */
1031     p = tag_start_position + 1;
1032     goto look_for_tag;
1033   }
1034
1035  finish:
1036   POOL_FREE (&pool);
1037   if (attr_pair_resized)
1038     xfree (pairs);
1039 }
1040
1041 #undef ADVANCE
1042 #undef SKIP_WS
1043 #undef SKIP_NON_WS
1044 \f
1045 #ifdef STANDALONE
1046 static void
1047 test_mapper (struct taginfo *taginfo, void *arg)
1048 {
1049   int i;
1050
1051   printf ("%s%s", taginfo->end_tag_p ? "/" : "", taginfo->name);
1052   for (i = 0; i < taginfo->nattrs; i++)
1053     printf (" %s=%s", taginfo->attrs[i].name, taginfo->attrs[i].value);
1054   putchar ('\n');
1055   ++*(int *)arg;
1056 }
1057
1058 int main ()
1059 {
1060   int size = 256;
1061   char *x = (char *)xmalloc (size);
1062   int length = 0;
1063   int read_count;
1064   int tag_counter = 0;
1065
1066   while ((read_count = fread (x + length, 1, size - length, stdin)))
1067     {
1068       length += read_count;
1069       size <<= 1;
1070       x = (char *)xrealloc (x, size);
1071     }
1072
1073   map_html_tags (x, length, test_mapper, &tag_counter, 0, NULL, NULL);
1074   printf ("TAGS: %d\n", tag_counter);
1075   printf ("Tag backouts:     %d\n", tag_backout_count);
1076   printf ("Comment backouts: %d\n", comment_backout_count);
1077   return 0;
1078 }
1079 #endif /* STANDALONE */