]> sjero.net Git - wget/blob - src/init.c
[svn] Applied Christian Fraenkel's patch "custom certificate patch for wget-1.7+dev;
[wget] / src / init.c
1 /* Reading/parsing the initialization file.
2    Copyright (C) 1995, 1996, 1997, 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 #include <ctype.h>
24 #include <sys/types.h>
25 #include <stdlib.h>
26 #ifdef HAVE_UNISTD_H
27 # include <unistd.h>
28 #endif
29 #ifdef HAVE_STRING_H
30 # include <string.h>
31 #else
32 # include <strings.h>
33 #endif
34 #include <errno.h>
35
36 #ifdef WINDOWS
37 # include <winsock.h>
38 #else
39 # include <sys/socket.h>
40 # include <netinet/in.h>
41 # include <arpa/inet.h>
42 #endif
43
44 #ifdef HAVE_PWD_H
45 #include <pwd.h>
46 #endif
47
48 #include "wget.h"
49 #include "utils.h"
50 #include "init.h"
51 #include "host.h"
52 #include "recur.h"
53 #include "netrc.h"
54
55 #ifndef errno
56 extern int errno;
57 #endif
58
59
60 #define CMD_DECLARE(func) static int func \
61   PARAMS ((const char *, const char *, void *))
62
63 CMD_DECLARE (cmd_address);
64 CMD_DECLARE (cmd_boolean);
65 CMD_DECLARE (cmd_bytes);
66 CMD_DECLARE (cmd_directory_vector);
67 CMD_DECLARE (cmd_lockable_boolean);
68 CMD_DECLARE (cmd_number);
69 CMD_DECLARE (cmd_number_inf);
70 CMD_DECLARE (cmd_string);
71 CMD_DECLARE (cmd_time);
72 CMD_DECLARE (cmd_vector);
73
74 CMD_DECLARE (cmd_spec_dirstruct);
75 CMD_DECLARE (cmd_spec_dotstyle);
76 CMD_DECLARE (cmd_spec_header);
77 CMD_DECLARE (cmd_spec_htmlify);
78 CMD_DECLARE (cmd_spec_mirror);
79 CMD_DECLARE (cmd_spec_recursive);
80 CMD_DECLARE (cmd_spec_useragent);
81
82 /* List of recognized commands, each consisting of name, closure and function.
83    When adding a new command, simply add it to the list, but be sure to keep the
84    list sorted alphabetically, as comind() depends on it.  Also, be sure to add
85    any entries that allocate memory (e.g. cmd_string and cmd_vector guys) to the
86    cleanup() function below. */
87 static struct {
88   char *name;
89   void *closure;
90   int (*action) PARAMS ((const char *, const char *, void *));
91 } commands[] = {
92   { "accept",           &opt.accepts,           cmd_vector },
93   { "addhostdir",       &opt.add_hostdir,       cmd_boolean },
94   { "alwaysrest",       &opt.always_rest,       cmd_boolean }, /* deprecated */
95   { "background",       &opt.background,        cmd_boolean },
96   { "backupconverted",  &opt.backup_converted,  cmd_boolean },
97   { "backups",          &opt.backups,           cmd_number },
98   { "base",             &opt.base_href,         cmd_string },
99   { "bindaddress",      &opt.bind_address,      cmd_address },
100   { "cache",            &opt.proxy_cache,       cmd_boolean },
101   { "continue",         &opt.always_rest,       cmd_boolean },
102   { "convertlinks",     &opt.convert_links,     cmd_boolean },
103   { "cutdirs",          &opt.cut_dirs,          cmd_number },
104 #ifdef DEBUG
105   { "debug",            &opt.debug,             cmd_boolean },
106 #endif
107   { "deleteafter",      &opt.delete_after,      cmd_boolean },
108   { "dirprefix",        &opt.dir_prefix,        cmd_string },
109   { "dirstruct",        NULL,                   cmd_spec_dirstruct },
110   { "domains",          &opt.domains,           cmd_vector },
111   { "dotbytes",         &opt.dot_bytes,         cmd_bytes },
112   { "dotsinline",       &opt.dots_in_line,      cmd_number },
113   { "dotspacing",       &opt.dot_spacing,       cmd_number },
114   { "dotstyle",         NULL,                   cmd_spec_dotstyle },
115   { "excludedirectories", &opt.excludes,        cmd_directory_vector },
116   { "excludedomains",   &opt.exclude_domains,   cmd_vector },
117   { "followftp",        &opt.follow_ftp,        cmd_boolean },
118   { "followtags",       &opt.follow_tags,       cmd_vector },
119   { "forcehtml",        &opt.force_html,        cmd_boolean },
120   { "ftpproxy",         &opt.ftp_proxy,         cmd_string },
121   { "glob",             &opt.ftp_glob,          cmd_boolean },
122   { "header",           NULL,                   cmd_spec_header },
123   { "htmlextension",    &opt.html_extension,    cmd_boolean },
124   { "htmlify",          NULL,                   cmd_spec_htmlify },
125   { "httpkeepalive",    &opt.http_keep_alive,   cmd_boolean },
126   { "httppasswd",       &opt.http_passwd,       cmd_string },
127   { "httpproxy",        &opt.http_proxy,        cmd_string },
128   { "httpuser",         &opt.http_user,         cmd_string },
129   { "httpsproxy",       &opt.https_proxy,       cmd_string },
130   { "ignorelength",     &opt.ignore_length,     cmd_boolean },
131   { "ignoretags",       &opt.ignore_tags,       cmd_vector },
132   { "includedirectories", &opt.includes,        cmd_directory_vector },
133   { "input",            &opt.input_filename,    cmd_string },
134   { "killlonger",       &opt.kill_longer,       cmd_boolean },
135   { "logfile",          &opt.lfilename,         cmd_string },
136   { "login",            &opt.ftp_acc,           cmd_string },
137   { "mirror",           NULL,                   cmd_spec_mirror },
138   { "netrc",            &opt.netrc,             cmd_boolean },
139   { "noclobber",        &opt.noclobber,         cmd_boolean },
140   { "noparent",         &opt.no_parent,         cmd_boolean },
141   { "noproxy",          &opt.no_proxy,          cmd_vector },
142   { "numtries",         &opt.ntry,              cmd_number_inf },/* deprecated*/
143   { "outputdocument",   &opt.output_document,   cmd_string },
144   { "pagerequisites",   &opt.page_requisites,   cmd_boolean },
145   { "passiveftp",       &opt.ftp_pasv,          cmd_lockable_boolean },
146   { "passwd",           &opt.ftp_pass,          cmd_string },
147   { "proxypasswd",      &opt.proxy_passwd,      cmd_string },
148   { "proxyuser",        &opt.proxy_user,        cmd_string },
149   { "quiet",            &opt.quiet,             cmd_boolean },
150   { "quota",            &opt.quota,             cmd_bytes },
151   { "reclevel",         &opt.reclevel,          cmd_number_inf },
152   { "recursive",        NULL,                   cmd_spec_recursive },
153   { "referer",          &opt.referer,           cmd_string },
154   { "reject",           &opt.rejects,           cmd_vector },
155   { "relativeonly",     &opt.relative_only,     cmd_boolean },
156   { "removelisting",    &opt.remove_listing,    cmd_boolean },
157   { "retrsymlinks",     &opt.retr_symlinks,     cmd_boolean },
158   { "robots",           &opt.use_robots,        cmd_boolean },
159   { "saveheaders",      &opt.save_headers,      cmd_boolean },
160   { "serverresponse",   &opt.server_response,   cmd_boolean },
161   { "simplehostcheck",  &opt.simple_check,      cmd_boolean },
162   { "spanhosts",        &opt.spanhost,          cmd_boolean },
163   { "spider",           &opt.spider,            cmd_boolean },
164 #ifdef HAVE_SSL
165   { "sslcertfile",      &opt.sslcertfile,       cmd_string },
166   { "sslcertkey",       &opt.sslcertkey,        cmd_string },
167 #endif /* HAVE_SSL */
168   { "timeout",          &opt.timeout,           cmd_time },
169   { "timestamping",     &opt.timestamping,      cmd_boolean },
170   { "tries",            &opt.ntry,              cmd_number_inf },
171   { "useproxy",         &opt.use_proxy,         cmd_boolean },
172   { "useragent",        NULL,                   cmd_spec_useragent },
173   { "verbose",          &opt.verbose,           cmd_boolean },
174   { "wait",             &opt.wait,              cmd_time },
175   { "waitretry",        &opt.waitretry,         cmd_time }
176 };
177
178 /* Return index of COM if it is a valid command, or -1 otherwise.  COM
179    is looked up in `commands' using binary search algorithm.  */
180 static int
181 comind (const char *com)
182 {
183   int min = 0, max = ARRAY_SIZE (commands) - 1;
184
185   do
186     {
187       int i = (min + max) / 2;
188       int cmp = strcasecmp (com, commands[i].name);
189       if (cmp == 0)
190         return i;
191       else if (cmp < 0)
192         max = i - 1;
193       else
194         min = i + 1;
195     }
196   while (min <= max);
197   return -1;
198 }
199 \f
200 /* Reset the variables to default values.  */
201 static void
202 defaults (void)
203 {
204   char *tmp;
205
206   /* Most of the default values are 0.  Just reset everything, and
207      fill in the non-zero values.  Note that initializing pointers to
208      NULL this way is technically illegal, but porting Wget to a
209      machine where NULL is not all-zero bit pattern will be the least
210      of the implementors' worries.  */
211   memset (&opt, 0, sizeof (opt));
212
213   opt.verbose = -1;
214   opt.dir_prefix = xstrdup (".");
215   opt.ntry = 20;
216   opt.reclevel = 5;
217   opt.add_hostdir = 1;
218   opt.ftp_acc = xstrdup ("anonymous");
219   /*opt.ftp_pass = xstrdup (ftp_getaddress ());*/
220   opt.netrc = 1;
221   opt.ftp_glob = 1;
222   opt.htmlify = 1;
223   opt.http_keep_alive = 1;
224   opt.use_proxy = 1;
225   tmp = getenv ("no_proxy");
226   if (tmp)
227     opt.no_proxy = sepstring (tmp);
228   opt.proxy_cache = 1;
229
230 #ifdef HAVE_SELECT
231   opt.timeout = 900;
232 #endif
233   opt.use_robots = 1;
234
235   opt.remove_listing = 1;
236
237   opt.dot_bytes = 1024;
238   opt.dot_spacing = 10;
239   opt.dots_in_line = 50;
240 }
241 \f
242 /* Return the user's home directory (strdup-ed), or NULL if none is
243    found.  */
244 char *
245 home_dir (void)
246 {
247   char *home = getenv ("HOME");
248
249   if (!home)
250     {
251 #ifndef WINDOWS
252       /* If HOME is not defined, try getting it from the password
253          file.  */
254       struct passwd *pwd = getpwuid (getuid ());
255       if (!pwd || !pwd->pw_dir)
256         return NULL;
257       home = pwd->pw_dir;
258 #else  /* WINDOWS */
259       home = "C:\\";
260       /* #### Maybe I should grab home_dir from registry, but the best
261          that I could get from there is user's Start menu.  It sucks!  */
262 #endif /* WINDOWS */
263     }
264
265   return home ? xstrdup (home) : NULL;
266 }
267
268 /* Return the path to the user's .wgetrc.  This is either the value of
269    `WGETRC' environment variable, or `$HOME/.wgetrc'.
270
271    If the `WGETRC' variable exists but the file does not exist, the
272    function will exit().  */
273 static char *
274 wgetrc_file_name (void)
275 {
276   char *env, *home;
277   char *file = NULL;
278
279   /* Try the environment.  */
280   env = getenv ("WGETRC");
281   if (env && *env)
282     {
283       if (!file_exists_p (env))
284         {
285           fprintf (stderr, "%s: %s: %s.\n", exec_name, file, strerror (errno));
286           exit (1);
287         }
288       return xstrdup (env);
289     }
290
291 #ifndef WINDOWS
292   /* If that failed, try $HOME/.wgetrc.  */
293   home = home_dir ();
294   if (home)
295     {
296       file = (char *)xmalloc (strlen (home) + 1 + strlen (".wgetrc") + 1);
297       sprintf (file, "%s/.wgetrc", home);
298     }
299   FREE_MAYBE (home);
300 #else  /* WINDOWS */
301   /* Under Windows, "home" is (for the purposes of this function) the
302      directory where `wget.exe' resides, and `wget.ini' will be used
303      as file name.  SYSTEM_WGETRC should not be defined under WINDOWS.
304
305      It is not as trivial as I assumed, because on 95 argv[0] is full
306      path, but on NT you get what you typed in command line.  --dbudor */
307   home = ws_mypath ();
308   if (home)
309     {
310       file = (char *)xmalloc (strlen (home) + strlen ("wget.ini") + 1);
311       sprintf (file, "%swget.ini", home);
312     }
313 #endif /* WINDOWS */
314
315   if (!file)
316     return NULL;
317   if (!file_exists_p (file))
318     {
319       xfree (file);
320       return NULL;
321     }
322   return file;
323 }
324
325 /* Initialize variables from a wgetrc file */
326 static void
327 run_wgetrc (const char *file)
328 {
329   FILE *fp;
330   char *line;
331   int ln;
332
333   fp = fopen (file, "rb");
334   if (!fp)
335     {
336       fprintf (stderr, _("%s: Cannot read %s (%s).\n"), exec_name,
337                file, strerror (errno));
338       return;
339     }
340   /* Reset line number.  */
341   ln = 1;
342   while ((line = read_whole_line (fp)))
343     {
344       char *com, *val;
345       int status;
346
347       /* Parse the line.  */
348       status = parse_line (line, &com, &val);
349       xfree (line);
350       /* If everything is OK, set the value.  */
351       if (status == 1)
352         {
353           if (!setval (com, val))
354             fprintf (stderr, _("%s: Error in %s at line %d.\n"), exec_name,
355                      file, ln);
356           xfree (com);
357           xfree (val);
358         }
359       else if (status == 0)
360         fprintf (stderr, _("%s: Error in %s at line %d.\n"), exec_name,
361                  file, ln);
362       ++ln;
363     }
364   fclose (fp);
365 }
366
367 /* Initialize the defaults and run the system wgetrc and user's own
368    wgetrc.  */
369 void
370 initialize (void)
371 {
372   char *file;
373
374   /* Load the hard-coded defaults.  */
375   defaults ();
376
377   /* If SYSTEM_WGETRC is defined, use it.  */
378 #ifdef SYSTEM_WGETRC
379   if (file_exists_p (SYSTEM_WGETRC))
380     run_wgetrc (SYSTEM_WGETRC);
381 #endif
382   /* Override it with your own, if one exists.  */
383   file = wgetrc_file_name ();
384   if (!file)
385     return;
386   /* #### We should somehow canonicalize `file' and SYSTEM_WGETRC,
387      really.  */
388 #ifdef SYSTEM_WGETRC
389   if (!strcmp (file, SYSTEM_WGETRC))
390     {
391       fprintf (stderr, _("\
392 %s: Warning: Both system and user wgetrc point to `%s'.\n"),
393                exec_name, file);
394     }
395   else
396 #endif
397     run_wgetrc (file);
398   xfree (file);
399   return;
400 }
401 \f
402 /* Parse the line pointed by line, with the syntax:
403    <sp>* command <sp>* = <sp>* value <newline>
404    Uses malloc to allocate space for command and value.
405    If the line is invalid, data is freed and 0 is returned.
406
407    Return values:
408     1 - success
409     0 - failure
410    -1 - empty */
411 int
412 parse_line (const char *line, char **com, char **val)
413 {
414   const char *p = line;
415   const char *orig_comptr, *end;
416   char *new_comptr;
417
418   /* Skip whitespace.  */
419   while (*p && ISSPACE (*p))
420     ++p;
421
422   /* Don't process empty lines.  */
423   if (!*p || *p == '#')
424     return -1;
425
426   for (orig_comptr = p; ISALPHA (*p) || *p == '_' || *p == '-'; p++)
427     ;
428   /* The next char should be space or '='.  */
429   if (!ISSPACE (*p) && (*p != '='))
430     return 0;
431   /* Here we cannot use strdupdelim() as we normally would because we
432      want to skip the `-' and `_' characters in the input string.  */
433   *com = (char *)xmalloc (p - orig_comptr + 1);
434   for (new_comptr = *com; orig_comptr < p; orig_comptr++)
435     {
436       if (*orig_comptr == '_' || *orig_comptr == '-')
437         continue;
438       *new_comptr++ = *orig_comptr;
439     }
440   *new_comptr = '\0';
441   /* If the command is invalid, exit now.  */
442   if (comind (*com) == -1)
443     {
444       xfree (*com);
445       return 0;
446     }
447
448   /* Skip spaces before '='.  */
449   for (; ISSPACE (*p); p++);
450   /* If '=' not found, bail out.  */
451   if (*p != '=')
452     {
453       xfree (*com);
454       return 0;
455     }
456   /* Skip spaces after '='.  */
457   for (++p; ISSPACE (*p); p++);
458   /* Get the ending position for VAL by starting with the end of the
459      line and skipping whitespace.  */
460   end = line + strlen (line) - 1;
461   while (end > p && ISSPACE (*end))
462     --end;
463   *val = strdupdelim (p, end + 1);
464   return 1;
465 }
466
467 /* Set COM to VAL.  This is the meat behind processing `.wgetrc'.  No
468    fatals -- error signal prints a warning and resets to default
469    value.  All error messages are printed to stderr, *not* to
470    opt.lfile, since opt.lfile wasn't even generated yet.  */
471 int
472 setval (const char *com, const char *val)
473 {
474   int ind;
475
476   if (!com || !val)
477     return 0;
478   ind = comind (com);
479   if (ind == -1)
480     {
481       /* #### Should I just abort()?  */
482 #ifdef DEBUG
483       fprintf (stderr, _("%s: BUG: unknown command `%s', value `%s'.\n"),
484                exec_name, com, val);
485 #endif
486       return 0;
487     }
488   return ((*commands[ind].action) (com, val, commands[ind].closure));
489 }
490 \f
491 /* Generic helper functions, for use with `commands'. */
492
493 static int myatoi PARAMS ((const char *s));
494
495 /* Store the address (specified as hostname or dotted-quad IP address) from VAL
496    to CLOSURE.  COM is ignored, except for error messages.  */
497 static int
498 cmd_address (const char *com, const char *val, void *closure)
499 {
500   struct sockaddr_in sin;
501
502   if (!store_hostaddress ((unsigned char *)&sin.sin_addr, val))
503     {
504       fprintf (stderr, _("%s: %s: Cannot convert `%s' to an IP address.\n"),
505                exec_name, com, val);
506       return 0;
507     }
508
509   sin.sin_family = AF_INET;
510   sin.sin_port = 0;
511
512   memcpy (closure, &sin, sizeof (sin));
513
514   return 1;
515 }
516
517 /* Store the boolean value from VAL to CLOSURE.  COM is ignored,
518    except for error messages.  */
519 static int
520 cmd_boolean (const char *com, const char *val, void *closure)
521 {
522   int bool_value;
523
524   if (!strcasecmp (val, "on")
525       || (*val == '1' && !*(val + 1)))
526     bool_value = 1;
527   else if (!strcasecmp (val, "off")
528            || (*val == '0' && !*(val + 1)))
529     bool_value = 0;
530   else
531     {
532       fprintf (stderr, _("%s: %s: Please specify on or off.\n"),
533                exec_name, com);
534       return 0;
535     }
536
537   *(int *)closure = bool_value;
538   return 1;
539 }
540
541 /* Store the lockable_boolean {2, 1, 0, -1} value from VAL to CLOSURE.  COM is
542    ignored, except for error messages.  Values 2 and -1 indicate that once
543    defined, the value may not be changed by successive wgetrc files or
544    command-line arguments.
545
546    Values: 2 - Enable a particular option for good ("always")
547            1 - Enable an option ("on")
548            0 - Disable an option ("off")
549           -1 - Disable an option for good ("never") */
550 static int
551 cmd_lockable_boolean (const char *com, const char *val, void *closure)
552 {
553   int lockable_boolean_value;
554
555   /*
556    * If a config file said "always" or "never", don't allow command line
557    * arguments to override the config file.
558    */
559   if (*(int *)closure == -1 || *(int *)closure == 2)
560     return 1;
561
562   if (!strcasecmp (val, "always")
563       || (*val == '2' && !*(val + 1)))
564     lockable_boolean_value = 2;
565   else if (!strcasecmp (val, "on")
566       || (*val == '1' && !*(val + 1)))
567     lockable_boolean_value = 1;
568   else if (!strcasecmp (val, "off")
569           || (*val == '0' && !*(val + 1)))
570     lockable_boolean_value = 0;
571   else if (!strcasecmp (val, "never")
572       || (*val == '-' && *(val + 1) == '1' && !*(val + 2)))
573     lockable_boolean_value = -1;
574   else
575     {
576       fprintf (stderr, _("%s: %s: Please specify always, on, off, "
577                          "or never.\n"),
578                exec_name, com);
579       return 0;
580     }
581
582   *(int *)closure = lockable_boolean_value;
583   return 1;
584 }
585
586 /* Set the non-negative integer value from VAL to CLOSURE.  With
587    incorrect specification, the number remains unchanged.  */
588 static int
589 cmd_number (const char *com, const char *val, void *closure)
590 {
591   int num = myatoi (val);
592
593   if (num == -1)
594     {
595       fprintf (stderr, _("%s: %s: Invalid specification `%s'.\n"),
596                exec_name, com, val);
597       return 0;
598     }
599   *(int *)closure = num;
600   return 1;
601 }
602
603 /* Similar to cmd_number(), only accepts `inf' as a synonym for 0.  */
604 static int
605 cmd_number_inf (const char *com, const char *val, void *closure)
606 {
607   if (!strcasecmp (val, "inf"))
608     {
609       *(int *)closure = 0;
610       return 1;
611     }
612   return cmd_number (com, val, closure);
613 }
614
615 /* Copy (strdup) the string at COM to a new location and place a
616    pointer to *CLOSURE.  */
617 static int
618 cmd_string (const char *com, const char *val, void *closure)
619 {
620   char **pstring = (char **)closure;
621
622   FREE_MAYBE (*pstring);
623   *pstring = xstrdup (val);
624   return 1;
625 }
626
627 /* Merge the vector (array of strings separated with `,') in COM with
628    the vector (NULL-terminated array of strings) pointed to by
629    CLOSURE.  */
630 static int
631 cmd_vector (const char *com, const char *val, void *closure)
632 {
633   char ***pvec = (char ***)closure;
634
635   if (*val)
636     *pvec = merge_vecs (*pvec, sepstring (val));
637   else
638     {
639       free_vec (*pvec);
640       *pvec = NULL;
641     }
642   return 1;
643 }
644
645 static int
646 cmd_directory_vector (const char *com, const char *val, void *closure)
647 {
648   char ***pvec = (char ***)closure;
649
650   if (*val)
651     {
652       /* Strip the trailing slashes from directories.  */
653       char **t, **seps;
654
655       seps = sepstring (val);
656       for (t = seps; t && *t; t++)
657         {
658           int len = strlen (*t);
659           /* Skip degenerate case of root directory.  */
660           if (len > 1)
661             {
662               if ((*t)[len - 1] == '/')
663                 (*t)[len - 1] = '\0';
664             }
665         }
666       *pvec = merge_vecs (*pvec, seps);
667     }
668   else
669     {
670       free_vec (*pvec);
671       *pvec = NULL;
672     }
673   return 1;
674 }
675
676 /* Set the value stored in VAL to CLOSURE (which should point to a
677    long int), allowing several postfixes, with the following syntax
678    (regexp):
679
680    [0-9]+       -> bytes
681    [0-9]+[kK]   -> bytes * 1024
682    [0-9]+[mM]   -> bytes * 1024 * 1024
683    inf          -> 0
684
685    Anything else is flagged as incorrect, and CLOSURE is unchanged.  */
686 static int
687 cmd_bytes (const char *com, const char *val, void *closure)
688 {
689   long result;
690   long *out = (long *)closure;
691   const char *p;
692
693   result = 0;
694   p = val;
695   /* Check for "inf".  */
696   if (p[0] == 'i' && p[1] == 'n' && p[2] == 'f' && p[3] == '\0')
697     {
698       *out = 0;
699       return 1;
700     }
701   /* Search for digits and construct result.  */
702   for (; *p && ISDIGIT (*p); p++)
703     result = (10 * result) + (*p - '0');
704   /* If no digits were found, or more than one character is following
705      them, bail out.  */
706   if (p == val || (*p != '\0' && *(p + 1) != '\0'))
707     {
708       printf (_("%s: Invalid specification `%s'\n"), com, val);
709       return 0;
710     }
711   /* Search for a designator.  */
712   switch (TOLOWER (*p))
713     {
714     case '\0':
715       /* None */
716       break;
717     case 'k':
718       /* Kilobytes */
719       result *= 1024;
720       break;
721     case 'm':
722       /* Megabytes */
723       result *= (long)1024 * 1024;
724       break;
725     case 'g':
726       /* Gigabytes */
727       result *= (long)1024 * 1024 * 1024;
728       break;
729     default:
730       printf (_("%s: Invalid specification `%s'\n"), com, val);
731       return 0;
732     }
733   *out = result;
734   return 1;
735 }
736
737 /* Store the value of VAL to *OUT, allowing suffixes for minutes and
738    hours.  */
739 static int
740 cmd_time (const char *com, const char *val, void *closure)
741 {
742   long result = 0;
743   const char *p = val;
744
745   /* Search for digits and construct result.  */
746   for (; *p && ISDIGIT (*p); p++)
747     result = (10 * result) + (*p - '0');
748   /* If no digits were found, or more than one character is following
749      them, bail out.  */
750   if (p == val || (*p != '\0' && *(p + 1) != '\0'))
751     {
752       printf (_("%s: Invalid specification `%s'\n"), com, val);
753       return 0;
754     }
755   /* Search for a suffix.  */
756   switch (TOLOWER (*p))
757     {
758     case '\0':
759       /* None */
760       break;
761     case 'm':
762       /* Minutes */
763       result *= 60;
764       break;
765     case 'h':
766       /* Seconds */
767       result *= 3600;
768       break;
769     case 'd':
770       /* Days (overflow on 16bit machines) */
771       result *= 86400L;
772       break;
773     case 'w':
774       /* Weeks :-) */
775       result *= 604800L;
776       break;
777     default:
778       printf (_("%s: Invalid specification `%s'\n"), com, val);
779       return 0;
780     }
781   *(long *)closure = result;
782   return 1;
783 }
784 \f
785 /* Specialized helper functions, used by `commands' to handle some
786    options specially.  */
787
788 static int check_user_specified_header PARAMS ((const char *));
789
790 static int
791 cmd_spec_dirstruct (const char *com, const char *val, void *closure)
792 {
793   if (!cmd_boolean (com, val, &opt.dirstruct))
794     return 0;
795   /* Since dirstruct behaviour is explicitly changed, no_dirstruct
796      must be affected inversely.  */
797   if (opt.dirstruct)
798     opt.no_dirstruct = 0;
799   else
800     opt.no_dirstruct = 1;
801   return 1;
802 }
803
804 static int
805 cmd_spec_dotstyle (const char *com, const char *val, void *closure)
806 {
807   /* Retrieval styles.  */
808   if (!strcasecmp (val, "default"))
809     {
810       /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
811          line.  */
812       opt.dot_bytes = 1024;
813       opt.dot_spacing = 10;
814       opt.dots_in_line = 50;
815     }
816   else if (!strcasecmp (val, "binary"))
817     {
818       /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
819          (384K) in a line.  */
820       opt.dot_bytes = 8192;
821       opt.dot_spacing = 16;
822       opt.dots_in_line = 48;
823     }
824   else if (!strcasecmp (val, "mega"))
825     {
826       /* "Mega" retrieval, for retrieving very long files; each dot is
827          64K, 8 dots in a cluster, 6 clusters (3M) in a line.  */
828       opt.dot_bytes = 65536L;
829       opt.dot_spacing = 8;
830       opt.dots_in_line = 48;
831     }
832   else if (!strcasecmp (val, "giga"))
833     {
834       /* "Giga" retrieval, for retrieving very very *very* long files;
835          each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
836          line.  */
837       opt.dot_bytes = (1L << 20);
838       opt.dot_spacing = 8;
839       opt.dots_in_line = 32;
840     }
841   else if (!strcasecmp (val, "micro"))
842     {
843       /* "Micro" retrieval, for retrieving very small files (and/or
844          slow connections); each dot is 128 bytes, 8 dots in a
845          cluster, 6 clusters (6K) in a line.  */
846       opt.dot_bytes = 128;
847       opt.dot_spacing = 8;
848       opt.dots_in_line = 48;
849     }
850   else
851     {
852       fprintf (stderr, _("%s: %s: Invalid specification `%s'.\n"),
853                exec_name, com, val);
854       return 0;
855     }
856   return 1;
857 }
858
859 static int
860 cmd_spec_header (const char *com, const char *val, void *closure)
861 {
862   if (!*val)
863     {
864       /* Empty header means reset headers.  */
865       FREE_MAYBE (opt.user_header);
866       opt.user_header = NULL;
867     }
868   else
869     {
870       int i;
871
872       if (!check_user_specified_header (val))
873         {
874           fprintf (stderr, _("%s: %s: Invalid specification `%s'.\n"),
875                    exec_name, com, val);
876           return 0;
877         }
878       i = opt.user_header ? strlen (opt.user_header) : 0;
879       opt.user_header = (char *)xrealloc (opt.user_header, i + strlen (val)
880                                           + 2 + 1);
881       strcpy (opt.user_header + i, val);
882       i += strlen (val);
883       opt.user_header[i++] = '\r';
884       opt.user_header[i++] = '\n';
885       opt.user_header[i] = '\0';
886     }
887   return 1;
888 }
889
890 static int
891 cmd_spec_htmlify (const char *com, const char *val, void *closure)
892 {
893   int flag = cmd_boolean (com, val, &opt.htmlify);
894   if (flag && !opt.htmlify)
895     opt.remove_listing = 0;
896   return flag;
897 }
898
899 static int
900 cmd_spec_mirror (const char *com, const char *val, void *closure)
901 {
902   int mirror;
903
904   if (!cmd_boolean (com, val, &mirror))
905     return 0;
906   if (mirror)
907     {
908       opt.recursive = 1;
909       if (!opt.no_dirstruct)
910         opt.dirstruct = 1;
911       opt.timestamping = 1;
912       opt.reclevel = INFINITE_RECURSION;
913       opt.remove_listing = 0;
914     }
915   return 1;
916 }
917
918 static int
919 cmd_spec_recursive (const char *com, const char *val, void *closure)
920 {
921   if (!cmd_boolean (com, val, &opt.recursive))
922     return 0;
923   else
924     {
925       if (opt.recursive && !opt.no_dirstruct)
926         opt.dirstruct = 1;
927     }
928   return 1;
929 }
930
931 static int
932 cmd_spec_useragent (const char *com, const char *val, void *closure)
933 {
934   /* Just check for empty string and newline, so we don't throw total
935      junk to the server.  */
936   if (!*val || strchr (val, '\n'))
937     {
938       fprintf (stderr, _("%s: %s: Invalid specification `%s'.\n"),
939                exec_name, com, val);
940       return 0;
941     }
942   opt.useragent = xstrdup (val);
943   return 1;
944 }
945 \f
946 /* Miscellaneous useful routines.  */
947
948 /* Return the integer value of a positive integer written in S, or -1
949    if an error was encountered.  */
950 static int
951 myatoi (const char *s)
952 {
953   int res;
954   const char *orig = s;
955
956   for (res = 0; *s && ISDIGIT (*s); s++)
957     res = 10 * res + (*s - '0');
958   if (*s || orig == s)
959     return -1;
960   else
961     return res;
962 }
963
964 #define ISODIGIT(x) ((x) >= '0' && (x) <= '7')
965
966 static int
967 check_user_specified_header (const char *s)
968 {
969   const char *p;
970
971   for (p = s; *p && *p != ':' && !ISSPACE (*p); p++);
972   /* The header MUST contain `:' preceded by at least one
973      non-whitespace character.  */
974   if (*p != ':' || p == s)
975     return 0;
976   /* The header MUST NOT contain newlines.  */
977   if (strchr (s, '\n'))
978     return 0;
979   return 1;
980 }
981 \f
982 void cleanup_html_url PARAMS ((void));
983 void downloaded_files_free PARAMS ((void));
984
985
986 /* Free the memory allocated by global variables.  */
987 void
988 cleanup (void)
989 {
990   extern acc_t *netrc_list;
991
992   recursive_cleanup ();
993   clean_hosts ();
994   free_netrc (netrc_list);
995   if (opt.dfp)
996     fclose (opt.dfp);
997   cleanup_html_url ();
998   downloaded_files_free ();
999   FREE_MAYBE (opt.lfilename);
1000   xfree (opt.dir_prefix);
1001   FREE_MAYBE (opt.input_filename);
1002   FREE_MAYBE (opt.output_document);
1003   free_vec (opt.accepts);
1004   free_vec (opt.rejects);
1005   free_vec (opt.excludes);
1006   free_vec (opt.includes);
1007   free_vec (opt.domains);
1008   free_vec (opt.follow_tags);
1009   free_vec (opt.ignore_tags);
1010   xfree (opt.ftp_acc);
1011   FREE_MAYBE (opt.ftp_pass);
1012   FREE_MAYBE (opt.ftp_proxy);
1013   FREE_MAYBE (opt.https_proxy);
1014   FREE_MAYBE (opt.http_proxy);
1015   free_vec (opt.no_proxy);
1016   FREE_MAYBE (opt.useragent);
1017   FREE_MAYBE (opt.referer);
1018   FREE_MAYBE (opt.http_user);
1019   FREE_MAYBE (opt.http_passwd);
1020   FREE_MAYBE (opt.user_header);
1021 #ifdef HAVE_SSL
1022   FREE_MAYBE (opt.sslcertkey);
1023   FREE_MAYBE (opt.sslcertfile);
1024 #endif /* HAVE_SSL */
1025 }