]> sjero.net Git - wget/blob - src/html-url.c
[svn] Use "incomplete" rather than "relative".
[wget] / src / html-url.c
1 /* Collect URLs from HTML source.
2    Copyright (C) 1998, 2000, 2001 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 #include <config.h>
21
22 #include <stdio.h>
23 #ifdef HAVE_STRING_H
24 # include <string.h>
25 #else
26 # include <strings.h>
27 #endif
28 #include <stdlib.h>
29 #include <errno.h>
30 #include <assert.h>
31
32 #include "wget.h"
33 #include "html-parse.h"
34 #include "url.h"
35 #include "utils.h"
36
37 #ifndef errno
38 extern int errno;
39 #endif
40
41 struct map_context;
42
43 typedef void (*tag_handler_t) PARAMS ((int, struct taginfo *,
44                                        struct map_context *));
45
46 #define DECLARE_TAG_HANDLER(fun)                                        \
47   static void fun PARAMS ((int, struct taginfo *, struct map_context *))
48
49 DECLARE_TAG_HANDLER (tag_find_urls);
50 DECLARE_TAG_HANDLER (tag_handle_base);
51 DECLARE_TAG_HANDLER (tag_handle_link);
52 DECLARE_TAG_HANDLER (tag_handle_meta);
53
54 /* The list of known tags and functions used for handling them.  Most
55    tags are simply harvested for URLs. */
56 static struct {
57   const char *name;
58   tag_handler_t handler;
59 } known_tags[] = {
60 #define TAG_A           0
61   { "a",        tag_find_urls },
62 #define TAG_APPLET      1
63   { "applet",   tag_find_urls },
64 #define TAG_AREA        2
65   { "area",     tag_find_urls },
66 #define TAG_BASE        3
67   { "base",     tag_handle_base },
68 #define TAG_BGSOUND     4
69   { "bgsound",  tag_find_urls },
70 #define TAG_BODY        5
71   { "body",     tag_find_urls },
72 #define TAG_EMBED       6
73   { "embed",    tag_find_urls },
74 #define TAG_FIG         7
75   { "fig",      tag_find_urls },
76 #define TAG_FRAME       8
77   { "frame",    tag_find_urls },
78 #define TAG_IFRAME      9
79   { "iframe",   tag_find_urls },
80 #define TAG_IMG         10
81   { "img",      tag_find_urls },
82 #define TAG_INPUT       11
83   { "input",    tag_find_urls },
84 #define TAG_LAYER       12
85   { "layer",    tag_find_urls },
86 #define TAG_LINK        13
87   { "link",     tag_handle_link },
88 #define TAG_META        14
89   { "meta",     tag_handle_meta },
90 #define TAG_OVERLAY     15
91   { "overlay",  tag_find_urls },
92 #define TAG_SCRIPT      16
93   { "script",   tag_find_urls },
94 #define TAG_TABLE       17
95   { "table",    tag_find_urls },
96 #define TAG_TD          18
97   { "td",       tag_find_urls },
98 #define TAG_TH          19
99   { "th",       tag_find_urls }
100 };
101
102 /* tag_url_attributes documents which attributes of which tags contain
103    URLs to harvest.  It is used by tag_find_urls.  */
104
105 /* Defines for the FLAGS field; currently only one flag is defined. */
106
107 /* This tag points to an external document not necessary for rendering this 
108    document (i.e. it's not an inlined image, stylesheet, etc.). */
109 #define TUA_EXTERNAL 1
110
111 /* For tags handled by tag_find_urls: attributes that contain URLs to
112    download. */
113 static struct {
114   int tagid;
115   const char *attr_name;
116   int flags;
117 } tag_url_attributes[] = {
118   { TAG_A,              "href",         TUA_EXTERNAL },
119   { TAG_APPLET,         "code",         0 },
120   { TAG_AREA,           "href",         TUA_EXTERNAL },
121   { TAG_BGSOUND,        "src",          0 },
122   { TAG_BODY,           "background",   0 },
123   { TAG_EMBED,          "href",         0 },
124   { TAG_EMBED,          "src",          0 },
125   { TAG_FIG,            "src",          0 },
126   { TAG_FRAME,          "src",          0 },
127   { TAG_IFRAME,         "src",          0 },
128   { TAG_IMG,            "href",         0 },
129   { TAG_IMG,            "lowsrc",       0 },
130   { TAG_IMG,            "src",          0 },
131   { TAG_INPUT,          "src",          0 },
132   { TAG_LAYER,          "src",          0 },
133   { TAG_OVERLAY,        "src",          0 },
134   { TAG_SCRIPT,         "src",          0 },
135   { TAG_TABLE,          "background",   0 },
136   { TAG_TD,             "background",   0 },
137   { TAG_TH,             "background",   0 }
138 };
139
140 /* The lists of interesting tags and attributes are built dynamically,
141    from the information above.  However, some places in the code refer
142    to the attributes not mentioned here.  We add them manually.  */
143 static const char *additional_attributes[] = {
144   "rel",                        /* for TAG_LINK */
145   "http-equiv",                 /* for TAG_META */
146   "name",                       /* for TAG_META */
147   "content"                     /* for TAG_META */
148 };
149
150 static const char **interesting_tags;
151 static const char **interesting_attributes;
152
153 static void
154 init_interesting (void)
155 {
156   /* Init the variables interesting_tags and interesting_attributes
157      that are used by the HTML parser to know which tags and
158      attributes we're interested in.  We initialize this only once,
159      for performance reasons.
160
161      Here we also make sure that what we put in interesting_tags
162      matches the user's preferences as specified through --ignore-tags
163      and --follow-tags.
164
165      This function is as large as this only because of the glorious
166      expressivity of the C programming language.  */
167
168   {
169     int i, ind = 0;
170     int size = ARRAY_SIZE (known_tags);
171     interesting_tags = (const char **)xmalloc ((size + 1) * sizeof (char *));
172
173     for (i = 0; i < size; i++)
174       {
175         const char *name = known_tags[i].name;
176
177         /* Normally here we could say:
178            interesting_tags[i] = name;
179            But we need to respect the settings of --ignore-tags and
180            --follow-tags, so the code gets a bit hairier.  */
181
182         if (opt.ignore_tags)
183           {
184             /* --ignore-tags was specified.  Do not match these
185                specific tags.  --ignore-tags takes precedence over
186                --follow-tags, so we process --ignore first and fall
187                through if there's no match. */
188             int j, lose = 0;
189             for (j = 0; opt.ignore_tags[j] != NULL; j++)
190               /* Loop through all the tags this user doesn't care about. */
191               if (strcasecmp(opt.ignore_tags[j], name) == EQ)
192                 {
193                   lose = 1;
194                   break;
195                 }
196             if (lose)
197               continue;
198           }
199
200         if (opt.follow_tags)
201           {
202             /* --follow-tags was specified.  Only match these specific tags, so
203                continue back to top of for if we don't match one of them. */
204             int j, win = 0;
205             for (j = 0; opt.follow_tags[j] != NULL; j++)
206               /* Loop through all the tags this user cares about. */
207               if (strcasecmp(opt.follow_tags[j], name) == EQ)
208                 {
209                   win = 1;
210                   break;
211                 }
212             if (!win)
213               continue;  /* wasn't one of the explicitly desired tags */
214           }
215
216         /* If we get to here, --follow-tags isn't being used or the
217            tag is among the ones that are followed, and --ignore-tags,
218            if specified, didn't include this tag, so it's an
219            "interesting" one. */
220         interesting_tags[ind++] = name;
221       }
222     interesting_tags[ind] = NULL;
223   }
224
225   /* The same for attributes, except we loop through tag_url_attributes.
226      Here we also need to make sure that the list of attributes is
227      unique, and to include the attributes from additional_attributes.  */
228   {
229     int i, ind;
230     const char **att = xmalloc ((ARRAY_SIZE (additional_attributes) + 1)
231                                 * sizeof (char *));
232     /* First copy the "additional" attributes. */
233     for (i = 0; i < ARRAY_SIZE (additional_attributes); i++)
234       att[i] = additional_attributes[i];
235     ind = i;
236     att[ind] = NULL;
237     for (i = 0; i < ARRAY_SIZE (tag_url_attributes); i++)
238       {
239         int j, seen = 0;
240         const char *look_for = tag_url_attributes[i].attr_name;
241         for (j = 0; j < ind - 1; j++)
242           if (!strcmp (att[j], look_for))
243             {
244               seen = 1;
245               break;
246             }
247         if (!seen)
248           {
249             att = xrealloc (att, (ind + 2) * sizeof (*att));
250             att[ind++] = look_for;
251             att[ind] = NULL;
252           }
253       }
254     interesting_attributes = att;
255   }
256 }
257
258 static int
259 find_tag (const char *tag_name)
260 {
261   int i;
262
263   /* This is linear search; if the number of tags grow, we can switch
264      to binary search.  */
265
266   for (i = 0; i < ARRAY_SIZE (known_tags); i++)
267     {
268       int cmp = strcasecmp (known_tags[i].name, tag_name);
269       /* known_tags are sorted alphabetically, so we can
270          micro-optimize.  */
271       if (cmp > 0)
272         break;
273       else if (cmp == 0)
274         return i;
275     }
276   return -1;
277 }
278
279 /* Find the value of attribute named NAME in the taginfo TAG.  If the
280    attribute is not present, return NULL.  If ATTRIND is non-NULL, the
281    index of the attribute in TAG will be stored there.  */
282 static char *
283 find_attr (struct taginfo *tag, const char *name, int *attrind)
284 {
285   int i;
286   for (i = 0; i < tag->nattrs; i++)
287     if (!strcasecmp (tag->attrs[i].name, name))
288       {
289         if (attrind)
290           *attrind = i;
291         return tag->attrs[i].value;
292       }
293   return NULL;
294 }
295
296 struct map_context {
297   char *text;                   /* HTML text. */
298   char *base;                   /* Base URI of the document, possibly
299                                    changed through <base href=...>. */
300   const char *parent_base;      /* Base of the current document. */
301   const char *document_file;    /* File name of this document. */
302   int nofollow;                 /* whether NOFOLLOW was specified in a
303                                    <meta name=robots> tag. */
304
305   struct urlpos *head, *tail;   /* List of URLs that is being
306                                    built. */
307 };
308
309 /* Append LINK_URI to the urlpos structure that is being built.
310
311    LINK_URI will be merged with the current document base.  TAG and
312    ATTRIND are the necessary context to store the position and
313    size.  */
314
315 static struct urlpos *
316 append_one_url (const char *link_uri, int inlinep,
317                 struct taginfo *tag, int attrind, struct map_context *ctx)
318 {
319   int link_has_scheme = url_has_scheme (link_uri);
320   struct urlpos *newel;
321   const char *base = ctx->base ? ctx->base : ctx->parent_base;
322   struct url *url;
323
324   if (!base)
325     {
326       DEBUGP (("%s: no base, merge will use \"%s\".\n",
327                ctx->document_file, link_uri));
328
329       if (!link_has_scheme)
330         {
331           /* Base URL is unavailable, and the link does not have a
332              location attached to it -- we have to give up.  Since
333              this can only happen when using `--force-html -i', print
334              a warning.  */
335           logprintf (LOG_NOTQUIET,
336                      _("%s: Cannot resolve incomplete link %s.\n"),
337                      ctx->document_file, link_uri);
338           return NULL;
339         }
340
341       url = url_parse (link_uri, NULL);
342       if (!url)
343         {
344           DEBUGP (("%s: link \"%s\" doesn't parse.\n",
345                    ctx->document_file, link_uri));
346           return NULL;
347         }
348     }
349   else
350     {
351       /* Merge BASE with LINK_URI, but also make sure the result is
352          canonicalized, i.e. that "../" have been resolved.
353          (parse_url will do that for us.) */
354
355       char *complete_uri = uri_merge (base, link_uri);
356
357       DEBUGP (("%s: merge(\"%s\", \"%s\") -> %s\n",
358                ctx->document_file, base, link_uri, complete_uri));
359
360       url = url_parse (complete_uri, NULL);
361       if (!url)
362         {
363           DEBUGP (("%s: merged link \"%s\" doesn't parse.\n",
364                    ctx->document_file, complete_uri));
365           xfree (complete_uri);
366           return NULL;
367         }
368       xfree (complete_uri);
369     }
370
371   DEBUGP (("appending \"%s\" to urlpos.\n", url->url));
372
373   newel = (struct urlpos *)xmalloc (sizeof (struct urlpos));
374   memset (newel, 0, sizeof (*newel));
375
376   newel->next = NULL;
377   newel->url = url;
378   newel->pos = tag->attrs[attrind].value_raw_beginning - ctx->text;
379   newel->size = tag->attrs[attrind].value_raw_size;
380   newel->link_inline_p = inlinep;
381
382   /* A URL is relative if the host is not named, and the name does not
383      start with `/'.  */
384   if (!link_has_scheme && *link_uri != '/')
385     newel->link_relative_p = 1;
386   else if (link_has_scheme)
387     newel->link_complete_p = 1;
388
389   if (ctx->tail)
390     {
391       ctx->tail->next = newel;
392       ctx->tail = newel;
393     }
394   else
395     ctx->tail = ctx->head = newel;
396
397   return newel;
398 }
399 \f
400 /* All the tag_* functions are called from collect_tags_mapper, as
401    specified by KNOWN_TAGS.  */
402
403 /* Default tag handler: collect URLs from attributes specified for
404    this tag by tag_url_attributes.  */
405
406 static void
407 tag_find_urls (int tagid, struct taginfo *tag, struct map_context *ctx)
408 {
409   int i, attrind, first = -1;
410   int size = ARRAY_SIZE (tag_url_attributes);
411
412   for (i = 0; i < size; i++)
413     if (tag_url_attributes[i].tagid == tagid)
414       {
415         /* We've found the index of tag_url_attributes where the
416            attributes of our tag begin.  */
417         first = i;
418         break;
419       }
420   assert (first != -1);
421
422   /* Loop over the "interesting" attributes of this tag.  In this
423      example, it will loop over "src" and "lowsrc".
424
425        <img src="foo.png" lowsrc="bar.png">
426
427      This has to be done in the outer loop so that the attributes are
428      processed in the same order in which they appear in the page.
429      This is required when converting links.  */
430
431   for (attrind = 0; attrind < tag->nattrs; attrind++)
432     {
433       /* Find whether TAG/ATTRIND is a combination that contains a
434          URL. */
435       char *link = tag->attrs[attrind].value;
436
437       /* If you're cringing at the inefficiency of the nested loops,
438          remember that they both iterate over a laughably small
439          quantity of items.  The worst-case inner loop is for the IMG
440          tag, which has three attributes.  */
441       for (i = first; i < size && tag_url_attributes[i].tagid == tagid; i++)
442         {
443           if (0 == strcasecmp (tag->attrs[attrind].name,
444                                tag_url_attributes[i].attr_name))
445             {
446               int flags = tag_url_attributes[i].flags;
447               append_one_url (link, !(flags & TUA_EXTERNAL), tag, attrind, ctx);
448             }
449         }
450     }
451 }
452
453 /* Handle the BASE tag, for <base href=...>. */
454
455 static void
456 tag_handle_base (int tagid, struct taginfo *tag, struct map_context *ctx)
457 {
458   struct urlpos *base_urlpos;
459   int attrind;
460   char *newbase = find_attr (tag, "href", &attrind);
461   if (!newbase)
462     return;
463
464   base_urlpos = append_one_url (newbase, 0, tag, attrind, ctx);
465   if (!base_urlpos)
466     return;
467   base_urlpos->ignore_when_downloading = 1;
468   base_urlpos->link_base_p = 1;
469
470   if (ctx->base)
471     xfree (ctx->base);
472   if (ctx->parent_base)
473     ctx->base = uri_merge (ctx->parent_base, newbase);
474   else
475     ctx->base = xstrdup (newbase);
476 }
477
478 /* Handle the LINK tag.  It requires special handling because how its
479    links will be followed in -p mode depends on the REL attribute.  */
480
481 static void
482 tag_handle_link (int tagid, struct taginfo *tag, struct map_context *ctx)
483 {
484   int attrind;
485   char *href = find_attr (tag, "href", &attrind);
486
487   /* All <link href="..."> link references are external,
488      except for <link rel="stylesheet" href="...">.  */
489   if (href)
490     {
491       char *rel  = find_attr (tag, "rel", NULL);
492       int inlinep = (rel && 0 == strcasecmp (rel, "stylesheet"));
493       append_one_url (href, inlinep, tag, attrind, ctx);
494     }
495 }
496
497 /* Handle the META tag.  This requires special handling because of the
498    refresh feature and because of robot exclusion.  */
499
500 static void
501 tag_handle_meta (int tagid, struct taginfo *tag, struct map_context *ctx)
502 {
503   char *name = find_attr (tag, "name", NULL);
504   char *http_equiv = find_attr (tag, "http-equiv", NULL);
505
506   if (http_equiv && 0 == strcasecmp (http_equiv, "refresh"))
507     {
508       /* Some pages use a META tag to specify that the page be
509          refreshed by a new page after a given number of seconds.  The
510          general format for this is:
511
512            <meta http-equiv=Refresh content="NUMBER; URL=index2.html">
513
514          So we just need to skip past the "NUMBER; URL=" garbage to
515          get to the URL.  */
516
517       struct urlpos *entry;
518
519       int attrind;
520       char *p, *refresh = find_attr (tag, "content", &attrind);
521       int timeout = 0;
522
523       for (p = refresh; ISDIGIT (*p); p++)
524         timeout = 10 * timeout + *p - '0';
525       if (*p++ != ';')
526         return;
527
528       while (ISSPACE (*p))
529         ++p;
530       if (!(   TOUPPER (*p)       == 'U'
531             && TOUPPER (*(p + 1)) == 'R'
532             && TOUPPER (*(p + 2)) == 'L'
533             &&          *(p + 3)  == '='))
534         return;
535       p += 4;
536       while (ISSPACE (*p))
537         ++p;
538
539       entry = append_one_url (p, 0, tag, attrind, ctx);
540       if (entry)
541         {
542           entry->link_refresh_p = 1;
543           entry->refresh_timeout = timeout;
544         }
545     }
546   else if (name && 0 == strcasecmp (name, "robots"))
547     {
548       /* Handle stuff like:
549          <meta name="robots" content="index,nofollow"> */
550       char *content = find_attr (tag, "content", NULL);
551       if (!content)
552         return;
553       if (!strcasecmp (content, "none"))
554         ctx->nofollow = 1;
555       else
556         {
557           while (*content)
558             {
559               /* Find the next occurrence of ',' or the end of
560                  the string.  */
561               char *end = strchr (content, ',');
562               if (end)
563                 ++end;
564               else
565                 end = content + strlen (content);
566               if (!strncasecmp (content, "nofollow", end - content))
567                 ctx->nofollow = 1;
568               content = end;
569             }
570         }
571     }
572 }
573
574 /* Examine name and attributes of TAG and take appropriate action
575    according to the tag.  */
576
577 static void
578 collect_tags_mapper (struct taginfo *tag, void *arg)
579 {
580   struct map_context *ctx = (struct map_context *)arg;
581   int tagid;
582   tag_handler_t handler;
583
584   tagid = find_tag (tag->name);
585   assert (tagid != -1);
586   handler = known_tags[tagid].handler;
587
588   handler (tagid, tag, ctx);
589 }
590 \f
591 /* Analyze HTML tags FILE and construct a list of URLs referenced from
592    it.  It merges relative links in FILE with URL.  It is aware of
593    <base href=...> and does the right thing.  */
594 struct urlpos *
595 get_urls_html (const char *file, const char *url, int *meta_disallow_follow)
596 {
597   struct file_memory *fm;
598   struct map_context ctx;
599
600   /* Load the file. */
601   fm = read_file (file);
602   if (!fm)
603     {
604       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
605       return NULL;
606     }
607   DEBUGP (("Loaded %s (size %ld).\n", file, fm->length));
608
609   ctx.text = fm->content;
610   ctx.head = ctx.tail = NULL;
611   ctx.base = NULL;
612   ctx.parent_base = url ? url : opt.base_href;
613   ctx.document_file = file;
614   ctx.nofollow = 0;
615
616   if (!interesting_tags)
617     init_interesting ();
618
619   map_html_tags (fm->content, fm->length, interesting_tags,
620                  interesting_attributes, collect_tags_mapper, &ctx);
621
622   DEBUGP (("no-follow in %s: %d\n", file, ctx.nofollow));
623   if (meta_disallow_follow)
624     *meta_disallow_follow = ctx.nofollow;
625
626   FREE_MAYBE (ctx.base);
627   read_file_free (fm);
628   return ctx.head;
629 }
630
631 void
632 cleanup_html_url (void)
633 {
634   FREE_MAYBE (interesting_tags);
635   FREE_MAYBE (interesting_attributes);
636 }