1 /* Quoting for a system command. 2 Copyright (C) 2012-2013 Free Software Foundation, Inc. 3 Written by Bruno Haible <bruno@clisp.org>, 2012. 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 17 18 #include <config.h> 19 20 /* Specification. */ 21 #include "system-quote.h" 22 23 #include <stdbool.h> 24 #include <stdlib.h> 25 #include <string.h> 26 27 #include "sh-quote.h" 28 #include "xalloc.h" 29 30 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ 31 32 /* The native Windows CreateProcess() function interprets characters like 33 ' ', '\t', '\\', '"' (but not '<' and '>') in a special way: 34 - Space and tab are interpreted as delimiters. They are not treated as 35 delimiters if they are surrounded by double quotes: "...". 36 - Unescaped double quotes are removed from the input. Their only effect is 37 that within double quotes, space and tab are treated like normal 38 characters. 39 - Backslashes not followed by double quotes are not special. 40 - But 2*n+1 backslashes followed by a double quote become 41 n backslashes followed by a double quote (n >= 0): 42 \" -> " 43 \\\" -> \" 44 \\\\\" -> \\" 45 - '*', '?' characters may get expanded through wildcard expansion in the 46 callee: By default, in the callee, the initialization code before main() 47 takes the result of GetCommandLine(), wildcard-expands it, and passes it 48 to main(). The exceptions to this rule are: 49 - programs that inspect GetCommandLine() and ignore argv, 50 - mingw programs that have a global variable 'int _CRT_glob = 0;', 51 - Cygwin programs, when invoked from a Cygwin program. 52 */ 53 # 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*?" 54 # 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" 55 56 /* Copies the quoted string to p and returns the number of bytes needed. 57 If p is non-NULL, there must be room for system_quote_length (string) 58 bytes at p. */ 59 static size_t 60 windows_createprocess_quote (char *p, const char *string) 61 { 62 size_t len = strlen (string); 63 bool quote_around = 64 (len == 0 || strpbrk (string, SHELL_SPECIAL_CHARS) != NULL); 65 size_t backslashes = 0; 66 size_t i = 0; 67 # define STORE(c) \ 68 do \ 69 { \ 70 if (p != NULL) \ 71 p[i] = (c); \ 72 i++; \ 73 } \ 74 while (0) 75 76 if (quote_around) 77 STORE ('"'); 78 for (; len > 0; string++, len--) 79 { 80 char c = *string; 81 82 if (c == '"') 83 { 84 size_t j; 85 86 for (j = backslashes + 1; j > 0; j--) 87 STORE ('\\'); 88 } 89 STORE (c); 90 if (c == '\\') 91 backslashes++; 92 else 93 backslashes = 0; 94 } 95 if (quote_around) 96 { 97 size_t j; 98 99 for (j = backslashes; j > 0; j--) 100 STORE ('\\'); 101 STORE ('"'); 102 } 103 # undef STORE 104 return i; 105 } 106 107 /* The native Windows cmd.exe command interpreter also interprets: 108 - '\n', '\r' as a command terminator - no way to escape it, 109 - '<', '>' as redirections, 110 - '|' as pipe operator, 111 - '%var%' as a reference to the environment variable VAR (uppercase), 112 even inside quoted strings, 113 - '&' '[' ']' '{' '}' '^' '=' ';' '!' '\'' '+' ',' '`' '~' for other 114 purposes, according to 115 <http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cmd.mspx?mfr=true> 116 We quote a string like '%var%' by putting the '%' characters outside of 117 double-quotes and the rest of the string inside double-quotes: %"var"%. 118 This is guaranteed to not be a reference to an environment variable. 119 */ 120 # 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!%&'*+,;<=>?[]^`{|}~" 121 # define CMD_FORBIDDEN_CHARS "\n\r" 122 123 /* Copies the quoted string to p and returns the number of bytes needed. 124 If p is non-NULL, there must be room for system_quote_length (string) 125 bytes at p. */ 126 static size_t 127 windows_cmd_quote (char *p, const char *string) 128 { 129 size_t len = strlen (string); 130 bool quote_around = 131 (len == 0 || strpbrk (string, CMD_SPECIAL_CHARS) != NULL); 132 size_t backslashes = 0; 133 size_t i = 0; 134 # define STORE(c) \ 135 do \ 136 { \ 137 if (p != NULL) \ 138 p[i] = (c); \ 139 i++; \ 140 } \ 141 while (0) 142 143 if (quote_around) 144 STORE ('"'); 145 for (; len > 0; string++, len--) 146 { 147 char c = *string; 148 149 if (c == '"') 150 { 151 size_t j; 152 153 for (j = backslashes + 1; j > 0; j--) 154 STORE ('\\'); 155 } 156 if (c == '%') 157 { 158 size_t j; 159 160 for (j = backslashes; j > 0; j--) 161 STORE ('\\'); 162 STORE ('"'); 163 } 164 STORE (c); 165 if (c == '%') 166 STORE ('"'); 167 if (c == '\\') 168 backslashes++; 169 else 170 backslashes = 0; 171 } 172 if (quote_around) 173 { 174 size_t j; 175 176 for (j = backslashes; j > 0; j--) 177 STORE ('\\'); 178 STORE ('"'); 179 } 180 return i; 181 } 182 183 #endif 184 185 size_t 186 system_quote_length (enum system_command_interpreter interpreter, 187 const char *string) 188 { 189 switch (interpreter) 190 { 191 #if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__) 192 case SCI_SYSTEM: 193 #endif 194 case SCI_POSIX_SH: 195 return shell_quote_length (string); 196 197 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ 198 case SCI_WINDOWS_CREATEPROCESS: 199 return windows_createprocess_quote (NULL, string); 200 201 case SCI_SYSTEM: 202 case SCI_WINDOWS_CMD: 203 return windows_cmd_quote (NULL, string); 204 #endif 205 206 default: 207 /* Invalid interpreter. */ 208 abort (); 209 } 210 } 211 212 char * 213 system_quote_copy (char *p, 214 enum system_command_interpreter interpreter, 215 const char *string) 216 { 217 switch (interpreter) 218 { 219 #if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__) 220 case SCI_SYSTEM: 221 #endif 222 case SCI_POSIX_SH: 223 return shell_quote_copy (p, string); 224 225 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ 226 case SCI_WINDOWS_CREATEPROCESS: 227 p += windows_createprocess_quote (p, string); 228 *p = '\0'; 229 return p; 230 231 case SCI_SYSTEM: 232 case SCI_WINDOWS_CMD: 233 p += windows_cmd_quote (p, string); 234 *p = '\0'; 235 return p; 236 #endif 237 238 default: 239 /* Invalid interpreter. */ 240 abort (); 241 } 242 } 243 244 char * 245 system_quote (enum system_command_interpreter interpreter, 246 const char *string) 247 { 248 switch (interpreter) 249 { 250 #if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__) 251 case SCI_SYSTEM: 252 #endif 253 case SCI_POSIX_SH: 254 return shell_quote (string); 255 256 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ 257 case SCI_WINDOWS_CREATEPROCESS: 258 case SCI_SYSTEM: 259 case SCI_WINDOWS_CMD: 260 { 261 size_t length = system_quote_length (interpreter, string); 262 char *quoted = XNMALLOC (length, char); 263 system_quote_copy (quoted, interpreter, string); 264 return quoted; 265 } 266 #endif 267 268 default: 269 /* Invalid interpreter. */ 270 abort (); 271 } 272 } 273 274 char * 275 system_quote_argv (enum system_command_interpreter interpreter, 276 char * const *argv) 277 { 278 if (*argv != NULL) 279 { 280 char * const *argp; 281 size_t length; 282 char *command; 283 char *p; 284 285 length = 0; 286 for (argp = argv; ; ) 287 { 288 length += system_quote_length (interpreter, *argp) + 1; 289 argp++; 290 if (*argp == NULL) 291 break; 292 } 293 294 command = XNMALLOC (length, char); 295 296 p = command; 297 for (argp = argv; ; ) 298 { 299 p = system_quote_copy (p, interpreter, *argp); 300 argp++; 301 if (*argp == NULL) 302 break; 303 *p++ = ' '; 304 } 305 *p = '\0'; 306 307 return command; 308 } 309 else 310 return xstrdup (""); 311 } 312