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