]> sjero.net Git - wget/blob - src/recur.c
[svn] * *.{gmo,po,pot}: Regenerated after modifying wget --help output.
[wget] / src / recur.c
1 /* Handling of recursive HTTP retrieving.
2    Copyright (C) 1995, 1996, 1997 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 #include <stdlib.h>
24 #ifdef HAVE_STRING_H
25 # include <string.h>
26 #else
27 # include <strings.h>
28 #endif /* HAVE_STRING_H */
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif /* HAVE_UNISTD_H */
32 #include <errno.h>
33 #include <assert.h>
34 #include <ctype.h>
35 #include <sys/types.h>
36
37 #include "wget.h"
38 #include "url.h"
39 #include "recur.h"
40 #include "utils.h"
41 #include "retr.h"
42 #include "ftp.h"
43 #include "fnmatch.h"
44 #include "host.h"
45
46 extern char *version_string;
47
48 #define ROBOTS_FILENAME "robots.txt"
49
50 /* #### Many of these lists should really be hashtables!  */
51
52 /* List of downloaded URLs.  */
53 static urlpos *urls_downloaded;
54
55 /* List of HTML URLs.  */
56 static slist *urls_html;
57
58 /* List of undesirable-to-load URLs.  */
59 static slist *ulist;
60
61 /* List of forbidden locations.  */
62 static char **forbidden = NULL;
63
64 /* Current recursion depth.  */
65 static int depth;
66
67 /* Base directory we're recursing from (used by no_parent).  */
68 static char *base_dir;
69
70 /* The host name for which we last checked robots.  */
71 static char *robots_host;
72
73 static int first_time = 1;
74
75 /* Construct the robots URL.  */
76 static struct urlinfo *robots_url PARAMS ((const char *, const char *));
77 static uerr_t retrieve_robots PARAMS ((const char *, const char *));
78 static char **parse_robots PARAMS ((const char *));
79 static int robots_match PARAMS ((struct urlinfo *, char **));
80
81
82 /* Cleanup the data structures associated with recursive retrieving
83    (the variables above).  */
84 void
85 recursive_cleanup (void)
86 {
87   free_slist (ulist);
88   ulist = NULL;
89   free_vec (forbidden);
90   forbidden = NULL;
91   free_slist (urls_html);
92   urls_html = NULL;
93   free_urlpos (urls_downloaded);
94   urls_downloaded = NULL;
95   FREE_MAYBE (base_dir);
96   FREE_MAYBE (robots_host);
97   first_time = 1;
98 }
99
100 /* Reset FIRST_TIME to 1, so that some action can be taken in
101    recursive_retrieve().  */
102 void
103 recursive_reset (void)
104 {
105   first_time = 1;
106 }
107
108 /* The core of recursive retrieving.  Endless recursion is avoided by
109    having all URLs stored to a linked list of URLs, which is checked
110    before loading any URL.  That way no URL can get loaded twice.
111
112    The function also supports specification of maximum recursion depth
113    and a number of other goodies.  */
114 uerr_t
115 recursive_retrieve (const char *file, const char *this_url)
116 {
117   char *constr, *filename, *newloc;
118   char *canon_this_url = NULL;
119   int dt, inl, dash_p_leaf_HTML = FALSE;
120   int this_url_ftp;            /* See below the explanation */
121   uerr_t err;
122   struct urlinfo *rurl;
123   urlpos *url_list, *cur_url;
124   char *rfile; /* For robots */
125   struct urlinfo *u;
126
127   assert (this_url != NULL);
128   assert (file != NULL);
129   /* If quota was exceeded earlier, bail out.  */
130   if (opt.quota && (opt.downloaded > opt.quota))
131     return QUOTEXC;
132   /* Cache the current URL in the list.  */
133   if (first_time)
134     {
135       ulist = add_slist (ulist, this_url, 0);
136       urls_downloaded = NULL;
137       urls_html = NULL;
138       /* Enter this_url to the slist, in original and "enhanced" form.  */
139       u = newurl ();
140       err = parseurl (this_url, u, 0);
141       if (err == URLOK)
142         {
143           ulist = add_slist (ulist, u->url, 0);
144           urls_downloaded = add_url (urls_downloaded, u->url, file);
145           urls_html = add_slist (urls_html, file, NOSORT);
146           if (opt.no_parent)
147             base_dir = xstrdup (u->dir); /* Set the base dir.  */
148           /* Set the canonical this_url to be sent as referer.  This
149              problem exists only when running the first time.  */
150           canon_this_url = xstrdup (u->url);
151         }
152       else
153         {
154           DEBUGP (("Double yuck!  The *base* URL is broken.\n"));
155           base_dir = NULL;
156         }
157       freeurl (u, 1);
158       depth = 1;
159       robots_host = NULL;
160       forbidden = NULL;
161       first_time = 0;
162     }
163   else
164     ++depth;
165
166   if (opt.reclevel != INFINITE_RECURSION && depth > opt.reclevel)
167     /* We've exceeded the maximum recursion depth specified by the user. */
168     {
169       if (opt.page_requisites && depth <= opt.reclevel + 1)
170         /* When -p is specified, we can do one more partial recursion from the
171            "leaf nodes" on the HTML document tree.  The recursion is partial in
172            that we won't traverse any <A> or <AREA> tags, nor any <LINK> tags
173            except for <LINK REL="stylesheet">. */
174         dash_p_leaf_HTML = TRUE;
175       else
176         /* Either -p wasn't specified or it was and we've already gone the one
177            extra (pseudo-)level that it affords us, so we need to bail out. */
178         {
179           DEBUGP (("Recursion depth %d exceeded max. depth %d.\n",
180                    depth, opt.reclevel));
181           --depth;
182           return RECLEVELEXC;
183         }
184     }
185
186   /* Determine whether this_url is an FTP URL.  If it is, it means
187      that the retrieval is done through proxy.  In that case, FTP
188      links will be followed by default and recursion will not be
189      turned off when following them.  */
190   this_url_ftp = (urlproto (this_url) == URLFTP);
191
192   /* Get the URL-s from an HTML file: */
193   url_list = get_urls_html (file, canon_this_url ? canon_this_url : this_url,
194                             0, dash_p_leaf_HTML);
195
196   /* Decide what to do with each of the URLs.  A URL will be loaded if
197      it meets several requirements, discussed later.  */
198   for (cur_url = url_list; cur_url; cur_url = cur_url->next)
199     {
200       /* If quota was exceeded earlier, bail out.  */
201       if (opt.quota && (opt.downloaded > opt.quota))
202         break;
203       /* Parse the URL for convenient use in other functions, as well
204          as to get the optimized form.  It also checks URL integrity.  */
205       u = newurl ();
206       if (parseurl (cur_url->url, u, 0) != URLOK)
207         {
208           DEBUGP (("Yuck!  A bad URL.\n"));
209           freeurl (u, 1);
210           continue;
211         }
212       if (u->proto == URLFILE)
213         {
214           DEBUGP (("Nothing to do with file:// around here.\n"));
215           freeurl (u, 1);
216           continue;
217         }
218       assert (u->url != NULL);
219       constr = xstrdup (u->url);
220
221       /* Several checkings whether a file is acceptable to load:
222          1. check if URL is ftp, and we don't load it
223          2. check for relative links (if relative_only is set)
224          3. check for domain
225          4. check for no-parent
226          5. check for excludes && includes
227          6. check for suffix
228          7. check for same host (if spanhost is unset), with possible
229          gethostbyname baggage
230          8. check for robots.txt
231
232          Addendum: If the URL is FTP, and it is to be loaded, only the
233          domain and suffix settings are "stronger".
234
235          Note that .html and (yuck) .htm will get loaded regardless of
236          suffix rules (but that is remedied later with unlink) unless
237          the depth equals the maximum depth.
238
239          More time- and memory- consuming tests should be put later on
240          the list.  */
241
242       /* inl is set if the URL we are working on (constr) is stored in
243          ulist.  Using it is crucial to avoid the incessant calls to
244          in_slist, which is quite slow.  */
245       inl = in_slist (ulist, constr);
246
247       /* If it is FTP, and FTP is not followed, chuck it out.  */
248       if (!inl)
249         if (u->proto == URLFTP && !opt.follow_ftp && !this_url_ftp)
250           {
251             DEBUGP (("Uh, it is FTP but i'm not in the mood to follow FTP.\n"));
252             ulist = add_slist (ulist, constr, 0);
253             inl = 1;
254           }
255       /* If it is absolute link and they are not followed, chuck it
256          out.  */
257       if (!inl && u->proto != URLFTP)
258         if (opt.relative_only && !(cur_url->flags & URELATIVE))
259           {
260             DEBUGP (("It doesn't really look like a relative link.\n"));
261             ulist = add_slist (ulist, constr, 0);
262             inl = 1;
263           }
264       /* If its domain is not to be accepted/looked-up, chuck it out.  */
265       if (!inl)
266         if (!accept_domain (u))
267           {
268             DEBUGP (("I don't like the smell of that domain.\n"));
269             ulist = add_slist (ulist, constr, 0);
270             inl = 1;
271           }
272       /* Check for parent directory.  */
273       if (!inl && opt.no_parent
274           /* If the new URL is FTP and the old was not, ignore
275              opt.no_parent.  */
276           && !(!this_url_ftp && u->proto == URLFTP))
277         {
278           /* Check for base_dir first.  */
279           if (!(base_dir && frontcmp (base_dir, u->dir)))
280             {
281               /* Failing that, check for parent dir.  */
282               struct urlinfo *ut = newurl ();
283               if (parseurl (this_url, ut, 0) != URLOK)
284                 DEBUGP (("Double yuck!  The *base* URL is broken.\n"));
285               else if (!frontcmp (ut->dir, u->dir))
286                 {
287                   /* Failing that too, kill the URL.  */
288                   DEBUGP (("Trying to escape parental guidance with no_parent on.\n"));
289                   ulist = add_slist (ulist, constr, 0);
290                   inl = 1;
291                 }
292               freeurl (ut, 1);
293             }
294         }
295       /* If the file does not match the acceptance list, or is on the
296          rejection list, chuck it out.  The same goes for the
297          directory exclude- and include- lists.  */
298       if (!inl && (opt.includes || opt.excludes))
299         {
300           if (!accdir (u->dir, ALLABS))
301             {
302               DEBUGP (("%s (%s) is excluded/not-included.\n", constr, u->dir));
303               ulist = add_slist (ulist, constr, 0);
304               inl = 1;
305             }
306         }
307       if (!inl)
308         {
309           char *suf = NULL;
310           /* We check for acceptance/rejection rules only for non-HTML
311              documents.  Since we don't know whether they really are
312              HTML, it will be deduced from (an OR-ed list):
313
314              1) u->file is "" (meaning it is a directory)
315              2) suffix exists, AND:
316              a) it is "html", OR
317              b) it is "htm"
318
319              If the file *is* supposed to be HTML, it will *not* be
320             subject to acc/rej rules, unless a finite maximum depth has
321             been specified and the current depth is the maximum depth. */
322           if (!
323               (!*u->file
324                || (((suf = suffix (constr)) != NULL)
325                   && ((!strcmp (suf, "html") || !strcmp (suf, "htm"))
326                       && ((opt.reclevel != INFINITE_RECURSION) &&
327                           (depth != opt.reclevel))))))
328             {
329               if (!acceptable (u->file))
330                 {
331                   DEBUGP (("%s (%s) does not match acc/rej rules.\n",
332                           constr, u->file));
333                   ulist = add_slist (ulist, constr, 0);
334                   inl = 1;
335                 }
336             }
337           FREE_MAYBE (suf);
338         }
339       /* Optimize the URL (which includes possible DNS lookup) only
340          after all other possibilities have been exhausted.  */
341       if (!inl)
342         {
343           if (!opt.simple_check)
344             opt_url (u);
345           else
346             {
347               char *p;
348               /* Just lowercase the hostname.  */
349               for (p = u->host; *p; p++)
350                 *p = TOLOWER (*p);
351               free (u->url);
352               u->url = str_url (u, 0);
353             }
354           free (constr);
355           constr = xstrdup (u->url);
356           inl = in_slist (ulist, constr);
357           if (!inl && !((u->proto == URLFTP) && !this_url_ftp))
358             if (!opt.spanhost && this_url && !same_host (this_url, constr))
359               {
360                 DEBUGP (("This is not the same hostname as the parent's.\n"));
361                 ulist = add_slist (ulist, constr, 0);
362                 inl = 1;
363               }
364         }
365       /* What about robots.txt?  */
366       if (!inl && opt.use_robots && u->proto == URLHTTP)
367         {
368           /* Since Wget knows about only one set of robot rules at a
369              time, /robots.txt must be reloaded whenever a new host is
370              accessed.
371
372              robots_host holds the host the current `forbid' variable
373              is assigned to.  */
374           if (!robots_host || !same_host (robots_host, u->host))
375             {
376               FREE_MAYBE (robots_host);
377               /* Now make robots_host the new host, no matter what the
378                  result will be.  So if there is no /robots.txt on the
379                  site, Wget will not retry getting robots all the
380                  time.  */
381               robots_host = xstrdup (u->host);
382               free_vec (forbidden);
383               forbidden = NULL;
384               err = retrieve_robots (constr, ROBOTS_FILENAME);
385               if (err == ROBOTSOK)
386                 {
387                   rurl = robots_url (constr, ROBOTS_FILENAME);
388                   rfile = url_filename (rurl);
389                   forbidden = parse_robots (rfile);
390                   freeurl (rurl, 1);
391                   free (rfile);
392                 }
393             }
394
395           /* Now that we have (or don't have) robots, we can check for
396              them.  */
397           if (!robots_match (u, forbidden))
398             {
399               DEBUGP (("Stuffing %s because %s forbids it.\n", this_url,
400                        ROBOTS_FILENAME));
401               ulist = add_slist (ulist, constr, 0);
402               inl = 1;
403             }
404         }
405
406       filename = NULL;
407       /* If it wasn't chucked out, do something with it.  */
408       if (!inl)
409         {
410           DEBUGP (("I've decided to load it -> "));
411           /* Add it to the list of already-loaded URL-s.  */
412           ulist = add_slist (ulist, constr, 0);
413           /* Automatically followed FTPs will *not* be downloaded
414              recursively.  */
415           if (u->proto == URLFTP)
416             {
417               /* Don't you adore side-effects?  */
418               opt.recursive = 0;
419             }
420           /* Reset its type.  */
421           dt = 0;
422           /* Retrieve it.  */
423           retrieve_url (constr, &filename, &newloc,
424                        canon_this_url ? canon_this_url : this_url, &dt);
425           if (u->proto == URLFTP)
426             {
427               /* Restore...  */
428               opt.recursive = 1;
429             }
430           if (newloc)
431             {
432               free (constr);
433               constr = newloc;
434             }
435           /* In case of convert_links: If there was no error, add it to
436              the list of downloaded URLs.  We might need it for
437              conversion.  */
438           if (opt.convert_links && filename)
439             {
440               if (dt & RETROKF)
441                 {
442                   urls_downloaded = add_url (urls_downloaded, constr, filename);
443                   /* If the URL is HTML, note it.  */
444                   if (dt & TEXTHTML)
445                     urls_html = add_slist (urls_html, filename, NOSORT);
446                 }
447             }
448           /* If there was no error, and the type is text/html, parse
449              it recursively.  */
450           if (dt & TEXTHTML)
451             {
452               if (dt & RETROKF)
453                 recursive_retrieve (filename, constr);
454             }
455           else
456             DEBUGP (("%s is not text/html so we don't chase.\n",
457                      filename ? filename: "(null)"));
458           /* If an suffix-rejected file was loaded only because it was HTML,
459              undo the error now */
460           if (opt.delete_after || (filename && !acceptable (filename)))
461             {
462               logprintf (LOG_VERBOSE,
463                          (opt.delete_after ? _("Removing %s.\n")
464                           : _("Removing %s since it should be rejected.\n")),
465                          filename);
466               if (unlink (filename))
467                 logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
468               dt &= ~RETROKF;
469             }
470           /* If everything was OK, and links are to be converted, let's
471              store the local filename.  */
472           if (opt.convert_links && (dt & RETROKF) && (filename != NULL))
473             {
474               cur_url->flags |= UABS2REL;
475               cur_url->local_name = xstrdup (filename);
476             }
477         }
478       DEBUGP (("%s already in list, so we don't load.\n", constr));
479       /* Free filename and constr.  */
480       FREE_MAYBE (filename);
481       FREE_MAYBE (constr);
482       freeurl (u, 1);
483       /* Increment the pbuf for the appropriate size.  */
484     }
485   if (opt.convert_links)
486     convert_links (file, url_list);
487   /* Free the linked list of URL-s.  */
488   free_urlpos (url_list);
489   /* Free the canonical this_url.  */
490   FREE_MAYBE (canon_this_url);
491   /* Decrement the recursion depth.  */
492   --depth;
493   if (opt.quota && (opt.downloaded > opt.quota))
494     return QUOTEXC;
495   else
496     return RETROK;
497 }
498 \f
499 /* Simple calls to convert_links will often fail because only the
500    downloaded files are converted, and Wget cannot know which files
501    will be converted in the future.  So, if we have file fileone.html
502    with:
503
504    <a href=/c/something.gif>
505
506    and /c/something.gif was not downloaded because it exceeded the
507    recursion depth, the reference will *not* be changed.
508
509    However, later we can encounter /c/something.gif from an "upper"
510    level HTML (let's call it filetwo.html), and it gets downloaded.
511
512    But now we have a problem because /c/something.gif will be
513    correctly transformed in filetwo.html, but not in fileone.html,
514    since Wget could not have known that /c/something.gif will be
515    downloaded in the future.
516
517    This is why Wget must, after the whole retrieval, call
518    convert_all_links to go once more through the entire list of
519    retrieved HTMLs, and re-convert them.
520
521    All the downloaded HTMLs are kept in urls_html, and downloaded URLs
522    in urls_downloaded.  From these two lists information is
523    extracted.  */
524 void
525 convert_all_links (void)
526 {
527   uerr_t res;
528   urlpos *l1, *l2, *urls;
529   struct urlinfo *u;
530   slist *html;
531   urlpos *urlhtml;
532
533   for (html = urls_html; html; html = html->next)
534     {
535       DEBUGP (("Rescanning %s\n", html->string));
536       /* Determine the URL of the HTML file.  get_urls_html will need
537          it.  */
538       for (urlhtml = urls_downloaded; urlhtml; urlhtml = urlhtml->next)
539         if (!strcmp (urlhtml->local_name, html->string))
540           break;
541       if (urlhtml)
542         DEBUGP (("It should correspond to %s.\n", urlhtml->url));
543       else
544         DEBUGP (("I cannot find the corresponding URL.\n"));
545       /* Parse the HTML file...  */
546       urls = get_urls_html (html->string, urlhtml ? urlhtml->url : NULL, 1,
547                             FALSE);
548       if (!urls)
549         continue;
550       for (l1 = urls; l1; l1 = l1->next)
551         {
552           /* The URL must be in canonical form to be compared.  */
553           u = newurl ();
554           res = parseurl (l1->url, u, 0);
555           if (res != URLOK)
556             {
557               freeurl (u, 1);
558               continue;
559             }
560           /* We decide the direction of conversion according to whether
561              a URL was downloaded.  Downloaded URLs will be converted
562              ABS2REL, whereas non-downloaded will be converted REL2ABS.
563              Note: not yet implemented; only ABS2REL works.  */
564           for (l2 = urls_downloaded; l2; l2 = l2->next)
565             if (!strcmp (l2->url, u->url))
566               {
567                 DEBUGP (("%s flagged for conversion, local %s\n",
568                          l2->url, l2->local_name));
569                 break;
570               }
571           /* Clear the flags.  */
572           l1->flags &= ~ (UABS2REL | UREL2ABS);
573           /* Decide on the conversion direction.  */
574           if (l2)
575             {
576               l1->flags |= UABS2REL;
577               l1->local_name = xstrdup (l2->local_name);
578             }
579           else
580             {
581               l1->flags |= UREL2ABS;
582               l1->local_name = NULL;
583             }
584           freeurl (u, 1);
585         }
586       /* Convert the links in the file.  */
587       convert_links (html->string, urls);
588       /* Free the data.  */
589       free_urlpos (urls);
590     }
591 }
592 \f
593 /* Robots support.  */
594
595 /* Construct the robots URL.  */
596 static struct urlinfo *
597 robots_url (const char *url, const char *robots_filename)
598 {
599   struct urlinfo *u = newurl ();
600   uerr_t err;
601
602   err = parseurl (url, u, 0);
603   assert (err == URLOK && u->proto == URLHTTP);
604   free (u->file);
605   free (u->dir);
606   free (u->url);
607   u->dir = xstrdup ("");
608   u->file = xstrdup (robots_filename);
609   u->url = str_url (u, 0);
610   return u;
611 }
612
613 /* Retrieves the robots_filename from the root server directory, if
614    possible.  Returns ROBOTSOK if robots were retrieved OK, and
615    NOROBOTS if robots could not be retrieved for any reason.  */
616 static uerr_t
617 retrieve_robots (const char *url, const char *robots_filename)
618 {
619   int dt;
620   uerr_t err;
621   struct urlinfo *u;
622
623   u = robots_url (url, robots_filename);
624   logputs (LOG_VERBOSE, _("Loading robots.txt; please ignore errors.\n"));
625   err = retrieve_url (u->url, NULL, NULL, NULL, &dt);
626   freeurl (u, 1);
627   if (err == RETROK)
628     return ROBOTSOK;
629   else
630     return NOROBOTS;
631 }
632
633 /* Parse the robots_filename and return the disallowed path components
634    in a malloc-ed vector of character pointers.
635
636    It should be fully compliant with the syntax as described in the
637    file norobots.txt, adopted by the robots mailing list
638    (robots@webcrawler.com).  */
639 static char **
640 parse_robots (const char *robots_filename)
641 {
642   FILE *fp;
643   char **entries;
644   char *line, *cmd, *str, *p;
645   char *base_version, *version;
646   int len, num, i;
647   int wget_matched;             /* is the part meant for Wget?  */
648
649   entries = NULL;
650
651   num = 0;
652   fp = fopen (robots_filename, "rb");
653   if (!fp)
654     return NULL;
655
656   /* Kill version number.  */
657     if (opt.useragent)
658       {
659         STRDUP_ALLOCA (base_version, opt.useragent);
660         STRDUP_ALLOCA (version, opt.useragent);
661       }
662     else
663       {
664         int len = 10 + strlen (version_string);
665         base_version = (char *)alloca (len);
666         sprintf (base_version, "Wget/%s", version_string);
667         version = (char *)alloca (len);
668         sprintf (version, "Wget/%s", version_string);
669       }
670   for (p = version; *p; p++)
671     *p = TOLOWER (*p);
672   for (p = base_version; *p && *p != '/'; p++)
673     *p = TOLOWER (*p);
674   *p = '\0';
675
676   /* Setting this to 1 means that Wget considers itself under
677      restrictions by default, even if the User-Agent field is not
678      present.  However, if it finds the user-agent set to anything
679      other than Wget, the rest will be ignored (up to the following
680      User-Agent field).  Thus you may have something like:
681
682      Disallow: 1
683      Disallow: 2
684      User-Agent: stupid-robot
685      Disallow: 3
686      Disallow: 4
687      User-Agent: Wget*
688      Disallow: 5
689      Disallow: 6
690      User-Agent: *
691      Disallow: 7
692
693      In this case the 1, 2, 5, 6 and 7 disallow lines will be
694      stored.  */
695   wget_matched = 1;
696   while ((line = read_whole_line (fp)))
697     {
698       len = strlen (line);
699       /* Destroy <CR> if there is one.  */
700       if (len && line[len - 1] == '\r')
701         line[len - 1] = '\0';
702       /* According to specifications, optional space may be at the
703          end...  */
704       DEBUGP (("Line: %s\n", line));
705       /* Skip spaces.  */
706       for (cmd = line; *cmd && ISSPACE (*cmd); cmd++);
707       if (!*cmd)
708         {
709           free (line);
710           DEBUGP (("(chucked out)\n"));
711           continue;
712         }
713       /* Look for ':'.  */
714       for (str = cmd; *str && *str != ':'; str++);
715       if (!*str)
716         {
717           free (line);
718           DEBUGP (("(chucked out)\n"));
719           continue;
720         }
721       /* Zero-terminate the command.  */
722       *str++ = '\0';
723       /* Look for the string beginning...  */
724       for (; *str && ISSPACE (*str); str++);
725       /* Look for comments or trailing spaces and kill them off.  */
726       for (p = str; *p; p++)
727         if (*p && ISSPACE (*p) && ((*(p + 1) == '#') || (*(p + 1) == '\0')))
728           {
729             /* We have found either a shell-style comment `<sp>+#' or some
730                trailing spaces.  Now rewind to the beginning of the spaces
731                and place '\0' there.  */
732             while (p > str && ISSPACE (*p))
733               --p;
734             if (p == str)
735               *p = '\0';
736             else
737               *(p + 1) = '\0';
738             break;
739           }
740       if (!strcasecmp (cmd, "User-agent"))
741         {
742           int match = 0;
743           /* Lowercase the agent string.  */
744           for (p = str; *p; p++)
745             *p = TOLOWER (*p);
746           /* If the string is `*', it matches.  */
747           if (*str == '*' && !*(str + 1))
748             match = 1;
749           else
750             {
751               /* If the string contains wildcards, we'll run it through
752                  fnmatch().  */
753               if (has_wildcards_p (str))
754                 {
755                   /* If the string contains '/', compare with the full
756                      version.  Else, compare it to base_version.  */
757                   if (strchr (str, '/'))
758                     match = !fnmatch (str, version, 0);
759                   else
760                     match = !fnmatch (str, base_version, 0);
761                 }
762               else                /* Substring search */
763                 {
764                   if (strstr (version, str))
765                     match = 1;
766                   else
767                     match = 0;
768                 }
769             }
770           /* If Wget is not matched, skip all the entries up to the
771              next User-agent field.  */
772           wget_matched = match;
773         }
774       else if (!wget_matched)
775         {
776           free (line);
777           DEBUGP (("(chucking out since it is not applicable for Wget)\n"));
778           continue;
779         }
780       else if (!strcasecmp (cmd, "Disallow"))
781         {
782           /* If "Disallow" is empty, the robot is welcome.  */
783           if (!*str)
784             {
785               free_vec (entries);
786               entries = (char **)xmalloc (sizeof (char *));
787               *entries = NULL;
788               num = 0;
789             }
790           else
791             {
792               entries = (char **)xrealloc (entries, (num + 2)* sizeof (char *));
793               entries[num] = xstrdup (str);
794               entries[++num] = NULL;
795               /* Strip trailing spaces, according to specifications.  */
796               for (i = strlen (str); i >= 0 && ISSPACE (str[i]); i--)
797                 if (ISSPACE (str[i]))
798                   str[i] = '\0';
799             }
800         }
801       else
802         {
803           /* unknown command */
804           DEBUGP (("(chucked out)\n"));
805         }
806       free (line);
807     }
808   fclose (fp);
809   return entries;
810 }
811
812 /* May the URL url be loaded according to disallowing rules stored in
813    forbidden?  */
814 static int
815 robots_match (struct urlinfo *u, char **forbidden)
816 {
817   int l;
818
819   if (!forbidden)
820     return 1;
821   DEBUGP (("Matching %s against: ", u->path));
822   for (; *forbidden; forbidden++)
823     {
824       DEBUGP (("%s ", *forbidden));
825       l = strlen (*forbidden);
826       /* If dir is forbidden, we may not load the file.  */
827       if (strncmp (u->path, *forbidden, l) == 0)
828         {
829           DEBUGP (("matched.\n"));
830           return 0; /* Matches, i.e. does not load...  */
831         }
832     }
833   DEBUGP (("not matched.\n"));
834   return 1;
835 }