]> sjero.net Git - wget/blob - src/html-url.c
[svn] html-url.c: A bunch of fixup of `--page-requisites'-related comments to reflect
[wget] / src / html-url.c
1 /* Collect URLs from HTML source.
2    Copyright (C) 1998, 2000 Free Software Foundation, Inc.
3
4 This file is part of Wget.
5
6 This program 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 This program 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 this program; 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 <ctype.h>
30 #include <errno.h>
31 #include <assert.h>
32
33 #include "wget.h"
34 #include "html-parse.h"
35 #include "url.h"
36 #include "utils.h"
37
38 #ifndef errno
39 extern int errno;
40 #endif
41
42 enum tag_category { TC_LINK, TC_SPEC };
43
44 /* Here we try to categorize the known tags.  Each tag has its ID and
45    cetegory.  Category TC_LINK means that one or more of its
46    attributes contain links that should be retrieved.  TC_SPEC means
47    that the tag is specific in some way, and has to be handled
48    specially. */
49 static struct {
50   const char *name;
51   enum tag_category category;
52 } known_tags[] = {
53 #define TAG_A           0
54   { "a",        TC_LINK },
55 #define TAG_APPLET      1
56   { "applet",   TC_LINK },
57 #define TAG_AREA        2
58   { "area",     TC_LINK },
59 #define TAG_BASE        3
60   { "base",     TC_SPEC },
61 #define TAG_BGSOUND     4
62   { "bgsound",  TC_LINK },
63 #define TAG_BODY        5
64   { "body",     TC_LINK },
65 #define TAG_EMBED       6
66   { "embed",    TC_LINK },
67 #define TAG_FIG         7
68   { "fig",      TC_LINK },
69 #define TAG_FRAME       8
70   { "frame",    TC_LINK },
71 #define TAG_IFRAME      9
72   { "iframe",   TC_LINK },
73 #define TAG_IMG         10
74   { "img",      TC_LINK },
75 #define TAG_INPUT       11
76   { "input",    TC_LINK },
77 #define TAG_LAYER       12
78   { "layer",    TC_LINK },
79 #define TAG_LINK        13
80   { "link",     TC_SPEC },
81 #define TAG_META        14
82   { "meta",     TC_SPEC },
83 #define TAG_OVERLAY     15
84   { "overlay",  TC_LINK },
85 #define TAG_SCRIPT      16
86   { "script",   TC_LINK },
87 #define TAG_TABLE       17
88   { "table",    TC_LINK },
89 #define TAG_TD          18
90   { "td",       TC_LINK },
91 #define TAG_TH          19
92   { "th",       TC_LINK }
93 };
94
95
96 /* Flags for specific url-attr pairs handled through TC_LINK: */
97
98 /* This tag points to an external document not necessary for rendering this 
99    document (i.e. it's not an inlined image, stylesheet, etc.). */
100 #define AF_EXTERNAL 1
101
102
103 /* For tags handled by TC_LINK: attributes that contain URLs to
104    download. */
105 static struct {
106   int tagid;
107   const char *attr_name;
108   int flags;
109 } url_tag_attr_map[] = {
110   { TAG_A,              "href",         AF_EXTERNAL },
111   { TAG_APPLET,         "code",         0 },
112   { TAG_AREA,           "href",         AF_EXTERNAL },
113   { TAG_BGSOUND,        "src",          0 },
114   { TAG_BODY,           "background",   0 },
115   { TAG_EMBED,          "src",          0 },
116   { TAG_FIG,            "src",          0 },
117   { TAG_FRAME,          "src",          0 },
118   { TAG_IFRAME,         "src",          0 },
119   { TAG_IMG,            "href",         0 },
120   { TAG_IMG,            "lowsrc",       0 },
121   { TAG_IMG,            "src",          0 },
122   { TAG_INPUT,          "src",          0 },
123   { TAG_LAYER,          "src",          0 },
124   { TAG_OVERLAY,        "src",          0 },
125   { TAG_SCRIPT,         "src",          0 },
126   { TAG_TABLE,          "background",   0 },
127   { TAG_TD,             "background",   0 },
128   { TAG_TH,             "background",   0 }
129 };
130
131 /* The lists of interesting tags and attributes are built dynamically,
132    from the information above.  However, some places in the code refer
133    to the attributes not mentioned here.  We add them manually.  */
134 static const char *additional_attributes[] = {
135   "rel",                        /* for TAG_LINK */
136   "http-equiv",                 /* for TAG_META */
137   "name",                       /* for TAG_META */
138   "content"                     /* for TAG_META */
139 };
140
141 static const char **interesting_tags;
142 static const char **interesting_attributes;
143
144 void
145 init_interesting (void)
146 {
147   /* Init the variables interesting_tags and interesting_attributes
148      that are used by the HTML parser to know which tags and
149      attributes we're interested in.  We initialize this only once,
150      for performance reasons.
151
152      Here we also make sure that what we put in interesting_tags
153      matches the user's preferences as specified through --ignore-tags
154      and --follow-tags.  */
155
156   {
157     int i, ind = 0;
158     int size = ARRAY_SIZE (known_tags);
159     interesting_tags = (const char **)xmalloc ((size + 1) * sizeof (char *));
160
161     for (i = 0; i < size; i++)
162       {
163         const char *name = known_tags[i].name;
164
165         /* Normally here we could say:
166            interesting_tags[i] = name;
167            But we need to respect the settings of --ignore-tags and
168            --follow-tags, so the code gets a bit hairier.  */
169
170         if (opt.ignore_tags)
171           {
172             /* --ignore-tags was specified.  Do not match these
173                specific tags.  --ignore-tags takes precedence over
174                --follow-tags, so we process --ignore first and fall
175                through if there's no match. */
176             int j, lose = 0;
177             for (j = 0; opt.ignore_tags[j] != NULL; j++)
178               /* Loop through all the tags this user doesn't care about. */
179               if (strcasecmp(opt.ignore_tags[j], name) == EQ)
180                 {
181                   lose = 1;
182                   break;
183                 }
184             if (lose)
185               continue;
186           }
187
188         if (opt.follow_tags)
189           {
190             /* --follow-tags was specified.  Only match these specific tags, so
191                continue back to top of for if we don't match one of them. */
192             int j, win = 0;
193             for (j = 0; opt.follow_tags[j] != NULL; j++)
194               /* Loop through all the tags this user cares about. */
195               if (strcasecmp(opt.follow_tags[j], name) == EQ)
196                 {
197                   win = 1;
198                   break;
199                 }
200             if (!win)
201               continue;  /* wasn't one of the explicitly desired tags */
202           }
203
204         /* If we get to here, --follow-tags isn't being used or the
205            tag is among the ones that are followed, and --ignore-tags,
206            if specified, didn't include this tag, so it's an
207            "interesting" one. */
208         interesting_tags[ind++] = name;
209       }
210     interesting_tags[ind] = NULL;
211   }
212
213   /* The same for attributes, except we loop through url_tag_attr_map.
214      Here we also need to make sure that the list of attributes is
215      unique, and to include the attributes from additional_attributes.  */
216   {
217     int i, ind;
218     const char **att = xmalloc ((ARRAY_SIZE (additional_attributes) + 1)
219                                 * sizeof (char *));
220     /* First copy the "additional" attributes. */
221     for (i = 0; i < ARRAY_SIZE (additional_attributes); i++)
222       att[i] = additional_attributes[i];
223     ind = i;
224     att[ind] = NULL;
225     for (i = 0; i < ARRAY_SIZE (url_tag_attr_map); i++)
226       {
227         int j, seen = 0;
228         const char *look_for = url_tag_attr_map[i].attr_name;
229         for (j = 0; j < ind - 1; j++)
230           if (!strcmp (att[j], look_for))
231             {
232               seen = 1;
233               break;
234             }
235         if (!seen)
236           {
237             att = xrealloc (att, (ind + 2) * sizeof (*att));
238             att[ind++] = look_for;
239             att[ind] = NULL;
240           }
241       }
242     interesting_attributes = att;
243   }
244 }
245
246 static int
247 find_tag (const char *tag_name)
248 {
249   int i;
250
251   /* This is linear search; if the number of tags grow, we can switch
252      to binary search.  */
253
254   for (i = 0; i < ARRAY_SIZE (known_tags); i++)
255     {
256       int cmp = strcasecmp (known_tags[i].name, tag_name);
257       /* known_tags are sorted alphabetically, so we can
258          micro-optimize.  */
259       if (cmp > 0)
260         break;
261       else if (cmp == 0)
262         return i;
263     }
264   return -1;
265 }
266
267 /* Find the value of attribute named NAME in the taginfo TAG.  If the
268    attribute is not present, return NULL.  If ATTRID is non-NULL, the
269    exact identity of the attribute will be returned.  */
270 static char *
271 find_attr (struct taginfo *tag, const char *name, int *attrid)
272 {
273   int i;
274   for (i = 0; i < tag->nattrs; i++)
275     if (!strcasecmp (tag->attrs[i].name, name))
276       {
277         if (attrid)
278           *attrid = i;
279         return tag->attrs[i].value;
280       }
281   return NULL;
282 }
283
284 struct collect_urls_closure {
285   char *text;                   /* HTML text. */
286   char *base;                   /* Base URI of the document, possibly
287                                    changed through <base href=...>. */
288   urlpos *head, *tail;          /* List of URLs */
289   const char *parent_base;      /* Base of the current document. */
290   const char *document_file;    /* File name of this document. */
291   int dash_p_leaf_HTML;         /* Whether -p is specified, and this
292                                    document is the "leaf" node of the
293                                    HTML tree. */
294   int nofollow;                 /* whether NOFOLLOW was specified in a
295                                    <meta name=robots> tag. */
296 };
297
298 /* Resolve LINK_URI and append it to closure->tail.  TAG and ATTRID
299    are the necessary context to store the position and size.  */
300
301 static void
302 handle_link (struct collect_urls_closure *closure, const char *link_uri,
303              struct taginfo *tag, int attrid)
304 {
305   int no_proto = !has_proto (link_uri);
306   urlpos *newel;
307
308   const char *base = closure->base ? closure->base : closure->parent_base;
309   char *complete_uri;
310
311   char *fragment = strrchr (link_uri, '#');
312
313   if (fragment)
314     {
315       /* Nullify the fragment identifier, i.e. everything after the
316          last occurrence of `#', inclusive.  This copying is
317          relatively inefficient, but it doesn't matter because
318          fragment identifiers don't come up all that often.  */
319       int hashlen = fragment - link_uri;
320       char *p = alloca (hashlen + 1);
321       memcpy (p, link_uri, hashlen);
322       p[hashlen] = '\0';
323       link_uri = p;
324     }
325
326   if (!base)
327     {
328       if (no_proto)
329         {
330           /* We have no base, and the link does not have a protocol or
331              a host attached to it.  Nothing we can do.  */
332           /* #### Should we print a warning here?  Wget 1.5.x used to.  */
333           return;
334         }
335       else
336         complete_uri = xstrdup (link_uri);
337     }
338   else
339     complete_uri = url_concat (base, link_uri);
340
341   DEBUGP (("%s: merge(\"%s\", \"%s\") -> %s\n",
342            closure->document_file, base ? base : "(null)",
343            link_uri, complete_uri));
344
345   newel = (urlpos *)xmalloc (sizeof (urlpos));
346
347   memset (newel, 0, sizeof (*newel));
348   newel->next = NULL;
349   newel->url = complete_uri;
350   newel->pos = tag->attrs[attrid].value_raw_beginning - closure->text;
351   newel->size = tag->attrs[attrid].value_raw_size;
352
353   /* A URL is relative if the host and protocol are not named, and the
354      name does not start with `/'.  */
355   if (no_proto && *link_uri != '/')
356     newel->link_relative_p = 1;
357   else if (!no_proto)
358     newel->link_complete_p = 1;
359
360   if (closure->tail)
361     {
362       closure->tail->next = newel;
363       closure->tail = newel;
364     }
365   else
366     closure->tail = closure->head = newel;
367 }
368
369 /* #### Document what this does.
370    #### It would be nice to split this into several functions.  */
371
372 static void
373 collect_tags_mapper (struct taginfo *tag, void *arg)
374 {
375   struct collect_urls_closure *closure = (struct collect_urls_closure *)arg;
376   int tagid = find_tag (tag->name);
377   assert (tagid != -1);
378
379   switch (known_tags[tagid].category)
380     {
381     case TC_LINK:
382       {
383         int i;
384         int size = ARRAY_SIZE (url_tag_attr_map);
385         for (i = 0; i < size; i++)
386           if (url_tag_attr_map[i].tagid == tagid)
387             break;
388         /* We've found the index of url_tag_attr_map where the
389            attributes of our tags begin.  Now, look for every one of
390            them, and handle it.  */
391         for (; (i < size && url_tag_attr_map[i].tagid == tagid); i++)
392           {
393             char *attr_value;
394             int id;
395             if (closure->dash_p_leaf_HTML
396                 && (url_tag_attr_map[i].flags & AF_EXTERNAL))
397               /* If we're at a -p leaf node, we don't want to retrieve
398                  links to references we know are external to this document,
399                  such as <a href=...>.  */
400               continue;
401
402             /* This find_attr() buried in a loop may seem inefficient
403                (O(n^2)), but it's not, since the number of attributes
404                (n) we loop over is extremely small.  In the worst case
405                of IMG with all its possible attributes, n^2 will be
406                only 9.  */
407             attr_value = find_attr (tag, url_tag_attr_map[i].attr_name, &id);
408             if (attr_value)
409               handle_link (closure, attr_value, tag, id);
410           }
411       }
412       break;
413     case TC_SPEC:
414       switch (tagid)
415         {
416         case TAG_BASE:
417           {
418             char *newbase = find_attr (tag, "href", NULL);
419             if (!newbase)
420               break;
421             if (closure->base)
422               xfree (closure->base);
423             if (closure->parent_base)
424               closure->base = url_concat (closure->parent_base, newbase);
425             else
426               closure->base = xstrdup (newbase);
427           }
428           break;
429         case TAG_LINK:
430           {
431             int id;
432             char *rel  = find_attr (tag, "rel", NULL);
433             char *href = find_attr (tag, "href", &id);
434             if (href)
435               {
436                 /* In the normal case, all <link href=...> tags are
437                    fair game.
438
439                    In the special case of when -p is active, however,
440                    and we're at a leaf node (relative to the -l
441                    max. depth) in the HTML document tree, the only
442                    <LINK> tag we'll follow is a <LINK REL=
443                    "stylesheet">, as it'll be necessary for displaying
444                    this document properly.  We won't follow other
445                    <LINK> tags, like <LINK REL="home">, for instance,
446                    as they refer to external documents.  */
447                 if (!closure->dash_p_leaf_HTML
448                     || (rel && !strcasecmp (rel, "stylesheet")))
449                   handle_link (closure, href, tag, id);
450               }
451           }
452           break;
453         case TAG_META:
454           /* Some pages use a META tag to specify that the page be
455              refreshed by a new page after a given number of seconds.
456              The general format for this is:
457
458              <meta http-equiv=Refresh content="NUMBER; URL=index2.html">
459
460              So we just need to skip past the "NUMBER; URL=" garbage
461              to get to the URL.  */
462           {
463             int id;
464             char *name = find_attr (tag, "name", NULL);
465             char *http_equiv = find_attr (tag, "http-equiv", &id);
466             if (http_equiv && !strcasecmp (http_equiv, "refresh"))
467               {
468                 char *refresh = find_attr (tag, "content", NULL);
469                 char *p = refresh;
470                 int offset;
471                 while (ISDIGIT (*p))
472                   ++p;
473                 if (*p++ != ';')
474                   return;
475                 while (ISSPACE (*p))
476                   ++p;
477                 if (!(TOUPPER (*p) == 'U'
478                       && TOUPPER (*(p + 1)) == 'R'
479                       && TOUPPER (*(p + 2)) == 'L'
480                       && *(p + 3) == '='))
481                   return;
482                 p += 4;
483                 while (ISSPACE (*p))
484                   ++p;
485                 offset = p - refresh;
486                 tag->attrs[id].value_raw_beginning += offset;
487                 tag->attrs[id].value_raw_size -= offset;
488                 handle_link (closure, p, tag, id);
489               }
490             else if (name && !strcasecmp (name, "robots"))
491               {
492                 /* Handle stuff like:
493                    <meta name="robots" content="index,nofollow"> */
494                 char *content = find_attr (tag, "content", NULL);
495                 if (!content)
496                   return;
497                 if (!strcasecmp (content, "none"))
498                   closure->nofollow = 1;
499                 else
500                   {
501                     while (*content)
502                       {
503                         /* Find the next occurrence of ',' or the end of
504                            the string.  */
505                         char *end = strchr (content, ',');
506                         if (end)
507                           ++end;
508                         else
509                           end = content + strlen (content);
510                         if (!strncasecmp (content, "nofollow", end - content))
511                           closure->nofollow = 1;
512                         content = end;
513                       }
514                   }
515               }
516           }
517           break;
518         default:
519           /* Category is TC_SPEC, but tag name is unhandled.  This
520              must not be.  */
521           abort ();
522         }
523       break;
524     }
525 }
526
527 /* Scan FILE, retrieving links to HTML documents from it.  Each link is 
528
529   Similar to get_urls_file, but for HTML files.  FILE is scanned as
530    an HTML document.  get_urls_html() constructs the URLs from the
531    relative href-s.
532
533    If SILENT is non-zero, do not barf on baseless relative links.  */
534 urlpos *
535 get_urls_html (const char *file, const char *this_url, int dash_p_leaf_HTML,
536                int *meta_disallow_follow)
537 {
538   struct file_memory *fm;
539   struct collect_urls_closure closure;
540
541   /* Load the file. */
542   fm = read_file (file);
543   if (!fm)
544     {
545       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
546       return NULL;
547     }
548   DEBUGP (("Loaded %s (size %ld).\n", file, fm->length));
549
550   closure.text = fm->content;
551   closure.head = closure.tail = NULL;
552   closure.base = NULL;
553   closure.parent_base = this_url ? this_url : opt.base_href;
554   closure.document_file = file;
555   closure.dash_p_leaf_HTML = dash_p_leaf_HTML;
556   closure.nofollow = 0;
557
558   if (!interesting_tags)
559     init_interesting ();
560
561   map_html_tags (fm->content, fm->length, interesting_tags,
562                  interesting_attributes, collect_tags_mapper, &closure);
563
564   DEBUGP (("no-follow in %s: %d\n", file, closure.nofollow));
565   if (meta_disallow_follow)
566     *meta_disallow_follow = closure.nofollow;
567
568   FREE_MAYBE (closure.base);
569   read_file_free (fm);
570   return closure.head;
571 }
572
573 void
574 cleanup_html_url (void)
575 {
576   FREE_MAYBE (interesting_tags);
577   FREE_MAYBE (interesting_attributes);
578 }