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