]> sjero.net Git - wget/blob - src/init.c
Merge in gerel's url-parsing stuff.
[wget] / src / init.c
1 /* Reading/parsing the initialization file.
2    Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
3    2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
4
5 This file is part of GNU Wget.
6
7 GNU Wget is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 GNU Wget is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Wget.  If not, see <http://www.gnu.org/licenses/>.
19
20 Additional permission under GNU GPL version 3 section 7
21
22 If you modify this program, or any covered work, by linking or
23 combining it with the OpenSSL project's OpenSSL library (or a
24 modified version of that library), containing parts covered by the
25 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26 grants you additional permission to convey the resulting work.
27 Corresponding Source for a non-source form of such a combination
28 shall include the source code for the parts of OpenSSL used as well
29 as that of the covered work.  */
30
31 #include "wget.h"
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #ifdef HAVE_UNISTD_H
36 # include <unistd.h>
37 #endif
38 #include <string.h>
39 #include <errno.h>
40
41 #ifdef HAVE_PWD_H
42 # include <pwd.h>
43 #endif
44 #include <assert.h>
45
46 #include "utils.h"
47 #include "init.h"
48 #include "host.h"
49 #include "netrc.h"
50 #include "progress.h"
51 #include "recur.h"              /* for INFINITE_RECURSION */
52 #include "convert.h"            /* for convert_cleanup */
53 #include "res.h"                /* for res_cleanup */
54 #include "http.h"               /* for http_cleanup */
55 #include "retr.h"               /* for output_stream */
56
57 #ifdef TESTING
58 #include "test.h"
59 #endif
60
61 /* We want tilde expansion enabled only when reading `.wgetrc' lines;
62    otherwise, it will be performed by the shell.  This variable will
63    be set by the wgetrc-reading function.  */
64
65 static bool enable_tilde_expansion;
66
67
68 #define CMD_DECLARE(func) static bool func (const char *, const char *, void *)
69
70 CMD_DECLARE (cmd_boolean);
71 CMD_DECLARE (cmd_bytes);
72 CMD_DECLARE (cmd_bytes_sum);
73 #ifdef HAVE_SSL
74 CMD_DECLARE (cmd_cert_type);
75 #endif
76 CMD_DECLARE (cmd_directory_vector);
77 CMD_DECLARE (cmd_number);
78 CMD_DECLARE (cmd_number_inf);
79 CMD_DECLARE (cmd_string);
80 CMD_DECLARE (cmd_file);
81 CMD_DECLARE (cmd_directory);
82 CMD_DECLARE (cmd_time);
83 CMD_DECLARE (cmd_vector);
84
85 CMD_DECLARE (cmd_spec_dirstruct);
86 CMD_DECLARE (cmd_spec_header);
87 CMD_DECLARE (cmd_spec_htmlify);
88 CMD_DECLARE (cmd_spec_mirror);
89 CMD_DECLARE (cmd_spec_prefer_family);
90 CMD_DECLARE (cmd_spec_progress);
91 CMD_DECLARE (cmd_spec_recursive);
92 CMD_DECLARE (cmd_spec_restrict_file_names);
93 #ifdef HAVE_SSL
94 CMD_DECLARE (cmd_spec_secure_protocol);
95 #endif
96 CMD_DECLARE (cmd_spec_timeout);
97 CMD_DECLARE (cmd_spec_useragent);
98 CMD_DECLARE (cmd_spec_verbose);
99
100 /* List of recognized commands, each consisting of name, place and
101    function.  When adding a new command, simply add it to the list,
102    but be sure to keep the list sorted alphabetically, as
103    command_by_name's binary search depends on it.  Also, be sure to
104    add any entries that allocate memory (e.g. cmd_string and
105    cmd_vector) to the cleanup() function below. */
106
107 static const struct {
108   const char *name;
109   void *place;
110   bool (*action) (const char *, const char *, void *);
111 } commands[] = {
112   /* KEEP THIS LIST ALPHABETICALLY SORTED */
113   { "accept",           &opt.accepts,           cmd_vector },
114   { "addhostdir",       &opt.add_hostdir,       cmd_boolean },
115   { "alwaysrest",       &opt.always_rest,       cmd_boolean }, /* deprecated */
116   { "askpassword",      &opt.ask_passwd,        cmd_boolean },
117   { "authnochallenge",  &opt.auth_without_challenge,
118                                                 cmd_boolean },
119   { "background",       &opt.background,        cmd_boolean },
120   { "backupconverted",  &opt.backup_converted,  cmd_boolean },
121   { "backups",          &opt.backups,           cmd_number },
122   { "base",             &opt.base_href,         cmd_string },
123   { "bindaddress",      &opt.bind_address,      cmd_string },
124 #ifdef HAVE_SSL
125   { "cacertificate",    &opt.ca_cert,           cmd_file },
126 #endif
127   { "cache",            &opt.allow_cache,       cmd_boolean },
128 #ifdef HAVE_SSL
129   { "cadirectory",      &opt.ca_directory,      cmd_directory },
130   { "certificate",      &opt.cert_file,         cmd_file },
131   { "certificatetype",  &opt.cert_type,         cmd_cert_type },
132   { "checkcertificate", &opt.check_cert,        cmd_boolean },
133 #endif
134   { "connecttimeout",   &opt.connect_timeout,   cmd_time },
135   { "contentdisposition", &opt.content_disposition, cmd_boolean },
136   { "continue",         &opt.always_rest,       cmd_boolean },
137   { "convertlinks",     &opt.convert_links,     cmd_boolean },
138   { "cookies",          &opt.cookies,           cmd_boolean },
139   { "cutdirs",          &opt.cut_dirs,          cmd_number },
140 #ifdef ENABLE_DEBUG
141   { "debug",            &opt.debug,             cmd_boolean },
142 #endif
143   { "defaultpage",      &opt.default_page,      cmd_string},
144   { "deleteafter",      &opt.delete_after,      cmd_boolean },
145   { "dirprefix",        &opt.dir_prefix,        cmd_directory },
146   { "dirstruct",        NULL,                   cmd_spec_dirstruct },
147   { "dnscache",         &opt.dns_cache,         cmd_boolean },
148   { "dnstimeout",       &opt.dns_timeout,       cmd_time },
149   { "domains",          &opt.domains,           cmd_vector },
150   { "dotbytes",         &opt.dot_bytes,         cmd_bytes },
151   { "dotsinline",       &opt.dots_in_line,      cmd_number },
152   { "dotspacing",       &opt.dot_spacing,       cmd_number },
153   { "dotstyle",         &opt.dot_style,         cmd_string },
154 #ifdef HAVE_SSL
155   { "egdfile",          &opt.egd_file,          cmd_file },
156 #endif
157   { "excludedirectories", &opt.excludes,        cmd_directory_vector },
158   { "excludedomains",   &opt.exclude_domains,   cmd_vector },
159   { "followftp",        &opt.follow_ftp,        cmd_boolean },
160   { "followtags",       &opt.follow_tags,       cmd_vector },
161   { "forcehtml",        &opt.force_html,        cmd_boolean },
162   { "ftppasswd",        &opt.ftp_passwd,        cmd_string }, /* deprecated */
163   { "ftppassword",      &opt.ftp_passwd,        cmd_string },
164   { "ftpproxy",         &opt.ftp_proxy,         cmd_string },
165   { "ftpuser",          &opt.ftp_user,          cmd_string },
166   { "glob",             &opt.ftp_glob,          cmd_boolean },
167   { "header",           NULL,                   cmd_spec_header },
168   { "htmlextension",    &opt.html_extension,    cmd_boolean },
169   { "htmlify",          NULL,                   cmd_spec_htmlify },
170   { "httpkeepalive",    &opt.http_keep_alive,   cmd_boolean },
171   { "httppasswd",       &opt.http_passwd,       cmd_string }, /* deprecated */
172   { "httppassword",     &opt.http_passwd,       cmd_string },
173   { "httpproxy",        &opt.http_proxy,        cmd_string },
174   { "httpsproxy",       &opt.https_proxy,       cmd_string },
175   { "httpuser",         &opt.http_user,         cmd_string },
176   { "ignorecase",       &opt.ignore_case,       cmd_boolean },
177   { "ignorelength",     &opt.ignore_length,     cmd_boolean },
178   { "ignoretags",       &opt.ignore_tags,       cmd_vector },
179   { "includedirectories", &opt.includes,        cmd_directory_vector },
180 #ifdef ENABLE_IPV6
181   { "inet4only",        &opt.ipv4_only,         cmd_boolean },
182   { "inet6only",        &opt.ipv6_only,         cmd_boolean },
183 #endif
184   { "input",            &opt.input_filename,    cmd_file },
185   { "keepsessioncookies", &opt.keep_session_cookies, cmd_boolean },
186   { "limitrate",        &opt.limit_rate,        cmd_bytes },
187   { "loadcookies",      &opt.cookies_input,     cmd_file },
188   { "logfile",          &opt.lfilename,         cmd_file },
189   { "login",            &opt.ftp_user,          cmd_string },/* deprecated*/
190   { "maxredirect",      &opt.max_redirect,      cmd_number },
191   { "mirror",           NULL,                   cmd_spec_mirror },
192   { "netrc",            &opt.netrc,             cmd_boolean },
193   { "noclobber",        &opt.noclobber,         cmd_boolean },
194   { "noparent",         &opt.no_parent,         cmd_boolean },
195   { "noproxy",          &opt.no_proxy,          cmd_vector },
196   { "numtries",         &opt.ntry,              cmd_number_inf },/* deprecated*/
197   { "outputdocument",   &opt.output_document,   cmd_file },
198   { "pagerequisites",   &opt.page_requisites,   cmd_boolean },
199   { "passiveftp",       &opt.ftp_pasv,          cmd_boolean },
200   { "passwd",           &opt.ftp_passwd,        cmd_string },/* deprecated*/
201   { "password",         &opt.passwd,            cmd_string },
202   { "postdata",         &opt.post_data,         cmd_string },
203   { "postfile",         &opt.post_file_name,    cmd_file },
204   { "preferfamily",     NULL,                   cmd_spec_prefer_family },
205   { "preservepermissions", &opt.preserve_perm,  cmd_boolean },
206 #ifdef HAVE_SSL
207   { "privatekey",       &opt.private_key,       cmd_file },
208   { "privatekeytype",   &opt.private_key_type,  cmd_cert_type },
209 #endif
210   { "progress",         &opt.progress_type,     cmd_spec_progress },
211   { "protocoldirectories", &opt.protocol_directories, cmd_boolean },
212   { "proxypasswd",      &opt.proxy_passwd,      cmd_string }, /* deprecated */
213   { "proxypassword",    &opt.proxy_passwd,      cmd_string },
214   { "proxyuser",        &opt.proxy_user,        cmd_string },
215   { "quiet",            &opt.quiet,             cmd_boolean },
216   { "quota",            &opt.quota,             cmd_bytes_sum },
217 #ifdef HAVE_SSL
218   { "randomfile",       &opt.random_file,       cmd_file },
219 #endif
220   { "randomwait",       &opt.random_wait,       cmd_boolean },
221   { "readtimeout",      &opt.read_timeout,      cmd_time },
222   { "reclevel",         &opt.reclevel,          cmd_number_inf },
223   { "recursive",        NULL,                   cmd_spec_recursive },
224   { "referer",          &opt.referer,           cmd_string },
225   { "reject",           &opt.rejects,           cmd_vector },
226   { "relativeonly",     &opt.relative_only,     cmd_boolean },
227   { "removelisting",    &opt.remove_listing,    cmd_boolean },
228   { "restrictfilenames", NULL,                  cmd_spec_restrict_file_names },
229   { "retrsymlinks",     &opt.retr_symlinks,     cmd_boolean },
230   { "retryconnrefused", &opt.retry_connrefused, cmd_boolean },
231   { "robots",           &opt.use_robots,        cmd_boolean },
232   { "savecookies",      &opt.cookies_output,    cmd_file },
233   { "saveheaders",      &opt.save_headers,      cmd_boolean },
234 #ifdef HAVE_SSL
235   { "secureprotocol",   &opt.secure_protocol,   cmd_spec_secure_protocol },
236 #endif
237   { "serverresponse",   &opt.server_response,   cmd_boolean },
238   { "spanhosts",        &opt.spanhost,          cmd_boolean },
239   { "spider",           &opt.spider,            cmd_boolean },
240   { "strictcomments",   &opt.strict_comments,   cmd_boolean },
241   { "timeout",          NULL,                   cmd_spec_timeout },
242   { "timestamping",     &opt.timestamping,      cmd_boolean },
243   { "tries",            &opt.ntry,              cmd_number_inf },
244   { "useproxy",         &opt.use_proxy,         cmd_boolean },
245   { "user",             &opt.user,              cmd_string },
246   { "useragent",        NULL,                   cmd_spec_useragent },
247   { "verbose",          NULL,                   cmd_spec_verbose },
248   { "wait",             &opt.wait,              cmd_time },
249   { "waitretry",        &opt.waitretry,         cmd_time },
250 #ifdef MSDOS
251   { "wdebug",           &opt.wdebug,            cmd_boolean },
252 #endif
253 };
254
255 /* Look up CMDNAME in the commands[] and return its position in the
256    array.  If CMDNAME is not found, return -1.  */
257
258 static int
259 command_by_name (const char *cmdname)
260 {
261   /* Use binary search for speed.  Wget has ~100 commands, which
262      guarantees a worst case performance of 7 string comparisons.  */
263   int lo = 0, hi = countof (commands) - 1;
264
265   while (lo <= hi)
266     {
267       int mid = (lo + hi) >> 1;
268       int cmp = strcasecmp (cmdname, commands[mid].name);
269       if (cmp < 0)
270         hi = mid - 1;
271       else if (cmp > 0)
272         lo = mid + 1;
273       else
274         return mid;
275     }
276   return -1;
277 }
278 \f
279 /* Reset the variables to default values.  */
280 static void
281 defaults (void)
282 {
283   char *tmp;
284
285   /* Most of the default values are 0 (and 0.0, NULL, and false).
286      Just reset everything, and fill in the non-zero values.  Note
287      that initializing pointers to NULL this way is technically
288      illegal, but porting Wget to a machine where NULL is not all-zero
289      bit pattern will be the least of the implementors' worries.  */
290   xzero (opt);
291
292   opt.cookies = true;
293   opt.verbose = -1;
294   opt.ntry = 20;
295   opt.reclevel = 5;
296   opt.add_hostdir = true;
297   opt.netrc = true;
298   opt.ftp_glob = true;
299   opt.htmlify = true;
300   opt.http_keep_alive = true;
301   opt.use_proxy = true;
302   tmp = getenv ("no_proxy");
303   if (tmp)
304     opt.no_proxy = sepstring (tmp);
305   opt.prefer_family = prefer_none;
306   opt.allow_cache = true;
307
308   opt.read_timeout = 900;
309   opt.use_robots = true;
310
311   opt.remove_listing = true;
312
313   opt.dot_bytes = 1024;
314   opt.dot_spacing = 10;
315   opt.dots_in_line = 50;
316
317   opt.dns_cache = true;
318   opt.ftp_pasv = true;
319
320 #ifdef HAVE_SSL
321   opt.check_cert = true;
322 #endif
323
324   /* The default for file name restriction defaults to the OS type. */
325 #if defined(WINDOWS) || defined(MSDOS) || defined(__CYGWIN__)
326   opt.restrict_files_os = restrict_windows;
327 #else
328   opt.restrict_files_os = restrict_unix;
329 #endif
330   opt.restrict_files_ctrl = true;
331   opt.restrict_files_case = restrict_no_case_restriction;
332
333   opt.max_redirect = 20;
334
335   opt.waitretry = 10;
336 }
337 \f
338 /* Return the user's home directory (strdup-ed), or NULL if none is
339    found.  */
340 char *
341 home_dir (void)
342 {
343   static char buf[PATH_MAX];
344   static char *home;
345
346   if (!home)
347     {
348       home = getenv ("HOME");
349       if (!home)
350         {
351 #if defined(MSDOS)
352           /* Under MSDOS, if $HOME isn't defined, use the directory where
353              `wget.exe' resides.  */
354           const char *_w32_get_argv0 (void); /* in libwatt.a/pcconfig.c */
355           char *p;
356
357           strcpy (buf, _w32_get_argv0 ());
358           p = strrchr (buf, '/');            /* djgpp */
359           if (!p)
360             p = strrchr (buf, '\\');          /* others */
361           assert (p);
362           *p = '\0';
363           home = buf;
364 #elif !defined(WINDOWS)
365           /* If HOME is not defined, try getting it from the password
366              file.  */
367           struct passwd *pwd = getpwuid (getuid ());
368           if (!pwd || !pwd->pw_dir)
369             return NULL;
370           strcpy (buf, pwd->pw_dir);
371           home = buf;
372 #else  /* !WINDOWS */
373           /* Under Windows, if $HOME isn't defined, use the directory where
374              `wget.exe' resides.  */
375           home = ws_mypath ();
376 #endif /* WINDOWS */
377         }
378     }
379
380   return home ? xstrdup (home) : NULL;
381 }
382
383 /* Check the 'WGETRC' environment variable and return the file name 
384    if  'WGETRC' is set and is a valid file.  
385    If the `WGETRC' variable exists but the file does not exist, the
386    function will exit().  */
387 char *
388 wgetrc_env_file_name (void) 
389 {
390   char *env = getenv ("WGETRC");
391   if (env && *env)
392     {
393       if (!file_exists_p (env))
394         {
395           fprintf (stderr, _("%s: WGETRC points to %s, which doesn't exist.\n"),
396                    exec_name, env);
397           exit (1);
398         }
399       return xstrdup (env);
400     }
401   return NULL;
402 }
403
404 /* Check for the existance of '$HOME/.wgetrc' and return it's path
405    if it exists and is set.  */
406 char *
407 wgetrc_user_file_name (void) 
408 {
409   char *home = home_dir ();
410   char *file = NULL;
411   if (home)
412     file = aprintf ("%s/.wgetrc", home);
413   xfree_null (home);
414   if (!file)
415     return NULL;
416   if (!file_exists_p (file))
417     {
418       xfree (file);
419       return NULL;
420     }
421   return file;
422 }
423
424 /* Return the path to the user's .wgetrc.  This is either the value of
425    `WGETRC' environment variable, or `$HOME/.wgetrc'.
426
427    Additionally, for windows, look in the directory where wget.exe 
428    resides.  */
429 char *
430 wgetrc_file_name (void)
431 {
432   char *home = NULL;
433   char *file = wgetrc_env_file_name ();
434   if (file && *file)
435     return file;
436   
437   file = wgetrc_user_file_name ();
438
439 #ifdef WINDOWS
440   /* Under Windows, if we still haven't found .wgetrc, look for the file
441      `wget.ini' in the directory where `wget.exe' resides; we do this for
442      backward compatibility with previous versions of Wget.
443      SYSTEM_WGETRC should not be defined under WINDOWS.  */
444   home = home_dir ();
445   if (!file || !file_exists_p (file))
446     {
447       xfree_null (file);
448       file = NULL;
449       home = ws_mypath ();
450       if (home)
451         file = aprintf ("%s/wget.ini", home);
452     }
453   xfree_null (home);
454 #endif /* WINDOWS */
455
456   if (!file)
457     return NULL;
458   if (!file_exists_p (file))
459     {
460       xfree (file);
461       return NULL;
462     }
463   return file;
464 }
465
466 /* Return values of parse_line. */
467 enum parse_line {
468   line_ok,
469   line_empty,
470   line_syntax_error,
471   line_unknown_command
472 };
473
474 static enum parse_line parse_line (const char *, char **, char **, int *);
475 static bool setval_internal (int, const char *, const char *);
476
477 /* Initialize variables from a wgetrc file.  Returns zero (failure) if
478    there were errors in the file.  */
479
480 static bool
481 run_wgetrc (const char *file)
482 {
483   FILE *fp;
484   char *line;
485   int ln;
486   int errcnt = 0;
487
488   fp = fopen (file, "rb");
489   if (!fp)
490     {
491       fprintf (stderr, _("%s: Cannot read %s (%s).\n"), exec_name,
492                file, strerror (errno));
493       return true;                      /* not a fatal error */
494     }
495   enable_tilde_expansion = true;
496   ln = 1;
497   while ((line = read_whole_line (fp)) != NULL)
498     {
499       char *com = NULL, *val = NULL;
500       int comind;
501
502       /* Parse the line.  */
503       switch (parse_line (line, &com, &val, &comind))
504         {
505         case line_ok:
506           /* If everything is OK, set the value.  */
507           if (!setval_internal (comind, com, val))
508             {
509               fprintf (stderr, _("%s: Error in %s at line %d.\n"),
510                        exec_name, file, ln);
511               ++errcnt;
512             }
513           break;
514         case line_syntax_error:
515           fprintf (stderr, _("%s: Syntax error in %s at line %d.\n"),
516                    exec_name, file, ln);
517           ++errcnt;
518           break;
519         case line_unknown_command:
520           fprintf (stderr, _("%s: Unknown command %s in %s at line %d.\n"),
521                    exec_name, quote (com), file, ln);
522           ++errcnt;
523           break;
524         case line_empty:
525           break;
526         default:
527           abort ();
528         }
529       xfree_null (com);
530       xfree_null (val);
531       xfree (line);
532       ++ln;
533     }
534   enable_tilde_expansion = false;
535   fclose (fp);
536
537   return errcnt == 0;
538 }
539
540 /* Initialize the defaults and run the system wgetrc and user's own
541    wgetrc.  */
542 void
543 initialize (void)
544 {
545   char *file, *env_sysrc;
546   int ok = true;
547
548   /* Load the hard-coded defaults.  */
549   defaults ();
550   
551   /* Run a non-standard system rc file when the according environment 
552      variable has been set. For internal testing purposes only!  */
553   env_sysrc = getenv ("SYSTEM_WGETRC");
554   if (env_sysrc && file_exists_p (env_sysrc))
555     ok &= run_wgetrc (env_sysrc);
556   /* Otherwise, if SYSTEM_WGETRC is defined, use it.  */
557 #ifdef SYSTEM_WGETRC
558   else if (file_exists_p (SYSTEM_WGETRC))
559     ok &= run_wgetrc (SYSTEM_WGETRC);
560 #endif
561   /* Override it with your own, if one exists.  */
562   file = wgetrc_file_name ();
563   if (!file)
564     return;
565   /* #### We should canonicalize `file' and SYSTEM_WGETRC with
566      something like realpath() before comparing them with `strcmp'  */
567 #ifdef SYSTEM_WGETRC
568   if (!strcmp (file, SYSTEM_WGETRC))
569     {
570       fprintf (stderr, _("\
571 %s: Warning: Both system and user wgetrc point to %s.\n"),
572                exec_name, quote (file));
573     }
574   else
575 #endif
576     ok &= run_wgetrc (file);
577
578   /* If there were errors processing either `.wgetrc', abort. */
579   if (!ok)
580     exit (2);
581
582   xfree (file);
583   return;
584 }
585
586 /* Remove dashes and underscores from S, modifying S in the
587    process. */
588
589 static void
590 dehyphen (char *s)
591 {
592   char *t = s;                  /* t - tortoise */
593   char *h = s;                  /* h - hare     */
594   while (*h)
595     if (*h == '_' || *h == '-')
596       ++h;
597     else
598       *t++ = *h++;
599   *t = '\0';
600 }
601
602 /* Parse the line pointed by line, with the syntax:
603    <sp>* command <sp>* = <sp>* value <sp>*
604    Uses malloc to allocate space for command and value.
605
606    Returns one of line_ok, line_empty, line_syntax_error, or
607    line_unknown_command.
608
609    In case of line_ok, *COM and *VAL point to freshly allocated
610    strings, and *COMIND points to com's index.  In case of error or
611    empty line, their values are unmodified.  */
612
613 static enum parse_line
614 parse_line (const char *line, char **com, char **val, int *comind)
615 {
616   const char *p;
617   const char *end = line + strlen (line);
618   const char *cmdstart, *cmdend;
619   const char *valstart, *valend;
620
621   char *cmdcopy;
622   int ind;
623
624   /* Skip leading and trailing whitespace.  */
625   while (*line && c_isspace (*line))
626     ++line;
627   while (end > line && c_isspace (end[-1]))
628     --end;
629
630   /* Skip empty lines and comments.  */
631   if (!*line || *line == '#')
632     return line_empty;
633
634   p = line;
635
636   cmdstart = p;
637   while (p < end && (c_isalnum (*p) || *p == '_' || *p == '-'))
638     ++p;
639   cmdend = p;
640
641   /* Skip '=', as well as any space before or after it. */
642   while (p < end && c_isspace (*p))
643     ++p;
644   if (p == end || *p != '=')
645     return line_syntax_error;
646   ++p;
647   while (p < end && c_isspace (*p))
648     ++p;
649
650   valstart = p;
651   valend   = end;
652
653   /* The syntax is valid (even though the command might not be).  Fill
654      in the command name and value.  */
655   *com = strdupdelim (cmdstart, cmdend);
656   *val = strdupdelim (valstart, valend);
657
658   /* The line now known to be syntactically correct.  Check whether
659      the command is valid.  */
660   BOUNDED_TO_ALLOCA (cmdstart, cmdend, cmdcopy);
661   dehyphen (cmdcopy);
662   ind = command_by_name (cmdcopy);
663   if (ind == -1)
664     return line_unknown_command;
665
666   /* Report success to the caller. */
667   *comind = ind;
668   return line_ok;
669 }
670
671 /* Run commands[comind].action. */
672
673 static bool
674 setval_internal (int comind, const char *com, const char *val)
675 {
676   assert (0 <= comind && ((size_t) comind) < countof (commands));
677   DEBUGP (("Setting %s (%s) to %s\n", com, commands[comind].name, val));
678   return commands[comind].action (com, val, commands[comind].place);
679 }
680
681 /* Run command COM with value VAL.  If running the command produces an
682    error, report the error and exit.
683
684    This is intended to be called from main() to modify Wget's behavior
685    through command-line switches.  Since COM is hard-coded in main(),
686    it is not canonicalized, and this aborts when COM is not found.
687
688    If COMIND's are exported to init.h, this function will be changed
689    to accept COMIND directly.  */
690
691 void
692 setoptval (const char *com, const char *val, const char *optname)
693 {
694   /* Prepend "--" to OPTNAME. */
695   char *dd_optname = (char *) alloca (2 + strlen (optname) + 1);
696   dd_optname[0] = '-';
697   dd_optname[1] = '-';
698   strcpy (dd_optname + 2, optname);
699
700   assert (val != NULL);
701   if (!setval_internal (command_by_name (com), dd_optname, val))
702     exit (2);
703 }
704
705 /* Parse OPT into command and value and run it.  For example,
706    run_command("foo=bar") is equivalent to setoptval("foo", "bar").
707    This is used by the `--execute' flag in main.c.  */
708
709 void
710 run_command (const char *opt)
711 {
712   char *com, *val;
713   int comind;
714   switch (parse_line (opt, &com, &val, &comind))
715     {
716     case line_ok:
717       if (!setval_internal (comind, com, val))
718         exit (2);
719       xfree (com);
720       xfree (val);
721       break;
722     default:
723       fprintf (stderr, _("%s: Invalid --execute command %s\n"),
724                exec_name, quote (opt));
725       exit (2);
726     }
727 }
728 \f
729 /* Generic helper functions, for use with `commands'. */
730
731 /* Forward declarations: */
732 struct decode_item {
733   const char *name;
734   int code;
735 };
736 static bool decode_string (const char *, const struct decode_item *, int, int *);
737 static bool simple_atoi (const char *, const char *, int *);
738 static bool simple_atof (const char *, const char *, double *);
739
740 #define CMP1(p, c0) (c_tolower((p)[0]) == (c0) && (p)[1] == '\0')
741
742 #define CMP2(p, c0, c1) (c_tolower((p)[0]) == (c0)        \
743                          && c_tolower((p)[1]) == (c1)     \
744                          && (p)[2] == '\0')
745
746 #define CMP3(p, c0, c1, c2) (c_tolower((p)[0]) == (c0)    \
747                      && c_tolower((p)[1]) == (c1)         \
748                      && c_tolower((p)[2]) == (c2)         \
749                      && (p)[3] == '\0')
750
751
752 /* Store the boolean value from VAL to PLACE.  COM is ignored,
753    except for error messages.  */
754 static bool
755 cmd_boolean (const char *com, const char *val, void *place)
756 {
757   bool value;
758
759   if (CMP2 (val, 'o', 'n') || CMP3 (val, 'y', 'e', 's') || CMP1 (val, '1'))
760     /* "on", "yes" and "1" mean true. */
761     value = true;
762   else if (CMP3 (val, 'o', 'f', 'f') || CMP2 (val, 'n', 'o') || CMP1 (val, '0'))
763     /* "off", "no" and "0" mean false. */
764     value = false;
765   else
766     {
767       fprintf (stderr,
768                _("%s: %s: Invalid boolean %s; use `on' or `off'.\n"),
769                exec_name, com, quote (val));
770       return false;
771     }
772
773   *(bool *) place = value;
774   return true;
775 }
776
777 /* Set the non-negative integer value from VAL to PLACE.  With
778    incorrect specification, the number remains unchanged.  */
779 static bool
780 cmd_number (const char *com, const char *val, void *place)
781 {
782   if (!simple_atoi (val, val + strlen (val), place)
783       || *(int *) place < 0)
784     {
785       fprintf (stderr, _("%s: %s: Invalid number %s.\n"),
786                exec_name, com, quote (val));
787       return false;
788     }
789   return true;
790 }
791
792 /* Similar to cmd_number(), only accepts `inf' as a synonym for 0.  */
793 static bool
794 cmd_number_inf (const char *com, const char *val, void *place)
795 {
796   if (!strcasecmp (val, "inf"))
797     {
798       *(int *) place = 0;
799       return true;
800     }
801   return cmd_number (com, val, place);
802 }
803
804 /* Copy (strdup) the string at COM to a new location and place a
805    pointer to *PLACE.  */
806 static bool
807 cmd_string (const char *com, const char *val, void *place)
808 {
809   char **pstring = (char **)place;
810
811   xfree_null (*pstring);
812   *pstring = xstrdup (val);
813   return true;
814 }
815
816 #if defined(WINDOWS) || defined(MSDOS)
817 # define ISSEP(c) ((c) == '/' || (c) == '\\')
818 #else
819 # define ISSEP(c) ((c) == '/')
820 #endif
821
822 /* Like the above, but handles tilde-expansion when reading a user's
823    `.wgetrc'.  In that case, and if VAL begins with `~', the tilde
824    gets expanded to the user's home directory.  */
825 static bool
826 cmd_file (const char *com, const char *val, void *place)
827 {
828   char **pstring = (char **)place;
829
830   xfree_null (*pstring);
831
832   /* #### If VAL is empty, perhaps should set *PLACE to NULL.  */
833
834   if (!enable_tilde_expansion || !(*val == '~' && ISSEP (val[1])))
835     {
836     noexpand:
837       *pstring = xstrdup (val);
838     }
839   else
840     {
841       int homelen;
842       char *home = home_dir ();
843       if (!home)
844         goto noexpand;
845
846       homelen = strlen (home);
847       while (homelen && ISSEP (home[homelen - 1]))
848         home[--homelen] = '\0';
849
850       /* Skip the leading "~/". */
851       for (++val; ISSEP (*val); val++)
852         ;
853
854       *pstring = concat_strings (home, "/", val, (char *) 0);
855     }
856
857 #if defined(WINDOWS) || defined(MSDOS)
858   /* Convert "\" to "/". */
859   {
860     char *s;
861     for (s = *pstring; *s; s++)
862       if (*s == '\\')
863         *s = '/';
864   }
865 #endif
866   return true;
867 }
868
869 /* Like cmd_file, but strips trailing '/' characters.  */
870 static bool
871 cmd_directory (const char *com, const char *val, void *place)
872 {
873   char *s, *t;
874
875   /* Call cmd_file() for tilde expansion and separator
876      canonicalization (backslash -> slash under Windows).  These
877      things should perhaps be in a separate function.  */
878   if (!cmd_file (com, val, place))
879     return false;
880
881   s = *(char **)place;
882   t = s + strlen (s);
883   while (t > s && *--t == '/')
884     *t = '\0';
885
886   return true;
887 }
888
889 /* Split VAL by space to a vector of values, and append those values
890    to vector pointed to by the PLACE argument.  If VAL is empty, the
891    PLACE vector is cleared instead.  */
892
893 static bool
894 cmd_vector (const char *com, const char *val, void *place)
895 {
896   char ***pvec = (char ***)place;
897
898   if (*val)
899     *pvec = merge_vecs (*pvec, sepstring (val));
900   else
901     {
902       free_vec (*pvec);
903       *pvec = NULL;
904     }
905   return true;
906 }
907
908 static bool
909 cmd_directory_vector (const char *com, const char *val, void *place)
910 {
911   char ***pvec = (char ***)place;
912
913   if (*val)
914     {
915       /* Strip the trailing slashes from directories.  */
916       char **t, **seps;
917
918       seps = sepstring (val);
919       for (t = seps; t && *t; t++)
920         {
921           int len = strlen (*t);
922           /* Skip degenerate case of root directory.  */
923           if (len > 1)
924             {
925               if ((*t)[len - 1] == '/')
926                 (*t)[len - 1] = '\0';
927             }
928         }
929       *pvec = merge_vecs (*pvec, seps);
930     }
931   else
932     {
933       free_vec (*pvec);
934       *pvec = NULL;
935     }
936   return true;
937 }
938
939 /* Engine for cmd_bytes and cmd_bytes_sum: converts a string such as
940    "100k" or "2.5G" to a floating point number.  */
941
942 static bool
943 parse_bytes_helper (const char *val, double *result)
944 {
945   double number, mult;
946   const char *end = val + strlen (val);
947
948   /* Check for "inf".  */
949   if (0 == strcmp (val, "inf"))
950     {
951       *result = 0;
952       return true;
953     }
954
955   /* Strip trailing whitespace.  */
956   while (val < end && c_isspace (end[-1]))
957     --end;
958   if (val == end)
959     return false;
960
961   switch (c_tolower (end[-1]))
962     {
963     case 'k':
964       --end, mult = 1024.0;
965       break;
966     case 'm':
967       --end, mult = 1048576.0;
968       break;
969     case 'g':
970       --end, mult = 1073741824.0;
971       break;
972     case 't':
973       --end, mult = 1099511627776.0;
974       break;
975     default:
976       /* Not a recognized suffix: assume it's a digit.  (If not,
977          simple_atof will raise an error.)  */
978       mult = 1;
979     }
980
981   /* Skip leading and trailing whitespace. */
982   while (val < end && c_isspace (*val))
983     ++val;
984   while (val < end && c_isspace (end[-1]))
985     --end;
986   if (val == end)
987     return false;
988
989   if (!simple_atof (val, end, &number) || number < 0)
990     return false;
991
992   *result = number * mult;
993   return true;
994 }
995
996 /* Parse VAL as a number and set its value to PLACE (which should
997    point to a wgint).
998
999    By default, the value is assumed to be in bytes.  If "K", "M", or
1000    "G" are appended, the value is multiplied with 1<<10, 1<<20, or
1001    1<<30, respectively.  Floating point values are allowed and are
1002    cast to integer before use.  The idea is to be able to use things
1003    like 1.5k instead of "1536".
1004
1005    The string "inf" is returned as 0.
1006
1007    In case of error, false is returned and memory pointed to by PLACE
1008    remains unmodified.  */
1009
1010 static bool
1011 cmd_bytes (const char *com, const char *val, void *place)
1012 {
1013   double byte_value;
1014   if (!parse_bytes_helper (val, &byte_value))
1015     {
1016       fprintf (stderr, _("%s: %s: Invalid byte value %s\n"),
1017                exec_name, com, quote (val));
1018       return false;
1019     }
1020   *(wgint *)place = (wgint)byte_value;
1021   return true;
1022 }
1023
1024 /* Like cmd_bytes, but PLACE is interpreted as a pointer to
1025    SIZE_SUM.  It works by converting the string to double, therefore
1026    working with values up to 2^53-1 without loss of precision.  This
1027    value (8192 TB) is large enough to serve for a while.  */
1028
1029 static bool
1030 cmd_bytes_sum (const char *com, const char *val, void *place)
1031 {
1032   double byte_value;
1033   if (!parse_bytes_helper (val, &byte_value))
1034     {
1035       fprintf (stderr, _("%s: %s: Invalid byte value %s\n"),
1036                exec_name, com, quote (val));
1037       return false;
1038     }
1039   *(SUM_SIZE_INT *) place = (SUM_SIZE_INT) byte_value;
1040   return true;
1041 }
1042
1043 /* Store the value of VAL to *OUT.  The value is a time period, by
1044    default expressed in seconds, but also accepting suffixes "m", "h",
1045    "d", and "w" for minutes, hours, days, and weeks respectively.  */
1046
1047 static bool
1048 cmd_time (const char *com, const char *val, void *place)
1049 {
1050   double number, mult;
1051   const char *end = val + strlen (val);
1052
1053   /* Strip trailing whitespace.  */
1054   while (val < end && c_isspace (end[-1]))
1055     --end;
1056
1057   if (val == end)
1058     {
1059     err:
1060       fprintf (stderr, _("%s: %s: Invalid time period %s\n"),
1061                exec_name, com, quote (val));
1062       return false;
1063     }
1064
1065   switch (c_tolower (end[-1]))
1066     {
1067     case 's':
1068       --end, mult = 1;          /* seconds */
1069       break;
1070     case 'm':
1071       --end, mult = 60;         /* minutes */
1072       break;
1073     case 'h':
1074       --end, mult = 3600;       /* hours */
1075       break;
1076     case 'd':
1077       --end, mult = 86400.0;    /* days */
1078       break;
1079     case 'w':
1080       --end, mult = 604800.0;   /* weeks */
1081       break;
1082     default:
1083       /* Not a recognized suffix: assume it belongs to the number.
1084          (If not, simple_atof will raise an error.)  */
1085       mult = 1;
1086     }
1087
1088   /* Skip leading and trailing whitespace. */
1089   while (val < end && c_isspace (*val))
1090     ++val;
1091   while (val < end && c_isspace (end[-1]))
1092     --end;
1093   if (val == end)
1094     goto err;
1095
1096   if (!simple_atof (val, end, &number))
1097     goto err;
1098
1099   *(double *)place = number * mult;
1100   return true;
1101 }
1102
1103 #ifdef HAVE_SSL
1104 static bool
1105 cmd_cert_type (const char *com, const char *val, void *place)
1106 {
1107   static const struct decode_item choices[] = {
1108     { "pem", keyfile_pem },
1109     { "der", keyfile_asn1 },
1110     { "asn1", keyfile_asn1 },
1111   };
1112   int ok = decode_string (val, choices, countof (choices), place);
1113   if (!ok)
1114     fprintf (stderr, _("%s: %s: Invalid value %s.\n"), exec_name, com, quote (val));
1115   return ok;
1116 }
1117 #endif
1118 \f
1119 /* Specialized helper functions, used by `commands' to handle some
1120    options specially.  */
1121
1122 static bool check_user_specified_header (const char *);
1123
1124 static bool
1125 cmd_spec_dirstruct (const char *com, const char *val, void *place_ignored)
1126 {
1127   if (!cmd_boolean (com, val, &opt.dirstruct))
1128     return false;
1129   /* Since dirstruct behaviour is explicitly changed, no_dirstruct
1130      must be affected inversely.  */
1131   if (opt.dirstruct)
1132     opt.no_dirstruct = false;
1133   else
1134     opt.no_dirstruct = true;
1135   return true;
1136 }
1137
1138 static bool
1139 cmd_spec_header (const char *com, const char *val, void *place_ignored)
1140 {
1141   /* Empty value means reset the list of headers. */
1142   if (*val == '\0')
1143     {
1144       free_vec (opt.user_headers);
1145       opt.user_headers = NULL;
1146       return true;
1147     }
1148
1149   if (!check_user_specified_header (val))
1150     {
1151       fprintf (stderr, _("%s: %s: Invalid header %s.\n"),
1152                exec_name, com, quote (val));
1153       return false;
1154     }
1155   opt.user_headers = vec_append (opt.user_headers, val);
1156   return true;
1157 }
1158
1159 static bool
1160 cmd_spec_htmlify (const char *com, const char *val, void *place_ignored)
1161 {
1162   int flag = cmd_boolean (com, val, &opt.htmlify);
1163   if (flag && !opt.htmlify)
1164     opt.remove_listing = false;
1165   return flag;
1166 }
1167
1168 /* Set the "mirror" mode.  It means: recursive download, timestamping,
1169    no limit on max. recursion depth, and don't remove listings.  */
1170
1171 static bool
1172 cmd_spec_mirror (const char *com, const char *val, void *place_ignored)
1173 {
1174   int mirror;
1175
1176   if (!cmd_boolean (com, val, &mirror))
1177     return false;
1178   if (mirror)
1179     {
1180       opt.recursive = true;
1181       if (!opt.no_dirstruct)
1182         opt.dirstruct = true;
1183       opt.timestamping = true;
1184       opt.reclevel = INFINITE_RECURSION;
1185       opt.remove_listing = false;
1186     }
1187   return true;
1188 }
1189
1190 /* Validate --prefer-family and set the choice.  Allowed values are
1191    "IPv4", "IPv6", and "none".  */
1192
1193 static bool
1194 cmd_spec_prefer_family (const char *com, const char *val, void *place_ignored)
1195 {
1196   static const struct decode_item choices[] = {
1197     { "IPv4", prefer_ipv4 },
1198     { "IPv6", prefer_ipv6 },
1199     { "none", prefer_none },
1200   };
1201   int prefer_family = prefer_none;
1202   int ok = decode_string (val, choices, countof (choices), &prefer_family);
1203   if (!ok)
1204     fprintf (stderr, _("%s: %s: Invalid value %s.\n"), exec_name, com, quote (val));
1205   opt.prefer_family = prefer_family;
1206   return ok;
1207 }
1208
1209 /* Set progress.type to VAL, but verify that it's a valid progress
1210    implementation before that.  */
1211
1212 static bool
1213 cmd_spec_progress (const char *com, const char *val, void *place_ignored)
1214 {
1215   if (!valid_progress_implementation_p (val))
1216     {
1217       fprintf (stderr, _("%s: %s: Invalid progress type %s.\n"),
1218                exec_name, com, quote (val));
1219       return false;
1220     }
1221   xfree_null (opt.progress_type);
1222
1223   /* Don't call set_progress_implementation here.  It will be called
1224      in main() when it becomes clear what the log output is.  */
1225   opt.progress_type = xstrdup (val);
1226   return true;
1227 }
1228
1229 /* Set opt.recursive to VAL as with cmd_boolean.  If opt.recursive is
1230    set to true, also set opt.dirstruct to true, unless opt.no_dirstruct
1231    is specified.  */
1232
1233 static bool
1234 cmd_spec_recursive (const char *com, const char *val, void *place_ignored)
1235 {
1236   if (!cmd_boolean (com, val, &opt.recursive))
1237     return false;
1238   else
1239     {
1240       if (opt.recursive && !opt.no_dirstruct)
1241         opt.dirstruct = true;
1242     }
1243   return true;
1244 }
1245
1246 static bool
1247 cmd_spec_restrict_file_names (const char *com, const char *val, void *place_ignored)
1248 {
1249   int restrict_os = opt.restrict_files_os;
1250   int restrict_ctrl = opt.restrict_files_ctrl;
1251   int restrict_case = opt.restrict_files_case;
1252
1253   const char *end;
1254
1255 #define VAL_IS(string_literal) BOUNDED_EQUAL (val, end, string_literal)
1256
1257   do
1258     {
1259       end = strchr (val, ',');
1260       if (!end)
1261         end = val + strlen (val);
1262       
1263       if (VAL_IS ("unix"))
1264         restrict_os = restrict_unix;
1265       else if (VAL_IS ("windows"))
1266         restrict_os = restrict_windows;
1267       else if (VAL_IS ("lowercase"))
1268         restrict_case = restrict_lowercase;
1269       else if (VAL_IS ("uppercase"))
1270         restrict_case = restrict_uppercase;
1271       else if (VAL_IS ("nocontrol"))
1272         restrict_ctrl = false;
1273       else
1274         {
1275           fprintf (stderr,
1276                    _("%s: %s: Invalid restriction %s, use [unix|windows],[lowercase|uppercase],[nocontrol].\n"),
1277                    exec_name, com, quote (val));
1278           return false;
1279         }
1280
1281       if (*end) 
1282         val = end + 1;
1283     }
1284   while (*val && *end);
1285
1286 #undef VAL_IS
1287
1288   opt.restrict_files_os = restrict_os;
1289   opt.restrict_files_ctrl = restrict_ctrl;
1290   opt.restrict_files_case = restrict_case;
1291   
1292   return true;
1293 }
1294
1295 #ifdef HAVE_SSL
1296 static bool
1297 cmd_spec_secure_protocol (const char *com, const char *val, void *place)
1298 {
1299   static const struct decode_item choices[] = {
1300     { "auto", secure_protocol_auto },
1301     { "sslv2", secure_protocol_sslv2 },
1302     { "sslv3", secure_protocol_sslv3 },
1303     { "tlsv1", secure_protocol_tlsv1 },
1304   };
1305   int ok = decode_string (val, choices, countof (choices), place);
1306   if (!ok)
1307     fprintf (stderr, _("%s: %s: Invalid value %s.\n"), exec_name, com, quote (val));
1308   return ok;
1309 }
1310 #endif
1311
1312 /* Set all three timeout values. */
1313
1314 static bool
1315 cmd_spec_timeout (const char *com, const char *val, void *place_ignored)
1316 {
1317   double value;
1318   if (!cmd_time (com, val, &value))
1319     return false;
1320   opt.read_timeout = value;
1321   opt.connect_timeout = value;
1322   opt.dns_timeout = value;
1323   return true;
1324 }
1325
1326 static bool
1327 cmd_spec_useragent (const char *com, const char *val, void *place_ignored)
1328 {
1329   /* Disallow embedded newlines.  */
1330   if (strchr (val, '\n'))
1331     {
1332       fprintf (stderr, _("%s: %s: Invalid value %s.\n"),
1333                exec_name, com, quote (val));
1334       return false;
1335     }
1336   xfree_null (opt.useragent);
1337   opt.useragent = xstrdup (val);
1338   return true;
1339 }
1340
1341 /* The "verbose" option cannot be cmd_boolean because the variable is
1342    not bool -- it's of type int (-1 means uninitialized because of
1343    some random hackery for disallowing -q -v).  */
1344
1345 static bool
1346 cmd_spec_verbose (const char *com, const char *val, void *place_ignored)
1347 {
1348   bool flag;
1349   if (cmd_boolean (com, val, &flag))
1350     {
1351       opt.verbose = flag;
1352       return true;
1353     }
1354   return false;
1355 }
1356 \f
1357 /* Miscellaneous useful routines.  */
1358
1359 /* A very simple atoi clone, more useful than atoi because it works on
1360    delimited strings, and has error reportage.  Returns true on success,
1361    false on failure.  If successful, stores result to *DEST.  */
1362
1363 static bool
1364 simple_atoi (const char *beg, const char *end, int *dest)
1365 {
1366   int result = 0;
1367   bool negative = false;
1368   const char *p = beg;
1369
1370   while (p < end && c_isspace (*p))
1371     ++p;
1372   if (p < end && (*p == '-' || *p == '+'))
1373     {
1374       negative = (*p == '-');
1375       ++p;
1376     }
1377   if (p == end)
1378     return false;
1379
1380   /* Read negative numbers in a separate loop because the most
1381      negative integer cannot be represented as a positive number.  */
1382
1383   if (!negative)
1384     for (; p < end && c_isdigit (*p); p++)
1385       {
1386         int next = (10 * result) + (*p - '0');
1387         if (next < result)
1388           return false;         /* overflow */
1389         result = next;
1390       }
1391   else
1392     for (; p < end && c_isdigit (*p); p++)
1393       {
1394         int next = (10 * result) - (*p - '0');
1395         if (next > result)
1396           return false;         /* underflow */
1397         result = next;
1398       }
1399
1400   if (p != end)
1401     return false;
1402
1403   *dest = result;
1404   return true;
1405 }
1406
1407 /* Trivial atof, with error reporting.  Handles "<digits>[.<digits>]",
1408    doesn't handle exponential notation.  Returns true on success,
1409    false on failure.  In case of success, stores its result to
1410    *DEST.  */
1411
1412 static bool
1413 simple_atof (const char *beg, const char *end, double *dest)
1414 {
1415   double result = 0;
1416
1417   bool negative = false;
1418   bool seen_dot = false;
1419   bool seen_digit = false;
1420   double divider = 1;
1421
1422   const char *p = beg;
1423
1424   while (p < end && c_isspace (*p))
1425     ++p;
1426   if (p < end && (*p == '-' || *p == '+'))
1427     {
1428       negative = (*p == '-');
1429       ++p;
1430     }
1431
1432   for (; p < end; p++)
1433     {
1434       char ch = *p;
1435       if (c_isdigit (ch))
1436         {
1437           if (!seen_dot)
1438             result = (10 * result) + (ch - '0');
1439           else
1440             result += (ch - '0') / (divider *= 10);
1441           seen_digit = true;
1442         }
1443       else if (ch == '.')
1444         {
1445           if (!seen_dot)
1446             seen_dot = true;
1447           else
1448             return false;
1449         }
1450       else
1451         return false;
1452     }
1453   if (!seen_digit)
1454     return false;
1455   if (negative)
1456     result = -result;
1457
1458   *dest = result;
1459   return true;
1460 }
1461
1462 /* Verify that the user-specified header in S is valid.  It must
1463    contain a colon preceded by non-white-space characters and must not
1464    contain newlines.  */
1465
1466 static bool
1467 check_user_specified_header (const char *s)
1468 {
1469   const char *p;
1470
1471   for (p = s; *p && *p != ':' && !c_isspace (*p); p++)
1472     ;
1473   /* The header MUST contain `:' preceded by at least one
1474      non-whitespace character.  */
1475   if (*p != ':' || p == s)
1476     return false;
1477   /* The header MUST NOT contain newlines.  */
1478   if (strchr (s, '\n'))
1479     return false;
1480   return true;
1481 }
1482
1483 /* Decode VAL into a number, according to ITEMS. */
1484
1485 static bool
1486 decode_string (const char *val, const struct decode_item *items, int itemcount,
1487                int *place)
1488 {
1489   int i;
1490   for (i = 0; i < itemcount; i++)
1491     if (0 == strcasecmp (val, items[i].name))
1492       {
1493         *place = items[i].code;
1494         return true;
1495       }
1496   return false;
1497 }
1498
1499 \f
1500 void cleanup_html_url (void);
1501
1502
1503 /* Free the memory allocated by global variables.  */
1504 void
1505 cleanup (void)
1506 {
1507   /* Free external resources, close files, etc. */
1508
1509   if (output_stream)
1510     fclose (output_stream);
1511   /* No need to check for error because Wget flushes its output (and
1512      checks for errors) after any data arrives.  */
1513
1514   /* We're exiting anyway so there's no real need to call free()
1515      hundreds of times.  Skipping the frees will make Wget exit
1516      faster.
1517
1518      However, when detecting leaks, it's crucial to free() everything
1519      because then you can find the real leaks, i.e. the allocated
1520      memory which grows with the size of the program.  */
1521
1522 #ifdef DEBUG_MALLOC
1523   convert_cleanup ();
1524   res_cleanup ();
1525   http_cleanup ();
1526   cleanup_html_url ();
1527   host_cleanup ();
1528   log_cleanup ();
1529
1530   {
1531     extern acc_t *netrc_list;
1532     free_netrc (netrc_list);
1533   }
1534   xfree_null (opt.lfilename);
1535   xfree_null (opt.dir_prefix);
1536   xfree_null (opt.input_filename);
1537   xfree_null (opt.output_document);
1538   free_vec (opt.accepts);
1539   free_vec (opt.rejects);
1540   free_vec (opt.excludes);
1541   free_vec (opt.includes);
1542   free_vec (opt.domains);
1543   free_vec (opt.follow_tags);
1544   free_vec (opt.ignore_tags);
1545   xfree_null (opt.progress_type);
1546   xfree_null (opt.ftp_user);
1547   xfree_null (opt.ftp_passwd);
1548   xfree_null (opt.ftp_proxy);
1549   xfree_null (opt.https_proxy);
1550   xfree_null (opt.http_proxy);
1551   free_vec (opt.no_proxy);
1552   xfree_null (opt.useragent);
1553   xfree_null (opt.referer);
1554   xfree_null (opt.http_user);
1555   xfree_null (opt.http_passwd);
1556   free_vec (opt.user_headers);
1557 # ifdef HAVE_SSL
1558   xfree_null (opt.cert_file);
1559   xfree_null (opt.private_key);
1560   xfree_null (opt.ca_directory);
1561   xfree_null (opt.ca_cert);
1562   xfree_null (opt.random_file);
1563   xfree_null (opt.egd_file);
1564 # endif
1565   xfree_null (opt.bind_address);
1566   xfree_null (opt.cookies_input);
1567   xfree_null (opt.cookies_output);
1568   xfree_null (opt.user);
1569   xfree_null (opt.passwd);
1570   xfree_null (opt.base_href);
1571   
1572 #endif /* DEBUG_MALLOC */
1573 }
1574 \f
1575 /* Unit testing routines.  */
1576
1577 #ifdef TESTING
1578
1579 const char *
1580 test_commands_sorted()
1581 {
1582   int prev_idx = 0, next_idx = 1;
1583   int command_count = countof (commands) - 1;
1584   int cmp = 0;
1585   while (next_idx <= command_count)
1586     {
1587       cmp = strcasecmp (commands[prev_idx].name, commands[next_idx].name);
1588       if (cmp > 0)
1589         {
1590           mu_assert ("FAILED", false);
1591           break;
1592         }     
1593       else
1594         { 
1595           prev_idx ++;
1596           next_idx ++;
1597         }
1598     }
1599   return NULL;
1600 }
1601
1602 const char *
1603 test_cmd_spec_restrict_file_names()
1604 {
1605   int i;
1606   struct {
1607     char *val;
1608     int expected_restrict_files_os;
1609     int expected_restrict_files_ctrl;
1610     int expected_restrict_files_case;
1611     bool result;
1612   } test_array[] = {
1613     { "windows", restrict_windows, true, restrict_no_case_restriction, true },
1614     { "windows,", restrict_windows, true, restrict_no_case_restriction, true },
1615     { "windows,lowercase", restrict_windows, true, restrict_lowercase, true },
1616     { "unix,nocontrol,lowercase,", restrict_unix, false, restrict_lowercase, true },
1617   };
1618   
1619   for (i = 0; i < sizeof(test_array)/sizeof(test_array[0]); ++i) 
1620     {
1621       bool res;
1622       
1623       defaults();
1624       res = cmd_spec_restrict_file_names ("dummy", test_array[i].val, NULL);
1625
1626       /*
1627       fprintf (stderr, "test_cmd_spec_restrict_file_names: TEST %d\n", i); fflush (stderr);
1628       fprintf (stderr, "opt.restrict_files_os: %d\n",   opt.restrict_files_os); fflush (stderr);
1629       fprintf (stderr, "opt.restrict_files_ctrl: %d\n", opt.restrict_files_ctrl); fflush (stderr);
1630       fprintf (stderr, "opt.restrict_files_case: %d\n", opt.restrict_files_case); fflush (stderr);
1631       */
1632       mu_assert ("test_cmd_spec_restrict_file_names: wrong result", 
1633                  res == test_array[i].result
1634                  && opt.restrict_files_os   == test_array[i].expected_restrict_files_os 
1635                  && opt.restrict_files_ctrl == test_array[i].expected_restrict_files_ctrl 
1636                  && opt.restrict_files_case == test_array[i].expected_restrict_files_case);
1637     }
1638
1639   return NULL;
1640 }
1641
1642 #endif /* TESTING */
1643