Skip to content

Commit f6c1e81

Browse files
authored
[Process API] Quoting enhancements (#12946)
1 parent 9a6f70d commit f6c1e81

File tree

5 files changed

+217
-9
lines changed

5 files changed

+217
-9
lines changed

include/SDL3/SDL_process.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,12 @@ typedef enum SDL_ProcessIO
195195
* run in the background. In this case the default input and output is
196196
* `SDL_PROCESS_STDIO_NULL` and the exitcode of the process is not
197197
* available, and will always be 0.
198+
* - `SDL_PROP_PROCESS_CREATE_CMDLINE_STRING`: a string containing the program
199+
* to run and any parameters. This string is passed directly to
200+
* `CreateProcess` on Windows, and does nothing on other platforms.
201+
* This property is only important if you want to start programs that does
202+
* non-standard command-line processing, and in most cases using
203+
* `SDL_PROP_PROCESS_CREATE_ARGS_POINTER` is sufficient.
198204
*
199205
* On POSIX platforms, wait() and waitpid(-1, ...) should not be called, and
200206
* SIGCHLD should not be ignored or handled because those would prevent SDL
@@ -231,6 +237,7 @@ extern SDL_DECLSPEC SDL_Process * SDLCALL SDL_CreateProcessWithProperties(SDL_Pr
231237
#define SDL_PROP_PROCESS_CREATE_STDERR_POINTER "SDL.process.create.stderr_source"
232238
#define SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN "SDL.process.create.stderr_to_stdout"
233239
#define SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN "SDL.process.create.background"
240+
#define SDL_PROP_PROCESS_CREATE_CMDLINE_STRING "SDL.process.create.cmdline"
234241

235242
/**
236243
* Get the properties associated with a process.

src/process/SDL_process.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,18 @@ SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio)
4545
SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props)
4646
{
4747
const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL);
48+
#if defined(SDL_PLATFORM_WINDOWS)
49+
const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL);
50+
if ((!args || !args[0] || !args[0][0]) && (!cmdline || !cmdline[0])) {
51+
SDL_SetError("Either SDL_PROP_PROCESS_CREATE_ARGS_POINTER or SDL_PROP_PROCESS_CREATE_CMDLINE_STRING must be valid");
52+
return NULL;
53+
}
54+
#else
4855
if (!args || !args[0] || !args[0][0]) {
4956
SDL_InvalidParamError("SDL_PROP_PROCESS_CREATE_ARGS_POINTER");
5057
return NULL;
5158
}
59+
#endif
5260

5361
SDL_Process *process = (SDL_Process *)SDL_calloc(1, sizeof(*process));
5462
if (!process) {

src/process/windows/SDL_windowsprocess.c

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,21 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out)
106106
len = 0;
107107
for (i = 0; args[i]; i++) {
108108
const char *a = args[i];
109+
bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL;
109110

110-
/* two double quotes to surround an argument with */
111-
len += 2;
111+
if (quotes) {
112+
/* surround the argument with double quote if it is empty or contains whitespaces */
113+
len += 2;
114+
}
112115

113116
for (; *a; a++) {
114117
switch (*a) {
115118
case '"':
116119
len += 2;
117120
break;
118121
case '\\':
119-
/* only escape backslashes that precede a double quote */
120-
len += (a[1] == '"' || a[1] == '\0') ? 2 : 1;
122+
/* only escape backslashes that precede a double quote (including the enclosing double quote) */
123+
len += (a[1] == '"' || (quotes && a[1] == '\0')) ? 2 : 1;
121124
break;
122125
case ' ':
123126
case '^':
@@ -149,8 +152,11 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out)
149152
i_out = 0;
150153
for (i = 0; args[i]; i++) {
151154
const char *a = args[i];
155+
bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL;
152156

153-
result[i_out++] = '"';
157+
if (quotes) {
158+
result[i_out++] = '"';
159+
}
154160
for (; *a; a++) {
155161
switch (*a) {
156162
case '"':
@@ -163,7 +169,7 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out)
163169
break;
164170
case '\\':
165171
result[i_out++] = *a;
166-
if (a[1] == '"' || a[1] == '\0') {
172+
if (a[1] == '"' || (quotes && a[1] == '\0')) {
167173
result[i_out++] = *a;
168174
}
169175
break;
@@ -188,7 +194,9 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out)
188194
break;
189195
}
190196
}
191-
result[i_out++] = '"';
197+
if (quotes) {
198+
result[i_out++] = '"';
199+
}
192200
result[i_out++] = ' ';
193201
}
194202
SDL_assert(i_out == len);
@@ -237,6 +245,7 @@ static bool join_env(char **env, LPWSTR *env_out)
237245
bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props)
238246
{
239247
const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL);
248+
const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL);
240249
SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment());
241250
char **envp = NULL;
242251
const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL);
@@ -286,7 +295,12 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID
286295
security_attributes.bInheritHandle = TRUE;
287296
security_attributes.lpSecurityDescriptor = NULL;
288297

289-
if (!join_arguments(args, &createprocess_cmdline)) {
298+
if (cmdline) {
299+
createprocess_cmdline = WIN_UTF8ToString(cmdline);
300+
if (!createprocess_cmdline) {
301+
goto done;
302+
}
303+
} else if (!join_arguments(args, &createprocess_cmdline)) {
290304
goto done;
291305
}
292306

test/childprocess.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
#include <SDL3/SDL_main.h>
33
#include <SDL3/SDL_test.h>
44

5+
#ifdef SDL_PLATFORM_WINDOWS
6+
#include <io.h>
7+
#include <fcntl.h>
8+
#endif
9+
510
#include <stdio.h>
611
#include <errno.h>
712

@@ -102,6 +107,11 @@ int main(int argc, char *argv[]) {
102107

103108
if (print_arguments) {
104109
int print_i;
110+
#ifdef SDL_PLATFORM_WINDOWS
111+
/* reopen stdout as binary to prevent newline conversion */
112+
_setmode(_fileno(stdout), _O_BINARY);
113+
#endif
114+
105115
for (print_i = 0; i + print_i < argc; print_i++) {
106116
fprintf(stdout, "|%d=%s|\r\n", print_i, argv[i + print_i]);
107117
}

test/testprocess.c

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ static int SDLCALL process_testArguments(void *arg)
8282
"",
8383
" ",
8484
"a b c",
85-
"a\tb\tc\t",
85+
"a\tb\tc\t\v\r\n",
8686
"\"a b\" c",
8787
"'a' 'b' 'c'",
8888
"%d%%%s",
@@ -965,6 +965,165 @@ static int process_testFileRedirection(void *arg)
965965
return TEST_COMPLETED;
966966
}
967967

968+
static int process_testWindowsCmdline(void *arg)
969+
{
970+
TestProcessData *data = (TestProcessData *)arg;
971+
const char *process_args[] = {
972+
data->childprocess_path,
973+
"--print-arguments",
974+
"--",
975+
"",
976+
" ",
977+
"a b c",
978+
"a\tb\tc\t",
979+
"\"a b\" c",
980+
"'a' 'b' 'c'",
981+
"%d%%%s",
982+
"\\t\\c",
983+
"evil\\",
984+
"a\\b\"c\\",
985+
"\"\\^&|<>%", /* characters with a special meaning */
986+
NULL
987+
};
988+
/* this will have the same result as process_args, but escaped in a different way */
989+
const char *process_cmdline_template =
990+
"%s "
991+
"--print-arguments "
992+
"-- "
993+
"\"\" "
994+
"\" \" "
995+
"a\" \"b\" \"c\t" /* using tab as delimiter */
996+
"\"a\tb\tc\t\" "
997+
"\"\"\"\"a b\"\"\" c\" "
998+
"\"'a' 'b' 'c'\" "
999+
"%%d%%%%%%s " /* will be passed to sprintf */
1000+
"\\t\\c "
1001+
"evil\\ "
1002+
"a\\b\"\\\"\"c\\ "
1003+
"\\\"\\^&|<>%%";
1004+
char process_cmdline[65535];
1005+
SDL_PropertiesID props;
1006+
SDL_Process *process = NULL;
1007+
char *buffer;
1008+
int exit_code;
1009+
int i;
1010+
size_t total_read = 0;
1011+
1012+
#ifndef SDL_PLATFORM_WINDOWS
1013+
SDLTest_AssertPass("SDL_PROP_PROCESS_CREATE_CMDLINE_STRING only works on Windows");
1014+
return TEST_SKIPPED;
1015+
#endif
1016+
1017+
props = SDL_CreateProperties();
1018+
SDLTest_AssertCheck(props != 0, "SDL_CreateProperties()");
1019+
if (!props) {
1020+
goto failed;
1021+
}
1022+
SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_APP);
1023+
SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP);
1024+
SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true);
1025+
1026+
process = SDL_CreateProcessWithProperties(props);
1027+
SDLTest_AssertCheck(process == NULL, "SDL_CreateProcessWithProperties() should fail");
1028+
1029+
SDL_snprintf(process_cmdline, SDL_arraysize(process_cmdline), process_cmdline_template, data->childprocess_path);
1030+
SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, process_cmdline);
1031+
1032+
process = SDL_CreateProcessWithProperties(props);
1033+
SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()");
1034+
if (!process) {
1035+
goto failed;
1036+
}
1037+
1038+
exit_code = 0xdeadbeef;
1039+
buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code);
1040+
SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()");
1041+
SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code);
1042+
if (!buffer) {
1043+
goto failed;
1044+
}
1045+
SDLTest_LogEscapedString("stdout of process: ", buffer, total_read);
1046+
1047+
for (i = 3; process_args[i]; i++) {
1048+
char line[64];
1049+
SDL_snprintf(line, sizeof(line), "|%d=%s|", i - 3, process_args[i]);
1050+
SDLTest_AssertCheck(!!SDL_strstr(buffer, line), "Check %s is in output", line);
1051+
}
1052+
SDL_free(buffer);
1053+
1054+
SDLTest_AssertPass("About to destroy process");
1055+
SDL_DestroyProcess(process);
1056+
1057+
return TEST_COMPLETED;
1058+
1059+
failed:
1060+
SDL_DestroyProcess(process);
1061+
return TEST_ABORTED;
1062+
}
1063+
1064+
static int process_testWindowsCmdlinePrecedence(void *arg)
1065+
{
1066+
TestProcessData *data = (TestProcessData *)arg;
1067+
const char *process_args[] = {
1068+
data->childprocess_path,
1069+
"--print-arguments",
1070+
"--",
1071+
"argument 1",
1072+
NULL
1073+
};
1074+
const char *process_cmdline_template = "%s --print-arguments -- \"argument 2\"";
1075+
char process_cmdline[65535];
1076+
SDL_PropertiesID props;
1077+
SDL_Process *process = NULL;
1078+
char *buffer;
1079+
int exit_code;
1080+
size_t total_read = 0;
1081+
1082+
#ifndef SDL_PLATFORM_WINDOWS
1083+
SDLTest_AssertPass("SDL_PROP_PROCESS_CREATE_CMDLINE_STRING only works on Windows");
1084+
return TEST_SKIPPED;
1085+
#endif
1086+
1087+
props = SDL_CreateProperties();
1088+
SDLTest_AssertCheck(props != 0, "SDL_CreateProperties()");
1089+
if (!props) {
1090+
goto failed;
1091+
}
1092+
1093+
SDL_snprintf(process_cmdline, SDL_arraysize(process_cmdline), process_cmdline_template, data->childprocess_path);
1094+
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args);
1095+
SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, (const char *)process_cmdline);
1096+
SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_APP);
1097+
SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP);
1098+
SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true);
1099+
1100+
process = SDL_CreateProcessWithProperties(props);
1101+
SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()");
1102+
if (!process) {
1103+
goto failed;
1104+
}
1105+
1106+
exit_code = 0xdeadbeef;
1107+
buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code);
1108+
SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()");
1109+
SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code);
1110+
if (!buffer) {
1111+
goto failed;
1112+
}
1113+
SDLTest_LogEscapedString("stdout of process: ", buffer, total_read);
1114+
SDLTest_AssertCheck(!!SDL_strstr(buffer, "|0=argument 2|"), "Check |0=argument 2| is printed");
1115+
SDL_free(buffer);
1116+
1117+
SDLTest_AssertPass("About to destroy process");
1118+
SDL_DestroyProcess(process);
1119+
1120+
return TEST_COMPLETED;
1121+
1122+
failed:
1123+
SDL_DestroyProcess(process);
1124+
return TEST_ABORTED;
1125+
}
1126+
9681127
static const SDLTest_TestCaseReference processTestArguments = {
9691128
process_testArguments, "process_testArguments", "Test passing arguments to child process", TEST_ENABLED
9701129
};
@@ -1017,6 +1176,14 @@ static const SDLTest_TestCaseReference processTestFileRedirection = {
10171176
process_testFileRedirection, "process_testFileRedirection", "Test redirection from/to files", TEST_ENABLED
10181177
};
10191178

1179+
static const SDLTest_TestCaseReference processTestWindowsCmdline = {
1180+
process_testWindowsCmdline, "process_testWindowsCmdline", "Test passing cmdline directly to CreateProcess", TEST_ENABLED
1181+
};
1182+
1183+
static const SDLTest_TestCaseReference processTestWindowsCmdlinePrecedence = {
1184+
process_testWindowsCmdlinePrecedence, "process_testWindowsCmdlinePrecedence", "Test SDL_PROP_PROCESS_CREATE_CMDLINE_STRING precedence over SDL_PROP_PROCESS_CREATE_ARGS_POINTER", TEST_ENABLED
1185+
};
1186+
10201187
static const SDLTest_TestCaseReference *processTests[] = {
10211188
&processTestArguments,
10221189
&processTestExitCode,
@@ -1031,6 +1198,8 @@ static const SDLTest_TestCaseReference *processTests[] = {
10311198
&processTestNonExistingExecutable,
10321199
&processTestBatBadButVulnerability,
10331200
&processTestFileRedirection,
1201+
&processTestWindowsCmdline,
1202+
&processTestWindowsCmdlinePrecedence,
10341203
NULL
10351204
};
10361205

0 commit comments

Comments
 (0)