1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

New abstraction for command-line arguments.

This begins the process of enabling our Windows applications to handle
Unicode characters on their command lines which don't fit in the
system code page.

Instead of passing plain strings to cmdline_process_param, we now pass
a partially opaque and platform-specific thing called a CmdlineArg.
This has a method that extracts the argument word as a default-encoded
string, and another one that tries to extract it as UTF-8 (though it
may fail if the UTF-8 isn't available).

On Windows, the command line is now constructed by calling
split_into_argv_w on the Unicode command line returned by
GetCommandLineW(), and the UTF-8 method returns text converted
directly from that wide-character form, not going via the system code
page. So it _can_ include UTF-8 characters that wouldn't have
round-tripped via CP_ACP.

This commit introduces the abstraction and switches over the
cross-platform and Windows argv-handling code to use it, with minimal
functional change. Nothing yet tries to call cmdline_arg_get_utf8().

I say 'cross-platform and Windows' because on the Unix side there's
still a lot of use of plain old argv which I haven't converted. That
would be a much larger project, and isn't currently needed: the
_current_ aim of this abstraction is to get the right things to happen
relating to Unicode on Windows, so for code that doesn't run on
Windows anyway, it's not adding value. (Also there's a tension with
GTK, which wants to talk to standard argv and extract arguments _it_
knows about, so at the very least we'd have to let it munge argv
before importing it into this new system.)
This commit is contained in:
Simon Tatham 2024-09-25 10:18:38 +01:00
parent 7980722f55
commit 841bf321d4
27 changed files with 724 additions and 283 deletions

View File

@ -31,7 +31,7 @@
#define NPRIORITIES 2
struct cmdline_saved_param {
char *p, *value;
CmdlineArg *p, *value;
};
struct cmdline_saved_param_set {
struct cmdline_saved_param *params;
@ -44,11 +44,11 @@ struct cmdline_saved_param_set {
*/
static struct cmdline_saved_param_set saves[NPRIORITIES];
static void cmdline_save_param(const char *p, const char *value, int pri)
static void cmdline_save_param(CmdlineArg *p, CmdlineArg *value, int pri)
{
sgrowarray(saves[pri].params, saves[pri].savesize, saves[pri].nsaved);
saves[pri].params[saves[pri].nsaved].p = dupstr(p);
saves[pri].params[saves[pri].nsaved].value = dupstr(value);
saves[pri].params[saves[pri].nsaved].p = p;
saves[pri].params[saves[pri].nsaved].value = value;
saves[pri].nsaved++;
}
@ -73,7 +73,7 @@ void cmdline_cleanup(void)
}
#define SAVEABLE(pri) do { \
if (need_save) { cmdline_save_param(p, value, pri); return ret; } \
if (need_save) { cmdline_save_param(arg, nextarg, pri); return ret; } \
} while (0)
/*
@ -190,10 +190,12 @@ static void set_port(Conf *conf, int port)
conf_set_int(conf, CONF_port, port);
}
int cmdline_process_param(const char *p, char *value,
int cmdline_process_param(CmdlineArg *arg, CmdlineArg *nextarg,
int need_save, Conf *conf)
{
int ret = 0;
const char *p = cmdline_arg_to_str(arg);
const char *value = cmdline_arg_to_str(nextarg);
if (p[0] != '-') {
if (need_save < 0)
@ -408,9 +410,8 @@ int cmdline_process_param(const char *p, char *value,
* pretend we received a -P argument, so that it will be
* deferred until it's a good moment to run it.
*/
char *dup = dupstr(p); /* 'value' is not a const char * */
int retd = cmdline_process_param("-P", dup, 1, conf);
sfree(dup);
int retd = cmdline_process_param(
cmdline_arg_from_str(arg->list, "-P"), arg, 1, conf);
assert(retd == 2);
seen_port_argument = true;
return 1;
@ -608,11 +609,9 @@ int cmdline_process_param(const char *p, char *value,
}
cmdline_password = dupstr(value);
/* Assuming that `value' is directly from argv, make a good faith
* attempt to trample it, to stop it showing up in `ps' output
* on Unix-like systems. Not guaranteed, of course. */
smemclr(value, strlen(value));
}
cmdline_arg_wipe(nextarg);
}
if (!strcmp(p, "-pwfile")) {
@ -779,7 +778,7 @@ int cmdline_process_param(const char *p, char *value,
conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6);
}
if (!strcmp(p, "-sercfg")) {
char* nextitem;
const char *nextitem;
RETURN(2);
UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
SAVEABLE(1);
@ -796,7 +795,6 @@ int cmdline_process_param(const char *p, char *value,
skip = 0;
} else {
length = end - nextitem;
nextitem[length] = '\0';
skip = 1;
}
if (length == 1) {
@ -851,7 +849,9 @@ int cmdline_process_param(const char *p, char *value,
/* Messy special case */
conf_set_int(conf, CONF_serstopbits, 3);
} else {
int serspeed = atoi(nextitem);
char *speedstr = dupprintf("%.*s", length, nextitem);
int serspeed = atoi(speedstr);
sfree(speedstr);
if (serspeed != 0) {
conf_set_int(conf, CONF_serspeed, serspeed);
} else {
@ -957,12 +957,9 @@ int cmdline_process_param(const char *p, char *value,
void cmdline_run_saved(Conf *conf)
{
for (size_t pri = 0; pri < NPRIORITIES; pri++) {
for (size_t i = 0; i < saves[pri].nsaved; i++) {
for (size_t i = 0; i < saves[pri].nsaved; i++)
cmdline_process_param(saves[pri].params[i].p,
saves[pri].params[i].value, 0, conf);
sfree(saves[pri].params[i].p);
sfree(saves[pri].params[i].value);
}
saves[pri].nsaved = 0;
}
}

2
defs.h
View File

@ -77,6 +77,8 @@ typedef struct conf_tag Conf;
typedef struct ConfKeyInfo ConfKeyInfo;
typedef struct ConfSaveEnumValue ConfSaveEnumValue;
typedef struct ConfSaveEnumType ConfSaveEnumType;
typedef struct CmdlineArgList CmdlineArgList;
typedef struct CmdlineArg CmdlineArg;
typedef struct terminal_tag Terminal;
typedef struct term_utf8_decode term_utf8_decode;

139
pscp.c
View File

@ -535,10 +535,20 @@ static void print_stats(const char *name, uint64_t size, uint64_t done,
}
/*
* Find a colon in str and return a pointer to the colon.
* This is used to separate hostname from filename.
* Find a colon in str and return a pointer to the colon.
* This is used to separate hostname from filename.
*
* Colons in bracketed IPv6 address literals are ignored, because
* they're logically part of the hostname.
*
* Like strchr in the C standard library, we accept a const char * as
* input, and produce a mutable char * as output. The intention is
* that you EITHER pass a mutable char * input and use the mutability
* of the output, OR pass a const char * as input and don't use the
* mutability, but don't use this to silently launder consts off
* things.
*/
static char *colon(char *str)
static char *colon(const char *str)
{
/* We ignore a leading colon, since the hostname cannot be
empty. We also ignore a colon as second character because
@ -548,7 +558,7 @@ static char *colon(char *str)
return (NULL);
str += host_strcspn(str, ":/\\");
if (*str == ':')
return (str);
return (char *)str;
else
return (NULL);
}
@ -1966,16 +1976,16 @@ static void sink(const char *targ, const char *src)
/*
* We will copy local files to a remote server.
*/
static void toremote(int argc, char *argv[])
static void toremote(CmdlineArg **args, size_t nargs)
{
char *src, *wtarg, *host, *user;
const char *targ;
char *wtarg, *host, *user;
const char *src, *targ;
char *cmd;
int i, wc_type;
int wc_type;
uploading = true;
wtarg = argv[argc - 1];
wtarg = dupstr(cmdline_arg_to_str(args[nargs - 1]));
/* Separate host from filename */
host = wtarg;
@ -2001,13 +2011,14 @@ static void toremote(int argc, char *argv[])
user = NULL;
}
if (argc == 2) {
if (colon(argv[0]) != NULL)
bump("%s: Remote to remote not supported", argv[0]);
if (nargs == 2) {
const char *arg0 = cmdline_arg_to_str(args[0]);
if (colon(arg0) != NULL)
bump("%s: Remote to remote not supported", arg0);
wc_type = test_wildcard(argv[0], true);
wc_type = test_wildcard(arg0, true);
if (wc_type == WCTYPE_NONEXISTENT)
bump("%s: No such file or directory\n", argv[0]);
bump("%s: No such file or directory\n", arg0);
else if (wc_type == WCTYPE_WILDCARD)
targetshouldbedirectory = true;
}
@ -2023,8 +2034,8 @@ static void toremote(int argc, char *argv[])
if (scp_source_setup(targ, targetshouldbedirectory))
return;
for (i = 0; i < argc - 1; i++) {
src = argv[i];
for (size_t i = 0; i < nargs - 1; i++) {
src = cmdline_arg_to_str(args[i]);
if (colon(src) != NULL) {
tell_user(stderr, "%s: Remote to remote not supported\n", src);
errs++;
@ -2061,19 +2072,19 @@ static void toremote(int argc, char *argv[])
/*
* We will copy files from a remote server to the local machine.
*/
static void tolocal(int argc, char *argv[])
static void tolocal(CmdlineArg **args, size_t nargs)
{
char *wsrc, *host, *user;
char *wsrc_orig, *wsrc, *host, *user;
const char *src, *targ;
char *cmd;
uploading = false;
if (argc != 2)
if (nargs != 2)
bump("More than one remote source not supported");
wsrc = argv[0];
targ = argv[1];
wsrc = wsrc_orig = dupstr(cmdline_arg_to_str(args[0]));
targ = cmdline_arg_to_str(args[1]);
/* Separate host from filename */
host = wsrc;
@ -2111,19 +2122,20 @@ static void tolocal(int argc, char *argv[])
return;
sink(targ, src);
sfree(wsrc_orig);
}
/*
* We will issue a list command to get a remote directory.
*/
static void get_dir_list(int argc, char *argv[])
static void get_dir_list(CmdlineArg **args, size_t nargs)
{
char *wsrc, *host, *user;
const char *src;
const char *q;
char c;
wsrc = argv[0];
wsrc = dupstr(cmdline_arg_to_str(args[0]));
/* Separate host from filename */
host = wsrc;
@ -2173,6 +2185,8 @@ static void get_dir_list(int argc, char *argv[])
put_byte(scc, c);
stripctrl_free(scc);
}
sfree(wsrc);
}
/*
@ -2256,9 +2270,8 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER;
* Main program. (Called `psftp_main' because it gets called from
* *sftp.c; bit silly, I know, but it had to be called _something_.)
*/
int psftp_main(int argc, char *argv[])
int psftp_main(CmdlineArgList *arglist)
{
int i;
bool sanitise_stderr = true;
sk_init();
@ -2267,56 +2280,59 @@ int psftp_main(int argc, char *argv[])
conf = conf_new();
do_defaults(NULL, conf);
for (i = 1; i < argc; i++) {
int ret;
if (argv[i][0] != '-')
size_t arglistpos = 0;
while (arglist->args[arglistpos]) {
CmdlineArg *arg = arglist->args[arglistpos++];
CmdlineArg *nextarg = arglist->args[arglistpos];
const char *argstr = cmdline_arg_to_str(arg);
if (argstr[0] != '-') {
arglistpos--; /* logically push that argument back on the list */
break;
ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, conf);
}
int ret = cmdline_process_param(arg, nextarg, 1, conf);
if (ret == -2) {
cmdline_error("option \"%s\" requires an argument", argv[i]);
cmdline_error("option \"%s\" requires an argument", argstr);
} else if (ret == 2) {
i++; /* skip next argument */
arglistpos++; /* skip next argument */
} else if (ret == 1) {
/* We have our own verbosity in addition to `flags'. */
if (cmdline_verbose())
verbose = true;
} else if (strcmp(argv[i], "-pgpfp") == 0) {
} else if (strcmp(argstr, "-pgpfp") == 0) {
pgp_fingerprints();
return 1;
} else if (strcmp(argv[i], "-r") == 0) {
} else if (strcmp(argstr, "-r") == 0) {
recursive = true;
} else if (strcmp(argv[i], "-p") == 0) {
} else if (strcmp(argstr, "-p") == 0) {
preserve = true;
} else if (strcmp(argv[i], "-q") == 0) {
} else if (strcmp(argstr, "-q") == 0) {
statistics = false;
} else if (strcmp(argv[i], "-h") == 0 ||
strcmp(argv[i], "-?") == 0 ||
strcmp(argv[i], "--help") == 0) {
} else if (strcmp(argstr, "-h") == 0 ||
strcmp(argstr, "-?") == 0 ||
strcmp(argstr, "--help") == 0) {
usage();
} else if (strcmp(argv[i], "-V") == 0 ||
strcmp(argv[i], "--version") == 0) {
} else if (strcmp(argstr, "-V") == 0 ||
strcmp(argstr, "--version") == 0) {
version();
} else if (strcmp(argv[i], "-ls") == 0) {
} else if (strcmp(argstr, "-ls") == 0) {
list = true;
} else if (strcmp(argv[i], "-unsafe") == 0) {
} else if (strcmp(argstr, "-unsafe") == 0) {
scp_unsafe_mode = true;
} else if (strcmp(argv[i], "-sftp") == 0) {
} else if (strcmp(argstr, "-sftp") == 0) {
try_scp = false; try_sftp = true;
} else if (strcmp(argv[i], "-scp") == 0) {
} else if (strcmp(argstr, "-scp") == 0) {
try_scp = true; try_sftp = false;
} else if (strcmp(argv[i], "-sanitise-stderr") == 0) {
} else if (strcmp(argstr, "-sanitise-stderr") == 0) {
sanitise_stderr = true;
} else if (strcmp(argv[i], "-no-sanitise-stderr") == 0) {
} else if (strcmp(argstr, "-no-sanitise-stderr") == 0) {
sanitise_stderr = false;
} else if (strcmp(argv[i], "--") == 0) {
i++;
} else if (strcmp(argstr, "--") == 0) {
arglistpos++;
break;
} else {
cmdline_error("unknown option \"%s\"", argv[i]);
cmdline_error("unknown option \"%s\"", argstr);
}
}
argc -= i;
argv += i;
backend = NULL;
stdio_sink_init(&stderr_ss, stderr);
@ -2328,22 +2344,26 @@ int psftp_main(int argc, char *argv[])
string_scc = stripctrl_new(NULL, false, L'\0');
CmdlineArg **scpargs = arglist->args + arglistpos;
size_t nscpargs = 0;
while (scpargs[nscpargs])
nscpargs++;
if (list) {
if (argc != 1)
if (nscpargs != 1)
usage();
get_dir_list(argc, argv);
get_dir_list(scpargs, nscpargs);
} else {
if (argc < 2)
if (nscpargs < 2)
usage();
if (argc > 2)
if (nscpargs > 2)
targetshouldbedirectory = true;
if (colon(argv[argc - 1]) != NULL)
toremote(argc, argv);
if (colon(cmdline_arg_to_str(scpargs[nscpargs - 1])) != NULL)
toremote(scpargs, nscpargs);
else
tolocal(argc, argv);
tolocal(scpargs, nscpargs);
}
if (backend && backend_connected(backend)) {
@ -2355,6 +2375,7 @@ int psftp_main(int argc, char *argv[])
random_save_seed();
cmdline_cleanup();
cmdline_arg_list_free(arglist);
if (backend) {
backend_free(backend);
backend = NULL;

62
psftp.c
View File

@ -2393,7 +2393,7 @@ static void do_sftp_cleanup(void)
}
}
int do_sftp(int mode, int modeflags, char *batchfile)
int do_sftp(int mode, int modeflags, const char *batchfile)
{
FILE *fp;
int ret;
@ -2793,15 +2793,15 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER;
/*
* Main program. Parse arguments etc.
*/
int psftp_main(int argc, char *argv[])
int psftp_main(CmdlineArgList *arglist)
{
int i, toret;
int toret;
int portnumber = 0;
char *userhost, *user;
int mode = 0;
int modeflags = 0;
bool sanitise_stderr = true;
char *batchfile = NULL;
const char *batchfile = NULL;
sk_init();
@ -2811,55 +2811,57 @@ int psftp_main(int argc, char *argv[])
conf = conf_new();
do_defaults(NULL, conf);
for (i = 1; i < argc; i++) {
int retd;
if (argv[i][0] != '-') {
size_t arglistpos = 0;
while (arglist->args[arglistpos]) {
CmdlineArg *arg = arglist->args[arglistpos++];
CmdlineArg *nextarg = arglist->args[arglistpos];
const char *argstr = cmdline_arg_to_str(arg);
if (argstr[0] != '-') {
if (userhost)
usage();
else
userhost = dupstr(argv[i]);
userhost = dupstr(argstr);
continue;
}
retd = cmdline_process_param(
argv[i], i+1 < argc ? argv[i+1] : NULL, 1, conf);
int retd = cmdline_process_param(arg, nextarg, 1, conf);
if (retd == -2) {
cmdline_error("option \"%s\" requires an argument", argv[i]);
cmdline_error("option \"%s\" requires an argument", argstr);
} else if (retd == 2) {
i++; /* skip next argument */
arglistpos++; /* skip next argument */
} else if (retd == 1) {
/* We have our own verbosity in addition to `flags'. */
if (cmdline_verbose())
verbose = true;
} else if (strcmp(argv[i], "-h") == 0 ||
strcmp(argv[i], "-?") == 0 ||
strcmp(argv[i], "--help") == 0) {
} else if (strcmp(argstr, "-h") == 0 ||
strcmp(argstr, "-?") == 0 ||
strcmp(argstr, "--help") == 0) {
usage();
} else if (strcmp(argv[i], "-pgpfp") == 0) {
} else if (strcmp(argstr, "-pgpfp") == 0) {
pgp_fingerprints();
return 1;
} else if (strcmp(argv[i], "-V") == 0 ||
strcmp(argv[i], "--version") == 0) {
} else if (strcmp(argstr, "-V") == 0 ||
strcmp(argstr, "--version") == 0) {
version();
} else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
} else if (strcmp(argstr, "-b") == 0 && nextarg) {
mode = 1;
batchfile = argv[++i];
} else if (strcmp(argv[i], "-bc") == 0) {
batchfile = cmdline_arg_to_str(nextarg);
arglistpos++;
} else if (strcmp(argstr, "-bc") == 0) {
modeflags = modeflags | 1;
} else if (strcmp(argv[i], "-be") == 0) {
} else if (strcmp(argstr, "-be") == 0) {
modeflags = modeflags | 2;
} else if (strcmp(argv[i], "-sanitise-stderr") == 0) {
} else if (strcmp(argstr, "-sanitise-stderr") == 0) {
sanitise_stderr = true;
} else if (strcmp(argv[i], "-no-sanitise-stderr") == 0) {
} else if (strcmp(argstr, "-no-sanitise-stderr") == 0) {
sanitise_stderr = false;
} else if (strcmp(argv[i], "--") == 0) {
i++;
} else if (strcmp(argstr, "--") == 0) {
arglistpos++;
break;
} else {
cmdline_error("unknown option \"%s\"", argv[i]);
cmdline_error("unknown option \"%s\"", argstr);
}
}
argc -= i;
argv += i;
backend = NULL;
stdio_sink_init(&stderr_ss, stderr);
@ -2897,6 +2899,8 @@ int psftp_main(int argc, char *argv[])
" to connect\n");
}
cmdline_arg_list_free(arglist);
toret = do_sftp(mode, modeflags, batchfile);
if (backend && backend_connected(backend)) {

View File

@ -55,7 +55,7 @@ void platform_psftp_pre_conn_setup(LogPolicy *lp);
* The main program in psftp.c. Called from main() in the platform-
* specific code, after doing any platform-specific initialisation.
*/
int psftp_main(int argc, char *argv[]);
int psftp_main(CmdlineArgList *arglist);
/*
* These functions are used by PSCP to transmit progress updates

View File

@ -50,7 +50,7 @@ struct psocks_state {
unsigned log_flags;
RecordDestination rec_dest;
char *rec_cmd;
strbuf *subcmd;
bool got_subcmd;
ConnectionLayer cl;
};
@ -409,7 +409,6 @@ psocks_state *psocks_new(const PsocksPlatform *platform)
ps->log_flags = LOG_CONNSTATUS;
ps->rec_dest = REC_NONE;
ps->platform = platform;
ps->subcmd = strbuf_new();
return ps;
}
@ -417,19 +416,19 @@ psocks_state *psocks_new(const PsocksPlatform *platform)
void psocks_free(psocks_state *ps)
{
portfwdmgr_free(ps->portfwdmgr);
strbuf_free(ps->subcmd);
sfree(ps->rec_cmd);
sfree(ps);
}
void psocks_cmdline(psocks_state *ps, int argc, char **argv)
void psocks_cmdline(psocks_state *ps, CmdlineArgList *arglist)
{
bool doing_opts = true;
bool accumulating_exec_args = false;
size_t arglistpos = 0;
size_t args_seen = 0;
while (--argc > 0) {
const char *p = *++argv;
while (arglist->args[arglistpos]) {
CmdlineArg *arg = arglist->args[arglistpos++];
const char *p = cmdline_arg_to_str(arg);
if (doing_opts && p[0] == '-' && p[1]) {
if (!strcmp(p, "--")) {
@ -446,8 +445,9 @@ void psocks_cmdline(psocks_state *ps, int argc, char **argv)
"platform\n");
exit(1);
}
if (--argc > 0) {
ps->rec_cmd = dupstr(*++argv);
if (arglist->args[arglistpos] > 0) {
ps->rec_cmd = dupstr(
cmdline_arg_to_str(arglist->args[arglistpos++]));
} else {
fprintf(stderr, "psocks: expected an argument to '-p'\n");
exit(1);
@ -459,10 +459,15 @@ void psocks_cmdline(psocks_state *ps, int argc, char **argv)
"supported on this platform\n");
exit(1);
}
accumulating_exec_args = true;
if (!arglist->args[arglistpos]) {
fprintf(stderr, "psocks: --exec requires a command\n");
exit(1);
}
/* Now consume all further argv words for the
* subcommand, even if they look like options */
doing_opts = false;
ps->platform->found_subcommand(arglist->args[arglistpos]);
ps->got_subcmd = true;
break;
} else if (!strcmp(p, "--help")) {
printf("usage: psocks [ -d ] [ -f");
if (ps->platform->open_pipes)
@ -490,9 +495,7 @@ void psocks_cmdline(psocks_state *ps, int argc, char **argv)
exit(1);
}
} else {
if (accumulating_exec_args) {
put_asciz(ps->subcmd, p);
} else switch (args_seen++) {
switch (args_seen++) {
case 0:
ps->listen_port = atoi(p);
break;
@ -515,8 +518,8 @@ void psocks_start(psocks_state *ps)
portfwdmgr_config(ps->portfwdmgr, conf);
if (ps->subcmd->len)
ps->platform->start_subcommand(ps->subcmd);
if (ps->got_subcmd)
ps->platform->start_subcommand();
conf_free(conf);
}

View File

@ -19,10 +19,11 @@ struct PsocksPlatform {
PsocksDataSink *(*open_pipes)(
const char *cmd, const char *const *direction_args,
const char *index_arg, char **err);
void (*start_subcommand)(strbuf *args);
void (*found_subcommand)(CmdlineArg *arg);
void (*start_subcommand)(void);
};
psocks_state *psocks_new(const PsocksPlatform *);
void psocks_free(psocks_state *ps);
void psocks_cmdline(psocks_state *ps, int argc, char **argv);
void psocks_cmdline(psocks_state *ps, CmdlineArgList *arglist);
void psocks_start(psocks_state *ps);

34
putty.h
View File

@ -2402,17 +2402,11 @@ void printer_finish_job(printer_job *);
* Exports from cmdline.c (and also cmdline_error(), which is
* defined differently in various places and required _by_
* cmdline.c).
*
* Note that cmdline_process_param takes a const option string, but a
* writable argument string. That's not a mistake - that's so it can
* zero out password arguments in the hope of not having them show up
* avoidably in Unix 'ps'.
*/
struct cmdline_get_passwd_input_state { bool tried; };
#define CMDLINE_GET_PASSWD_INPUT_STATE_INIT { .tried = false }
extern const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new;
int cmdline_process_param(const char *, char *, int, Conf *);
int cmdline_process_param(CmdlineArg *, CmdlineArg *, int, Conf *);
void cmdline_run_saved(Conf *);
void cmdline_cleanup(void);
SeatPromptResult cmdline_get_passwd_input(
@ -2421,6 +2415,32 @@ bool cmdline_host_ok(Conf *);
bool cmdline_verbose(void);
bool cmdline_loaded_session(void);
/*
* Abstraction provided by each platform to represent a command-line
* argument. May not be as simple as a default-encoded string: on
* Windows, command lines can be Unicode representing characters not
* in the system codepage, so you might need to retrieve the argument
* in a richer form.
*/
struct CmdlineArgList {
/* args[0], args[1], ... represent the original arguments in the
* command line. Then there's a null pointer. Further arguments
* can be invented to add to the array after that, in which case
* they'll be freed with the rest of the CmdlineArgList, but
* aren't logically part of the original command line. */
CmdlineArg **args;
size_t nargs, argssize;
};
struct CmdlineArg {
CmdlineArgList *list;
};
const char *cmdline_arg_to_utf8(CmdlineArg *arg); /* may fail */
const char *cmdline_arg_to_str(CmdlineArg *arg); /* must not fail */
void cmdline_arg_wipe(CmdlineArg *arg);
CmdlineArg *cmdline_arg_from_str(CmdlineArgList *list, const char *string);
/* Platforms provide their own constructors for CmdlineArgList */
void cmdline_arg_list_free(CmdlineArgList *list);
/*
* Here we have a flags word provided by each tool, which describes
* the capabilities of that tool that cmdline.c needs to know about.

View File

@ -4,6 +4,7 @@ add_sources_from_current_dir(utils
utils/arm_arch_queries.c
utils/block_signal.c
utils/cloexec.c
utils/cmdline_arg.c
utils/dputs.c
utils/filename.c
utils/fontspec.c

View File

@ -312,7 +312,6 @@ void window_setup_error(const char *errmsg)
bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
{
bool err = false;
char *val;
/*
* Macros to make argument handling easier.
@ -323,20 +322,26 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
* {...} else ((void)0).
*/
#define EXPECTS_ARG if (1) { \
if (--argc <= 0) { \
if (!nextarg) { \
err = true; \
fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
continue; \
} else \
val = *++argv; \
} else { \
arglistpos++; \
} \
} else ((void)0)
#define SECOND_PASS_ONLY if (1) { \
if (!do_everything) \
continue; \
} else ((void)0)
while (--argc > 0) {
const char *p = *++argv;
CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv);
size_t arglistpos = 0;
while (arglist->args[arglistpos]) {
CmdlineArg *arg = arglist->args[arglistpos++];
CmdlineArg *nextarg = arglist->args[arglistpos];
const char *p = cmdline_arg_to_str(arg);
const char *val = cmdline_arg_to_str(nextarg);
int ret;
/*
@ -350,13 +355,13 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
!strcmp(p, "-T"))
p = "-title";
ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
do_everything ? 1 : -1, conf);
ret = cmdline_process_param(
arg, nextarg, do_everything ? 1 : -1, conf);
if (ret == -2) {
cmdline_error("option \"%s\" requires an argument", p);
} else if (ret == 2) {
--argc, ++argv; /* skip next argument */
arglistpos++;
continue;
} else if (ret == 1) {
continue;
@ -458,13 +463,8 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
if (!do_everything)
break;
if (--argc > 0) {
int i;
pty_argv = snewn(argc+1, char *);
++argv;
for (i = 0; i < argc; i++)
pty_argv[i] = argv[i];
pty_argv[argc] = NULL;
if (nextarg) {
pty_argv = cmdline_arg_remainder(nextarg);
break; /* finished command-line processing */
} else
err = true, fprintf(stderr, "%s: -e expects an argument\n",
@ -552,6 +552,8 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
}
}
cmdline_arg_list_free(arglist);
return err;
}

View File

@ -477,4 +477,8 @@ void plug_closing_errno(Plug *plug, int error);
SeatPromptResult make_spr_sw_abort_errno(const char *prefix, int errno_value);
/* Unix-specific extra functions in cmdline_arg.c */
CmdlineArgList *cmdline_arg_list_from_argv(int argc, char **argv);
char **cmdline_arg_remainder(CmdlineArg *argp);
#endif /* PUTTY_UNIX_PLATFORM_H */

View File

@ -723,16 +723,19 @@ int main(int argc, char **argv)
}
}
}
while (--argc) {
char *p = *++argv;
int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
1, conf);
CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv);
size_t arglistpos = 0;
while (arglist->args[arglistpos]) {
CmdlineArg *arg = arglist->args[arglistpos++];
CmdlineArg *nextarg = arglist->args[arglistpos];
const char *p = cmdline_arg_to_str(arg);
int ret = cmdline_process_param(arg, nextarg, 1, conf);
if (ret == -2) {
fprintf(stderr,
"plink: option \"%s\" requires an argument\n", p);
errors = true;
} else if (ret == 2) {
--argc, ++argv;
arglistpos++;
} else if (ret == 1) {
continue;
} else if (!strcmp(p, "-s")) {
@ -781,12 +784,11 @@ int main(int argc, char **argv)
} else if (*p != '-') {
strbuf *cmdbuf = strbuf_new();
while (argc > 0) {
while (arg) {
if (cmdbuf->len > 0)
put_byte(cmdbuf, ' '); /* add space separator */
put_dataz(cmdbuf, p);
if (--argc > 0)
p = *++argv;
put_dataz(cmdbuf, cmdline_arg_to_str(arg));
arg = arglist->args[arglistpos++];
}
conf_set_str(conf, CONF_remote_cmd, cmdbuf->s);
@ -815,6 +817,8 @@ int main(int argc, char **argv)
*/
cmdline_run_saved(conf);
cmdline_arg_list_free(arglist);
/*
* If we have no better ideas for the remote username, use the local
* one, as 'ssh' does.

View File

@ -82,7 +82,14 @@ static pid_t subcommand_pid = -1;
static bool still_running = true;
static void start_subcommand(strbuf *args)
static char **exec_args = NULL;
static void found_subcommand(CmdlineArg *arg)
{
exec_args = cmdline_arg_remainder(arg);
}
static void start_subcommand(void)
{
pid_t pid;
@ -95,24 +102,6 @@ static void start_subcommand(strbuf *args)
}
putty_signal(SIGCHLD, sigchld);
/*
* Make an array of argument pointers that execvp will like.
*/
size_t nargs = 0;
for (size_t i = 0; i < args->len; i++)
if (args->s[i] == '\0')
nargs++;
char **exec_args = snewn(nargs + 1, char *);
char *p = args->s;
for (size_t a = 0; a < nargs; a++) {
exec_args[a] = p;
size_t len = strlen(p);
assert(len < args->len - (p - args->s));
p += 1 + len;
}
exec_args[nargs] = NULL;
pid = fork();
if (pid < 0) {
perror("fork");
@ -123,12 +112,12 @@ static void start_subcommand(strbuf *args)
_exit(127);
} else {
subcommand_pid = pid;
sfree(exec_args);
}
}
static const PsocksPlatform platform = {
open_pipes,
found_subcommand,
start_subcommand,
};
@ -163,11 +152,13 @@ static bool psocks_continue(void *ctx, bool found_any_fd,
int main(int argc, char **argv)
{
psocks_state *ps = psocks_new(&platform);
psocks_cmdline(ps, argc, argv);
CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv);
psocks_cmdline(ps, arglist);
sk_init();
uxsel_init();
psocks_start(ps);
cmdline_arg_list_free(arglist);
cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL);
}

View File

@ -577,5 +577,6 @@ const bool buildinfo_gtk_relevant = false;
int main(int argc, char *argv[])
{
uxsel_init();
return psftp_main(argc, argv);
CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv);
return psftp_main(arglist);
}

158
unix/utils/cmdline_arg.c Normal file
View File

@ -0,0 +1,158 @@
/*
* Implementation of the CmdlineArg abstraction for Unix
*/
#include "putty.h"
typedef struct CmdlineArgUnix CmdlineArgUnix;
struct CmdlineArgUnix {
/*
* This is a writable char *, because the arguments received by
* main() really are writable, and moreover, you _want_ to write
* over them in some circumstances, to manipulate how your program
* shows up in ps(1). Our example is wiping out the argument to
* the -pw option. This isn't robust - you need to not use that
* option at all if you want zero risk of password exposure
* through ps - but we do the best we can.
*
* Some CmdlineArg structures are invented after the program
* starts, in which case they don't correspond to real argv words
* at all, and this pointer is NULL.
*/
char *argv_word;
/*
* A CmdlineArg invented later might need to store a string that
* will be freed when it goes away. This pointer is non-NULL if
* freeing needs to happen.
*/
char *to_free;
/*
* This const char * is the real string value of the argument.
*/
const char *value;
/*
* Our index in the CmdlineArgList, or (size_t)-1 if we don't have
* one and are an argument invented later.
*/
size_t index;
/*
* Public part of the structure.
*/
CmdlineArg argp;
};
static CmdlineArgUnix *cmdline_arg_new_in_list(CmdlineArgList *list)
{
CmdlineArgUnix *arg = snew(CmdlineArgUnix);
arg->argv_word = NULL;
arg->to_free = NULL;
arg->value = NULL;
arg->index = (size_t)-1;
arg->argp.list = list;
sgrowarray(list->args, list->argssize, list->nargs);
list->args[list->nargs++] = &arg->argp;
return arg;
}
static CmdlineArg *cmdline_arg_from_argv_word(CmdlineArgList *list, char *word)
{
CmdlineArgUnix *arg = cmdline_arg_new_in_list(list);
arg->argv_word = word;
arg->value = arg->argv_word;
return &arg->argp;
}
CmdlineArgList *cmdline_arg_list_from_argv(int argc, char **argv)
{
CmdlineArgList *list = snew(CmdlineArgList);
list->args = NULL;
list->nargs = list->argssize = 0;
for (int i = 1; i < argc; i++) {
CmdlineArg *argp = cmdline_arg_from_argv_word(list, argv[i]);
CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp);
arg->index = i - 1; /* index in list->args[], not in argv[] */
}
sgrowarray(list->args, list->argssize, list->nargs);
list->args[list->nargs++] = NULL;
return list;
}
void cmdline_arg_free(CmdlineArg *argp)
{
if (!argp)
return;
CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp);
if (arg->to_free)
burnstr(arg->to_free);
sfree(arg);
}
void cmdline_arg_list_free(CmdlineArgList *list)
{
for (size_t i = 0; i < list->nargs; i++)
cmdline_arg_free(list->args[i]);
sfree(list->args);
sfree(list);
}
CmdlineArg *cmdline_arg_from_str(CmdlineArgList *list, const char *string)
{
CmdlineArgUnix *arg = cmdline_arg_new_in_list(list);
arg->to_free = dupstr(string);
arg->value = arg->to_free;
return &arg->argp;
}
const char *cmdline_arg_to_str(CmdlineArg *argp)
{
if (!argp)
return NULL;
CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp);
return arg->value;
}
const char *cmdline_arg_to_utf8(CmdlineArg *argp)
{
/* For the moment, return NULL. But perhaps it makes sense to
* convert from the default locale into UTF-8? */
return NULL;
}
void cmdline_arg_wipe(CmdlineArg *argp)
{
if (!argp)
return;
CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp);
if (arg->argv_word)
smemclr(arg->argv_word, strlen(arg->argv_word));
}
char **cmdline_arg_remainder(CmdlineArg *argp)
{
CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp);
CmdlineArgList *list = argp->list;
size_t index = arg->index;
assert(index != (size_t)-1);
size_t nargs = 0;
while (list->args[index + nargs])
nargs++;
char **argv = snewn(nargs + 1, char *);
for (size_t i = 0; i < nargs; i++) {
CmdlineArg *ith_argp = list->args[index + i];
CmdlineArgUnix *ith_arg = container_of(ith_argp, CmdlineArgUnix, argp);
argv[i] = ith_arg->argv_word;
}
argv[nargs] = NULL;
return argv;
}

View File

@ -6,6 +6,7 @@ add_sources_from_current_dir(utils
utils/arm_arch_queries.c
utils/aux_match_opt.c
utils/centre_window.c
utils/cmdline_arg.c
utils/cryptoapi.c
utils/defaults.c
utils/dll_hijacking_protection.c

View File

@ -1537,8 +1537,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
const char *command = NULL;
const char *unixsocket = NULL;
bool show_keylist_on_startup = false;
int argc;
char **argv, **argstart;
const char *openssh_config_file = NULL;
typedef struct CommandLineKey {
@ -1598,24 +1596,23 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
* started up the main agent. Details of keys to be added are
* stored in the 'clkeys' array.
*/
split_into_argv(cmdline, false, &argc, &argv, &argstart);
bool add_keys_encrypted = false;
AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error);
AuxMatchOpt amo = aux_match_opt_init(opt_error);
while (!aux_match_done(&amo)) {
char *val;
CmdlineArg *valarg;
#define match_opt(...) aux_match_opt( \
&amo, NULL, __VA_ARGS__, (const char *)NULL)
#define match_optval(...) aux_match_opt( \
&amo, &val, __VA_ARGS__, (const char *)NULL)
&amo, &valarg, __VA_ARGS__, (const char *)NULL)
if (aux_match_arg(&amo, &val)) {
if (aux_match_arg(&amo, &valarg)) {
/*
* Non-option arguments are expected to be key files, and
* added to clkeys.
*/
sgrowarray(clkeys, clkeysize, nclkeys);
CommandLineKey *clkey = &clkeys[nclkeys++];
clkey->fn = filename_from_str(val);
clkey->fn = filename_from_str(cmdline_arg_to_str(valarg));
clkey->add_encrypted = add_keys_encrypted;
} else if (match_opt("-pgpfp")) {
pgp_fingerprints_msgbox(NULL);
@ -1631,21 +1628,26 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
} else if (match_opt("-keylist")) {
show_keylist_on_startup = true;
} else if (match_optval("-openssh-config", "-openssh_config")) {
openssh_config_file = val;
openssh_config_file = cmdline_arg_to_str(valarg);
} else if (match_optval("-unix")) {
unixsocket = val;
unixsocket = cmdline_arg_to_str(valarg);
} else if (match_opt("-c")) {
/*
* If we see `-c', then the rest of the command line
* should be treated as a command to be spawned.
*/
if (amo.index < amo.argc)
command = argstart[amo.index];
else
if (amo.arglist->args[amo.index]) {
/* UNICODE: should use the UTF-8 or wide version, and
* CreateProcessW, to pass through arbitrary command lines */
command = cmdline_arg_remainder_acp(
amo.arglist->args[amo.index]);
} else {
command = "";
}
break;
} else {
opt_error("unrecognised option '%s'\n", amo.argv[amo.index]);
opt_error("unrecognised option '%s'\n",
cmdline_arg_to_str(amo.arglist->args[amo.index]));
}
}

View File

@ -848,15 +848,15 @@ void unlock_interprocess_mutex(HANDLE mutex);
typedef void (*aux_opt_error_fn_t)(const char *, ...);
typedef struct AuxMatchOpt {
int index, argc;
char **argv;
CmdlineArgList *arglist;
size_t index;
bool doing_opts;
aux_opt_error_fn_t error;
} AuxMatchOpt;
AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index,
aux_opt_error_fn_t opt_error);
bool aux_match_arg(AuxMatchOpt *amo, char **val);
bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...);
AuxMatchOpt aux_match_opt_init(aux_opt_error_fn_t opt_error);
bool aux_match_arg(AuxMatchOpt *amo, CmdlineArg **val);
bool aux_match_opt(AuxMatchOpt *amo, CmdlineArg **val,
const char *optname, ...);
bool aux_match_done(AuxMatchOpt *amo);
char *save_screenshot(HWND hwnd, const char *outfile);
@ -864,4 +864,11 @@ void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend);
void setup_gui_timing(void);
/* Windows-specific extra functions in cmdline_arg.c */
CmdlineArgList *cmdline_arg_list_from_GetCommandLineW(void);
const wchar_t *cmdline_arg_remainder_wide(CmdlineArg *);
char *cmdline_arg_remainder_acp(CmdlineArg *);
char *cmdline_arg_remainder_utf8(CmdlineArg *);
CmdlineArg *cmdline_arg_from_utf8(CmdlineArgList *list, const char *string);
#endif /* PUTTY_WINDOWS_PLATFORM_H */

View File

@ -328,16 +328,19 @@ int main(int argc, char **argv)
}
}
}
while (--argc) {
char *p = *++argv;
int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
1, conf);
CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW();
size_t arglistpos = 0;
while (arglist->args[arglistpos]) {
CmdlineArg *arg = arglist->args[arglistpos++];
CmdlineArg *nextarg = arglist->args[arglistpos];
const char *p = cmdline_arg_to_str(arg);
int ret = cmdline_process_param(arg, nextarg, 1, conf);
if (ret == -2) {
fprintf(stderr,
"plink: option \"%s\" requires an argument\n", p);
errors = true;
} else if (ret == 2) {
--argc, ++argv;
arglistpos++;
} else if (ret == 1) {
continue;
} else if (!strcmp(p, "-s")) {
@ -369,12 +372,11 @@ int main(int argc, char **argv)
} else if (*p != '-') {
strbuf *cmdbuf = strbuf_new();
while (argc > 0) {
while (arg) {
if (cmdbuf->len > 0)
put_byte(cmdbuf, ' '); /* add space separator */
put_dataz(cmdbuf, p);
if (--argc > 0)
p = *++argv;
put_dataz(cmdbuf, cmdline_arg_to_str(arg));
arg = arglist->args[arglistpos++];
}
conf_set_str(conf, CONF_remote_cmd, cmdbuf->s);

View File

@ -8,13 +8,15 @@
static const PsocksPlatform platform = {
NULL /* open_pipes */,
NULL /* found_subcommand */,
NULL /* start_subcommand */,
};
int main(int argc, char **argv)
{
psocks_state *ps = psocks_new(&platform);
psocks_cmdline(ps, argc, argv);
CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW();
psocks_cmdline(ps, arglist);
sk_init();
winselcli_setup();

View File

@ -15,33 +15,34 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline)
handle_special_filemapping_cmdline(cmdline, conf))
return;
int argc;
char **argv, **argstart;
split_into_argv(cmdline, false, &argc, &argv, &argstart);
for (int i = 0; i < argc; i++) {
char *arg = argv[i];
int retd = cmdline_process_param(
arg, i+1<argc?argv[i+1]:NULL, 1, conf);
CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW();
size_t arglistpos = 0;
while (arglist->args[arglistpos]) {
CmdlineArg *arg = arglist->args[arglistpos++];
CmdlineArg *nextarg = arglist->args[arglistpos];
const char *argstr = cmdline_arg_to_str(arg);
int retd = cmdline_process_param(arg, nextarg, 1, conf);
if (retd == -2) {
cmdline_error("option \"%s\" requires an argument", arg);
cmdline_error("option \"%s\" requires an argument", argstr);
} else if (retd == 2) {
i++; /* skip next argument */
arglistpos++; /* skip next argument */
} else if (retd == 1) {
continue; /* nothing further needs doing */
} else if (!strcmp(arg, "-e")) {
if (i+1 < argc) {
} else if (!strcmp(argstr, "-e")) {
if (nextarg) {
/* The command to execute is taken to be the unparsed
* version of the whole remainder of the command line. */
conf_set_str(conf, CONF_remote_cmd, argstart[i+1]);
char *cmd = cmdline_arg_remainder_acp(nextarg);
conf_set_str(conf, CONF_remote_cmd, cmd);
sfree(cmd);
return;
} else {
cmdline_error("option \"%s\" requires an argument", arg);
cmdline_error("option \"%s\" requires an argument", argstr);
}
} else if (arg[0] == '-') {
cmdline_error("unrecognised option \"%s\"", arg);
} else if (argstr[0] == '-') {
cmdline_error("unrecognised option \"%s\"", argstr);
} else {
cmdline_error("unexpected non-option argument \"%s\"", arg);
cmdline_error("unexpected non-option argument \"%s\"", argstr);
}
}

View File

@ -48,21 +48,17 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline)
* Otherwise, break up the command line and deal with
* it sensibly.
*/
int argc, i;
char **argv;
split_into_argv(cmdline, false, &argc, &argv, NULL);
for (i = 0; i < argc; i++) {
char *p = argv[i];
int ret;
ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL,
1, conf);
CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW();
size_t arglistpos = 0;
while (arglist->args[arglistpos]) {
CmdlineArg *arg = arglist->args[arglistpos++];
CmdlineArg *nextarg = arglist->args[arglistpos];
const char *p = cmdline_arg_to_str(arg);
int ret = cmdline_process_param(arg, nextarg, 1, conf);
if (ret == -2) {
cmdline_error("option \"%s\" requires an argument", p);
} else if (ret == 2) {
i++; /* skip next argument */
arglistpos++; /* skip next argument */
} else if (ret == 1) {
continue; /* nothing further needs doing */
} else if (!strcmp(p, "-cleanup")) {
@ -98,18 +94,22 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline)
show_ca_config_box(NULL);
exit(0);
} else if (!strcmp(p, "-demo-config-box")) {
if (i+1 >= argc) {
if (!arglist->args[arglistpos]) {
cmdline_error("%s expects an output filename", p);
} else {
demo_config_box = true;
dialog_box_demo_screenshot_filename = argv[++i];
dialog_box_demo_screenshot_filename =
cmdline_arg_to_str(arglist->args[arglistpos++]);
}
} else if (!strcmp(p, "-demo-terminal")) {
if (i+2 >= argc) {
if (!arglist->args[arglistpos] ||
!arglist->args[arglistpos+1]) {
cmdline_error("%s expects input and output filenames", p);
} else {
const char *infile = argv[++i];
terminal_demo_screenshot_filename = argv[++i];
const char *infile =
cmdline_arg_to_str(arglist->args[arglistpos++]);
terminal_demo_screenshot_filename =
cmdline_arg_to_str(arglist->args[arglistpos++]);
FILE *fp = fopen(infile, "rb");
if (!fp)
cmdline_error("can't open input file '%s'", infile);

View File

@ -26,7 +26,7 @@
#define DEFAULT_ECCURVE_INDEX 0
#define DEFAULT_EDCURVE_INDEX 0
static char *cmdline_keyfile = NULL;
static const char *cmdline_keyfile = NULL;
static ptrlen cmdline_demo_keystr;
static const char *demo_screenshot_filename = NULL;
@ -2392,8 +2392,6 @@ static NORETURN void opt_error(const char *fmt, ...)
int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
{
int argc;
char **argv;
int ret;
struct InitialParams params[1];
@ -2417,27 +2415,26 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
save_params = ppk_save_default_parameters;
split_into_argv(cmdline, false, &argc, &argv, NULL);
int argbits = -1;
AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error);
AuxMatchOpt amo = aux_match_opt_init(opt_error);
while (!aux_match_done(&amo)) {
char *val;
CmdlineArg *valarg;
#define match_opt(...) aux_match_opt( \
&amo, NULL, __VA_ARGS__, (const char *)NULL)
#define match_optval(...) aux_match_opt( \
&amo, &val, __VA_ARGS__, (const char *)NULL)
&amo, &valarg, __VA_ARGS__, (const char *)NULL)
if (aux_match_arg(&amo, &val)) {
if (aux_match_arg(&amo, &valarg)) {
if (!cmdline_keyfile) {
/*
* Assume the first argument to be a private key file, and
* attempt to load it.
*/
cmdline_keyfile = val;
cmdline_keyfile = cmdline_arg_to_str(valarg);
continue;
} else {
opt_error("unexpected extra argument '%s'\n", val);
opt_error("unexpected extra argument '%s'\n",
cmdline_arg_to_str(valarg));
}
} else if (match_opt("-pgpfp")) {
pgp_fingerprints_msgbox(NULL);
@ -2446,6 +2443,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
"-restrictacl")) {
restrict_process_acl();
} else if (match_optval("-t")) {
const char *val = cmdline_arg_to_str(valarg);
if (!strcmp(val, "rsa") || !strcmp(val, "rsa2")) {
params->keybutton = IDC_KEYSSH2RSA;
} else if (!strcmp(val, "rsa1")) {
@ -2466,8 +2464,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
opt_error("unknown key type '%s'\n", val);
}
} else if (match_optval("-b")) {
argbits = atoi(val);
argbits = atoi(cmdline_arg_to_str(valarg));
} else if (match_optval("-E")) {
const char *val = cmdline_arg_to_str(valarg);
if (!strcmp(val, "md5"))
params->fptype = SSH_FPTYPE_MD5;
else if (!strcmp(val, "sha256"))
@ -2475,6 +2474,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
else
opt_error("unknown fingerprint type '%s'\n", val);
} else if (match_optval("-primes")) {
const char *val = cmdline_arg_to_str(valarg);
if (!strcmp(val, "probable") ||
!strcmp(val, "probabilistic")) {
params->primepolicybutton = IDC_PRIMEGEN_PROB;
@ -2495,6 +2495,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
} else if (match_opt("-strong-rsa")) {
params->rsa_strong = true;
} else if (match_optval("-ppk-param", "-ppk-params")) {
char *val = dupstr(cmdline_arg_to_str(valarg));
char *nextval;
for (; val; val = nextval) {
nextval = strchr(val, ',');
@ -2547,8 +2548,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
opt_error("unrecognised PPK parameter '%s'\n", val);
}
}
sfree(val);
} else if (match_optval("-demo-screenshot")) {
demo_screenshot_filename = val;
demo_screenshot_filename = cmdline_arg_to_str(valarg);
cmdline_demo_keystr = PTRLEN_LITERAL(
"PuTTY-User-Key-File-3: ssh-ed25519\n"
"Encryption: none\n"
@ -2564,7 +2566,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
params->keybutton = IDC_KEYSSH2EDDSA;
argbits = 255;
} else {
opt_error("unrecognised option '%s'\n", amo.argv[amo.index]);
opt_error("unrecognised option '%s'\n",
cmdline_arg_to_str(amo.arglist->args[amo.index]));
}
}

View File

@ -650,7 +650,8 @@ int main(int argc, char *argv[])
dll_hijacking_protection();
ret = psftp_main(argc, argv);
CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW();
ret = psftp_main(arglist);
return ret;
}

View File

@ -17,20 +17,21 @@ int main(int argc, char **argv)
{
const char *outfile = NULL;
AuxMatchOpt amo = aux_match_opt_init(argc-1, argv+1, 0, fatal_error);
AuxMatchOpt amo = aux_match_opt_init(fatal_error);
while (!aux_match_done(&amo)) {
char *val;
CmdlineArg *val;
#define match_opt(...) aux_match_opt( \
&amo, NULL, __VA_ARGS__, (const char *)NULL)
#define match_optval(...) aux_match_opt( \
&amo, &val, __VA_ARGS__, (const char *)NULL)
if (aux_match_arg(&amo, &val)) {
fatal_error("unexpected argument '%s'", val);
fatal_error("unexpected argument '%s'", cmdline_arg_to_str(val));
} else if (match_optval("-o", "--output")) {
outfile = val;
outfile = cmdline_arg_to_str(val);
} else {
fatal_error("unrecognised option '%s'\n", amo.argv[amo.index]);
fatal_error("unrecognised option '%s'\n",
cmdline_arg_to_str(amo.arglist->args[amo.index]));
}
}

View File

@ -13,14 +13,12 @@
/*
* Call this to initialise the state structure.
*/
AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index,
aux_opt_error_fn_t opt_error)
AuxMatchOpt aux_match_opt_init(aux_opt_error_fn_t opt_error)
{
AuxMatchOpt amo[1];
amo->argc = argc;
amo->argv = argv;
amo->index = start_index;
amo->arglist = cmdline_arg_list_from_GetCommandLineW();
amo->index = 0;
amo->doing_opts = true;
amo->error = opt_error;
@ -32,12 +30,14 @@ AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index,
* Point 'val' at a char * to receive the option argument, if one is
* expected. Set 'val' to NULL if no argument is expected.
*/
bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...)
bool aux_match_opt(AuxMatchOpt *amo, CmdlineArg **val,
const char *optname, ...)
{
assert(amo->index < amo->argc);
/* Find the end of the option name */
char *opt = amo->argv[amo->index];
CmdlineArg *optarg = amo->arglist->args[amo->index];
assert(optarg);
const char *opt = cmdline_arg_to_utf8(optarg);
ptrlen argopt;
argopt.ptr = opt;
argopt.len = strcspn(opt, "=");
@ -72,14 +72,14 @@ bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...)
if (opt[argopt.len]) {
if (!val)
amo->error("option '%s' does not expect a value", opt);
*val = opt + argopt.len + 1;
*val = cmdline_arg_from_utf8(optarg->list, opt + argopt.len + 1);
amo->index++;
} else if (!val) {
amo->index++;
} else {
if (amo->index + 1 >= amo->argc)
if (!amo->arglist->args[amo->index + 1])
amo->error("option '%s' expects a value", opt);
*val = amo->argv[amo->index + 1];
*val = amo->arglist->args[amo->index + 1];
amo->index += 2;
}
@ -89,13 +89,14 @@ bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...)
/*
* Call this to return a non-option argument in *val.
*/
bool aux_match_arg(AuxMatchOpt *amo, char **val)
bool aux_match_arg(AuxMatchOpt *amo, CmdlineArg **val)
{
assert(amo->index < amo->argc);
char *opt = amo->argv[amo->index];
CmdlineArg *optarg = amo->arglist->args[amo->index];
assert(optarg);
const char *opt = cmdline_arg_to_utf8(optarg);
if (!amo->doing_opts || opt[0] != '-' || !strcmp(opt, "-")) {
*val = opt;
*val = optarg;
amo->index++;
return true;
}
@ -108,10 +109,12 @@ bool aux_match_arg(AuxMatchOpt *amo, char **val)
*/
bool aux_match_done(AuxMatchOpt *amo)
{
if (amo->index < amo->argc && !strcmp(amo->argv[amo->index], "--")) {
CmdlineArg *optarg = amo->arglist->args[amo->index];
const char *opt = cmdline_arg_to_utf8(optarg);
if (opt && !strcmp(opt, "--")) {
amo->doing_opts = false;
amo->index++;
}
return amo->index >= amo->argc;
return amo->arglist->args[amo->index] == NULL;
}

209
windows/utils/cmdline_arg.c Normal file
View File

@ -0,0 +1,209 @@
/*
* Implementation of the CmdlineArg abstraction for Windows
*/
#include <wchar.h>
#include "putty.h"
typedef struct CmdlineArgWin CmdlineArgWin;
struct CmdlineArgWin {
/*
* The original wide-character argument.
*/
wchar_t *wide;
/*
* Two translations of the wide-character argument into UTF-8
* (maximally faithful to the original) and CP_ACP (the normal
* system code page).
*/
char *utf8, *acp;
/*
* Our index in the CmdlineArgList, or (size_t)-1 if we don't have
* one and are an argument invented later.
*/
size_t index;
/*
* Public part of the structure.
*/
CmdlineArg argp;
};
typedef struct CmdlineArgListWin CmdlineArgListWin;
struct CmdlineArgListWin {
/*
* Wide string pointer returned from GetCommandLineW. This points
* to the 'official' version of the command line, in the sense
* that overwriting it causes different text to show up in the
* Task Manager display of the process's command line. (So this is
* what we'll need to overwrite _on purpose_ for cmdline_arg_wipe.)
*/
wchar_t *cmdline;
/*
* Data returned from split_into_argv_w.
*/
size_t argc;
wchar_t **argv, **argstart;
/*
* Public part of the structure.
*/
CmdlineArgList listp;
};
static CmdlineArgWin *cmdline_arg_new_in_list(CmdlineArgList *listp)
{
CmdlineArgWin *arg = snew(CmdlineArgWin);
arg->wide = NULL;
arg->utf8 = arg->acp = NULL;
arg->index = (size_t)-1;
arg->argp.list = listp;
sgrowarray(listp->args, listp->argssize, listp->nargs);
listp->args[listp->nargs++] = &arg->argp;
return arg;
}
static CmdlineArg *cmdline_arg_from_wide_argv_word(
CmdlineArgList *list, wchar_t *word)
{
CmdlineArgWin *arg = cmdline_arg_new_in_list(list);
arg->wide = dupwcs(word);
arg->utf8 = dup_wc_to_mb(CP_UTF8, word, "");
arg->acp = dup_wc_to_mb(CP_ACP, word, "");
return &arg->argp;
}
CmdlineArgList *cmdline_arg_list_from_GetCommandLineW(void)
{
CmdlineArgListWin *list = snew(CmdlineArgListWin);
CmdlineArgList *listp = &list->listp;
list->cmdline = GetCommandLineW();
int argc;
split_into_argv_w(list->cmdline, true,
&argc, &list->argv, &list->argstart);
list->argc = (size_t)argc;
listp->args = NULL;
listp->nargs = listp->argssize = 0;
for (size_t i = 1; i < list->argc; i++) {
CmdlineArg *argp = cmdline_arg_from_wide_argv_word(
listp, list->argv[i]);
CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp);
arg->index = i - 1; /* index in list->args[], not in argv[] */
}
sgrowarray(listp->args, listp->argssize, listp->nargs);
listp->args[listp->nargs++] = NULL;
return listp;
}
void cmdline_arg_free(CmdlineArg *argp)
{
if (!argp)
return;
CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp);
burnwcs(arg->wide);
burnstr(arg->utf8);
burnstr(arg->acp);
sfree(arg);
}
void cmdline_arg_list_free(CmdlineArgList *listp)
{
CmdlineArgListWin *list = container_of(listp, CmdlineArgListWin, listp);
for (size_t i = 0; i < listp->nargs; i++)
cmdline_arg_free(listp->args[i]);
/* list->argv[0] points at the start of the string allocated by
* split_into_argv_w */
sfree(list->argv[0]);
sfree(list->argv);
sfree(list->argstart);
sfree(list);
}
CmdlineArg *cmdline_arg_from_str(CmdlineArgList *listp, const char *string)
{
CmdlineArgWin *arg = cmdline_arg_new_in_list(listp);
arg->acp = dupstr(string);
arg->wide = dup_mb_to_wc(CP_ACP, string);
arg->utf8 = dup_wc_to_mb(CP_UTF8, arg->wide, "");
return &arg->argp;
}
CmdlineArg *cmdline_arg_from_utf8(CmdlineArgList *listp, const char *string)
{
CmdlineArgWin *arg = cmdline_arg_new_in_list(listp);
arg->acp = dupstr(string);
arg->wide = dup_mb_to_wc(CP_UTF8, string);
arg->utf8 = dup_wc_to_mb(CP_ACP, arg->wide, "");
return &arg->argp;
}
const char *cmdline_arg_to_str(CmdlineArg *argp)
{
if (!argp)
return NULL;
CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp);
return arg->acp;
}
const char *cmdline_arg_to_utf8(CmdlineArg *argp)
{
if (!argp)
return NULL;
CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp);
return arg->utf8;
}
void cmdline_arg_wipe(CmdlineArg *argp)
{
if (!argp)
return;
CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp);
if (arg->index != (size_t)-1) {
CmdlineArgList *listp = argp->list;
CmdlineArgListWin *list = container_of(
listp, CmdlineArgListWin, listp);
/* arg->index starts from the first argument _after_ program
* name, whereas argstart is indexed from argv[0] */
wchar_t *p = list->argstart[arg->index + 1];
wchar_t *end = (arg->index + 2 < list->argc ?
list->argstart[arg->index + 2] :
p + wcslen(p));
while (p < end)
*p++ = L' ';
}
}
const wchar_t *cmdline_arg_remainder_wide(CmdlineArg *argp)
{
CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp);
CmdlineArgList *listp = argp->list;
CmdlineArgListWin *list = container_of(listp, CmdlineArgListWin, listp);
size_t index = arg->index;
assert(index != (size_t)-1);
/* arg->index starts from the first argument _after_ program
* name, whereas argstart is indexed from argv[0] */
return list->argstart[index + 1];
}
char *cmdline_arg_remainder_acp(CmdlineArg *argp)
{
return dup_wc_to_mb(CP_ACP, cmdline_arg_remainder_wide(argp), "");
}
char *cmdline_arg_remainder_utf8(CmdlineArg *argp)
{
return dup_wc_to_mb(CP_UTF8, cmdline_arg_remainder_wide(argp), "");
}