From 089c9439674e8ecbc64f0ba924e6fb447bbc2b9d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 4 Mar 2021 16:52:03 +0100 Subject: [PATCH] common,w32: Implement globing of command line args. * common/w32-misc.c [W32]: Include windows.h (struct add_arg_s): New. (add_arg): New. (glob_arg): New. (parse_cmdstring): Add arg argvflags and set it. (w32_parse_commandline): Add arg r_itemsalloced. Add globing. * common/init.c (prepare_w32_commandline): Mark glob created items as leaked. * common/t-w32-cmdline.c : Include windows.h (test_all): Add simple glob test for Unix. (main): Add manual test mode for Windows. -- GnuPG-bug-id: 4398 --- common/init.c | 9 +- common/t-w32-cmdline.c | 73 ++++++++++-- common/w32-misc.c | 257 +++++++++++++++++++++++++++++++++++++++-- common/w32help.h | 4 +- 4 files changed, 325 insertions(+), 18 deletions(-) diff --git a/common/init.c b/common/init.c index 06fd30956..ba20cd8d0 100644 --- a/common/init.c +++ b/common/init.c @@ -318,7 +318,7 @@ prepare_w32_commandline (int *r_argc, char ***r_argv) int argc; char **argv; const char *s; - int globing; + int i, globing, itemsalloced; s = gpgrt_strusage (95); globing = (s && *s == '1'); @@ -349,13 +349,18 @@ prepare_w32_commandline (int *r_argc, char ***r_argv) } gpgrt_annotate_leaked_object (cmdline); - argv = w32_parse_commandline (cmdline, globing, &argc); + argv = w32_parse_commandline (cmdline, globing, &argc, &itemsalloced); if (!argv) { log_error ("parsing command line failed: %s\n", "internal error"); return; /* Ooops. */ } gpgrt_annotate_leaked_object (argv); + if (itemsalloced) + { + for (i=0; i < argc; i++) + gpgrt_annotate_leaked_object (argv[i]); + } *r_argv = argv; *r_argc = argc; } diff --git a/common/t-w32-cmdline.c b/common/t-w32-cmdline.c index 172489c70..a1039d07a 100644 --- a/common/t-w32-cmdline.c +++ b/common/t-w32-cmdline.c @@ -33,8 +33,13 @@ #include #include #include +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include +#endif #include "t-support.h" +#include "utf8conv.h" #include "w32help.h" #define PGM "t-w32-cmdline" @@ -51,6 +56,7 @@ test_all (void) const char *cmdline; int argc; /* Expected number of args. */ char *argv[10]; /* Expected results. */ + int use_glob; } tests[] = { /* Examples from "Parsing C++ Command-Line Arguments" dated 11/18/2006. * https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85) @@ -81,10 +87,19 @@ test_all (void) /* 4, { "e:a", "a", "bc\"", "de f\"gh " }},*/ { "\"foo bar\"", 1 , { "foo bar" }}, + +#ifndef HAVE_W32_SYSTEM + /* We actually don't use this code on Unix but we provide a way to + * test some of the blobing code. */ + { "foo", 1, { "foo" }, 1 }, + { "foo*", 2, { "[* follows]", "foo*" }, 1 }, + { "foo?", 2, { "[? follows]", "foo?" }, 1 }, + { "? \"*\" *", 5, { "[? follows]", "?", "*", "[* follows]", "*" }, 1 }, +#endif /*!HAVE_W32_SYSTEM*/ { "", 1 , { "" }} }; int tidx; - int i, any, argc; + int i, any, itemsalloced, argc; char *cmdline; char **argv; @@ -95,7 +110,8 @@ test_all (void) putchar ('\n'); if (verbose) printf ("test %d: line ->%s<-\n", tidx, cmdline); - argv = w32_parse_commandline (cmdline, 0, &argc); + argv = w32_parse_commandline (cmdline, tests[tidx].use_glob, + &argc, &itemsalloced); if (!argv) { fail (tidx); @@ -129,7 +145,14 @@ test_all (void) tidx, verbose? "":" (use --verbose)"); errcount++; } + + if (itemsalloced) + { + for (i=0; i < argc; i++) + xfree (argv[i]); + } xfree (argv); + xfree (cmdline); } } @@ -154,7 +177,7 @@ main (int argc, char **argv) } else if (!strcmp (*argv, "--help")) { - fputs ("usage: " PGM " [FILE]\n" + fputs ("usage: " PGM " [test args]\n" "Options:\n" " --verbose Print timings etc.\n" " --debug Flyswatter\n" @@ -181,11 +204,47 @@ main (int argc, char **argv) if (argc) { - fprintf (stderr, PGM ": no arguments allowed\n"); - exit (1); - } +#ifdef HAVE_W32_SYSTEM + const wchar_t *wcmdline; + char *cmdline; + int i, myargc; + char **myargv; - test_all (); + wcmdline = GetCommandLineW (); + if (!wcmdline) + { + fprintf (stderr, PGM ": GetCommandLine failed\n"); + exit (1); + } + + cmdline = wchar_to_utf8 (wcmdline); + if (!cmdline) + { + fprintf (stderr, PGM ": wchar_to_utf8 failed\n"); + exit (1); + } + + printf ("cmdline ->%s<\n", cmdline); + myargv = w32_parse_commandline (cmdline, 1, &myargc, NULL); + if (!myargv) + { + fprintf (stderr, PGM ": w32_parse_commandline failed\n"); + exit (1); + } + + for (i=0; i < myargc; i++) + printf ("argv[%d] ->%s<-\n", i, myargv[i]); + fflush (stdout); + + xfree (myargv); + xfree (cmdline); +#else + fprintf (stderr, PGM ": manual test mode not available on Unix\n"); + errcount++; +#endif + } + else + test_all (); return !!errcount; } diff --git a/common/w32-misc.c b/common/w32-misc.c index ae194facb..2a0ba86e5 100644 --- a/common/w32-misc.c +++ b/common/w32-misc.c @@ -29,10 +29,163 @@ #include +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include +#endif /*!HAVE_W32_SYSTEM*/ + #include "util.h" #include "w32help.h" +/* Helper object for add_arg. */ +struct add_arg_s +{ + char **argv; /* Calloced array. */ + int argc; /* Number of items in argc. */ + int size; /* Allocated size of argv. */ +}; + + +/* Add STRING to the argv of PARM. Returns 0 on success; on error + * sets ERRNO and returns -1. */ +static int +add_arg (struct add_arg_s *parm, const char *string) +{ + if (parm->argc == parm->size) + { + char **newargv; + int newsize; + + if (parm->size < 256) + newsize = ((parm->size + 31) / 32 + 1) * 32; + else + newsize = ((parm->size + 255) / 256 + 1) * 256; + /* We allocate one more item for the trailing NULL. */ + newargv = xtryreallocarray (parm->argv, parm->size, newsize+1, + sizeof *newargv); + if (!newargv) + return -1; + parm->argv = newargv; + parm->size = newsize; + } + parm->argv[parm->argc] = xtrystrdup (string); + if (!parm->argv[parm->argc]) + return -1; + parm->argc++; + return 0; +} + + +/* Glob PATTERN and add to the argv of PARM. Returns 0 on success; on + * error sets ERRNO and returns -1. */ +static int +glob_arg (struct add_arg_s *parm, const char *pattern) +{ + int rc; + const char *s; + +#ifdef HAVE_W32_SYSTEM + HANDLE hd; + WIN32_FIND_DATAW dir; + uintptr_t pos; /* Offset to the last slash in pattern/buffer or 0. */ + char *buffer, *p; + int any = 0; + + s = strpbrk (pattern, "*?"); + if (!s) + { + /* Called without wildcards. */ + return add_arg (parm, pattern); + } + for (; s != pattern && *s != '/' && *s != '\\'; s--) + ; + pos = s - pattern; + if (*s == '/' || *s == L'\\') + pos++; + + { + wchar_t *wpattern; + + wpattern = utf8_to_wchar (pattern); + if (!wpattern) + return -1; + + hd = FindFirstFileW (wpattern, &dir); + xfree (wpattern); + } + if (hd == INVALID_HANDLE_VALUE) + return add_arg (parm, pattern); + + /* We allocate enough space to hold all kind of UTF-8 strings. */ + buffer = xtrymalloc (strlen (pattern) + MAX_PATH*6 + 1); + if (!buffer) + { + FindClose (hd); + return -1; + } + mem2str (buffer, pattern, pos+1); + for (p=buffer; *p; p++) + if (*p == '\\') + *p = '/'; + + do + { + if (!(dir.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + char *name; + + name = wchar_to_utf8 (dir.cFileName); + if (!name) + rc = -1; + else + { + mem2str (buffer + pos, name, MAX_PATH*6); + xfree (name); + rc = add_arg (parm, buffer); + } + if (rc) + { + FindClose (hd); + xfree (buffer); + return rc; + } + any = 1; + } + } + while (FindNextFileW (hd, &dir)); + + FindClose (hd); + xfree (buffer); + + rc = any? 0 : add_arg (parm, pattern); + +#else /* Unix */ + + /* We use some dummy code here because this is only used in the Unix + * test suite. */ + s = strpbrk (pattern, "*?"); + if (!s) + { + /* Called without wildcards. */ + return add_arg (parm, pattern); + } + + if (strchr (pattern, '?')) + rc = add_arg (parm, "[? follows]"); + else if (strchr (pattern, '*')) + rc = add_arg (parm, "[* follows]"); + else + rc = add_arg (parm, "[no glob!]"); /* Should not happen. */ + if (!rc) + rc = add_arg (parm, pattern); + +#endif /* Unix */ + + return rc; +} + + /* Return the number of backslashes. */ static unsigned int count_backslashes (const char *s) @@ -84,9 +237,12 @@ strip_one_arg (char *string, int endquote) } -/* Helper for parse_w32_commandline. */ +/* Helper for parse_w32_commandline. If ARGV and ARGVFLAGS are not + * NULL, ARGVFLAGS is expected to be allocated at the same size of + * ARGV and zeroed; on return 1 is stored for all arguments which are + * quoted (args like (foo"bar"baz") also count as quoted. */ static int -parse_cmdstring (char *string, char **argv) +parse_cmdstring (char *string, char **argv, unsigned char *argvflags) { int argc = 0; int inquote = 0; @@ -111,6 +267,8 @@ parse_cmdstring (char *string, char **argv) *p = 0; strip_one_arg (p0, 1); argv[argc] = p0; + if (argvflags) + argvflags[argc] = 1; } argc++; p0 = NULL; @@ -145,6 +303,8 @@ parse_cmdstring (char *string, char **argv) *p = 0; strip_one_arg (p0, inquote); argv[argc] = p0; + if (argvflags && inquote) + argvflags[argc] = 1; } argc++; p0 = NULL; @@ -163,6 +323,8 @@ parse_cmdstring (char *string, char **argv) *p = 0; strip_one_arg (p0, inquote); argv[argc] = p0; + if (argvflags && inquote) + argvflags[argc] = 1; } argc++; } @@ -176,16 +338,21 @@ parse_cmdstring (char *string, char **argv) * function. The returned array points into CMDLINE, so this should * not be freed. If GLOBING is set to true globing is done for all * items. Returns NULL on error. The number of items in the array is - * returned at R_ARGC. */ + * returned at R_ARGC. If R_ITEMSALLOCED is NOT NULL, it's value is + * set to true if the items at R_ALLOC are allocated and not point + * into to CMDLINE. */ char ** -w32_parse_commandline (char *cmdline, int globing, int *r_argc) +w32_parse_commandline (char *cmdline, int globing, int *r_argc, + int *r_itemsalloced) { int argc, i; char **argv; + char *argvflags; - (void)globing; + if (r_itemsalloced) + *r_itemsalloced = 0; - argc = parse_cmdstring (cmdline, NULL); + argc = parse_cmdstring (cmdline, NULL, NULL); if (!argc) { log_error ("%s failed: %s\n", __func__, "internal error"); @@ -194,16 +361,90 @@ w32_parse_commandline (char *cmdline, int globing, int *r_argc) argv = xtrycalloc (argc+1, sizeof *argv); if (!argv) { - log_error ("%s failed: %s\n", __func__, strerror (errno)); + log_error ("%s failed: %s\n", __func__, + gpg_strerror (gpg_error_from_syserror ())); return NULL; /* Ooops. */ } - i = parse_cmdstring (cmdline, argv); + if (globing) + { + argvflags = xtrycalloc (argc+1, sizeof *argvflags); + if (!argvflags) + { + log_error ("%s failed: %s\n", __func__, + gpg_strerror (gpg_error_from_syserror ())); + xfree (argv); + return NULL; /* Ooops. */ + } + } + else + argvflags = NULL; + + i = parse_cmdstring (cmdline, argv, argvflags); if (argc != i) { log_error ("%s failed (argc=%d i=%d)\n", __func__, argc, i); xfree (argv); + xfree (argvflags); return NULL; /* Ooops. */ } + + if (globing) + { + for (i=0; i < argc; i++) + if (argvflags[i] != 1 && strpbrk (argv[i], "*?")) + break; + if (i < argc) + { + /* Indeed some unquoted arguments contain wildcards. We + * need to do the globing and thus a dynamically re-allocate + * the argv array and strdup all items. */ + struct add_arg_s parm; + int rc; + + if (argc < 32) + parm.size = ((argc + 31) / 32 + 1) * 32; + else + parm.size = ((argc + 255) / 256 + 1) * 256; + parm.argc = 0; + /* We allocate one more item for the trailing NULL. */ + parm.argv = xtryreallocarray (NULL, 0, parm.size + 1, + sizeof *parm.argv); + if (!parm.argv) + { + log_error ("%s: error allocating array: %s\n", __func__, + gpg_strerror (gpg_error_from_syserror ())); + xfree (argv); + xfree (argvflags); + return NULL; /* Ooops. */ + } + rc = 0; + for (i=0; i < argc; i++) + { + if (argvflags[i] != 1) + rc = glob_arg (&parm, argv[i]); + else + rc = add_arg (&parm, argv[i]); + if (rc) + { + log_error ("%s: error adding or blobing: %s\n", __func__, + gpg_strerror (gpg_error_from_syserror ())); + for (i=0; i < parm.argc; i++) + xfree (parm.argv[i]); + xfree (parm.argv); + xfree (argv); + xfree (argvflags); + return NULL; /* Ooops. */ + } + } + xfree (argv); + argv = parm.argv; + argc = parm.argc; + if (r_itemsalloced) + *r_itemsalloced = 1; + } + } + + xfree (argvflags); *r_argc = argc; return argv; } diff --git a/common/w32help.h b/common/w32help.h index ca5ccf8bd..7f97f0d3e 100644 --- a/common/w32help.h +++ b/common/w32help.h @@ -32,8 +32,10 @@ #define GNUPG_COMMON_W32HELP_H /*-- w32-misc.c --*/ + /* This module is also part of the Unix tests. */ -char **w32_parse_commandline (char *cmdline, int globing, int *r_argc); +char **w32_parse_commandline (char *cmdline, int globing, int *r_argv, + int *r_itemsalloced);