mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-14 10:24:29 +01:00
Add utility to serialize windows arguments
Add a function to convert an argv array into a single escaped string to be passed to CreateProcess() on Windows. Refs <https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments> Refs <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw>
This commit is contained in:
@@ -75,6 +75,7 @@ conf.set('_GNU_SOURCE', true)
|
||||
if host_machine.system() == 'windows'
|
||||
windows = import('windows')
|
||||
src += [
|
||||
'src/util/command.c',
|
||||
'src/sys/win/file.c',
|
||||
'src/sys/win/process.c',
|
||||
windows.compile_resources('scrcpy-windows.rc'),
|
||||
@@ -238,6 +239,12 @@ if get_option('buildtype') == 'debug'
|
||||
'src/util/strbuf.c',
|
||||
'src/util/term.c',
|
||||
]],
|
||||
['test_command_windows', [
|
||||
'tests/test_command_windows.c',
|
||||
'src/util/command.c',
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
]],
|
||||
['test_control_msg_serialize', [
|
||||
'tests/test_control_msg_serialize.c',
|
||||
'src/control_msg.c',
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
#include <processthreadsapi.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
#include "util/strbuf.h"
|
||||
|
||||
#define CMD_MAX_LEN 8192
|
||||
|
||||
|
||||
99
app/src/util/command.c
Normal file
99
app/src/util/command.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "command.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/strbuf.h"
|
||||
|
||||
char *
|
||||
sc_command_serialize_windows(const char *const argv[]) {
|
||||
// <https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments>
|
||||
// <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw>
|
||||
|
||||
struct sc_strbuf buf;
|
||||
bool ok = sc_strbuf_init(&buf, 1024);
|
||||
if (!ok) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define BUF_PUSH(C) \
|
||||
do { \
|
||||
ok = sc_strbuf_append_char(&buf, C); \
|
||||
if (!ok) { \
|
||||
goto end; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
while (*argv) {
|
||||
const char *arg = *argv;
|
||||
|
||||
BUF_PUSH('"');
|
||||
|
||||
// <https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments>
|
||||
//
|
||||
// """
|
||||
// If an even number of backslashes is followed by a double quote mark,
|
||||
// then one backslash (\) is placed in the argv array for every pair of
|
||||
// backslashes (\\), and the double quote mark (") is interpreted as a
|
||||
// string delimiter.
|
||||
//
|
||||
// If an odd number of backslashes is followed by a double quote mark,
|
||||
// then one backslash (\) is placed in the argv array for every pair of
|
||||
// backslashes (\\). The double quote mark is interpreted as an escape
|
||||
// sequence by the remaining backslash, causing a literal double quote
|
||||
// mark (") to be placed in argv.
|
||||
// """
|
||||
//
|
||||
// To produce correct escaping according to what the parser will do, we
|
||||
// must count the number of successive backslashes.
|
||||
unsigned backslashes = 0;
|
||||
|
||||
for (const char *c = arg; *c; c++) {
|
||||
switch (*c) {
|
||||
case '"':
|
||||
while (backslashes) {
|
||||
// Double all backslashes before a quote
|
||||
BUF_PUSH('\\');
|
||||
BUF_PUSH('\\');
|
||||
--backslashes;
|
||||
}
|
||||
BUF_PUSH('\\');
|
||||
BUF_PUSH('"');
|
||||
backslashes = 0;
|
||||
break;
|
||||
case '\\':
|
||||
++backslashes;
|
||||
break;
|
||||
default:
|
||||
while (backslashes) {
|
||||
// Put all backslashes as literals
|
||||
BUF_PUSH('\\');
|
||||
--backslashes;
|
||||
}
|
||||
BUF_PUSH(*c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (backslashes) {
|
||||
// Double all backslashes before a quote
|
||||
BUF_PUSH('\\');
|
||||
BUF_PUSH('\\');
|
||||
--backslashes;
|
||||
}
|
||||
|
||||
BUF_PUSH('"');
|
||||
|
||||
++argv;
|
||||
|
||||
// Argument separator
|
||||
if (*argv) {
|
||||
BUF_PUSH(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return buf.s;
|
||||
|
||||
end:
|
||||
free(buf.s);
|
||||
return NULL;
|
||||
}
|
||||
17
app/src/util/command.h
Normal file
17
app/src/util/command.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef SC_COMMAND_H
|
||||
#define SC_COMMAND_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* Serialize an argv array for Windows
|
||||
*
|
||||
* Convert a NULL-terminated argument array into a single escaped string
|
||||
* suitable for massing to CreateProcess() on Windows.
|
||||
*
|
||||
* The returned value must be freed by the caller.
|
||||
*/
|
||||
char *
|
||||
sc_command_serialize_windows(const char *const argv[]);
|
||||
|
||||
#endif
|
||||
61
app/tests/test_command_windows.c
Normal file
61
app/tests/test_command_windows.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/command.h"
|
||||
|
||||
static void test_command_with_spaces(void) {
|
||||
const char *const argv[] = {
|
||||
"C:\\Program Files\\scrcpy\\adb",
|
||||
"-s",
|
||||
"serial with spaces",
|
||||
"push",
|
||||
"E:\\some folder\\scrcpy-server",
|
||||
"/data/local/tmp/scrcpy-server.jar",
|
||||
NULL,
|
||||
};
|
||||
char *cmd = sc_command_serialize_windows(argv);
|
||||
const char *expected = "\"C:\\Program Files\\scrcpy\\adb\" "
|
||||
"\"-s\" "
|
||||
"\"serial with spaces\" "
|
||||
"\"push\" "
|
||||
"\"E:\\some folder\\scrcpy-server\" "
|
||||
"\"/data/local/tmp/scrcpy-server.jar\"";
|
||||
|
||||
assert(!strcmp(expected, cmd));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
static void test_command_with_backslashes(void) {
|
||||
const char *const argv[] = {
|
||||
"a\\\\ b\\",
|
||||
"def \\",
|
||||
"gh\"i\" \\\\",
|
||||
"jkl\\\\",
|
||||
"mno\\",
|
||||
"p\\\"qr",
|
||||
NULL,
|
||||
};
|
||||
|
||||
char *cmd = sc_command_serialize_windows(argv);
|
||||
const char *expected = "\"a\\\\ b\\\\\" "
|
||||
"\"def \\\\\" "
|
||||
"\"gh\\\"i\\\" \\\\\\\\\" "
|
||||
"\"jkl\\\\\\\\\" "
|
||||
"\"mno\\\\\" "
|
||||
"\"p\\\\\\\"qr\"";
|
||||
|
||||
assert(!strcmp(expected, cmd));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
test_command_with_spaces();
|
||||
test_command_with_backslashes();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user