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