]> sjero.net Git - wget/blob - src/res.c
[svn] Fix the broken URLs that pointed to info.webcrawler.com to point to
[wget] / src / res.c
1 /* Support for Robot Exclusion Standard (RES).
2    Copyright (C) 2001 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 (at
9 your option) any later version.
10
11 This program is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 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 /* This file implements the Robot Exclusion Standard (RES).
21
22    RES is a simple protocol that enables site admins to signalize to
23    the web crawlers that certain parts of the site should not be
24    accessed.  All the admin needs to do is create a "robots.txt" file
25    in the web server root, and use simple commands to allow or
26    disallow access to certain parts of the site.
27
28    The first specification was written by Martijn Koster in 1994, and
29    is still available at <http://www.robotstxt.org/wc/norobots.html>.
30    In 1996, Martijn wrote an Internet Draft specifying an improved RES
31    specification; however, that work was apparently abandoned since
32    the draft has expired in 1997 and hasn't been replaced since.  The
33    draft is available at
34    <http://www.robotstxt.org/wc/norobots-rfc.html>.
35
36    This file implements RES as specified by the draft.  Note that this
37    only handles the "robots.txt" support.  The META tag that controls
38    whether the links should be followed is handled in `html-url.c'.
39
40    Known deviations:
41
42    * The end-of-line comment recognition is more in the spirit of the
43      Bourne Shell (as specified by RES-1994).  That means that
44      "foo#bar" is taken literally, whereas "foo #bar" is interpreted
45      as "foo".  The Draft apparently specifies that both should be
46      interpreted as "foo".
47
48    * We don't recognize sole CR as the line ending.
49
50    * We don't implement expiry mechanism for /robots.txt specs.  I
51      consider it non-necessary for a relatively short-lived
52      application such as Wget.  Besides, it is highly questionable
53      whether anyone deploys the recommended expiry scheme for
54      robots.txt.
55
56    Entry points are functions res_parse, res_parse_from_file,
57    res_match_path, res_register_specs, res_get_specs, and
58    res_retrieve_file.  */
59
60 #ifdef HAVE_CONFIG_H
61 # include <config.h>
62 #endif
63
64 #include <stdio.h>
65 #include <stdlib.h>
66 #ifdef HAVE_STRING_H
67 # include <string.h>
68 #else
69 # include <strings.h>
70 #endif /* HAVE_STRING_H */
71 #include <errno.h>
72 #include <assert.h>
73
74 #include "wget.h"
75 #include "utils.h"
76 #include "hash.h"
77 #include "url.h"
78 #include "retr.h"
79 #include "res.h"
80
81 struct path_info {
82   char *path;
83   int allowedp;
84   int user_agent_exact_p;
85 };
86
87 struct robot_specs {
88   int count;
89   int size;
90   struct path_info *paths;
91 };
92 \f
93 /* Parsing the robot spec. */
94
95 /* Check whether AGENT (a string of length LENGTH) equals "wget" or
96    "*".  If it is either of them, *matches is set to one.  If it is
97    "wget", *exact_match is set to one.  */
98
99 static void
100 match_user_agent (const char *agent, int length,
101                   int *matches, int *exact_match)
102 {
103   if (length == 1 && *agent == '*')
104     {
105       *matches = 1;
106       *exact_match = 0;
107     }
108   else if (BOUNDED_EQUAL_NO_CASE (agent, agent + length, "wget"))
109     {
110       *matches = 1;
111       *exact_match = 1;
112     }
113   else
114     {
115       *matches = 0;
116       *exact_match = 0;
117     }
118 }
119
120 /* Add a path specification between PATH_B and PATH_E as one of the
121    paths in SPECS.  */
122
123 static void
124 add_path (struct robot_specs *specs, const char *path_b, const char *path_e,
125           int allowedp, int exactp)
126 {
127   struct path_info pp;
128   if (path_b < path_e && *path_b == '/')
129     /* Our path representation doesn't use a leading slash, so remove
130        one from theirs. */
131     ++path_b;
132   pp.path     = strdupdelim (path_b, path_e);
133   pp.allowedp = allowedp;
134   pp.user_agent_exact_p = exactp;
135   ++specs->count;
136   if (specs->count > specs->size)
137     {
138       if (specs->size == 0)
139         specs->size = 1;
140       else
141         specs->size <<= 1;
142       specs->paths = xrealloc (specs->paths,
143                                specs->size * sizeof (struct path_info));
144     }
145   specs->paths[specs->count - 1] = pp;
146 }
147
148 /* Recreate SPECS->paths with only those paths that have non-zero
149    user_agent_exact_p.  */
150
151 static void
152 prune_non_exact (struct robot_specs *specs)
153 {
154   struct path_info *newpaths;
155   int i, j, cnt;
156   cnt = 0;
157   for (i = 0; i < specs->count; i++)
158     if (specs->paths[i].user_agent_exact_p)
159       ++cnt;
160   newpaths = xmalloc (cnt * sizeof (struct path_info));
161   for (i = 0, j = 0; i < specs->count; i++)
162     if (specs->paths[i].user_agent_exact_p)
163       newpaths[j++] = specs->paths[i];
164   assert (j == cnt);
165   xfree (specs->paths);
166   specs->paths = newpaths;
167   specs->count = cnt;
168   specs->size  = cnt;
169 }
170
171 #define EOL(p) ((p) >= lineend)
172
173 #define SKIP_SPACE(p) do {              \
174   while (!EOL (p) && ISSPACE (*p))      \
175     ++p;                                \
176 } while (0)
177
178 #define FIELD_IS(string_literal)        \
179   BOUNDED_EQUAL_NO_CASE (field_b, field_e, string_literal)
180
181 /* Parse textual RES specs beginning with SOURCE of length LENGTH.
182    Return a specs objects ready to be fed to res_match_path.
183
184    The parsing itself is trivial, but creating a correct SPECS object
185    is trickier than it seems, because RES is surprisingly byzantine if
186    you attempt to implement it correctly.
187
188    A "record" is a block of one or more `User-Agent' lines followed by
189    one or more `Allow' or `Disallow' lines.  Record is accepted by
190    Wget if one of the `User-Agent' lines was "wget", or if the user
191    agent line was "*".
192
193    After all the lines have been read, we examine whether an exact
194    ("wget") user-agent field was specified.  If so, we delete all the
195    lines read under "User-Agent: *" blocks because we have our own
196    Wget-specific blocks.  This enables the admin to say:
197
198        User-Agent: *
199        Disallow: /
200
201        User-Agent: google
202        User-Agent: wget
203        Disallow: /cgi-bin
204
205    This means that to Wget and to Google, /cgi-bin is disallowed,
206    whereas for all other crawlers, everything is disallowed.
207    res_parse is implemented so that the order of records doesn't
208    matter.  In the case above, the "User-Agent: *" could have come
209    after the other one.  */
210
211 struct robot_specs *
212 res_parse (const char *source, int length)
213 {
214   int line_count = 1;
215
216   const char *p   = source;
217   const char *end = source + length;
218
219   /* non-zero if last applicable user-agent field matches Wget. */
220   int user_agent_applies = 0;
221
222   /* non-zero if last applicable user-agent field *exactly* matches
223      Wget.  */
224   int user_agent_exact = 0;
225
226   /* whether we ever encountered exact user agent. */
227   int found_exact = 0;
228
229   /* count of allow/disallow lines in the current "record", i.e. after
230      the last `user-agent' instructions.  */
231   int record_count = 0;
232
233   struct robot_specs *specs = xmalloc (sizeof (struct robot_specs));
234   memset (specs, '\0', sizeof (struct robot_specs));
235
236   while (1)
237     {
238       const char *lineend, *lineend_real;
239       const char *field_b, *field_e;
240       const char *value_b, *value_e;
241
242       if (p == end)
243         break;
244       lineend_real = memchr (p, '\n', end - p);
245       if (lineend_real)
246         ++lineend_real;
247       else
248         lineend_real = end;
249       lineend = lineend_real;
250
251       /* Before doing anything else, check whether the line is empty
252          or comment-only. */
253       SKIP_SPACE (p);
254       if (EOL (p) || *p == '#')
255         goto next;
256
257       /* Make sure the end-of-line comments are respected by setting
258          lineend to a location preceding the first comment.  Real line
259          ending remains in lineend_real.  */
260       for (lineend = p; lineend < lineend_real; lineend++)
261         if ((lineend == p || ISSPACE (*(lineend - 1)))
262             && *lineend == '#')
263           break;
264
265       /* Ignore trailing whitespace in the same way. */
266       while (lineend > p && ISSPACE (*(lineend - 1)))
267         --lineend;
268
269       assert (!EOL (p));
270
271       field_b = p;
272       while (!EOL (p) && (ISALNUM (*p) || *p == '-'))
273         ++p;
274       field_e = p;
275
276       SKIP_SPACE (p);
277       if (field_b == field_e || EOL (p) || *p != ':')
278         {
279           DEBUGP (("Ignoring malformed line %d", line_count));
280           goto next;
281         }
282       ++p;                      /* skip ':' */
283       SKIP_SPACE (p);
284
285       value_b = p;
286       while (!EOL (p))
287         ++p;
288       value_e = p;
289
290       /* Finally, we have a syntactically valid line. */
291       if (FIELD_IS ("user-agent"))
292         {
293           /* We have to support several cases:
294
295              --previous records--
296
297              User-Agent: foo
298              User-Agent: Wget
299              User-Agent: bar
300              ... matching record ...
301
302              User-Agent: baz
303              User-Agent: qux
304              ... non-matching record ...
305
306              User-Agent: *
307              ... matching record, but will be pruned later ...
308
309              We have to respect `User-Agent' at the beginning of each
310              new record simply because we don't know if we're going to
311              encounter "Wget" among the agents or not.  Hence,
312              match_user_agent is called when record_count != 0.
313
314              But if record_count is 0, we have to keep calling it
315              until it matches, and if that happens, we must not call
316              it any more, until the next record.  Hence the other part
317              of the condition.  */
318           if (record_count != 0 || user_agent_applies == 0)
319             match_user_agent (value_b, value_e - value_b,
320                               &user_agent_applies, &user_agent_exact);
321           if (user_agent_exact)
322             found_exact = 1;
323           record_count = 0;
324         }
325       else if (FIELD_IS ("allow"))
326         {
327           if (user_agent_applies)
328             {
329               add_path (specs, value_b, value_e, 1, user_agent_exact);
330             }
331           ++record_count;
332         }
333       else if (FIELD_IS ("disallow"))
334         {
335           if (user_agent_applies)
336             {
337               int allowed = 0;
338               if (value_b == value_e)
339                 /* Empty "disallow" line means everything is
340                    *allowed*!  */
341                 allowed = 1;
342               add_path (specs, value_b, value_e, allowed, user_agent_exact);
343             }
344           ++record_count;
345         }
346       else
347         {
348           DEBUGP (("Ignoring unknown field at line %d", line_count));
349           goto next;
350         }
351
352     next:
353       p = lineend_real;
354       ++line_count;
355     }
356
357   if (found_exact)
358     {
359       /* We've encountered an exactly matching user-agent.  Throw out
360          all the stuff with user-agent: *.  */
361       prune_non_exact (specs);
362     }
363   else if (specs->size > specs->count)
364     {
365       /* add_path normally over-allocates specs->paths.  Reallocate it
366          to the correct size in order to conserve some memory.  */
367       specs->paths = xrealloc (specs->paths,
368                                specs->count * sizeof (struct path_info));
369       specs->size = specs->count;
370     }
371
372   return specs;
373 }
374
375 /* The same like res_parse, but first map the FILENAME into memory,
376    and then parse it.  */
377
378 struct robot_specs *
379 res_parse_from_file (const char *filename)
380 {
381   struct robot_specs *specs;
382   struct file_memory *fm = read_file (filename);
383   if (!fm)
384     {
385       logprintf (LOG_NOTQUIET, "Cannot open %s: %s",
386                  filename, strerror (errno));
387       return NULL;
388     }
389   specs = res_parse (fm->content, fm->length);
390   read_file_free (fm);
391   return specs;
392 }
393
394 static void
395 free_specs (struct robot_specs *specs)
396 {
397   int i;
398   for (i = 0; i < specs->count; i++)
399     xfree (specs->paths[i].path);
400   FREE_MAYBE (specs->paths);
401   xfree (specs);
402 }
403 \f
404 /* Matching of a path according to the specs. */
405
406 /* If C is '%' and (ptr[1], ptr[2]) form a hexadecimal number, and if
407    that number is not a numerical representation of '/', decode C and
408    advance the pointer.  */
409
410 #define DECODE_MAYBE(c, ptr) do {                                       \
411   if (c == '%' && ISXDIGIT (ptr[1]) && ISXDIGIT (ptr[2]))               \
412     {                                                                   \
413       char decoded                                                      \
414         = (XCHAR_TO_XDIGIT (ptr[1]) << 4) + XCHAR_TO_XDIGIT (ptr[2]);   \
415       if (decoded != '/')                                               \
416         {                                                               \
417           c = decoded;                                                  \
418           ptr += 2;                                                     \
419         }                                                               \
420     }                                                                   \
421 } while (0)
422
423 /* The inner matching engine: return non-zero if RECORD_PATH matches
424    URL_PATH.  The rules for matching are described at
425    <http://www.robotstxt.org/wc/norobots-rfc.txt>, section 3.2.2.  */
426
427 static int
428 matches (const char *record_path, const char *url_path)
429 {
430   const char *rp = record_path;
431   const char *up = url_path;
432
433   for (; ; ++rp, ++up)
434     {
435       char rc = *rp;
436       char uc = *up;
437       if (!rc)
438         return 1;
439       if (!uc)
440         return 0;
441       DECODE_MAYBE(rc, rp);
442       DECODE_MAYBE(uc, up);
443       if (rc != uc)
444         return 0;
445     }
446 }
447
448 /* Iterate through all paths in SPECS.  For the first one that
449    matches, return its allow/reject status.  If none matches,
450    retrieval is by default allowed.  */
451
452 int
453 res_match_path (const struct robot_specs *specs, const char *path)
454 {
455   int i;
456   if (!specs)
457     return 1;
458   for (i = 0; i < specs->count; i++)
459     if (matches (specs->paths[i].path, path))
460       {
461         int allowedp = specs->paths[i].allowedp;
462         DEBUGP (("%s path %s because of rule `%s'.\n",
463                  allowedp ? "Allowing" : "Rejecting",
464                  path, specs->paths[i].path));
465         return allowedp;
466       }
467   return 1;
468 }
469 \f
470 /* Registering the specs. */
471
472 static struct hash_table *registered_specs;
473
474 /* Stolen from cookies.c. */
475 #define SET_HOSTPORT(host, port, result) do {           \
476   int HP_len = strlen (host);                           \
477   result = alloca (HP_len + 1 + numdigit (port) + 1);   \
478   memcpy (result, host, HP_len);                        \
479   result[HP_len] = ':';                                 \
480   number_to_string (result + HP_len + 1, port);         \
481 } while (0)
482
483 /* Register RES specs that below to server on HOST:PORT.  They will
484    later be retrievable using res_get_specs.  */
485
486 void
487 res_register_specs (const char *host, int port, struct robot_specs *specs)
488 {
489   struct robot_specs *old;
490   char *hp, *hp_old;
491   SET_HOSTPORT (host, port, hp);
492
493   if (!registered_specs)
494     registered_specs = make_nocase_string_hash_table (0);
495
496   /* Required to shut up the compiler. */
497   old    = NULL;
498   hp_old = NULL;
499
500   if (hash_table_get_pair (registered_specs, hp, hp_old, old))
501     {
502       if (old)
503         free_specs (old);
504       hash_table_put (registered_specs, hp_old, specs);
505     }
506   else
507     {
508       hash_table_put (registered_specs, xstrdup (hp), specs);
509     }
510 }
511
512 /* Get the specs that belong to HOST:PORT. */
513
514 struct robot_specs *
515 res_get_specs (const char *host, int port)
516 {
517   char *hp;
518   SET_HOSTPORT (host, port, hp);
519   if (!registered_specs)
520     return NULL;
521   return hash_table_get (registered_specs, hp);
522 }
523 \f
524 /* Loading the robots file.  */
525
526 #define RES_SPECS_LOCATION "/robots.txt"
527
528 /* Retrieve the robots.txt from the server root of the server that
529    serves URL.  The file will be named according to the currently
530    active rules, and the file name will be returned in *file.
531
532    Return non-zero if robots were retrieved OK, zero otherwise.  */
533
534 int
535 res_retrieve_file (const char *url, char **file)
536 {
537   uerr_t err;
538   char *robots_url = uri_merge (url, RES_SPECS_LOCATION);
539
540   logputs (LOG_VERBOSE, _("Loading robots.txt; please ignore errors.\n"));
541   *file = NULL;
542   err = retrieve_url (robots_url, file, NULL, NULL, NULL);
543   xfree (robots_url);
544
545   if (err != RETROK && *file != NULL)
546     {
547       /* If the file is not retrieved correctly, but retrieve_url
548          allocated the file name, deallocate is here so that the
549          caller doesn't have to worry about it.  */
550       xfree (*file);
551       *file = NULL;
552     }
553   return err == RETROK;
554 }
555 \f
556 static int
557 cleanup_hash_table_mapper (void *key, void *value, void *arg_ignored)
558 {
559   xfree (key);
560   free_specs (value);
561   return 0;
562 }
563
564 void
565 res_cleanup (void)
566 {
567   if (registered_specs)
568     {
569       hash_table_map (registered_specs, cleanup_hash_table_mapper, NULL);
570       hash_table_destroy (registered_specs);
571       registered_specs = NULL;
572     }
573 }