#pragma once #ifndef COMMANDLINETOARGVA_H #define COMMANDLINETOARGVA_H #include #include // https://github.com/wine-mirror/wine/blob/5946973021285dd6ecb8df224956fea4817f8fed/dlls/shell32/shell32_main.c // https://github.com/futurist/CommandLineToArgvA/blob/09765ffef215f0a066d6b0ea5c1edda88b6739af/CommandLineToArgvA.c /************************************************************************* * CommandLineToArgvA [SHELL32.@] * * We must interpret the quotes in the command line to rebuild the argv * array correctly: * - arguments are separated by spaces or tabs * - quotes serve as optional argument delimiters * '"a b"' -> 'a b' * - escaped quotes must be converted back to '"' * '\"' -> '"' * - consecutive backslashes preceding a quote see their number halved with * the remainder escaping the quote: * 2n backslashes + quote -> n backslashes + quote as an argument delimiter * 2n+1 backslashes + quote -> n backslashes + literal quote * - backslashes that are not followed by a quote are copied literally: * 'a\b' -> 'a\b' * 'a\\b' -> 'a\\b' * - in quoted strings, consecutive quotes see their number divided by three * with the remainder modulo 3 deciding whether to close the string or not. * Note that the opening quote must be counted in the consecutive quotes, * that's the (1+) below: * (1+) 3n quotes -> n quotes * (1+) 3n+1 quotes -> n quotes plus closes the quoted string * (1+) 3n+2 quotes -> n+1 quotes plus closes the quoted string * - in unquoted strings, the first quote opens the quoted string and the * remaining consecutive quotes follow the above rule. */ LPSTR WINAPI *CommandLineToArgvA(LPCSTR lpCmdline, int *numargs) { DWORD argc; LPSTR *argv; LPCSTR s; LPSTR d; LPSTR cmdline; int qcount, bcount; if (!numargs) { SetLastError(ERROR_INVALID_PARAMETER); return NULL; } if (lpCmdline == NULL || *lpCmdline == 0) { /* Return the path to the executable */ DWORD len, deslen = MAX_PATH, size; size = sizeof(LPSTR) * 2 + deslen * sizeof(char); for (;;) { if (!(argv = LocalAlloc(LMEM_FIXED, size))) return NULL; len = GetModuleFileNameA(0, (LPSTR)(argv + 2), deslen); if (!len) { LocalFree(argv); return NULL; } if (len < deslen) break; deslen *= 2; size = sizeof(LPSTR) * 2 + deslen * sizeof(char); LocalFree(argv); } argv[0] = (LPSTR)(argv + 2); argv[1] = NULL; *numargs = 1; return argv; } /* --- First count the arguments */ argc = 1; s = lpCmdline; /* The first argument, the executable path, follows special rules */ if (*s == '"') { /* The executable path ends at the next quote, no matter what */ s++; while (*s) if (*s++ == '"') break; } else { /* The executable path ends at the next space, no matter what */ while (*s && *s != ' ' && *s != '\t') s++; } /* skip to the first argument, if any */ while (*s == ' ' || *s == '\t') s++; if (*s) argc++; /* Analyze the remaining arguments */ qcount = bcount = 0; while (*s) { if ((*s == ' ' || *s == '\t') && qcount == 0) { /* skip to the next argument and count it if any */ while (*s == ' ' || *s == '\t') s++; if (*s) argc++; bcount = 0; } else if (*s == '\\') { /* '\', count them */ bcount++; s++; } else if (*s == '"') { /* '"' */ if ((bcount & 1) == 0) qcount++; /* unescaped '"' */ s++; bcount = 0; /* consecutive quotes, see comment in copying code below */ while (*s == '"') { qcount++; s++; } qcount = qcount % 3; if (qcount == 2) qcount = 0; } else { /* a regular character */ bcount = 0; s++; } } /* Allocate in a single lump, the string array, and the strings that go * with it. This way the caller can make a single LocalFree() call to free * both, as per MSDN. */ argv = LocalAlloc(LMEM_FIXED, (argc + 1) * sizeof(LPSTR) + (strlen(lpCmdline) + 1) * sizeof(char)); if (!argv) return NULL; cmdline = (LPSTR)(argv + argc + 1); strcpy(cmdline, lpCmdline); /* --- Then split and copy the arguments */ argv[0] = d = cmdline; argc = 1; /* The first argument, the executable path, follows special rules */ if (*d == '"') { /* The executable path ends at the next quote, no matter what */ s = d + 1; while (*s) { if (*s == '"') { s++; break; } *d++ = *s++; } } else { /* The executable path ends at the next space, no matter what */ while (*d && *d != ' ' && *d != '\t') d++; s = d; if (*s) s++; } /* close the executable path */ *d++ = 0; /* skip to the first argument and initialize it if any */ while (*s == ' ' || *s == '\t') s++; if (!*s) { /* There are no parameters so we are all done */ argv[argc] = NULL; *numargs = (int) argc; return argv; } /* Split and copy the remaining arguments */ argv[argc++] = d; qcount = bcount = 0; while (*s) { if ((*s == ' ' || *s == '\t') && qcount == 0) { /* close the argument */ *d++ = 0; bcount = 0; /* skip to the next one and initialize it if any */ do { s++; } while (*s == ' ' || *s == '\t'); if (*s) argv[argc++] = d; } else if (*s == '\\') { *d++ = *s++; bcount++; } else if (*s == '"') { if ((bcount & 1) == 0) { /* Preceded by an even number of '\', this is half that * number of '\', plus a quote which we erase. */ d -= bcount / 2; qcount++; } else { /* Preceded by an odd number of '\', this is half that * number of '\' followed by a '"' */ d = d - bcount / 2 - 1; *d++ = '"'; } s++; bcount = 0; /* Now count the number of consecutive quotes. Note that qcount * already takes into account the opening quote if any, as well as * the quote that lead us here. */ while (*s == '"') { if (++qcount == 3) { *d++ = '"'; qcount = 0; } s++; } if (qcount == 2) qcount = 0; } else { /* a regular character */ *d++ = *s++; bcount = 0; } } *d = '\0'; argv[argc] = NULL; *numargs = (int) argc; return argv; } #endif