]> sjero.net Git - wget/blob - src/init.c
Implement --config.
[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 #include <stdbool.h>
36 #ifdef HAVE_UNISTD_H
37 # include <unistd.h>
38 #endif
39 #include <string.h>
40 #include <errno.h>
41 #include <limits.h>
42 /* not all systems provide PATH_MAX in limits.h */
43 #ifndef PATH_MAX
44 # include <sys/param.h>
45 # ifndef PATH_MAX
46 #  define PATH_MAX MAXPATHLEN
47 # endif
48 #endif
49
50
51 #ifdef HAVE_PWD_H
52 # include <pwd.h>
53 #endif
54 #include <assert.h>
55
56 #include "utils.h"
57 #include "init.h"
58 #include "host.h"
59 #include "netrc.h"
60 #include "progress.h"
61 #include "recur.h"              /* for INFINITE_RECURSION */
62 #include "convert.h"            /* for convert_cleanup */
63 #include "res.h"                /* for res_cleanup */
64 #include "http.h"               /* for http_cleanup */
65 #include "retr.h"               /* for output_stream */
66
67 #ifdef TESTING
68 #include "test.h"
69 #endif
70
71
72
73 #define CMD_DECLARE(func) static bool func (const char *, const char *, void *)
74
75 CMD_DECLARE (cmd_boolean);
76 CMD_DECLARE (cmd_bytes);
77 CMD_DECLARE (cmd_bytes_sum);
78 #ifdef HAVE_SSL
79 CMD_DECLARE (cmd_cert_type);
80 #endif
81 CMD_DECLARE (cmd_directory_vector);
82 CMD_DECLARE (cmd_number);
83 CMD_DECLARE (cmd_number_inf);
84 CMD_DECLARE (cmd_string);
85 CMD_DECLARE (cmd_file);
86 CMD_DECLARE (cmd_directory);
87 CMD_DECLARE (cmd_time);
88 CMD_DECLARE (cmd_vector);
89
90 CMD_DECLARE (cmd_spec_dirstruct);
91 CMD_DECLARE (cmd_spec_header);
92 CMD_DECLARE (cmd_spec_htmlify);
93 CMD_DECLARE (cmd_spec_mirror);
94 CMD_DECLARE (cmd_spec_prefer_family);
95 CMD_DECLARE (cmd_spec_progress);
96 CMD_DECLARE (cmd_spec_recursive);
97 CMD_DECLARE (cmd_spec_restrict_file_names);
98 #ifdef HAVE_SSL
99 CMD_DECLARE (cmd_spec_secure_protocol);
100 #endif
101 CMD_DECLARE (cmd_spec_timeout);
102 CMD_DECLARE (cmd_spec_useragent);
103 CMD_DECLARE (cmd_spec_verbose);
104
105 /* List of recognized commands, each consisting of name, place and
106    function.  When adding a new command, simply add it to the list,
107    but be sure to keep the list sorted alphabetically, as
108    command_by_name's binary search depends on it.  Also, be sure to
109    add any entries that allocate memory (e.g. cmd_string and
110    cmd_vector) to the cleanup() function below. */
111
112 static const struct {
113   const char *name;
114   void *place;
115   bool (*action) (const char *, const char *, void *);
116 } commands[] = {
117   /* KEEP THIS LIST ALPHABETICALLY SORTED */
118   { "accept",           &opt.accepts,           cmd_vector },
119   { "addhostdir",       &opt.add_hostdir,       cmd_boolean },
120   { "adjustextension",  &opt.adjust_extension,  cmd_boolean },
121   { "alwaysrest",       &opt.always_rest,       cmd_boolean }, /* deprecated */
122   { "askpassword",      &opt.ask_passwd,        cmd_boolean },
123   { "authnochallenge",  &opt.auth_without_challenge,
124                                                 cmd_boolean },
125   { "background",       &opt.background,        cmd_boolean },
126   { "backupconverted",  &opt.backup_converted,  cmd_boolean },
127   { "backups",          &opt.backups,           cmd_number },
128   { "base",             &opt.base_href,         cmd_string },
129   { "bindaddress",      &opt.bind_address,      cmd_string },
130 #ifdef HAVE_SSL
131   { "cacertificate",    &opt.ca_cert,           cmd_file },
132 #endif
133   { "cache",            &opt.allow_cache,       cmd_boolean },
134 #ifdef HAVE_SSL
135   { "cadirectory",      &opt.ca_directory,      cmd_directory },
136   { "certificate",      &opt.cert_file,         cmd_file },
137   { "certificatetype",  &opt.cert_type,         cmd_cert_type },
138   { "checkcertificate", &opt.check_cert,        cmd_boolean },
139 #endif
140   { "chooseconfig",     &opt.choose_config,     cmd_file },
141   { "connecttimeout",   &opt.connect_timeout,   cmd_time },
142   { "contentdisposition", &opt.content_disposition, cmd_boolean },
143   { "continue",         &opt.always_rest,       cmd_boolean },
144   { "convertlinks",     &opt.convert_links,     cmd_boolean },
145   { "cookies",          &opt.cookies,           cmd_boolean },
146   { "cutdirs",          &opt.cut_dirs,          cmd_number },
147 #ifdef ENABLE_DEBUG
148   { "debug",            &opt.debug,             cmd_boolean },
149 #endif
150   { "defaultpage",      &opt.default_page,      cmd_string},
151   { "deleteafter",      &opt.delete_after,      cmd_boolean },
152   { "dirprefix",        &opt.dir_prefix,        cmd_directory },
153   { "dirstruct",        NULL,                   cmd_spec_dirstruct },
154   { "dnscache",         &opt.dns_cache,         cmd_boolean },
155   { "dnstimeout",       &opt.dns_timeout,       cmd_time },
156   { "domains",          &opt.domains,           cmd_vector },
157   { "dotbytes",         &opt.dot_bytes,         cmd_bytes },
158   { "dotsinline",       &opt.dots_in_line,      cmd_number },
159   { "dotspacing",       &opt.dot_spacing,       cmd_number },
160   { "dotstyle",         &opt.dot_style,         cmd_string }, /* deprecated */
161 #ifdef HAVE_SSL
162   { "egdfile",          &opt.egd_file,          cmd_file },
163 #endif
164   { "excludedirectories", &opt.excludes,        cmd_directory_vector },
165   { "excludedomains",   &opt.exclude_domains,   cmd_vector },
166   { "followftp",        &opt.follow_ftp,        cmd_boolean },
167   { "followtags",       &opt.follow_tags,       cmd_vector },
168   { "forcehtml",        &opt.force_html,        cmd_boolean },
169   { "ftppasswd",        &opt.ftp_passwd,        cmd_string }, /* deprecated */
170   { "ftppassword",      &opt.ftp_passwd,        cmd_string },
171   { "ftpproxy",         &opt.ftp_proxy,         cmd_string },
172 #ifdef __VMS
173   { "ftpstmlf",         &opt.ftp_stmlf,         cmd_boolean },
174 #endif /* def __VMS */
175   { "ftpuser",          &opt.ftp_user,          cmd_string },
176   { "glob",             &opt.ftp_glob,          cmd_boolean },
177   { "header",           NULL,                   cmd_spec_header },
178   { "htmlextension",    &opt.adjust_extension,  cmd_boolean }, /* deprecated */
179   { "htmlify",          NULL,                   cmd_spec_htmlify },
180   { "httpkeepalive",    &opt.http_keep_alive,   cmd_boolean },
181   { "httppasswd",       &opt.http_passwd,       cmd_string }, /* deprecated */
182   { "httppassword",     &opt.http_passwd,       cmd_string },
183   { "httpproxy",        &opt.http_proxy,        cmd_string },
184   { "httpsproxy",       &opt.https_proxy,       cmd_string },
185   { "httpuser",         &opt.http_user,         cmd_string },
186   { "ignorecase",       &opt.ignore_case,       cmd_boolean },
187   { "ignorelength",     &opt.ignore_length,     cmd_boolean },
188   { "ignoretags",       &opt.ignore_tags,       cmd_vector },
189   { "includedirectories", &opt.includes,        cmd_directory_vector },
190 #ifdef ENABLE_IPV6
191   { "inet4only",        &opt.ipv4_only,         cmd_boolean },
192   { "inet6only",        &opt.ipv6_only,         cmd_boolean },
193 #endif
194   { "input",            &opt.input_filename,    cmd_file },
195   { "iri",              &opt.enable_iri,        cmd_boolean },
196   { "keepsessioncookies", &opt.keep_session_cookies, cmd_boolean },
197   { "limitrate",        &opt.limit_rate,        cmd_bytes },
198   { "loadcookies",      &opt.cookies_input,     cmd_file },
199   { "localencoding",    &opt.locale,            cmd_string },
200   { "logfile",          &opt.lfilename,         cmd_file },
201   { "login",            &opt.ftp_user,          cmd_string },/* deprecated*/
202   { "maxredirect",      &opt.max_redirect,      cmd_number },
203   { "mirror",           NULL,                   cmd_spec_mirror },
204   { "netrc",            &opt.netrc,             cmd_boolean },
205   { "noclobber",        &opt.noclobber,         cmd_boolean },
206   { "noparent",         &opt.no_parent,         cmd_boolean },
207   { "noproxy",          &opt.no_proxy,          cmd_vector },
208   { "numtries",         &opt.ntry,              cmd_number_inf },/* deprecated*/
209   { "outputdocument",   &opt.output_document,   cmd_file },
210   { "pagerequisites",   &opt.page_requisites,   cmd_boolean },
211   { "passiveftp",       &opt.ftp_pasv,          cmd_boolean },
212   { "passwd",           &opt.ftp_passwd,        cmd_string },/* deprecated*/
213   { "password",         &opt.passwd,            cmd_string },
214   { "postdata",         &opt.post_data,         cmd_string },
215   { "postfile",         &opt.post_file_name,    cmd_file },
216   { "preferfamily",     NULL,                   cmd_spec_prefer_family },
217   { "preservepermissions", &opt.preserve_perm,  cmd_boolean },/* deprecated */
218 #ifdef HAVE_SSL
219   { "privatekey",       &opt.private_key,       cmd_file },
220   { "privatekeytype",   &opt.private_key_type,  cmd_cert_type },
221 #endif
222   { "progress",         &opt.progress_type,     cmd_spec_progress },
223   { "protocoldirectories", &opt.protocol_directories, cmd_boolean },
224   { "proxypasswd",      &opt.proxy_passwd,      cmd_string }, /* deprecated */
225   { "proxypassword",    &opt.proxy_passwd,      cmd_string },
226   { "proxyuser",        &opt.proxy_user,        cmd_string },
227   { "quiet",            &opt.quiet,             cmd_boolean },
228   { "quota",            &opt.quota,             cmd_bytes_sum },
229 #ifdef HAVE_SSL
230   { "randomfile",       &opt.random_file,       cmd_file },
231 #endif
232   { "randomwait",       &opt.random_wait,       cmd_boolean },
233   { "readtimeout",      &opt.read_timeout,      cmd_time },
234   { "reclevel",         &opt.reclevel,          cmd_number_inf },
235   { "recursive",        NULL,                   cmd_spec_recursive },
236   { "referer",          &opt.referer,           cmd_string },
237   { "reject",           &opt.rejects,           cmd_vector },
238   { "relativeonly",     &opt.relative_only,     cmd_boolean },
239   { "remoteencoding",   &opt.encoding_remote,   cmd_string },
240   { "removelisting",    &opt.remove_listing,    cmd_boolean },
241   { "restrictfilenames", NULL,                  cmd_spec_restrict_file_names },
242   { "retrsymlinks",     &opt.retr_symlinks,     cmd_boolean },
243   { "retryconnrefused", &opt.retry_connrefused, cmd_boolean },
244   { "robots",           &opt.use_robots,        cmd_boolean },
245   { "savecookies",      &opt.cookies_output,    cmd_file },
246   { "saveheaders",      &opt.save_headers,      cmd_boolean },
247 #ifdef HAVE_SSL
248   { "secureprotocol",   &opt.secure_protocol,   cmd_spec_secure_protocol },
249 #endif
250   { "serverresponse",   &opt.server_response,   cmd_boolean },
251   { "spanhosts",        &opt.spanhost,          cmd_boolean },
252   { "spider",           &opt.spider,            cmd_boolean },
253   { "strictcomments",   &opt.strict_comments,   cmd_boolean },
254   { "timeout",          NULL,                   cmd_spec_timeout },
255   { "timestamping",     &opt.timestamping,      cmd_boolean },
256   { "tries",            &opt.ntry,              cmd_number_inf },
257   { "trustservernames", &opt.trustservernames,  cmd_boolean },
258   { "unlink",           &opt.unlink,            cmd_boolean },
259   { "useproxy",         &opt.use_proxy,         cmd_boolean },
260   { "user",             &opt.user,              cmd_string },
261   { "useragent",        NULL,                   cmd_spec_useragent },
262   { "useservertimestamps", &opt.useservertimestamps, cmd_boolean },
263   { "verbose",          NULL,                   cmd_spec_verbose },
264   { "wait",             &opt.wait,              cmd_time },
265   { "waitretry",        &opt.waitretry,         cmd_time },
266 #ifdef USE_WATT32
267   { "wdebug",           &opt.wdebug,            cmd_boolean },
268 #endif
269 };
270
271 /* Look up CMDNAME in the commands[] and return its position in the
272    array.  If CMDNAME is not found, return -1.  */
273
274 static int
275 command_by_name (const char *cmdname)
276 {
277   /* Use binary search for speed.  Wget has ~100 commands, which
278      guarantees a worst case performance of 7 string comparisons.  */
279   int lo = 0, hi = countof (commands) - 1;
280
281   while (lo <= hi)
282     {
283       int mid = (lo + hi) >> 1;
284       int cmp = strcasecmp (cmdname, commands[mid].name);
285       if (cmp < 0)
286         hi = mid - 1;
287       else if (cmp > 0)
288         lo = mid + 1;
289       else
290         return mid;
291     }
292   return -1;
293 }
294 \f
295 /* Reset the variables to default values.  */
296 void
297 defaults (void)
298 {
299   char *tmp;
300
301   /* Most of the default values are 0 (and 0.0, NULL, and false).
302      Just reset everything, and fill in the non-zero values.  Note
303      that initializing pointers to NULL this way is technically
304      illegal, but porting Wget to a machine where NULL is not all-zero
305      bit pattern will be the least of the implementors' worries.  */
306   xzero (opt);
307
308   opt.cookies = true;
309   opt.verbose = -1;
310   opt.ntry = 20;
311   opt.reclevel = 5;
312   opt.add_hostdir = true;
313   opt.netrc = true;
314   opt.ftp_glob = true;
315   opt.htmlify = true;
316   opt.http_keep_alive = true;
317   opt.use_proxy = true;
318   tmp = getenv ("no_proxy");
319   if (tmp)
320     opt.no_proxy = sepstring (tmp);
321   opt.prefer_family = prefer_none;
322   opt.allow_cache = true;
323
324   opt.read_timeout = 900;
325   opt.use_robots = true;
326
327   opt.remove_listing = true;
328
329   opt.dot_bytes = 1024;
330   opt.dot_spacing = 10;
331   opt.dots_in_line = 50;
332
333   opt.dns_cache = true;
334   opt.ftp_pasv = true;
335
336 #ifdef HAVE_SSL
337   opt.check_cert = true;
338 #endif
339
340   /* The default for file name restriction defaults to the OS type. */
341 #if defined(WINDOWS) || defined(MSDOS) || defined(__CYGWIN__)
342   opt.restrict_files_os = restrict_windows;
343 #else
344   opt.restrict_files_os = restrict_unix;
345 #endif
346   opt.restrict_files_ctrl = true;
347   opt.restrict_files_nonascii = false;
348   opt.restrict_files_case = restrict_no_case_restriction;
349
350   opt.max_redirect = 20;
351
352   opt.waitretry = 10;
353
354 #ifdef ENABLE_IRI
355   opt.enable_iri = true;
356 #else
357   opt.enable_iri = false;
358 #endif
359   opt.locale = NULL;
360   opt.encoding_remote = NULL;
361
362   opt.useservertimestamps = true;
363 }
364 \f
365 /* Return the user's home directory (strdup-ed), or NULL if none is
366    found.  */
367 char *
368 home_dir (void)
369 {
370   static char buf[PATH_MAX];
371   static char *home;
372
373   if (!home)
374     {
375       home = getenv ("HOME");
376       if (!home)
377         {
378 #if defined(MSDOS)
379           /* Under MSDOS, if $HOME isn't defined, use the directory where
380              `wget.exe' resides.  */
381           const char *_w32_get_argv0 (void); /* in libwatt.a/pcconfig.c */
382           char *p;
383
384           strcpy (buf, _w32_get_argv0 ());
385           p = strrchr (buf, '/');            /* djgpp */
386           if (!p)
387             p = strrchr (buf, '\\');          /* others */
388           assert (p);
389           *p = '\0';
390           home = buf;
391 #elif !defined(WINDOWS)
392           /* If HOME is not defined, try getting it from the password
393              file.  */
394           struct passwd *pwd = getpwuid (getuid ());
395           if (!pwd || !pwd->pw_dir)
396             return NULL;
397           strcpy (buf, pwd->pw_dir);
398           home = buf;
399 #else  /* !WINDOWS */
400           /* Under Windows, if $HOME isn't defined, use the directory where
401              `wget.exe' resides.  */
402           home = ws_mypath ();
403 #endif /* WINDOWS */
404         }
405     }
406
407   return home ? xstrdup (home) : NULL;
408 }
409
410 /* Check the 'WGETRC' environment variable and return the file name
411    if  'WGETRC' is set and is a valid file.
412    If the `WGETRC' variable exists but the file does not exist, the
413    function will exit().  */
414 char *
415 wgetrc_env_file_name (void)
416 {
417   char *env = getenv ("WGETRC");
418   if (env && *env)
419     {
420       if (!file_exists_p (env))
421         {
422           fprintf (stderr, _("%s: WGETRC points to %s, which doesn't exist.\n"),
423                    exec_name, env);
424           exit (1);
425         }
426       return xstrdup (env);
427     }
428   return NULL;
429 }
430
431 /* Check for the existance of '$HOME/.wgetrc' and return it's path
432    if it exists and is set.  */
433 char *
434 wgetrc_user_file_name (void)
435 {
436   char *home;
437   char *file = NULL;
438   /* If that failed, try $HOME/.wgetrc (or equivalent).  */
439
440 #ifdef __VMS
441   file = "SYS$LOGIN:.wgetrc";
442 #else /* def __VMS */
443   home = home_dir ();
444   if (home)
445     file = aprintf ("%s/.wgetrc", home);
446   xfree_null (home);
447 #endif /* def __VMS [else] */
448
449   if (!file)
450     return NULL;
451   if (!file_exists_p (file))
452     {
453       xfree (file);
454       return NULL;
455     }
456   return file;
457 }
458
459 /* Return the path to the user's .wgetrc.  This is either the value of
460    `WGETRC' environment variable, or `$HOME/.wgetrc'.
461
462    Additionally, for windows, look in the directory where wget.exe
463    resides.  */
464 char *
465 wgetrc_file_name (void)
466 {
467   char *file = wgetrc_env_file_name ();
468   if (file && *file)
469     return file;
470
471   file = wgetrc_user_file_name ();
472
473 #ifdef WINDOWS
474   /* Under Windows, if we still haven't found .wgetrc, look for the file
475      `wget.ini' in the directory where `wget.exe' resides; we do this for
476      backward compatibility with previous versions of Wget.
477      SYSTEM_WGETRC should not be defined under WINDOWS.  */
478   if (!file)
479     {
480       char *home = home_dir ();
481       xfree_null (file);
482       file = NULL;
483       home = ws_mypath ();
484       if (home)
485         {
486           file = aprintf ("%s/wget.ini", home);
487           if (!file_exists_p (file))
488             {
489               xfree (file);
490               file = NULL;
491             }
492           xfree (home);
493         }
494     }
495 #endif /* WINDOWS */
496
497   return file;
498 }
499
500 /* Return values of parse_line. */
501 enum parse_line {
502   line_ok,
503   line_empty,
504   line_syntax_error,
505   line_unknown_command
506 };
507
508 static enum parse_line parse_line (const char *, char **, char **, int *);
509 static bool setval_internal (int, const char *, const char *);
510 static bool setval_internal_tilde (int, const char *, const char *);
511
512 /* Initialize variables from a wgetrc file.  Returns zero (failure) if
513    there were errors in the file.  */
514
515 bool
516 run_wgetrc (const char *file)
517 {
518   FILE *fp;
519   char *line;
520   int ln;
521   int errcnt = 0;
522
523   fp = fopen (file, "r");
524   if (!fp)
525     {
526       fprintf (stderr, _("%s: Cannot read %s (%s).\n"), exec_name,
527                file, strerror (errno));
528       return true;                      /* not a fatal error */
529     }
530   ln = 1;
531   while ((line = read_whole_line (fp)) != NULL)
532     {
533       char *com = NULL, *val = NULL;
534       int comind;
535
536       /* Parse the line.  */
537       switch (parse_line (line, &com, &val, &comind))
538         {
539         case line_ok:
540           /* If everything is OK, set the value.  */
541           if (!setval_internal_tilde (comind, com, val))
542             {
543               fprintf (stderr, _("%s: Error in %s at line %d.\n"),
544                        exec_name, file, ln);
545               ++errcnt;
546             }
547           break;
548         case line_syntax_error:
549           fprintf (stderr, _("%s: Syntax error in %s at line %d.\n"),
550                    exec_name, file, ln);
551           ++errcnt;
552           break;
553         case line_unknown_command:
554           fprintf (stderr, _("%s: Unknown command %s in %s at line %d.\n"),
555                    exec_name, quote (com), file, ln);
556           ++errcnt;
557           break;
558         case line_empty:
559           break;
560         default:
561           abort ();
562         }
563       xfree_null (com);
564       xfree_null (val);
565       xfree (line);
566       ++ln;
567     }
568   fclose (fp);
569
570   return errcnt == 0;
571 }
572
573 /* Initialize the defaults and run the system wgetrc and user's own
574    wgetrc.  */
575 void
576 initialize (void)
577 {
578   char *file, *env_sysrc;
579   bool ok = true;
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.choose_config);
1581   xfree_null (opt.lfilename);
1582   xfree_null (opt.dir_prefix);
1583   xfree_null (opt.input_filename);
1584   xfree_null (opt.output_document);
1585   free_vec (opt.accepts);
1586   free_vec (opt.rejects);
1587   free_vec (opt.excludes);
1588   free_vec (opt.includes);
1589   free_vec (opt.domains);
1590   free_vec (opt.follow_tags);
1591   free_vec (opt.ignore_tags);
1592   xfree_null (opt.progress_type);
1593   xfree_null (opt.ftp_user);
1594   xfree_null (opt.ftp_passwd);
1595   xfree_null (opt.ftp_proxy);
1596   xfree_null (opt.https_proxy);
1597   xfree_null (opt.http_proxy);
1598   free_vec (opt.no_proxy);
1599   xfree_null (opt.useragent);
1600   xfree_null (opt.referer);
1601   xfree_null (opt.http_user);
1602   xfree_null (opt.http_passwd);
1603   free_vec (opt.user_headers);
1604 # ifdef HAVE_SSL
1605   xfree_null (opt.cert_file);
1606   xfree_null (opt.private_key);
1607   xfree_null (opt.ca_directory);
1608   xfree_null (opt.ca_cert);
1609   xfree_null (opt.random_file);
1610   xfree_null (opt.egd_file);
1611 # endif
1612   xfree_null (opt.bind_address);
1613   xfree_null (opt.cookies_input);
1614   xfree_null (opt.cookies_output);
1615   xfree_null (opt.user);
1616   xfree_null (opt.passwd);
1617   xfree_null (opt.base_href);
1618
1619 #endif /* DEBUG_MALLOC */
1620 }
1621 \f
1622 /* Unit testing routines.  */
1623
1624 #ifdef TESTING
1625
1626 const char *
1627 test_commands_sorted()
1628 {
1629   int prev_idx = 0, next_idx = 1;
1630   int command_count = countof (commands) - 1;
1631   int cmp = 0;
1632   while (next_idx <= command_count)
1633     {
1634       cmp = strcasecmp (commands[prev_idx].name, commands[next_idx].name);
1635       if (cmp > 0)
1636         {
1637           mu_assert ("FAILED", false);
1638           break;
1639         }
1640       else
1641         {
1642           prev_idx ++;
1643           next_idx ++;
1644         }
1645     }
1646   return NULL;
1647 }
1648
1649 const char *
1650 test_cmd_spec_restrict_file_names()
1651 {
1652   int i;
1653   struct {
1654     char *val;
1655     int expected_restrict_files_os;
1656     int expected_restrict_files_ctrl;
1657     int expected_restrict_files_case;
1658     bool result;
1659   } test_array[] = {
1660     { "windows", restrict_windows, true, restrict_no_case_restriction, true },
1661     { "windows,", restrict_windows, true, restrict_no_case_restriction, true },
1662     { "windows,lowercase", restrict_windows, true, restrict_lowercase, true },
1663     { "unix,nocontrol,lowercase,", restrict_unix, false, restrict_lowercase, true },
1664   };
1665
1666   for (i = 0; i < sizeof(test_array)/sizeof(test_array[0]); ++i)
1667     {
1668       bool res;
1669
1670       defaults();
1671       res = cmd_spec_restrict_file_names ("dummy", test_array[i].val, NULL);
1672
1673       /*
1674       fprintf (stderr, "test_cmd_spec_restrict_file_names: TEST %d\n", i); fflush (stderr);
1675       fprintf (stderr, "opt.restrict_files_os: %d\n",   opt.restrict_files_os); fflush (stderr);
1676       fprintf (stderr, "opt.restrict_files_ctrl: %d\n", opt.restrict_files_ctrl); fflush (stderr);
1677       fprintf (stderr, "opt.restrict_files_case: %d\n", opt.restrict_files_case); fflush (stderr);
1678       */
1679       mu_assert ("test_cmd_spec_restrict_file_names: wrong result",
1680                  res == test_array[i].result
1681                  && opt.restrict_files_os   == test_array[i].expected_restrict_files_os
1682                  && opt.restrict_files_ctrl == test_array[i].expected_restrict_files_ctrl
1683                  && opt.restrict_files_case == test_array[i].expected_restrict_files_case);
1684     }
1685
1686   return NULL;
1687 }
1688
1689 #endif /* TESTING */
1690