]> sjero.net Git - wget/blob - src/html-url.c
[svn] Don't descend into HTML that was downloaded by following <img src=...>
[wget] / src / html-url.c
1 /* Collect URLs from HTML source.
2    Copyright (C) 1998, 2000, 2001, 2002, 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
9  (at 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 #include <config.h>
31
32 #include <stdio.h>
33 #ifdef HAVE_STRING_H
34 # include <string.h>
35 #else
36 # include <strings.h>
37 #endif
38 #include <stdlib.h>
39 #include <errno.h>
40 #include <assert.h>
41
42 #include "wget.h"
43 #include "html-parse.h"
44 #include "url.h"
45 #include "utils.h"
46 #include "hash.h"
47 #include "convert.h"
48
49 #ifndef errno
50 extern int errno;
51 #endif
52
53 struct map_context;
54
55 typedef void (*tag_handler_t) PARAMS ((int, struct taginfo *,
56                                        struct map_context *));
57
58 #define DECLARE_TAG_HANDLER(fun)                                        \
59   static void fun PARAMS ((int, struct taginfo *, struct map_context *))
60
61 DECLARE_TAG_HANDLER (tag_find_urls);
62 DECLARE_TAG_HANDLER (tag_handle_base);
63 DECLARE_TAG_HANDLER (tag_handle_form);
64 DECLARE_TAG_HANDLER (tag_handle_link);
65 DECLARE_TAG_HANDLER (tag_handle_meta);
66
67 enum {
68   TAG_A,
69   TAG_APPLET,
70   TAG_AREA,
71   TAG_BASE,
72   TAG_BGSOUND,
73   TAG_BODY,
74   TAG_EMBED,
75   TAG_FIG,
76   TAG_FORM,
77   TAG_FRAME,
78   TAG_IFRAME,
79   TAG_IMG,
80   TAG_INPUT,
81   TAG_LAYER,
82   TAG_LINK,
83   TAG_META,
84   TAG_OVERLAY,
85   TAG_SCRIPT,
86   TAG_TABLE,
87   TAG_TD,
88   TAG_TH
89 };
90
91 /* The list of known tags and functions used for handling them.  Most
92    tags are simply harvested for URLs. */
93 static struct known_tag {
94   int tagid;
95   const char *name;
96   tag_handler_t handler;
97 } known_tags[] = {
98   { TAG_A,       "a",           tag_find_urls },
99   { TAG_APPLET,  "applet",      tag_find_urls },
100   { TAG_AREA,    "area",        tag_find_urls },
101   { TAG_BASE,    "base",        tag_handle_base },
102   { TAG_BGSOUND, "bgsound",     tag_find_urls },
103   { TAG_BODY,    "body",        tag_find_urls },
104   { TAG_EMBED,   "embed",       tag_find_urls },
105   { TAG_FIG,     "fig",         tag_find_urls },
106   { TAG_FORM,    "form",        tag_handle_form },
107   { TAG_FRAME,   "frame",       tag_find_urls },
108   { TAG_IFRAME,  "iframe",      tag_find_urls },
109   { TAG_IMG,     "img",         tag_find_urls },
110   { TAG_INPUT,   "input",       tag_find_urls },
111   { TAG_LAYER,   "layer",       tag_find_urls },
112   { TAG_LINK,    "link",        tag_handle_link },
113   { TAG_META,    "meta",        tag_handle_meta },
114   { TAG_OVERLAY, "overlay",     tag_find_urls },
115   { TAG_SCRIPT,  "script",      tag_find_urls },
116   { TAG_TABLE,   "table",       tag_find_urls },
117   { TAG_TD,      "td",          tag_find_urls },
118   { TAG_TH,      "th",          tag_find_urls }
119 };
120
121 /* tag_url_attributes documents which attributes of which tags contain
122    URLs to harvest.  It is used by tag_find_urls.  */
123
124 /* Defines for the FLAGS. */
125
126 /* The link is "inline", i.e. needs to be retrieved for this document
127    to be correctly rendered.  Inline links include inlined images,
128    stylesheets, children frames, etc.  */
129 #define ATTR_INLINE     1
130
131 /* The link is expected to yield HTML contents.  It's important not to
132    try to follow HTML obtained by following e.g. <img src="...">
133    regardless of content-type.  Doing this causes infinite loops for
134    "images" that return non-404 error pages with links to the same
135    image.  */
136 #define ATTR_HTML       2
137
138 /* For tags handled by tag_find_urls: attributes that contain URLs to
139    download. */
140 static struct {
141   int tagid;
142   const char *attr_name;
143   int flags;
144 } tag_url_attributes[] = {
145   { TAG_A,              "href",         ATTR_HTML },
146   { TAG_APPLET,         "code",         ATTR_INLINE },
147   { TAG_AREA,           "href",         ATTR_HTML },
148   { TAG_BGSOUND,        "src",          ATTR_INLINE },
149   { TAG_BODY,           "background",   ATTR_INLINE },
150   { TAG_EMBED,          "href",         ATTR_HTML },
151   { TAG_EMBED,          "src",          ATTR_INLINE | ATTR_HTML },
152   { TAG_FIG,            "src",          ATTR_INLINE },
153   { TAG_FRAME,          "src",          ATTR_INLINE | ATTR_HTML },
154   { TAG_IFRAME,         "src",          ATTR_INLINE | ATTR_HTML },
155   { TAG_IMG,            "href",         ATTR_INLINE },
156   { TAG_IMG,            "lowsrc",       ATTR_INLINE },
157   { TAG_IMG,            "src",          ATTR_INLINE },
158   { TAG_INPUT,          "src",          ATTR_INLINE },
159   { TAG_LAYER,          "src",          ATTR_INLINE | ATTR_HTML },
160   { TAG_OVERLAY,        "src",          ATTR_INLINE | ATTR_HTML },
161   { TAG_SCRIPT,         "src",          ATTR_INLINE },
162   { TAG_TABLE,          "background",   ATTR_INLINE },
163   { TAG_TD,             "background",   ATTR_INLINE },
164   { TAG_TH,             "background",   ATTR_INLINE }
165 };
166
167 /* The lists of interesting tags and attributes are built dynamically,
168    from the information above.  However, some places in the code refer
169    to the attributes not mentioned here.  We add them manually.  */
170 static const char *additional_attributes[] = {
171   "rel",                        /* used by tag_handle_link */
172   "http-equiv",                 /* used by tag_handle_meta */
173   "name",                       /* used by tag_handle_meta */
174   "content",                    /* used by tag_handle_meta */
175   "action"                      /* used by tag_handle_form */
176 };
177
178 struct hash_table *interesting_tags;
179 struct hash_table *interesting_attributes;
180
181 static void
182 init_interesting (void)
183 {
184   /* Init the variables interesting_tags and interesting_attributes
185      that are used by the HTML parser to know which tags and
186      attributes we're interested in.  We initialize this only once,
187      for performance reasons.
188
189      Here we also make sure that what we put in interesting_tags
190      matches the user's preferences as specified through --ignore-tags
191      and --follow-tags.  */
192
193   int i;
194   interesting_tags = make_nocase_string_hash_table (countof (known_tags));
195
196   /* First, add all the tags we know hot to handle, mapped to their
197      respective entries in known_tags.  */
198   for (i = 0; i < countof (known_tags); i++)
199     hash_table_put (interesting_tags, known_tags[i].name, known_tags + i);
200
201   /* Then remove the tags ignored through --ignore-tags.  */
202   if (opt.ignore_tags)
203     {
204       char **ignored;
205       for (ignored = opt.ignore_tags; *ignored; ignored++)
206         hash_table_remove (interesting_tags, *ignored);
207     }
208
209   /* If --follow-tags is specified, use only those tags.  */
210   if (opt.follow_tags)
211     {
212       /* Create a new table intersecting --follow-tags and known_tags,
213          and use it as interesting_tags.  */
214       struct hash_table *intersect = make_nocase_string_hash_table (0);
215       char **followed;
216       for (followed = opt.follow_tags; *followed; followed++)
217         {
218           struct known_tag *t = hash_table_get (interesting_tags, *followed);
219           if (!t)
220             continue;           /* ignore unknown --follow-tags entries. */
221           hash_table_put (intersect, *followed, t);
222         }
223       hash_table_destroy (interesting_tags);
224       interesting_tags = intersect;
225     }
226
227   /* Add the attributes we care about. */
228   interesting_attributes = make_nocase_string_hash_table (10);
229   for (i = 0; i < countof (additional_attributes); i++)
230     string_set_add (interesting_attributes, additional_attributes[i]);
231   for (i = 0; i < countof (tag_url_attributes); i++)
232     string_set_add (interesting_attributes, tag_url_attributes[i].attr_name);
233 }
234
235 /* Find the value of attribute named NAME in the taginfo TAG.  If the
236    attribute is not present, return NULL.  If ATTRIND is non-NULL, the
237    index of the attribute in TAG will be stored there.  */
238
239 static char *
240 find_attr (struct taginfo *tag, const char *name, int *attrind)
241 {
242   int i;
243   for (i = 0; i < tag->nattrs; i++)
244     if (!strcasecmp (tag->attrs[i].name, name))
245       {
246         if (attrind)
247           *attrind = i;
248         return tag->attrs[i].value;
249       }
250   return NULL;
251 }
252
253 struct map_context {
254   char *text;                   /* HTML text. */
255   char *base;                   /* Base URI of the document, possibly
256                                    changed through <base href=...>. */
257   const char *parent_base;      /* Base of the current document. */
258   const char *document_file;    /* File name of this document. */
259   int nofollow;                 /* whether NOFOLLOW was specified in a
260                                    <meta name=robots> tag. */
261
262   struct urlpos *head, *tail;   /* List of URLs that is being
263                                    built. */
264 };
265
266 /* Append LINK_URI to the urlpos structure that is being built.
267
268    LINK_URI will be merged with the current document base.  TAG and
269    ATTRIND are the necessary context to store the position and
270    size.  */
271
272 static struct urlpos *
273 append_one_url (const char *link_uri,
274                 struct taginfo *tag, int attrind, struct map_context *ctx)
275 {
276   int link_has_scheme = url_has_scheme (link_uri);
277   struct urlpos *newel;
278   const char *base = ctx->base ? ctx->base : ctx->parent_base;
279   struct url *url;
280
281   if (!base)
282     {
283       DEBUGP (("%s: no base, merge will use \"%s\".\n",
284                ctx->document_file, link_uri));
285
286       if (!link_has_scheme)
287         {
288           /* Base URL is unavailable, and the link does not have a
289              location attached to it -- we have to give up.  Since
290              this can only happen when using `--force-html -i', print
291              a warning.  */
292           logprintf (LOG_NOTQUIET,
293                      _("%s: Cannot resolve incomplete link %s.\n"),
294                      ctx->document_file, link_uri);
295           return NULL;
296         }
297
298       url = url_parse (link_uri, NULL);
299       if (!url)
300         {
301           DEBUGP (("%s: link \"%s\" doesn't parse.\n",
302                    ctx->document_file, link_uri));
303           return NULL;
304         }
305     }
306   else
307     {
308       /* Merge BASE with LINK_URI, but also make sure the result is
309          canonicalized, i.e. that "../" have been resolved.
310          (parse_url will do that for us.) */
311
312       char *complete_uri = uri_merge (base, link_uri);
313
314       DEBUGP (("%s: merge(\"%s\", \"%s\") -> %s\n",
315                ctx->document_file, base, link_uri, complete_uri));
316
317       url = url_parse (complete_uri, NULL);
318       if (!url)
319         {
320           DEBUGP (("%s: merged link \"%s\" doesn't parse.\n",
321                    ctx->document_file, complete_uri));
322           xfree (complete_uri);
323           return NULL;
324         }
325       xfree (complete_uri);
326     }
327
328   DEBUGP (("appending \"%s\" to urlpos.\n", url->url));
329
330   newel = (struct urlpos *)xmalloc (sizeof (struct urlpos));
331   memset (newel, 0, sizeof (*newel));
332
333   newel->next = NULL;
334   newel->url = url;
335   newel->pos = tag->attrs[attrind].value_raw_beginning - ctx->text;
336   newel->size = tag->attrs[attrind].value_raw_size;
337
338   /* A URL is relative if the host is not named, and the name does not
339      start with `/'.  */
340   if (!link_has_scheme && *link_uri != '/')
341     newel->link_relative_p = 1;
342   else if (link_has_scheme)
343     newel->link_complete_p = 1;
344
345   if (ctx->tail)
346     {
347       ctx->tail->next = newel;
348       ctx->tail = newel;
349     }
350   else
351     ctx->tail = ctx->head = newel;
352
353   return newel;
354 }
355 \f
356 /* All the tag_* functions are called from collect_tags_mapper, as
357    specified by KNOWN_TAGS.  */
358
359 /* Default tag handler: collect URLs from attributes specified for
360    this tag by tag_url_attributes.  */
361
362 static void
363 tag_find_urls (int tagid, struct taginfo *tag, struct map_context *ctx)
364 {
365   int i, attrind;
366   int first = -1;
367
368   for (i = 0; i < countof (tag_url_attributes); i++)
369     if (tag_url_attributes[i].tagid == tagid)
370       {
371         /* We've found the index of tag_url_attributes where the
372            attributes of our tag begin.  */
373         first = i;
374         break;
375       }
376   assert (first != -1);
377
378   /* Loop over the "interesting" attributes of this tag.  In this
379      example, it will loop over "src" and "lowsrc".
380
381        <img src="foo.png" lowsrc="bar.png">
382
383      This has to be done in the outer loop so that the attributes are
384      processed in the same order in which they appear in the page.
385      This is required when converting links.  */
386
387   for (attrind = 0; attrind < tag->nattrs; attrind++)
388     {
389       /* Find whether TAG/ATTRIND is a combination that contains a
390          URL. */
391       char *link = tag->attrs[attrind].value;
392       const int size = countof (tag_url_attributes);
393
394       /* If you're cringing at the inefficiency of the nested loops,
395          remember that they both iterate over a very small number of
396          items.  The worst-case inner loop is for the IMG tag, which
397          has three attributes.  */
398       for (i = first; i < size && tag_url_attributes[i].tagid == tagid; i++)
399         {
400           if (0 == strcasecmp (tag->attrs[attrind].name,
401                                tag_url_attributes[i].attr_name))
402             {
403               struct urlpos *up = append_one_url (link, tag, attrind, ctx);
404               if (up)
405                 {
406                   int flags = tag_url_attributes[i].flags;
407                   if (flags & ATTR_INLINE)
408                     up->link_inline_p = 1;
409                   if (flags & ATTR_HTML)
410                     up->link_expect_html = 1;
411                 }
412             }
413         }
414     }
415 }
416
417 /* Handle the BASE tag, for <base href=...>. */
418
419 static void
420 tag_handle_base (int tagid, struct taginfo *tag, struct map_context *ctx)
421 {
422   struct urlpos *base_urlpos;
423   int attrind;
424   char *newbase = find_attr (tag, "href", &attrind);
425   if (!newbase)
426     return;
427
428   base_urlpos = append_one_url (newbase, tag, attrind, ctx);
429   if (!base_urlpos)
430     return;
431   base_urlpos->ignore_when_downloading = 1;
432   base_urlpos->link_base_p = 1;
433
434   if (ctx->base)
435     xfree (ctx->base);
436   if (ctx->parent_base)
437     ctx->base = uri_merge (ctx->parent_base, newbase);
438   else
439     ctx->base = xstrdup (newbase);
440 }
441
442 /* Mark the URL found in <form action=...> for conversion. */
443
444 static void
445 tag_handle_form (int tagid, struct taginfo *tag, struct map_context *ctx)
446 {
447   int attrind;
448   char *action = find_attr (tag, "action", &attrind);
449   if (action)
450     {
451       struct urlpos *up = append_one_url (action, tag, attrind, ctx);
452       if (up)
453         up->ignore_when_downloading = 1;
454     }
455 }
456
457 /* Handle the LINK tag.  It requires special handling because how its
458    links will be followed in -p mode depends on the REL attribute.  */
459
460 static void
461 tag_handle_link (int tagid, struct taginfo *tag, struct map_context *ctx)
462 {
463   int attrind;
464   char *href = find_attr (tag, "href", &attrind);
465
466   /* All <link href="..."> link references are external, except those
467      known not to be, such as style sheet and shortcut icon:
468
469        <link rel="stylesheet" href="...">
470        <link rel="shortcut icon" href="...">
471   */
472   if (href)
473     {
474       struct urlpos *up = append_one_url (href, tag, attrind, ctx);
475       if (up)
476         {
477           char *rel = find_attr (tag, "rel", NULL);
478           if (rel
479               && (0 == strcasecmp (rel, "stylesheet")
480                   || 0 == strcasecmp (rel, "shortcut icon")))
481             up->link_inline_p = 1;
482         }
483     }
484 }
485
486 /* Handle the META tag.  This requires special handling because of the
487    refresh feature and because of robot exclusion.  */
488
489 static void
490 tag_handle_meta (int tagid, struct taginfo *tag, struct map_context *ctx)
491 {
492   char *name = find_attr (tag, "name", NULL);
493   char *http_equiv = find_attr (tag, "http-equiv", NULL);
494
495   if (http_equiv && 0 == strcasecmp (http_equiv, "refresh"))
496     {
497       /* Some pages use a META tag to specify that the page be
498          refreshed by a new page after a given number of seconds.  The
499          general format for this is:
500
501            <meta http-equiv=Refresh content="NUMBER; URL=index2.html">
502
503          So we just need to skip past the "NUMBER; URL=" garbage to
504          get to the URL.  */
505
506       struct urlpos *entry;
507       int attrind;
508       int timeout = 0;
509       char *p;
510
511       char *refresh = find_attr (tag, "content", &attrind);
512       if (!refresh)
513         return;
514
515       for (p = refresh; ISDIGIT (*p); p++)
516         timeout = 10 * timeout + *p - '0';
517       if (*p++ != ';')
518         return;
519
520       while (ISSPACE (*p))
521         ++p;
522       if (!(   TOUPPER (*p)       == 'U'
523             && TOUPPER (*(p + 1)) == 'R'
524             && TOUPPER (*(p + 2)) == 'L'
525             &&          *(p + 3)  == '='))
526         return;
527       p += 4;
528       while (ISSPACE (*p))
529         ++p;
530
531       entry = append_one_url (p, tag, attrind, ctx);
532       if (entry)
533         {
534           entry->link_refresh_p = 1;
535           entry->refresh_timeout = timeout;
536         }
537     }
538   else if (name && 0 == strcasecmp (name, "robots"))
539     {
540       /* Handle stuff like:
541          <meta name="robots" content="index,nofollow"> */
542       char *content = find_attr (tag, "content", NULL);
543       if (!content)
544         return;
545       if (!strcasecmp (content, "none"))
546         ctx->nofollow = 1;
547       else
548         {
549           while (*content)
550             {
551               /* Find the next occurrence of ',' or the end of
552                  the string.  */
553               char *end = strchr (content, ',');
554               if (end)
555                 ++end;
556               else
557                 end = content + strlen (content);
558               if (!strncasecmp (content, "nofollow", end - content))
559                 ctx->nofollow = 1;
560               content = end;
561             }
562         }
563     }
564 }
565
566 /* Dispatch the tag handler appropriate for the tag we're mapping
567    over.  See known_tags[] for definition of tag handlers.  */
568
569 static void
570 collect_tags_mapper (struct taginfo *tag, void *arg)
571 {
572   struct map_context *ctx = (struct map_context *)arg;
573
574   /* Find the tag in our table of tags.  This must not fail because
575      map_html_tags only returns tags found in interesting_tags.  */
576   struct known_tag *t = hash_table_get (interesting_tags, tag->name);
577   assert (t != NULL);
578
579   t->handler (t->tagid, tag, ctx);
580 }
581 \f
582 /* Analyze HTML tags FILE and construct a list of URLs referenced from
583    it.  It merges relative links in FILE with URL.  It is aware of
584    <base href=...> and does the right thing.  */
585
586 struct urlpos *
587 get_urls_html (const char *file, const char *url, int *meta_disallow_follow)
588 {
589   struct file_memory *fm;
590   struct map_context ctx;
591   int flags;
592
593   /* Load the file. */
594   fm = read_file (file);
595   if (!fm)
596     {
597       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
598       return NULL;
599     }
600   DEBUGP (("Loaded %s (size %ld).\n", file, fm->length));
601
602   ctx.text = fm->content;
603   ctx.head = ctx.tail = NULL;
604   ctx.base = NULL;
605   ctx.parent_base = url ? url : opt.base_href;
606   ctx.document_file = file;
607   ctx.nofollow = 0;
608
609   if (!interesting_tags)
610     init_interesting ();
611
612   /* Specify MHT_TRIM_VALUES because of buggy HTML generators that
613      generate <a href=" foo"> instead of <a href="foo"> (Netscape
614      ignores spaces as well.)  If you really mean space, use &32; or
615      %20.  */
616   flags = MHT_TRIM_VALUES;
617   if (opt.strict_comments)
618     flags |= MHT_STRICT_COMMENTS;
619
620   map_html_tags (fm->content, fm->length, collect_tags_mapper, &ctx, flags,
621                  interesting_tags, interesting_attributes);
622
623   DEBUGP (("no-follow in %s: %d\n", file, ctx.nofollow));
624   if (meta_disallow_follow)
625     *meta_disallow_follow = ctx.nofollow;
626
627   FREE_MAYBE (ctx.base);
628   read_file_free (fm);
629   return ctx.head;
630 }
631
632 /* This doesn't really have anything to do with HTML, but it's similar
633    to get_urls_html, so we put it here.  */
634
635 struct urlpos *
636 get_urls_file (const char *file)
637 {
638   struct file_memory *fm;
639   struct urlpos *head, *tail;
640   const char *text, *text_end;
641
642   /* Load the file.  */
643   fm = read_file (file);
644   if (!fm)
645     {
646       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
647       return NULL;
648     }
649   DEBUGP (("Loaded %s (size %ld).\n", file, fm->length));
650
651   head = tail = NULL;
652   text = fm->content;
653   text_end = fm->content + fm->length;
654   while (text < text_end)
655     {
656       int up_error_code;
657       char *url_text;
658       struct urlpos *entry;
659       struct url *url;
660
661       const char *line_beg = text;
662       const char *line_end = memchr (text, '\n', text_end - text);
663       if (!line_end)
664         line_end = text_end;
665       else
666         ++line_end;
667       text = line_end;
668
669       /* Strip whitespace from the beginning and end of line. */
670       while (line_beg < line_end && ISSPACE (*line_beg))
671         ++line_beg;
672       while (line_end > line_beg && ISSPACE (*(line_end - 1)))
673         --line_end;
674
675       if (line_beg == line_end)
676         continue;
677
678       /* The URL is in the [line_beg, line_end) region. */
679
680       /* We must copy the URL to a zero-terminated string, and we
681          can't use alloca because we're in a loop.  *sigh*.  */
682       url_text = strdupdelim (line_beg, line_end);
683
684       if (opt.base_href)
685         {
686           /* Merge opt.base_href with URL. */
687           char *merged = uri_merge (opt.base_href, url_text);
688           xfree (url_text);
689           url_text = merged;
690         }
691
692       url = url_parse (url_text, &up_error_code);
693       if (!url)
694         {
695           logprintf (LOG_NOTQUIET, "%s: Invalid URL %s: %s\n",
696                      file, url_text, url_error (up_error_code));
697           xfree (url_text);
698           continue;
699         }
700       xfree (url_text);
701
702       entry = (struct urlpos *)xmalloc (sizeof (struct urlpos));
703       memset (entry, 0, sizeof (*entry));
704       entry->next = NULL;
705       entry->url = url;
706
707       if (!head)
708         head = entry;
709       else
710         tail->next = entry;
711       tail = entry;
712     }
713   read_file_free (fm);
714   return head;
715 }
716
717 void
718 cleanup_html_url (void)
719 {
720   FREE_MAYBE (interesting_tags);
721   FREE_MAYBE (interesting_attributes);
722 }