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