| /* Test of system-quote module. |
| Copyright (C) 2012-2020 Free Software Foundation, Inc. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, see <https://www.gnu.org/licenses/>. */ |
| |
| /* Written by Bruno Haible <bruno@clisp.org>, 2012. */ |
| |
| #include <config.h> |
| |
| /* Specification. */ |
| #include "system-quote.h" |
| |
| #if defined _WIN32 && !defined __CYGWIN__ |
| # define WINDOWS_NATIVE |
| #endif |
| |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #ifdef WINDOWS_NATIVE |
| # define WIN32_LEAN_AND_MEAN |
| # include <windows.h> |
| #endif |
| |
| #include "macros.h" |
| |
| #define EXPECTED_DATA_FILE "t-sq-data.tmp" |
| |
| static int failed; |
| |
| static void |
| check_one (enum system_command_interpreter interpreter, const char *prog, |
| const char *input) |
| { |
| char buf[1000]; |
| size_t output_len; |
| char *output; |
| char *bufend; |
| |
| output_len = system_quote_length (interpreter, input); |
| |
| output = system_quote (interpreter, input); |
| ASSERT (strlen (output) == output_len); |
| |
| ASSERT (output_len <= sizeof (buf) - 2); |
| memset (buf, '\0', output_len + 1); |
| buf[output_len + 1] = '%'; |
| bufend = system_quote_copy (buf, interpreter, input); |
| ASSERT (bufend == buf + output_len); |
| ASSERT (memcmp (buf, output, output_len + 1) == 0); |
| ASSERT (buf[output_len + 1] == '%'); |
| |
| /* Store INPUT in EXPECTED_DATA_FILE, for verification by the child |
| process. */ |
| { |
| FILE *fp = fopen (EXPECTED_DATA_FILE, "wb"); |
| if (fp == NULL) |
| exit (3); |
| if (fwrite (input, 1, strlen (input), fp) != strlen (input)) |
| exit (4); |
| if (fclose (fp)) |
| exit (5); |
| } |
| |
| /* Invoke the child process through system() and popen(). */ |
| { |
| char command[1000]; |
| |
| sprintf (command, "%s %s", prog, output); |
| |
| switch (interpreter) |
| { |
| case SCI_SYSTEM: |
| #ifdef WINDOWS_NATIVE |
| case SCI_WINDOWS_CMD: |
| #endif |
| { |
| int exitcode = system (command); |
| if (exitcode != 0) |
| { |
| fprintf (stderr, "for input = |%s|: system() command failed with status %d: %s\n", |
| input, exitcode, command); |
| failed = 1; |
| } |
| } |
| { |
| FILE *fp = popen (command, "r"); |
| int exitcode = pclose (fp); |
| if (exitcode != 0) |
| { |
| fprintf (stderr, "for input = |%s|: popen() command failed with status %d: %s\n", |
| input, exitcode, command); |
| failed = 1; |
| } |
| } |
| break; |
| #ifdef WINDOWS_NATIVE |
| case SCI_WINDOWS_CREATEPROCESS: |
| { |
| PROCESS_INFORMATION pinfo; |
| STARTUPINFO sinfo; |
| sinfo.cb = sizeof (STARTUPINFO); |
| sinfo.lpReserved = NULL; |
| sinfo.lpDesktop = NULL; |
| sinfo.lpTitle = NULL; |
| sinfo.cbReserved2 = 0; |
| sinfo.lpReserved2 = NULL; |
| sinfo.dwFlags = STARTF_USESTDHANDLES; |
| sinfo.hStdInput = GetStdHandle (STD_INPUT_HANDLE); |
| sinfo.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE); |
| sinfo.hStdError = GetStdHandle (STD_ERROR_HANDLE); |
| |
| if (CreateProcess (NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, |
| &sinfo, &pinfo)) |
| { |
| DWORD exitcode; |
| CloseHandle (pinfo.hThread); |
| if (WaitForSingleObject (pinfo.hProcess, INFINITE) == WAIT_OBJECT_0) |
| { |
| if (GetExitCodeProcess (pinfo.hProcess, &exitcode)) |
| { |
| if (exitcode != 0) |
| { |
| fprintf (stderr, "for input = |%s|: CreateProcess() command failed with status %d: %s\n", |
| input, exitcode, command); |
| failed = 1; |
| } |
| } |
| else |
| { |
| fprintf (stderr, "for input = |%s|: GetExitCodeProcess failed, GetLastError() = %u\n", |
| input, GetLastError ()); |
| failed = 1; |
| } |
| } |
| else |
| { |
| fprintf (stderr, "for input = |%s|: WaitForSingleObject failed\n", |
| input); |
| failed = 1; |
| } |
| CloseHandle (pinfo.hProcess); |
| } |
| else |
| { |
| fprintf (stderr, "for input = |%s|: CreateProcess failed, GetLastError() = %u\n", |
| input, GetLastError ()); |
| failed = 1; |
| } |
| } |
| break; |
| #endif |
| default: |
| break; |
| } |
| } |
| |
| free (output); |
| } |
| |
| static void |
| check_all (enum system_command_interpreter interpreter, |
| bool windows_cmd_limitations, |
| const char *prog) |
| { |
| /* Check the system_quote_length, system_quote_copy, system_quote |
| functions. */ |
| { |
| int c; |
| |
| /* Empty argument. */ |
| check_one (interpreter, prog, ""); |
| |
| /* Identifier or number. */ |
| check_one (interpreter, prog, "foo"); |
| check_one (interpreter, prog, "phr0ck"); |
| |
| /* Whitespace would be interpreted as argument separator by the shell. */ |
| check_one (interpreter, prog, "foo\tbar"); |
| if (!windows_cmd_limitations) |
| { |
| check_one (interpreter, prog, "foo\nbar"); |
| check_one (interpreter, prog, "foo\rbar"); |
| } |
| check_one (interpreter, prog, "foo bar"); |
| |
| /* '!' at the beginning of argv[0] would introduce a negated command. */ |
| check_one (interpreter, prog, "!foo"); |
| |
| /* '"' would be interpreted as the start of a string. */ |
| check_one (interpreter, prog, "\"foo\"bar"); |
| |
| /* '#' at the beginning of an argument would be interpreted as the start |
| of a comment. */ |
| check_one (interpreter, prog, "#foo"); |
| |
| /* '$' at the beginning of an argument would be interpreted as a variable |
| reference. */ |
| check_one (interpreter, prog, "$foo"); |
| |
| /* '&' at the beginning of an argument would be interpreted as a background |
| task indicator. */ |
| check_one (interpreter, prog, "&"); |
| |
| /* "'" would be interpreted as the start of a string. */ |
| check_one (interpreter, prog, "'foo'bar"); |
| |
| /* '(' at the beginning of argv[0] would introduce a subshell command. */ |
| check_one (interpreter, prog, "("); |
| |
| /* ')' at the beginning of an argument would be interpreted as the end of |
| the command. */ |
| check_one (interpreter, prog, ")"); |
| |
| /* '*' would be interpreted as a wildcard character. */ |
| check_one (interpreter, prog, "*"); |
| check_one (interpreter, prog, "*foo"); |
| |
| /* ';' at the beginning of an argument would be interpreted as an empty |
| statement in argv[0] and as the end of the command otherwise. */ |
| check_one (interpreter, prog, ";"); |
| check_one (interpreter, prog, "foo;"); |
| |
| /* '<' would be interpreted as a redirection of stdin. */ |
| check_one (interpreter, prog, "<"); |
| |
| /* '=' inside argv[0] would be interpreted as an environment variable |
| assignment. */ |
| check_one (interpreter, prog, "foo=bar"); |
| |
| /* '>' would be interpreted as a redirection of stdout. */ |
| check_one (interpreter, prog, ">"); |
| |
| /* '?' would be interpreted as a wildcard character. */ |
| check_one (interpreter, prog, "?"); |
| check_one (interpreter, prog, "??"); |
| check_one (interpreter, prog, "???"); |
| check_one (interpreter, prog, "????"); |
| check_one (interpreter, prog, "?????"); |
| check_one (interpreter, prog, "??????"); |
| check_one (interpreter, prog, "???????"); |
| check_one (interpreter, prog, "????????"); |
| check_one (interpreter, prog, "?????????"); |
| check_one (interpreter, prog, "??????????"); |
| check_one (interpreter, prog, "foo?bar"); |
| |
| /* '^' would be interpreted in old /bin/sh, e.g. SunOS 4.1.4. */ |
| check_one (interpreter, prog, "^"); |
| |
| /* "[...]" would be interpreted as a wildcard pattern. */ |
| check_one (interpreter, prog, "["); |
| check_one (interpreter, prog, "]"); |
| |
| /* '\' would be interpreted as an escape character. */ |
| check_one (interpreter, prog, "\\foo"); |
| |
| /* '`' would be interpreted as the start of a command substitution. */ |
| check_one (interpreter, prog, "`foo"); |
| |
| /* '{' at the beginning of argv[0] would introduce a complex command. */ |
| check_one (interpreter, prog, "{"); |
| |
| /* '|' at the beginning of an argument would be interpreted as a pipe |
| between commands. */ |
| check_one (interpreter, prog, "|"); |
| |
| /* '}' at the beginning of an argument would be interpreted as the end of |
| the command. */ |
| check_one (interpreter, prog, "}"); |
| |
| /* '~' at the beginning of an argument would be interpreted as a reference |
| to a user's home directory. */ |
| check_one (interpreter, prog, "~"); |
| check_one (interpreter, prog, "~foo"); |
| |
| /* A string that contains both ' and ". */ |
| check_one (interpreter, prog, "foo'bar\"baz"); |
| |
| /* '%' is used for environment variable references in Windows cmd.exe. */ |
| check_one (interpreter, prog, "%"); |
| check_one (interpreter, prog, "%%"); |
| check_one (interpreter, prog, "%foo%"); |
| check_one (interpreter, prog, "%PATH%"); |
| |
| /* All other characters don't need quoting. */ |
| for (c = 1; c <= UCHAR_MAX; c++) |
| if (strchr ("\t\n\r !\"#$&'()*;<=>?^[\\]`{|}~", c) == NULL) |
| { |
| char s[5]; |
| s[0] = 'a'; |
| s[1] = (char) c; |
| s[2] = 'z'; |
| s[3] = (char) c; |
| s[4] = '\0'; |
| |
| check_one (interpreter, prog, s); |
| } |
| } |
| } |
| |
| int |
| main (int argc, char *argv[]) |
| { |
| char *prog; |
| |
| if (argc != 2) |
| { |
| fprintf (stderr, "%s: need 1 argument\n", argv[0]); |
| return 2; |
| } |
| prog = argv[1]; |
| |
| #ifdef WINDOWS_NATIVE |
| /* Make PROG suitable for native Windows system calls and cmd.exe: |
| Replace '/' with '\\'. */ |
| { |
| char *p; |
| for (p = prog; *p != '\0'; p++) |
| if (*p == '/') |
| *p = '\\'; |
| } |
| #endif |
| |
| #ifdef WINDOWS_NATIVE |
| check_all (SCI_SYSTEM, true, prog); /* equivalent to SCI_WINDOWS_CMD */ |
| check_all (SCI_WINDOWS_CREATEPROCESS, false, prog); |
| check_all (SCI_WINDOWS_CMD, true, prog); |
| #else |
| check_all (SCI_SYSTEM, false, prog); /* equivalent to SCI_POSIX_SH */ |
| #endif |
| |
| /* Clean up. */ |
| unlink (EXPECTED_DATA_FILE); |
| |
| return failed; |
| } |