Blame lib/system-quote.c

Packit 33f14e
/* Quoting for a system command.
Packit 33f14e
   Copyright (C) 2012-2017 Free Software Foundation, Inc.
Packit 33f14e
   Written by Bruno Haible <bruno@clisp.org>, 2012.
Packit 33f14e
Packit 33f14e
   This program is free software: you can redistribute it and/or modify
Packit 33f14e
   it under the terms of the GNU General Public License as published by
Packit 33f14e
   the Free Software Foundation; either version 3 of the License, or
Packit 33f14e
   (at your option) any later version.
Packit 33f14e
Packit 33f14e
   This program is distributed in the hope that it will be useful,
Packit 33f14e
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 33f14e
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 33f14e
   GNU General Public License for more details.
Packit 33f14e
Packit 33f14e
   You should have received a copy of the GNU General Public License
Packit 33f14e
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit 33f14e
Packit 33f14e
#include <config.h>
Packit 33f14e
Packit 33f14e
/* Specification.  */
Packit 33f14e
#include "system-quote.h"
Packit 33f14e
Packit 33f14e
#include <stdbool.h>
Packit 33f14e
#include <stdlib.h>
Packit 33f14e
#include <string.h>
Packit 33f14e
Packit 33f14e
#include "sh-quote.h"
Packit 33f14e
#include "xalloc.h"
Packit 33f14e
Packit 33f14e
#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
Packit 33f14e
Packit 33f14e
/* The native Windows CreateProcess() function interprets characters like
Packit 33f14e
   ' ', '\t', '\\', '"' (but not '<' and '>') in a special way:
Packit 33f14e
   - Space and tab are interpreted as delimiters. They are not treated as
Packit 33f14e
     delimiters if they are surrounded by double quotes: "...".
Packit 33f14e
   - Unescaped double quotes are removed from the input. Their only effect is
Packit 33f14e
     that within double quotes, space and tab are treated like normal
Packit 33f14e
     characters.
Packit 33f14e
   - Backslashes not followed by double quotes are not special.
Packit 33f14e
   - But 2*n+1 backslashes followed by a double quote become
Packit 33f14e
     n backslashes followed by a double quote (n >= 0):
Packit 33f14e
       \" -> "
Packit 33f14e
       \\\" -> \"
Packit 33f14e
       \\\\\" -> \\"
Packit 33f14e
   - '*', '?' characters may get expanded through wildcard expansion in the
Packit 33f14e
     callee: By default, in the callee, the initialization code before main()
Packit 33f14e
     takes the result of GetCommandLine(), wildcard-expands it, and passes it
Packit 33f14e
     to main(). The exceptions to this rule are:
Packit 33f14e
       - programs that inspect GetCommandLine() and ignore argv,
Packit 33f14e
       - mingw programs that have a global variable 'int _CRT_glob = 0;',
Packit 33f14e
       - Cygwin programs, when invoked from a Cygwin program.
Packit 33f14e
 */
Packit 33f14e
# define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037*?"
Packit 33f14e
# define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
Packit 33f14e
Packit 33f14e
/* Copies the quoted string to p and returns the number of bytes needed.
Packit 33f14e
   If p is non-NULL, there must be room for system_quote_length (string)
Packit 33f14e
   bytes at p.  */
Packit 33f14e
static size_t
Packit 33f14e
windows_createprocess_quote (char *p, const char *string)
Packit 33f14e
{
Packit 33f14e
  size_t len = strlen (string);
Packit 33f14e
  bool quote_around =
Packit 33f14e
    (len == 0 || strpbrk (string, SHELL_SPECIAL_CHARS) != NULL);
Packit 33f14e
  size_t backslashes = 0;
Packit 33f14e
  size_t i = 0;
Packit 33f14e
# define STORE(c) \
Packit 33f14e
  do                 \
Packit 33f14e
    {                \
Packit 33f14e
      if (p != NULL) \
Packit 33f14e
        p[i] = (c);  \
Packit 33f14e
      i++;           \
Packit 33f14e
    }                \
Packit 33f14e
  while (0)
Packit 33f14e
Packit 33f14e
  if (quote_around)
Packit 33f14e
    STORE ('"');
Packit 33f14e
  for (; len > 0; string++, len--)
Packit 33f14e
    {
Packit 33f14e
      char c = *string;
Packit 33f14e
Packit 33f14e
      if (c == '"')
Packit 33f14e
        {
Packit 33f14e
          size_t j;
Packit 33f14e
Packit 33f14e
          for (j = backslashes + 1; j > 0; j--)
Packit 33f14e
            STORE ('\\');
Packit 33f14e
        }
Packit 33f14e
      STORE (c);
Packit 33f14e
      if (c == '\\')
Packit 33f14e
        backslashes++;
Packit 33f14e
      else
Packit 33f14e
        backslashes = 0;
Packit 33f14e
    }
Packit 33f14e
  if (quote_around)
Packit 33f14e
    {
Packit 33f14e
      size_t j;
Packit 33f14e
Packit 33f14e
      for (j = backslashes; j > 0; j--)
Packit 33f14e
        STORE ('\\');
Packit 33f14e
      STORE ('"');
Packit 33f14e
    }
Packit 33f14e
# undef STORE
Packit 33f14e
  return i;
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
/* The native Windows cmd.exe command interpreter also interprets:
Packit 33f14e
   - '\n', '\r' as a command terminator - no way to escape it,
Packit 33f14e
   - '<', '>' as redirections,
Packit 33f14e
   - '|' as pipe operator,
Packit 33f14e
   - '%var%' as a reference to the environment variable VAR (uppercase),
Packit 33f14e
     even inside quoted strings,
Packit 33f14e
   - '&' '[' ']' '{' '}' '^' '=' ';' '!' '\'' '+' ',' '`' '~' for other
Packit 33f14e
     purposes, according to
Packit 33f14e
     <http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cmd.mspx?mfr=true>
Packit 33f14e
   We quote a string like '%var%' by putting the '%' characters outside of
Packit 33f14e
   double-quotes and the rest of the string inside double-quotes: %"var"%.
Packit 33f14e
   This is guaranteed to not be a reference to an environment variable.
Packit 33f14e
 */
Packit 33f14e
# define CMD_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037!%&'*+,;<=>?[]^`{|}~"
Packit 33f14e
# define CMD_FORBIDDEN_CHARS "\n\r"
Packit 33f14e
Packit 33f14e
/* Copies the quoted string to p and returns the number of bytes needed.
Packit 33f14e
   If p is non-NULL, there must be room for system_quote_length (string)
Packit 33f14e
   bytes at p.  */
Packit 33f14e
static size_t
Packit 33f14e
windows_cmd_quote (char *p, const char *string)
Packit 33f14e
{
Packit 33f14e
  size_t len = strlen (string);
Packit 33f14e
  bool quote_around =
Packit 33f14e
    (len == 0 || strpbrk (string, CMD_SPECIAL_CHARS) != NULL);
Packit 33f14e
  size_t backslashes = 0;
Packit 33f14e
  size_t i = 0;
Packit 33f14e
# define STORE(c) \
Packit 33f14e
  do                 \
Packit 33f14e
    {                \
Packit 33f14e
      if (p != NULL) \
Packit 33f14e
        p[i] = (c);  \
Packit 33f14e
      i++;           \
Packit 33f14e
    }                \
Packit 33f14e
  while (0)
Packit 33f14e
Packit 33f14e
  if (quote_around)
Packit 33f14e
    STORE ('"');
Packit 33f14e
  for (; len > 0; string++, len--)
Packit 33f14e
    {
Packit 33f14e
      char c = *string;
Packit 33f14e
Packit 33f14e
      if (c == '"')
Packit 33f14e
        {
Packit 33f14e
          size_t j;
Packit 33f14e
Packit 33f14e
          for (j = backslashes + 1; j > 0; j--)
Packit 33f14e
            STORE ('\\');
Packit 33f14e
        }
Packit 33f14e
      if (c == '%')
Packit 33f14e
        {
Packit 33f14e
          size_t j;
Packit 33f14e
Packit 33f14e
          for (j = backslashes; j > 0; j--)
Packit 33f14e
            STORE ('\\');
Packit 33f14e
          STORE ('"');
Packit 33f14e
        }
Packit 33f14e
      STORE (c);
Packit 33f14e
      if (c == '%')
Packit 33f14e
        STORE ('"');
Packit 33f14e
      if (c == '\\')
Packit 33f14e
        backslashes++;
Packit 33f14e
      else
Packit 33f14e
        backslashes = 0;
Packit 33f14e
    }
Packit 33f14e
  if (quote_around)
Packit 33f14e
    {
Packit 33f14e
      size_t j;
Packit 33f14e
Packit 33f14e
      for (j = backslashes; j > 0; j--)
Packit 33f14e
        STORE ('\\');
Packit 33f14e
      STORE ('"');
Packit 33f14e
    }
Packit 33f14e
  return i;
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
#endif
Packit 33f14e
Packit 33f14e
size_t
Packit 33f14e
system_quote_length (enum system_command_interpreter interpreter,
Packit 33f14e
                     const char *string)
Packit 33f14e
{
Packit 33f14e
  switch (interpreter)
Packit 33f14e
    {
Packit 33f14e
#if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
Packit 33f14e
    case SCI_SYSTEM:
Packit 33f14e
#endif
Packit 33f14e
    case SCI_POSIX_SH:
Packit 33f14e
      return shell_quote_length (string);
Packit 33f14e
Packit 33f14e
#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
Packit 33f14e
    case SCI_WINDOWS_CREATEPROCESS:
Packit 33f14e
      return windows_createprocess_quote (NULL, string);
Packit 33f14e
Packit 33f14e
    case SCI_SYSTEM:
Packit 33f14e
    case SCI_WINDOWS_CMD:
Packit 33f14e
      return windows_cmd_quote (NULL, string);
Packit 33f14e
#endif
Packit 33f14e
Packit 33f14e
    default:
Packit 33f14e
      /* Invalid interpreter.  */
Packit 33f14e
      abort ();
Packit 33f14e
    }
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
char *
Packit 33f14e
system_quote_copy (char *p,
Packit 33f14e
                   enum system_command_interpreter interpreter,
Packit 33f14e
                   const char *string)
Packit 33f14e
{
Packit 33f14e
  switch (interpreter)
Packit 33f14e
    {
Packit 33f14e
#if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
Packit 33f14e
    case SCI_SYSTEM:
Packit 33f14e
#endif
Packit 33f14e
    case SCI_POSIX_SH:
Packit 33f14e
      return shell_quote_copy (p, string);
Packit 33f14e
Packit 33f14e
#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
Packit 33f14e
    case SCI_WINDOWS_CREATEPROCESS:
Packit 33f14e
      p += windows_createprocess_quote (p, string);
Packit 33f14e
      *p = '\0';
Packit 33f14e
      return p;
Packit 33f14e
Packit 33f14e
    case SCI_SYSTEM:
Packit 33f14e
    case SCI_WINDOWS_CMD:
Packit 33f14e
      p += windows_cmd_quote (p, string);
Packit 33f14e
      *p = '\0';
Packit 33f14e
      return p;
Packit 33f14e
#endif
Packit 33f14e
Packit 33f14e
    default:
Packit 33f14e
      /* Invalid interpreter.  */
Packit 33f14e
      abort ();
Packit 33f14e
    }
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
char *
Packit 33f14e
system_quote (enum system_command_interpreter interpreter,
Packit 33f14e
              const char *string)
Packit 33f14e
{
Packit 33f14e
  switch (interpreter)
Packit 33f14e
    {
Packit 33f14e
#if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
Packit 33f14e
    case SCI_SYSTEM:
Packit 33f14e
#endif
Packit 33f14e
    case SCI_POSIX_SH:
Packit 33f14e
      return shell_quote (string);
Packit 33f14e
Packit 33f14e
#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
Packit 33f14e
    case SCI_WINDOWS_CREATEPROCESS:
Packit 33f14e
    case SCI_SYSTEM:
Packit 33f14e
    case SCI_WINDOWS_CMD:
Packit 33f14e
      {
Packit 33f14e
        size_t length = system_quote_length (interpreter, string);
Packit 33f14e
        char *quoted = XNMALLOC (length, char);
Packit 33f14e
        system_quote_copy (quoted, interpreter, string);
Packit 33f14e
        return quoted;
Packit 33f14e
      }
Packit 33f14e
#endif
Packit 33f14e
Packit 33f14e
    default:
Packit 33f14e
      /* Invalid interpreter.  */
Packit 33f14e
      abort ();
Packit 33f14e
    }
Packit 33f14e
}
Packit 33f14e
Packit 33f14e
char *
Packit 33f14e
system_quote_argv (enum system_command_interpreter interpreter,
Packit 33f14e
                   char * const *argv)
Packit 33f14e
{
Packit 33f14e
  if (*argv != NULL)
Packit 33f14e
    {
Packit 33f14e
      char * const *argp;
Packit 33f14e
      size_t length;
Packit 33f14e
      char *command;
Packit 33f14e
      char *p;
Packit 33f14e
Packit 33f14e
      length = 0;
Packit 33f14e
      for (argp = argv; ; )
Packit 33f14e
        {
Packit 33f14e
          length += system_quote_length (interpreter, *argp) + 1;
Packit 33f14e
          argp++;
Packit 33f14e
          if (*argp == NULL)
Packit 33f14e
            break;
Packit 33f14e
        }
Packit 33f14e
Packit 33f14e
      command = XNMALLOC (length, char);
Packit 33f14e
Packit 33f14e
      p = command;
Packit 33f14e
      for (argp = argv; ; )
Packit 33f14e
        {
Packit 33f14e
          p = system_quote_copy (p, interpreter, *argp);
Packit 33f14e
          argp++;
Packit 33f14e
          if (*argp == NULL)
Packit 33f14e
            break;
Packit 33f14e
          *p++ = ' ';
Packit 33f14e
        }
Packit 33f14e
      *p = '\0';
Packit 33f14e
Packit 33f14e
      return command;
Packit 33f14e
    }
Packit 33f14e
  else
Packit 33f14e
    return xstrdup ("");
Packit 33f14e
}