diff --git a/Recipe b/Recipe index b0b849bf..a80ed804 100644 --- a/Recipe +++ b/Recipe @@ -90,7 +90,7 @@ # GUI front end and terminal emulator (putty, puttytel). GUITERM = window windlg winctrls terminal sizetip wcwidth unicode - + logging printing + + logging printing winutils # Non-SSH back ends (putty, puttytel, plink). NONSSH = telnet raw rlogin ldisc @@ -105,7 +105,7 @@ SFTP = sftp int64 logging # Miscellaneous objects appearing in all the network utilities (not # Pageant or PuTTYgen). -MISC = misc version winstore settings tree234 winnet proxy +MISC = misc version winstore settings tree234 winnet proxy cmdline # Standard libraries, and the same with WinSocks 1 and 2. LIBS = advapi32.lib user32.lib gdi32.lib comctl32.lib comdlg32.lib diff --git a/cmdline.c b/cmdline.c new file mode 100644 index 00000000..f26625ba --- /dev/null +++ b/cmdline.c @@ -0,0 +1,246 @@ +#include +#include +#include +#include +#include "putty.h" + +/* + * Some command-line parameters need to be saved up until after + * we've loaded the saved session which will form the basis of our + * eventual running configuration. For this we use the macro + * SAVEABLE, which notices if the `need_save' parameter is set and + * saves the parameter and value on a list. + * + * We also assign priorities to saved parameters, just to slightly + * ameliorate silly ordering problems. For example, if you specify + * a saved session to load, it will be loaded _before_ all your + * local modifications such as -L are evaluated; and if you specify + * a protocol and a port, the protocol is set up first so that the + * port can override its choice of port number. + */ + +#define NPRIORITIES 3 + +struct cmdline_saved_param { + char *p, *value; +}; +struct cmdline_saved_param_set { + struct cmdline_saved_param *params; + int nsaved, savesize; +}; + +/* + * C guarantees this structure will be initialised to all zero at + * program start, which is exactly what we want. + */ +static struct cmdline_saved_param_set saves[NPRIORITIES]; + +static void cmdline_save_param(char *p, char *value, int pri) +{ + if (saves[pri].nsaved >= saves[pri].savesize) { + saves[pri].savesize = saves[pri].nsaved + 32; + saves[pri].params = + srealloc(saves[pri].params, + saves[pri].savesize*sizeof(*saves[pri].params)); + } + saves[pri].params[saves[pri].nsaved].p = p; + saves[pri].params[saves[pri].nsaved].value = value; + saves[pri].nsaved++; +} + +#define SAVEABLE(pri) do { \ + if (need_save) { cmdline_save_param(p, value, pri); return ret; } \ +} while (0) + +char *cmdline_password = NULL; + +static int cmdline_get_line(const char *prompt, char *str, + int maxlen, int is_pw) +{ + static int tried_once = 0; + + assert(is_pw && cmdline_password); + + if (tried_once) { + return 0; + } else { + strncpy(str, cmdline_password, maxlen); + str[maxlen - 1] = '\0'; + tried_once = 1; + return 1; + } +} + +/* + * Here we have a flags word which describes the capabilities of + * the particular tool on whose behalf we're running. We will + * refuse certain command-line options if a particular tool + * inherently can't do anything sensible. For example, the file + * transfer tools (psftp, pscp) can't do a great deal with protocol + * selections (ever tried running scp over telnet?) or with port + * forwarding (even if it wasn't a hideously bad idea, they don't + * have the select() infrastructure to make them work). + */ +int cmdline_tooltype = 0; + +static int cmdline_check_unavailable(int flag, char *p) +{ + if (cmdline_tooltype & flag) { + cmdline_error("option \"%s\" not available in this tool", p); + return 1; + } + return 0; +} + +#define UNAVAILABLE_IN(flag) do { \ + if (cmdline_check_unavailable(flag, p)) return ret; \ +} while (0) + +/* + * Process a standard command-line parameter. `p' is the parameter + * in question; `value' is the subsequent element of argv, which + * may or may not be required as an operand to the parameter. + * Return value is 2 if both arguments were used; 1 if only p was + * used; 0 if the parameter wasn't one we recognised; -2 if it + * should have been 2 but value was NULL. + */ + +#define RETURN(x) do { \ + if ((x) == 2 && !value) return -2; \ + ret = x; \ +} while (0) + +int cmdline_process_param(char *p, char *value, int need_save) +{ + int ret = 0; + + if (!strcmp(p, "-load")) { + RETURN(2); + SAVEABLE(0); /* very high priority */ + do_defaults(value, &cfg); + return 2; + } + if (!strcmp(p, "-ssh")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); + SAVEABLE(1); + default_protocol = cfg.protocol = PROT_SSH; + default_port = cfg.port = 22; + return 1; + } + if (!strcmp(p, "-telnet")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); + SAVEABLE(1); + default_protocol = cfg.protocol = PROT_TELNET; + default_port = cfg.port = 23; + return 1; + } + if (!strcmp(p, "-rlogin")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); + SAVEABLE(1); + default_protocol = cfg.protocol = PROT_RLOGIN; + default_port = cfg.port = 513; + return 1; + } + if (!strcmp(p, "-raw")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); + SAVEABLE(1); + default_protocol = cfg.protocol = PROT_RAW; + } + if (!strcmp(p, "-v")) { + RETURN(1); + flags |= FLAG_VERBOSE; + } + if (!strcmp(p, "-l")) { + RETURN(2); + SAVEABLE(1); + strncpy(cfg.username, value, sizeof(cfg.username)); + cfg.username[sizeof(cfg.username) - 1] = '\0'; + } + if ((!strcmp(p, "-L") || !strcmp(p, "-R"))) { + char *fwd, *ptr, *q; + int i=0; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); + SAVEABLE(1); + fwd = value; + ptr = cfg.portfwd; + /* if multiple forwards, find end of list */ + if (ptr[0]=='R' || ptr[0]=='L') { + for (i = 0; i < sizeof(cfg.portfwd) - 2; i++) + if (ptr[i]=='\000' && ptr[i+1]=='\000') + break; + ptr = ptr + i + 1; /* point to next forward slot */ + } + ptr[0] = p[1]; /* insert a 'L' or 'R' at the start */ + if (strlen(fwd) > sizeof(cfg.portfwd) - i - 2) { + cmdline_error("out of space for port forwardings"); + return ret; + } + strncpy(ptr+1, fwd, sizeof(cfg.portfwd) - i); + q = strchr(ptr, ':'); + if (q) *q = '\t'; /* replace first : with \t */ + cfg.portfwd[sizeof(cfg.portfwd) - 1] = '\0'; + cfg.portfwd[sizeof(cfg.portfwd) - 2] = '\0'; + ptr[strlen(ptr)+1] = '\000'; /* append two '\000' */ + } + if (!strcmp(p, "-m")) { + char *filename, *command; + int cmdlen, cmdsize; + FILE *fp; + int c, d; + + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); + SAVEABLE(1); + + filename = value; + + cmdlen = cmdsize = 0; + command = NULL; + fp = fopen(filename, "r"); + if (!fp) { + cmdline_error("unable to open command " + "file \"%s\"", filename); + return ret; + } + do { + c = fgetc(fp); + d = c; + if (c == EOF) + d = 0; + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = srealloc(command, cmdsize); + } + command[cmdlen++] = d; + } while (c != EOF); + cfg.remote_cmd_ptr = command; + cfg.remote_cmd_ptr2 = NULL; + cfg.nopty = TRUE; /* command => no terminal */ + } + if (!strcmp(p, "-P")) { + RETURN(2); + SAVEABLE(2); /* lower priority than -ssh,-telnet */ + cfg.port = atoi(value); + } + if (!strcmp(p, "-pw")) { + RETURN(2); + cmdline_password = value; + ssh_get_line = cmdline_get_line; + ssh_getline_pw_only = TRUE; + } + return ret; /* unrecognised */ +} + +void cmdline_run_saved(void) +{ + int pri, i; + for (pri = 0; pri < NPRIORITIES; pri++) + for (i = 0; i < saves[pri].nsaved; i++) + cmdline_process_param(saves[pri].params[i].p, + saves[pri].params[i].value, 0); +} diff --git a/console.c b/console.c index 1078986f..e49eaa09 100644 --- a/console.c +++ b/console.c @@ -261,27 +261,12 @@ void logevent(char *string) { } -char *console_password = NULL; - int console_get_line(const char *prompt, char *str, int maxlen, int is_pw) { HANDLE hin, hout; DWORD savemode, newmode, i; - if (is_pw && console_password) { - static int tried_once = 0; - - if (tried_once) { - return 0; - } else { - strncpy(str, console_password, maxlen); - str[maxlen - 1] = '\0'; - tried_once = 1; - return 1; - } - } - if (console_batch_mode) { if (maxlen > 0) str[0] = '\0'; diff --git a/plink.c b/plink.c index a76570ee..91484c66 100644 --- a/plink.c +++ b/plink.c @@ -40,6 +40,16 @@ void connection_fatal(char *p, ...) WSACleanup(); cleanup_exit(1); } +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "plink: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} static char *password = NULL; @@ -221,7 +231,6 @@ int main(int argc, char **argv) int skcount, sksize; int connopen; int exitcode; - char extra_portfwd[sizeof(cfg.portfwd)]; ssh_get_line = console_get_line; @@ -261,80 +270,18 @@ int main(int argc, char **argv) while (--argc) { char *p = *++argv; if (*p == '-') { - if (!strcmp(p, "-ssh")) { - default_protocol = cfg.protocol = PROT_SSH; - default_port = cfg.port = 22; - } else if (!strcmp(p, "-telnet")) { - default_protocol = cfg.protocol = PROT_TELNET; - default_port = cfg.port = 23; - } else if (!strcmp(p, "-rlogin")) { - default_protocol = cfg.protocol = PROT_RLOGIN; - default_port = cfg.port = 513; - } else if (!strcmp(p, "-raw")) { - default_protocol = cfg.protocol = PROT_RAW; + int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), 1); + if (ret == -2) { + fprintf(stderr, + "plink: option \"%s\" requires an argument\n", p); + } else if (ret == 2) { + --argc, ++argv; + } else if (ret == 1) { + continue; } else if (!strcmp(p, "-batch")) { - console_batch_mode = TRUE; - } else if (!strcmp(p, "-v")) { - flags |= FLAG_VERBOSE; + console_batch_mode = 1; } else if (!strcmp(p, "-log")) { logfile = "putty.log"; - } else if (!strcmp(p, "-pw") && argc > 1) { - --argc, console_password = *++argv; - } else if (!strcmp(p, "-l") && argc > 1) { - char *username; - --argc, username = *++argv; - strncpy(cfg.username, username, sizeof(cfg.username)); - cfg.username[sizeof(cfg.username) - 1] = '\0'; - } else if ((!strcmp(p, "-L") || !strcmp(p, "-R")) && argc > 1) { - char *fwd, *ptr, *q; - int i=0; - --argc, fwd = *++argv; - ptr = extra_portfwd; - /* if multiple forwards, find end of list */ - if (ptr[0]=='R' || ptr[0]=='L') { - for (i = 0; i < sizeof(extra_portfwd) - 2; i++) - if (ptr[i]=='\000' && ptr[i+1]=='\000') - break; - ptr = ptr + i + 1; /* point to next forward slot */ - } - ptr[0] = p[1]; /* insert a 'L' or 'R' at the start */ - strncpy(ptr+1, fwd, sizeof(extra_portfwd) - i); - q = strchr(ptr, ':'); - if (q) *q = '\t'; /* replace first : with \t */ - ptr[strlen(ptr)+1] = '\000'; /* append two '\000' */ - extra_portfwd[sizeof(extra_portfwd) - 1] = '\0'; - } else if (!strcmp(p, "-m") && argc > 1) { - char *filename, *command; - int cmdlen, cmdsize; - FILE *fp; - int c, d; - - --argc, filename = *++argv; - - cmdlen = cmdsize = 0; - command = NULL; - fp = fopen(filename, "r"); - if (!fp) { - fprintf(stderr, "plink: unable to open command " - "file \"%s\"\n", filename); - return 1; - } - do { - c = fgetc(fp); - d = c; - if (c == EOF) - d = 0; - if (cmdlen >= cmdsize) { - cmdsize = cmdlen + 512; - command = srealloc(command, cmdsize); - } - command[cmdlen++] = d; - } while (c != EOF); - cfg.remote_cmd_ptr = command; - cfg.remote_cmd_ptr2 = NULL; - cfg.nopty = TRUE; /* command => no terminal */ - } else if (!strcmp(p, "-P") && argc > 1) { - --argc, portnumber = atoi(*++argv); } } else if (*p) { if (!*cfg.host) { @@ -481,6 +428,11 @@ int main(int argc, char **argv) } } + /* + * Perform command-line overrides on session configuration. + */ + cmdline_run_saved(); + /* * Trim a colon suffix off the hostname if it's there. */ @@ -508,30 +460,6 @@ int main(int argc, char **argv) } } - /* - * Add extra port forwardings (accumulated on command line) to - * cfg. - */ - { - int i; - char *p; - p = extra_portfwd; - i = 0; - while (cfg.portfwd[i]) - i += strlen(cfg.portfwd+i) + 1; - while (*p) { - if (strlen(p)+2 > sizeof(cfg.portfwd)-i) { - fprintf(stderr, "Internal fault: not enough space for all" - " port forwardings\n"); - break; - } - strncpy(cfg.portfwd+i, p, sizeof(cfg.portfwd)-i-1); - i += strlen(cfg.portfwd+i) + 1; - cfg.portfwd[i] = '\0'; - p += strlen(p)+1; - } - } - /* * Select port. */ diff --git a/psftp.c b/psftp.c index ae509f37..d498b07d 100644 --- a/psftp.c +++ b/psftp.c @@ -1687,6 +1687,11 @@ static int psftp_connect(char *userhost, char *user, int portnumber) cfg.port = 22; } + /* + * Enact command-line overrides. + */ + cmdline_run_saved(); + /* * Trim leading whitespace off the hostname if it's there. */ @@ -1789,6 +1794,17 @@ static int psftp_connect(char *userhost, char *user, int portnumber) return 0; } +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "pscp: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + /* * Main program. Parse arguments etc. */ @@ -1802,6 +1818,7 @@ int main(int argc, char *argv[]) char *batchfile = NULL; flags = FLAG_STDERR | FLAG_INTERACTIVE; + cmdline_tooltype = TOOLTYPE_FILETRANSFER; ssh_get_line = &console_get_line; init_winsock(); sk_init(); @@ -1809,29 +1826,35 @@ int main(int argc, char *argv[]) userhost = user = NULL; for (i = 1; i < argc; i++) { + int ret; if (argv[i][0] != '-') { - if (userhost) - usage(); - else - userhost = dupstr(argv[i]); - } else if (strcmp(argv[i], "-v") == 0) { - verbose = 1, flags |= FLAG_VERBOSE; + if (userhost) + usage(); + else + userhost = dupstr(argv[i]); + continue; + } + ret = cmdline_process_param(argv[i], i+1 1 && isspace(p[i - 1])) @@ -341,53 +304,107 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) WSACleanup(); return 0; } - } else if (*p) { - char *q = p; - /* - * If the hostname starts with "telnet:", set the - * protocol to Telnet and process the string as a - * Telnet URL. - */ - if (!strncmp(q, "telnet:", 7)) { - char c; - - q += 7; - if (q[0] == '/' && q[1] == '/') - q += 2; - cfg.protocol = PROT_TELNET; - p = q; - while (*p && *p != ':' && *p != '/') - p++; - c = *p; - if (*p) - *p++ = '\0'; - if (c == ':') - cfg.port = atoi(p); - else - cfg.port = -1; - strncpy(cfg.host, q, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; - } else { - while (*p && !isspace(*p)) - p++; - if (*p) - *p++ = '\0'; - strncpy(cfg.host, q, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; - while (*p && isspace(*p)) - p++; - if (*p) - cfg.port = atoi(p); - else - cfg.port = -1; - } } else { - if (!do_config()) { - WSACleanup(); - return 0; + /* + * Otherwise, break up the command line and deal with + * it sensibly. + */ + int argc, i; + char **argv; + + split_into_argv(cmdline, &argc, &argv); + + for (i = 0; i < argc; i++) { + char *p = argv[i]; + int ret; + + ret = cmdline_process_param(p, i+1 +#include + +#define lenof(x) ( sizeof((x)) / sizeof(*(x)) ) + +#ifdef TESTMODE +/* Definitions to allow this module to be compiled standalone for testing. */ +#define smalloc malloc +#endif + +/* + * Split a complete command line into argc/argv, attempting to do + * it exactly the same way Windows itself would do it (so that + * console utilities, which receive argc and argv from Windows, + * will have their command lines processed in the same way as GUI + * utilities which get a whole command line and must break it + * themselves). + * + * Does not modify the input command line (just in case). + */ +void split_into_argv(const char *cmdline, int *argc, char ***argv) +{ + const char *p; + char *outputline, *q; + char **outputargv; + int outputargc; + + /* + * At first glance the rules appeared to be: + * + * - Single quotes are not special characters. + * + * - Double quotes are removed, but within them spaces cease + * to be special. + * + * - Backslashes are _only_ special when a sequence of them + * appear just before a double quote. In this situation, + * they are treated like C backslashes: so \" just gives a + * literal quote, \\" gives a literal backslash and then + * opens or closes a double-quoted segment, \\\" gives a + * literal backslash and then a literal quote, \\\\" gives + * two literal backslashes and then opens/closes a + * double-quoted segment, and so forth. Note that this + * behaviour is identical inside and outside double quotes. + * + * - Two successive double quotes become one literal double + * quote, but only _inside_ a double-quoted segment. + * Outside, they just form an empty double-quoted segment + * (which may cause an empty argument word). + * + * - That only leaves the interesting question of what happens + * when one or more backslashes precedes two or more double + * quotes, starting inside a double-quoted string. And the + * answer to that appears somewhat bizarre. Here I tabulate + * number of backslashes (across the top) against number of + * quotes (down the left), and indicate how many backslashes + * are output, how many quotes are output, and whether a + * quoted segment is open at the end of the sequence: + * + * backslashes + * + * 0 1 2 3 4 + * + * 0 0,0,y | 1,0,y 2,0,y 3,0,y 4,0,y + * --------+----------------------------- + * 1 0,0,n | 0,1,y 1,0,n 1,1,y 2,0,n + * q 2 0,1,n | 0,1,n 1,1,n 1,1,n 2,1,n + * u 3 0,1,y | 0,2,n 1,1,y 1,2,n 2,1,y + * o 4 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n + * t 5 0,2,n | 0,2,n 1,2,n 1,2,n 2,2,n + * e 6 0,2,y | 0,3,n 1,2,y 1,3,n 2,2,y + * s 7 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n + * 8 0,3,n | 0,3,n 1,3,n 1,3,n 2,3,n + * 9 0,3,y | 0,4,n 1,3,y 1,4,n 2,3,y + * 10 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n + * 11 0,4,n | 0,4,n 1,4,n 1,4,n 2,4,n + * + * + * [Test fragment was of the form "a\\\"""b c" d.] + * + * There is very weird mod-3 behaviour going on here in the + * number of quotes, and it even applies when there aren't any + * backslashes! How ghastly. + * + * With a bit of thought, this extremely odd diagram suddenly + * coalesced itself into a coherent, if still ghastly, model of + * how things work: + * + * - As before, backslashes are only special when one or more + * of them appear contiguously before at least one double + * quote. In this situation the backslashes do exactly what + * you'd expect: each one quotes the next thing in front of + * it, so you end up with n/2 literal backslashes (if n is + * even) or (n-1)/2 literal backslashes and a literal quote + * (if n is odd). In the latter case the double quote + * character right after the backslashes is used up. + * + * - After that, any remaining double quotes are processed. A + * string of contiguous unescaped double quotes has a mod-3 + * behaviour: + * + * * inside a quoted segment, a quote ends the segment. + * * _immediately_ after ending a quoted segment, a quote + * simply produces a literal quote. + * * otherwise, outside a quoted segment, a quote begins a + * quoted segment. + * + * So, for example, if we started inside a quoted segment + * then two contiguous quotes would close the segment and + * produce a literal quote; three would close the segment, + * produce a literal quote, and open a new segment. If we + * started outside a quoted segment, then two contiguous + * quotes would open and then close a segment, producing no + * output (but potentially creating a zero-length argument); + * but three quotes would open and close a segment and then + * produce a literal quote. + */ + + /* + * This will guaranteeably be big enough; we can realloc it + * down later. + */ + outputline = malloc(1+strlen(cmdline)); + outputargv = malloc(sizeof(char *) * (strlen(cmdline)+1 / 2)); + + p = cmdline; q = outputline; outputargc = 0; + + while (*p) { + int quote; + + /* Skip whitespace searching for start of argument. */ + while (*p && isspace(*p)) p++; + if (!*p) break; + + /* We have an argument; start it. */ + outputargv[outputargc++] = q; + quote = 0; + + /* Copy data into the argument until it's finished. */ + while (*p) { + if (!quote && isspace(*p)) + break; /* argument is finished */ + + if (*p == '"' || *p == '\\') { + /* + * We have a sequence of zero or more backslashes + * followed by a sequence of zero or more quotes. + * Count up how many of each, and then deal with + * them as appropriate. + */ + int i, slashes = 0, quotes = 0; + while (*p == '\\') slashes++, p++; + while (*p == '"') quotes++, p++; + + if (!quotes) { + /* + * Special case: if there are no quotes, + * slashes are not special at all, so just copy + * n slashes to the output string. + */ + while (slashes--) *q++ = '\\'; + } else { + /* Slashes annihilate in pairs. */ + while (slashes >= 2) slashes -= 2, *q++ = '\\'; + + /* One remaining slash takes out the first quote. */ + if (slashes) quotes--, *q++ = '"'; + + if (quotes > 0) { + /* Outside a quote segment, a quote starts one. */ + if (!quote) quotes--, quote = 1; + + /* Now we produce (n+1)/3 literal quotes... */ + for (i = 3; i <= quotes+1; i += 3) *q++ = '"'; + + /* ... and end in a quote segment iff 3 divides n. */ + quote = (quotes % 3 == 0); + } + } + } else { + *q++ = *p++; + } + } + + /* At the end of an argument, just append a trailing NUL. */ + *q++ = '\0'; + } + + outputargv = realloc(outputargv, sizeof(char *) * outputargc); + + if (argc) *argc = outputargc; + if (argv) *argv = outputargv; +} + +#ifdef TESTMODE + +const struct argv_test { + const char *cmdline; + const char *argv[10]; +} argv_tests[] = { + /* + * We generate this set of tests by invoking ourself with + * `-generate'. + */ + {"ab c\" d", {"ab", "c d", NULL}}, + {"a\"b c\" d", {"ab c", "d", NULL}}, + {"a\"\"b c\" d", {"ab", "c d", NULL}}, + {"a\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\"\"\"\"b c\" d", {"a\"b c", "d", NULL}}, + {"a\"\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\\b c\" d", {"a\\b", "c d", NULL}}, + {"a\\\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\\\"\"b c\" d", {"a\"b c", "d", NULL}}, + {"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\\\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"a\\\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, + {"a\\\\b c\" d", {"a\\\\b", "c d", NULL}}, + {"a\\\\\"b c\" d", {"a\\b c", "d", NULL}}, + {"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}}, + {"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\"\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"a\\\\\"\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}}, + {"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, + {"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}}, + {"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}}, + {"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"\"ab c\" d", {"ab c", "d", NULL}}, + {"\"a\"b c\" d", {"ab", "c d", NULL}}, + {"\"a\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"\"a\"\"\"b c\" d", {"a\"b c", "d", NULL}}, + {"\"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"\"a\\b c\" d", {"a\\b c", "d", NULL}}, + {"\"a\\\"b c\" d", {"a\"b c", "d", NULL}}, + {"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"\"a\\\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"\"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, + {"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}}, + {"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}}, + {"\"a\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"\"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}}, + {"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}}, + {"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, +}; + +int main(int argc, char **argv) +{ + int i, j; + + if (argc > 1) { + /* + * Generation of tests. + * + * Given `-splat ', we print out a C-style + * representation of each argument (in the form "a", "b", + * NULL), backslash-escaping each backslash and double + * quote. + * + * Given `-split ', we first doctor `string' by + * turning forward slashes into backslashes, single quotes + * into double quotes and underscores into spaces; and then + * we feed the resulting string to ourself with `-splat'. + * + * Given `-generate', we concoct a variety of fun test + * cases, encode them in quote-safe form (mapping \, " and + * space to /, ' and _ respectively) and feed each one to + * `-split'. + */ + if (!strcmp(argv[1], "-splat")) { + int i; + char *p; + for (i = 2; i < argc; i++) { + putchar('"'); + for (p = argv[i]; *p; p++) { + if (*p == '\\' || *p == '"') + putchar('\\'); + putchar(*p); + } + printf("\", "); + } + printf("NULL"); + return 0; + } + + if (!strcmp(argv[1], "-split") && argc > 2) { + char *str = malloc(20 + strlen(argv[0]) + strlen(argv[2])); + char *p, *q; + + q = str + sprintf(str, "%s -splat ", argv[0]); + printf(" {\""); + for (p = argv[2]; *p; p++, q++) { + switch (*p) { + case '/': printf("\\\\"); *q = '\\'; break; + case '\'': printf("\\\""); *q = '"'; break; + case '_': printf(" "); *q = ' '; break; + default: putchar(*p); *q = *p; break; + } + } + *p = '\0'; + printf("\", {"); + fflush(stdout); + + system(str); + + printf("}},\n"); + + return 0; + } + + if (!strcmp(argv[1], "-generate")) { + char *teststr, *p; + int i, initialquote, backslashes, quotes; + + teststr = malloc(200 + strlen(argv[0])); + + for (initialquote = 0; initialquote <= 1; initialquote++) { + for (backslashes = 0; backslashes < 5; backslashes++) { + for (quotes = 0; quotes < 9; quotes++) { + p = teststr + sprintf(teststr, "%s -split ", argv[0]); + if (initialquote) *p++ = '\''; + *p++ = 'a'; + for (i = 0; i < backslashes; i++) *p++ = '/'; + for (i = 0; i < quotes; i++) *p++ = '\''; + *p++ = 'b'; + *p++ = '_'; + *p++ = 'c'; + *p++ = '\''; + *p++ = '_'; + *p++ = 'd'; + *p = '\0'; + + system(teststr); + } + } + } + return 0; + } + + fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]); + return 1; + } + + /* + * If we get here, we were invoked with no arguments, so just + * run the tests. + */ + + for (i = 0; i < lenof(argv_tests); i++) { + int ac; + char **av; + + split_into_argv(argv_tests[i].cmdline, &ac, &av); + + for (j = 0; j < ac && argv_tests[i].argv[j]; j++) { + if (strcmp(av[j], argv_tests[i].argv[j])) { + printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n", + i, argv_tests[i].cmdline, + j, av[j], argv_tests[i].argv[j]); + } +#ifdef VERBOSE + else { + printf("test %d (|%s|) arg %d: |%s| == |%s|\n", + i, argv_tests[i].cmdline, + j, av[j], argv_tests[i].argv[j]); + } +#endif + } + if (j < ac) + printf("failed test %d (|%s|): %d args returned, should be %d\n", + i, argv_tests[i].cmdline, ac, j); + if (argv_tests[i].argv[j]) + printf("failed test %d (|%s|): %d args returned, should be more\n", + i, argv_tests[i].cmdline, ac); + } + + return 0; +} + +#endif \ No newline at end of file