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