]> sjero.net Git - wget/blob - src/html-url.c
Merging Ted Mielczarek's CSS changes with tip.
[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"
45 #include "html-url.h"
46 #include "css-url.h"
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   "style"                       /* used by check_style_attr */
172 };
173
174 static struct hash_table *interesting_tags;
175 static struct hash_table *interesting_attributes;
176
177 static void
178 init_interesting (void)
179 {
180   /* Init the variables interesting_tags and interesting_attributes
181      that are used by the HTML parser to know which tags and
182      attributes we're interested in.  We initialize this only once,
183      for performance reasons.
184
185      Here we also make sure that what we put in interesting_tags
186      matches the user's preferences as specified through --ignore-tags
187      and --follow-tags.  */
188
189   int i;
190   interesting_tags = make_nocase_string_hash_table (countof (known_tags));
191
192   /* First, add all the tags we know hot to handle, mapped to their
193      respective entries in known_tags.  */
194   for (i = 0; i < countof (known_tags); i++)
195     hash_table_put (interesting_tags, known_tags[i].name, known_tags + i);
196
197   /* Then remove the tags ignored through --ignore-tags.  */
198   if (opt.ignore_tags)
199     {
200       char **ignored;
201       for (ignored = opt.ignore_tags; *ignored; ignored++)
202         hash_table_remove (interesting_tags, *ignored);
203     }
204
205   /* If --follow-tags is specified, use only those tags.  */
206   if (opt.follow_tags)
207     {
208       /* Create a new table intersecting --follow-tags and known_tags,
209          and use it as interesting_tags.  */
210       struct hash_table *intersect = make_nocase_string_hash_table (0);
211       char **followed;
212       for (followed = opt.follow_tags; *followed; followed++)
213         {
214           struct known_tag *t = hash_table_get (interesting_tags, *followed);
215           if (!t)
216             continue;           /* ignore unknown --follow-tags entries. */
217           hash_table_put (intersect, *followed, t);
218         }
219       hash_table_destroy (interesting_tags);
220       interesting_tags = intersect;
221     }
222
223   /* Add the attributes we care about. */
224   interesting_attributes = make_nocase_string_hash_table (10);
225   for (i = 0; i < countof (additional_attributes); i++)
226     hash_table_put (interesting_attributes, additional_attributes[i], "1");
227   for (i = 0; i < countof (tag_url_attributes); i++)
228     hash_table_put (interesting_attributes,
229                     tag_url_attributes[i].attr_name, "1");
230 }
231
232 /* Find the value of attribute named NAME in the taginfo TAG.  If the
233    attribute is not present, return NULL.  If ATTRIND is non-NULL, the
234    index of the attribute in TAG will be stored there.  */
235
236 static char *
237 find_attr (struct taginfo *tag, const char *name, int *attrind)
238 {
239   int i;
240   for (i = 0; i < tag->nattrs; i++)
241     if (!strcasecmp (tag->attrs[i].name, name))
242       {
243         if (attrind)
244           *attrind = i;
245         return tag->attrs[i].value;
246       }
247   return NULL;
248 }
249
250 /* used for calls to append_url */
251 #define ATTR_POS(tag, attrind, ctx) \
252  (tag->attrs[attrind].value_raw_beginning - ctx->text)
253 #define ATTR_SIZE(tag, attrind) \
254  (tag->attrs[attrind].value_raw_size)
255
256 /* Append LINK_URI to the urlpos structure that is being built.
257
258    LINK_URI will be merged with the current document base.
259 */
260
261 struct urlpos *
262 append_url (const char *link_uri, int position, int size,
263             struct map_context *ctx)
264 {
265   int link_has_scheme = url_has_scheme (link_uri);
266   struct urlpos *newel;
267   const char *base = ctx->base ? ctx->base : ctx->parent_base;
268   struct url *url;
269
270   if (!base)
271     {
272       DEBUGP (("%s: no base, merge will use \"%s\".\n",
273                ctx->document_file, link_uri));
274
275       if (!link_has_scheme)
276         {
277           /* Base URL is unavailable, and the link does not have a
278              location attached to it -- we have to give up.  Since
279              this can only happen when using `--force-html -i', print
280              a warning.  */
281           logprintf (LOG_NOTQUIET,
282                      _("%s: Cannot resolve incomplete link %s.\n"),
283                      ctx->document_file, link_uri);
284           return NULL;
285         }
286
287       url = url_parse (link_uri, NULL);
288       if (!url)
289         {
290           DEBUGP (("%s: link \"%s\" doesn't parse.\n",
291                    ctx->document_file, link_uri));
292           return NULL;
293         }
294     }
295   else
296     {
297       /* Merge BASE with LINK_URI, but also make sure the result is
298          canonicalized, i.e. that "../" have been resolved.
299          (parse_url will do that for us.) */
300
301       char *complete_uri = uri_merge (base, link_uri);
302
303       DEBUGP (("%s: merge(\"%s\", \"%s\") -> %s\n",
304                ctx->document_file, base, link_uri, complete_uri));
305
306       url = url_parse (complete_uri, NULL);
307       if (!url)
308         {
309           DEBUGP (("%s: merged link \"%s\" doesn't parse.\n",
310                    ctx->document_file, complete_uri));
311           xfree (complete_uri);
312           return NULL;
313         }
314       xfree (complete_uri);
315     }
316
317   DEBUGP (("appending \"%s\" to urlpos.\n", url->url));
318
319   newel = xnew0 (struct urlpos);
320   newel->url = url;
321   newel->pos = position;
322   newel->size = size;
323
324   /* A URL is relative if the host is not named, and the name does not
325      start with `/'.  */
326   if (!link_has_scheme && *link_uri != '/')
327     newel->link_relative_p = 1;
328   else if (link_has_scheme)
329     newel->link_complete_p = 1;
330
331   if (ctx->tail)
332     {
333       ctx->tail->next = newel;
334       ctx->tail = newel;
335     }
336   else
337     ctx->tail = ctx->head = newel;
338
339   return newel;
340 }
341 \f
342 static void
343 check_style_attr (struct taginfo *tag, struct map_context *ctx)
344 {
345   int attrind;
346   char *style = find_attr (tag, "style", &attrind);
347   if (!style)
348     return;
349
350   /* raw pos and raw size include the quotes, hence the +1 -2 */
351   get_urls_css (ctx, ATTR_POS(tag,attrind,ctx)+1, ATTR_SIZE(tag,attrind)-2);
352 }
353
354 /* All the tag_* functions are called from collect_tags_mapper, as
355    specified by KNOWN_TAGS.  */
356
357 /* Default tag handler: collect URLs from attributes specified for
358    this tag by tag_url_attributes.  */
359
360 static void
361 tag_find_urls (int tagid, struct taginfo *tag, struct map_context *ctx)
362 {
363   int i, attrind;
364   int first = -1;
365
366   for (i = 0; i < countof (tag_url_attributes); i++)
367     if (tag_url_attributes[i].tagid == tagid)
368       {
369         /* We've found the index of tag_url_attributes where the
370            attributes of our tag begin.  */
371         first = i;
372         break;
373       }
374   assert (first != -1);
375
376   /* Loop over the "interesting" attributes of this tag.  In this
377      example, it will loop over "src" and "lowsrc".
378
379        <img src="foo.png" lowsrc="bar.png">
380
381      This has to be done in the outer loop so that the attributes are
382      processed in the same order in which they appear in the page.
383      This is required when converting links.  */
384
385   for (attrind = 0; attrind < tag->nattrs; attrind++)
386     {
387       /* Find whether TAG/ATTRIND is a combination that contains a
388          URL. */
389       char *link = tag->attrs[attrind].value;
390       const int size = countof (tag_url_attributes);
391
392       /* If you're cringing at the inefficiency of the nested loops,
393          remember that they both iterate over a very small number of
394          items.  The worst-case inner loop is for the IMG tag, which
395          has three attributes.  */
396       for (i = first; i < size && tag_url_attributes[i].tagid == tagid; i++)
397         {
398           if (0 == strcasecmp (tag->attrs[attrind].name,
399                                tag_url_attributes[i].attr_name))
400             {
401               struct urlpos *up = append_url (link, ATTR_POS(tag,attrind,ctx),
402                                               ATTR_SIZE(tag,attrind), ctx);
403               if (up)
404                 {
405                   int flags = tag_url_attributes[i].flags;
406                   if (flags & ATTR_INLINE)
407                     up->link_inline_p = 1;
408                   if (flags & ATTR_HTML)
409                     up->link_expect_html = 1;
410                 }
411             }
412         }
413     }
414 }
415
416 /* Handle the BASE tag, for <base href=...>. */
417
418 static void
419 tag_handle_base (int tagid, struct taginfo *tag, struct map_context *ctx)
420 {
421   struct urlpos *base_urlpos;
422   int attrind;
423   char *newbase = find_attr (tag, "href", &attrind);
424   if (!newbase)
425     return;
426
427   base_urlpos = append_url (newbase, ATTR_POS(tag,attrind,ctx),
428                             ATTR_SIZE(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
450   if (action)
451     {
452       struct urlpos *up = append_url (action, ATTR_POS(tag,attrind,ctx),
453                                       ATTR_SIZE(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, ATTR_POS(tag,attrind,ctx),
477                                       ATTR_SIZE(tag,attrind), ctx);
478       if (up)
479         {
480           char *rel = find_attr (tag, "rel", NULL);
481           if (rel)
482             {
483               if (0 == strcasecmp (rel, "stylesheet"))
484                 {
485                   up->link_inline_p = 1;
486                   up->link_expect_css = 1;
487                 }
488               else if (0 == strcasecmp (rel, "shortcut icon"))
489                 {
490                   up->link_inline_p = 1;
491                 }
492             }
493           else
494             /* The external ones usually point to HTML pages, such as
495                <link rel="next" href="..."> */
496             up->link_expect_html = 1;
497         }
498     }
499 }
500
501 /* Handle the META tag.  This requires special handling because of the
502    refresh feature and because of robot exclusion.  */
503
504 static void
505 tag_handle_meta (int tagid, struct taginfo *tag, struct map_context *ctx)
506 {
507   char *name = find_attr (tag, "name", NULL);
508   char *http_equiv = find_attr (tag, "http-equiv", NULL);
509
510   if (http_equiv && 0 == strcasecmp (http_equiv, "refresh"))
511     {
512       /* Some pages use a META tag to specify that the page be
513          refreshed by a new page after a given number of seconds.  The
514          general format for this is:
515
516            <meta http-equiv=Refresh content="NUMBER; URL=index2.html">
517
518          So we just need to skip past the "NUMBER; URL=" garbage to
519          get to the URL.  */
520
521       struct urlpos *entry;
522       int attrind;
523       int timeout = 0;
524       char *p;
525
526       char *refresh = find_attr (tag, "content", &attrind);
527       if (!refresh)
528         return;
529
530       for (p = refresh; c_isdigit (*p); p++)
531         timeout = 10 * timeout + *p - '0';
532       if (*p++ != ';')
533         return;
534
535       while (c_isspace (*p))
536         ++p;
537       if (!(   c_toupper (*p)       == 'U'
538             && c_toupper (*(p + 1)) == 'R'
539             && c_toupper (*(p + 2)) == 'L'
540             &&          *(p + 3)  == '='))
541         return;
542       p += 4;
543       while (c_isspace (*p))
544         ++p;
545
546       entry = append_url (p, ATTR_POS(tag,attrind,ctx),
547                           ATTR_SIZE(tag,attrind), ctx);
548       if (entry)
549         {
550           entry->link_refresh_p = 1;
551           entry->refresh_timeout = timeout;
552           entry->link_expect_html = 1;
553         }
554     }
555   else if (name && 0 == strcasecmp (name, "robots"))
556     {
557       /* Handle stuff like:
558          <meta name="robots" content="index,nofollow"> */
559       char *content = find_attr (tag, "content", NULL);
560       if (!content)
561         return;
562       if (!strcasecmp (content, "none"))
563         ctx->nofollow = true;
564       else
565         {
566           while (*content)
567             {
568               /* Find the next occurrence of ',' or the end of
569                  the string.  */
570               char *end = strchr (content, ',');
571               if (end)
572                 ++end;
573               else
574                 end = content + strlen (content);
575               if (!strncasecmp (content, "nofollow", end - content))
576                 ctx->nofollow = true;
577               content = end;
578             }
579         }
580     }
581 }
582
583 /* Dispatch the tag handler appropriate for the tag we're mapping
584    over.  See known_tags[] for definition of tag handlers.  */
585
586 static void
587 collect_tags_mapper (struct taginfo *tag, void *arg)
588 {
589   struct map_context *ctx = (struct map_context *)arg;
590
591   /* Find the tag in our table of tags.  This must not fail because
592      map_html_tags only returns tags found in interesting_tags.
593      
594      I've changed this for now, I'm passing NULL as interesting_tags
595      to map_html_tags.  This way we can check all tags for a style
596      attribute.
597   */
598   struct known_tag *t = hash_table_get (interesting_tags, tag->name);
599
600   if (t != NULL)
601     t->handler (t->tagid, tag, ctx);
602
603   check_style_attr (tag, ctx);
604
605   if (tag->end_tag_p && (0 == strcasecmp (tag->name, "style")) &&
606       tag->contents_begin && tag->contents_end)
607   {
608     /* parse contents */
609     get_urls_css (ctx, tag->contents_begin - ctx->text,
610                   tag->contents_end - tag->contents_begin);
611   }
612 }
613 \f
614 /* Analyze HTML tags FILE and construct a list of URLs referenced from
615    it.  It merges relative links in FILE with URL.  It is aware of
616    <base href=...> and does the right thing.  */
617
618 struct urlpos *
619 get_urls_html (const char *file, const char *url, bool *meta_disallow_follow)
620 {
621   struct file_memory *fm;
622   struct map_context ctx;
623   int flags;
624
625   /* Load the file. */
626   fm = read_file (file);
627   if (!fm)
628     {
629       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
630       return NULL;
631     }
632   DEBUGP (("Loaded %s (size %s).\n", file, number_to_static_string (fm->length)));
633
634   ctx.text = fm->content;
635   ctx.head = ctx.tail = NULL;
636   ctx.base = NULL;
637   ctx.parent_base = url ? url : opt.base_href;
638   ctx.document_file = file;
639   ctx.nofollow = false;
640
641   if (!interesting_tags)
642     init_interesting ();
643
644   /* Specify MHT_TRIM_VALUES because of buggy HTML generators that
645      generate <a href=" foo"> instead of <a href="foo"> (browsers
646      ignore spaces as well.)  If you really mean space, use &32; or
647      %20.  MHT_TRIM_VALUES also causes squashing of embedded newlines,
648      e.g. in <img src="foo.[newline]html">.  Such newlines are also
649      ignored by IE and Mozilla and are presumably introduced by
650      writing HTML with editors that force word wrap.  */
651   flags = MHT_TRIM_VALUES;
652   if (opt.strict_comments)
653     flags |= MHT_STRICT_COMMENTS;
654
655   /* the NULL here used to be interesting_tags */
656   map_html_tags (fm->content, fm->length, collect_tags_mapper, &ctx, flags,
657                  NULL, interesting_attributes);
658
659   DEBUGP (("no-follow in %s: %d\n", file, ctx.nofollow));
660   if (meta_disallow_follow)
661     *meta_disallow_follow = ctx.nofollow;
662
663   xfree_null (ctx.base);
664   read_file_free (fm);
665   return ctx.head;
666 }
667
668 /* This doesn't really have anything to do with HTML, but it's similar
669    to get_urls_html, so we put it here.  */
670
671 struct urlpos *
672 get_urls_file (const char *file)
673 {
674   struct file_memory *fm;
675   struct urlpos *head, *tail;
676   const char *text, *text_end;
677
678   /* Load the file.  */
679   fm = read_file (file);
680   if (!fm)
681     {
682       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
683       return NULL;
684     }
685   DEBUGP (("Loaded %s (size %s).\n", file, number_to_static_string (fm->length)));
686
687   head = tail = NULL;
688   text = fm->content;
689   text_end = fm->content + fm->length;
690   while (text < text_end)
691     {
692       int up_error_code;
693       char *url_text;
694       struct urlpos *entry;
695       struct url *url;
696
697       const char *line_beg = text;
698       const char *line_end = memchr (text, '\n', text_end - text);
699       if (!line_end)
700         line_end = text_end;
701       else
702         ++line_end;
703       text = line_end;
704
705       /* Strip whitespace from the beginning and end of line. */
706       while (line_beg < line_end && c_isspace (*line_beg))
707         ++line_beg;
708       while (line_end > line_beg && c_isspace (*(line_end - 1)))
709         --line_end;
710
711       if (line_beg == line_end)
712         continue;
713
714       /* The URL is in the [line_beg, line_end) region. */
715
716       /* We must copy the URL to a zero-terminated string, and we
717          can't use alloca because we're in a loop.  *sigh*.  */
718       url_text = strdupdelim (line_beg, line_end);
719
720       if (opt.base_href)
721         {
722           /* Merge opt.base_href with URL. */
723           char *merged = uri_merge (opt.base_href, url_text);
724           xfree (url_text);
725           url_text = merged;
726         }
727
728       url = url_parse (url_text, &up_error_code);
729       if (!url)
730         {
731           logprintf (LOG_NOTQUIET, _("%s: Invalid URL %s: %s\n"),
732                      file, url_text, url_error (up_error_code));
733           xfree (url_text);
734           continue;
735         }
736       xfree (url_text);
737
738       entry = xnew0 (struct urlpos);
739       entry->url = url;
740
741       if (!head)
742         head = entry;
743       else
744         tail->next = entry;
745       tail = entry;
746     }
747   read_file_free (fm);
748   return head;
749 }
750
751 void
752 cleanup_html_url (void)
753 {
754   /* Destroy the hash tables.  The hash table keys and values are not
755      allocated by this code, so we don't need to free them here.  */
756   if (interesting_tags)
757     hash_table_destroy (interesting_tags);
758   if (interesting_attributes)
759     hash_table_destroy (interesting_attributes);
760 }