Author: mjansen Date: Fri Aug 12 21:31:32 2016 New Revision: 72214 URL: http://svn.reactos.org/svn/reactos?rev=72214&view=rev Log: [CRT][CRT_APITEST] Fix __getmainargs and __wgetmainargs parsing, verified with apitests. Patch by Yaroslav Veremenko. CORE-11673 #resolve #comment Thanks!
Added: trunk/rostests/apitests/crt/__getmainargs.c (with props) Modified: trunk/reactos/sdk/lib/crt/misc/getargs.c trunk/rostests/apitests/crt/msvcrt_crt_apitest.cmake trunk/rostests/apitests/crt/testlist.c Modified: trunk/reactos/sdk/lib/crt/misc/getargs.c URL: http://svn.reactos.org/svn/reactos/trunk/reactos/sdk/lib/crt/misc/getargs.c?rev=72214&r1=72213&r2=72214&view=diff ============================================================================== --- trunk/reactos/sdk/lib/crt/misc/getargs.c [iso-8859-1] (original) +++ trunk/reactos/sdk/lib/crt/misc/getargs.c [iso-8859-1] Fri Aug 12 21:31:32 2016 @@ -181,16 +181,18 @@ */ void __getmainargs(int* argc, char*** argv, char*** env, int expand_wildcards, int* new_mode) { - int i, afterlastspace, ignorespace, doexpand; + int i, doexpand, slashesAdded, escapedQuote, inQuotes, bufferIndex; size_t len; - char* aNewCmdln; + char* buffer; /* missing threading init */ i = 0; - afterlastspace = 0; - ignorespace = 0; doexpand = expand_wildcards; + escapedQuote = FALSE; + slashesAdded = 0; + inQuotes = 0; + bufferIndex = 0; if (__argv && _environ) { @@ -203,58 +205,94 @@ __argc = 0; len = strlen(_acmdln); - - /* Allocate a temporary buffer to be used instead of the original _acmdln parameter. */ - aNewCmdln = strndup(_acmdln, len); - - while (aNewCmdln[i]) - { - if (aNewCmdln[i] == '"') - { - if(ignorespace) - { - ignorespace = 0; - } - else - { - ignorespace = 1; - doexpand = 0; - } - memmove(aNewCmdln + i, aNewCmdln + i + 1, len - i); - len--; - continue; - } - - if (aNewCmdln[i] == ' ' && !ignorespace) - { - aexpand(strndup(aNewCmdln + afterlastspace, i - afterlastspace), doexpand); - i++; - while (aNewCmdln[i] == ' ') - i++; - afterlastspace=i; - doexpand = expand_wildcards; - } - else - { - i++; - } - } - - if (aNewCmdln[afterlastspace] != 0) - { - aexpand(strndup(aNewCmdln + afterlastspace, i - afterlastspace), doexpand); + buffer = malloc(sizeof(char) * len); + + // Reference: https://msdn.microsoft.com/en-us/library/a1y7w461(v=vs.71).aspx + while (TRUE) + { + // Arguments are delimited by white space, which is either a space or a tab. + if (i >= len || ((_acmdln[i] == ' ' || _acmdln[i] == '\t') && !inQuotes)) + { + aexpand(strndup(buffer, bufferIndex), doexpand); + // Copy the last element from buffer and quit the loop + if (i >= len) + { + break; + } + + while (_acmdln[i] == ' ' || _acmdln[i] == '\t') + ++i; + bufferIndex = 0; + slashesAdded = 0; + escapedQuote = FALSE; + continue; + } + + if (_acmdln[i] == '\\') + { + buffer[bufferIndex++] = _acmdln[i]; + ++slashesAdded; + ++i; + escapedQuote = FALSE; + continue; + } + + if (_acmdln[i] == '\"') + { + if (slashesAdded > 0) + { + if (slashesAdded % 2 == 0) + { + // If an even number of backslashes is followed by a double quotation mark, then one backslash (\) + // is placed in the argv array for every pair of backslashes (\\), and the double quotation mark (") + // is interpreted as a string delimiter. + bufferIndex -= slashesAdded / 2; + } + else + { + // If an odd number of backslashes is followed by a double quotation mark, then one backslash (\) + // is placed in the argv array for every pair of backslashes (\\) and the double quotation mark is + // interpreted as an escape sequence by the remaining backslash, causing a literal double quotation mark (") + // to be placed in argv. + bufferIndex -= slashesAdded / 2 + 1; + buffer[bufferIndex++] = '\"'; + slashesAdded = 0; + escapedQuote = TRUE; + ++i; + continue; + } + slashesAdded = 0; + } + else if (!inQuotes && i > 0 && _acmdln[i - 1] == '\"' && !escapedQuote) + { + buffer[bufferIndex++] = '\"'; + ++i; + escapedQuote = TRUE; + continue; + } + slashesAdded = 0; + escapedQuote = FALSE; + inQuotes = !inQuotes; + doexpand = inQuotes ? FALSE : expand_wildcards; + ++i; + continue; + } + + buffer[bufferIndex++] = _acmdln[i]; + slashesAdded = 0; + escapedQuote = FALSE; + ++i; } /* Free the temporary buffer. */ - free(aNewCmdln); - + free(buffer); HeapValidate(GetProcessHeap(), 0, NULL); *argc = __argc; if (__argv == NULL) { - __argv = (char**)malloc(sizeof(char*)); - __argv[0] = 0; + __argv = (char**)malloc(sizeof(char*)); + __argv[0] = 0; } *argv = __argv; *env = _environ; @@ -269,16 +307,18 @@ void __wgetmainargs(int* argc, wchar_t*** wargv, wchar_t*** wenv, int expand_wildcards, int* new_mode) { - int i, afterlastspace, ignorespace, doexpand; + int i, doexpand, slashesAdded, escapedQuote, inQuotes, bufferIndex; size_t len; - wchar_t* wNewCmdln; + wchar_t* buffer; /* missing threading init */ i = 0; - afterlastspace = 0; - ignorespace = 0; doexpand = expand_wildcards; + escapedQuote = FALSE; + slashesAdded = 0; + inQuotes = 0; + bufferIndex = 0; if (__wargv && __winitenv) { @@ -291,58 +331,95 @@ __argc = 0; len = wcslen(_wcmdln); - - /* Allocate a temporary buffer to be used instead of the original _wcmdln parameter. */ - wNewCmdln = wcsndup(_wcmdln, len); - - while (wNewCmdln[i]) - { - if (wNewCmdln[i] == L'"') - { - if(ignorespace) - { - ignorespace = 0; - } - else - { - ignorespace = 1; - doexpand = 0; - } - memmove(wNewCmdln + i, wNewCmdln + i + 1, (len - i) * sizeof(wchar_t)); - len--; - continue; - } - - if (wNewCmdln[i] == L' ' && !ignorespace) - { - wexpand(wcsndup(wNewCmdln + afterlastspace, i - afterlastspace), doexpand); - i++; - while (wNewCmdln[i] == L' ') - i++; - afterlastspace=i; - doexpand = expand_wildcards; - } - else - { - i++; - } - } - - if (wNewCmdln[afterlastspace] != 0) - { - wexpand(wcsndup(wNewCmdln + afterlastspace, i - afterlastspace), doexpand); + buffer = malloc(sizeof(wchar_t) * len); + + // Reference: https://msdn.microsoft.com/en-us/library/a1y7w461(v=vs.71).aspx + while (TRUE) + { + // Arguments are delimited by white space, which is either a space or a tab. + if (i >= len || ((_wcmdln[i] == ' ' || _wcmdln[i] == '\t') && !inQuotes)) + { + wexpand(wcsndup(buffer, bufferIndex), doexpand); + // Copy the last element from buffer and quit the loop + if (i >= len) + { + break; + } + + while (_wcmdln[i] == ' ' || _wcmdln[i] == '\t') + ++i; + bufferIndex = 0; + slashesAdded = 0; + escapedQuote = FALSE; + continue; + } + + if (_wcmdln[i] == '\\') + { + buffer[bufferIndex++] = _wcmdln[i]; + ++slashesAdded; + ++i; + escapedQuote = FALSE; + continue; + } + + if (_wcmdln[i] == '\"') + { + if (slashesAdded > 0) + { + if (slashesAdded % 2 == 0) + { + // If an even number of backslashes is followed by a double quotation mark, then one backslash (\) + // is placed in the argv array for every pair of backslashes (\\), and the double quotation mark (") + // is interpreted as a string delimiter. + bufferIndex -= slashesAdded / 2; + } + else + { + // If an odd number of backslashes is followed by a double quotation mark, then one backslash (\) + // is placed in the argv array for every pair of backslashes (\\) and the double quotation mark is + // interpreted as an escape sequence by the remaining backslash, causing a literal double quotation mark (") + // to be placed in argv. + bufferIndex -= slashesAdded / 2 + 1; + buffer[bufferIndex++] = '\"'; + slashesAdded = 0; + escapedQuote = TRUE; + ++i; + continue; + } + slashesAdded = 0; + } + else if (!inQuotes && i > 0 && _wcmdln[i - 1] == '\"' && !escapedQuote) + { + buffer[bufferIndex++] = '\"'; + ++i; + escapedQuote = TRUE; + continue; + } + slashesAdded = 0; + escapedQuote = FALSE; + inQuotes = !inQuotes; + doexpand = inQuotes ? FALSE : expand_wildcards; + ++i; + continue; + } + + buffer[bufferIndex++] = _wcmdln[i]; + slashesAdded = 0; + escapedQuote = FALSE; + ++i; } /* Free the temporary buffer. */ - free(wNewCmdln); + free(buffer); HeapValidate(GetProcessHeap(), 0, NULL); *argc = __argc; if (__wargv == NULL) { - __wargv = (wchar_t**)malloc(sizeof(wchar_t*)); - __wargv[0] = 0; + __wargv = (wchar_t**)malloc(sizeof(wchar_t*)); + __wargv[0] = 0; } *wargv = __wargv; *wenv = __winitenv; Added: trunk/rostests/apitests/crt/__getmainargs.c URL: http://svn.reactos.org/svn/reactos/trunk/rostests/apitests/crt/__getmainargs.c?rev=72214 ============================================================================== --- trunk/rostests/apitests/crt/__getmainargs.c (added) +++ trunk/rostests/apitests/crt/__getmainargs.c [iso-8859-1] Fri Aug 12 21:31:32 2016 @@ -0,0 +1,100 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: GPLv2+ - See COPYING in the top level directory + * PURPOSE: Test for __getmainargs and __wgetmainargs + * PROGRAMMER: Yaroslav Veremenko <yaros...@veremenko.info> + */ + +#include <apitest.h> +#include <stdio.h> +#include <string.h> + + +const char **__p__acmdln(void); +void __getmainargs(int* argc, char*** argv, char*** env, int expand_wildcards, int* new_mode); +const wchar_t **__p__wcmdln(void); +void __wgetmainargs(int* argc, wchar_t*** wargv, wchar_t*** wenv, int expand_wildcards, int* new_mode); + + +#define winetest_ok_str(x, y) \ + winetest_ok(strcmp(x, y) == 0, "Wrong string. Expected '%s', got '%s'\n", y, x) +#define winetest_ok_wstr(x, y) \ + winetest_ok(wcscmp(x, y) == 0, "Wrong string. Expected '%s', got '%s'\n", wine_dbgstr_w(y), wine_dbgstr_w(x)) +#define ok_argsA (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : ok_argsA_imp +#define ok_argsW (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : ok_argsW_imp + + +void +ok_argsA_imp(const char* input_args, const char* arg1, const char* arg2, const char* arg3) +{ + int argc = 0, mode = 0; + char** argv, **env; + + /* Remove cached argv, setup our input as program argument. */ + *__p___argv() = NULL; + *__p__acmdln() = input_args; + + /* Process the commandline stored in _acmdln */ + __getmainargs(&argc, &argv, &env, 0, &mode); + + winetest_ok(argc == 4, "Wrong value for argc, expected: 4, got: %d\n", argc); + if(argc != 4) + return; + + winetest_ok_str(argv[0], "test.exe"); + winetest_ok_str(argv[1], arg1); + winetest_ok_str(argv[2], arg2); + winetest_ok_str(argv[3], arg3); +} + +void +ok_argsW_imp(const wchar_t* input_args, const wchar_t* arg1, const wchar_t* arg2, const wchar_t* arg3) +{ + int argc = 0, mode = 0; + wchar_t** argv, **env; + + /* Remove cached wargv, setup our input as program argument. */ + *__p___wargv() = NULL; + *__p__wcmdln() = input_args; + + /* Process the commandline stored in _wcmdln */ + __wgetmainargs(&argc, &argv, &env, 0, &mode); + + winetest_ok(argc == 4, "Wrong value for argc, expected: 4, got: %d\n", argc); + if(argc != 4) + return; + + winetest_ok_wstr(argv[0], L"test.exe"); + winetest_ok_wstr(argv[1], arg1); + winetest_ok_wstr(argv[2], arg2); + winetest_ok_wstr(argv[3], arg3); +} + +START_TEST(__getmainargs) +{ + ok_argsA("test.exe \"a b c\" d e", "a b c", "d", "e"); + ok_argsA("test.exe \"ab\\\"c\" \"\\\\\" d", "ab\"c", "\\", "d"); + ok_argsA("test.exe a\\\\\\b d\"e f\"g h", "a\\\\\\b", "de fg", "h"); + ok_argsA("test.exe a\\\\\\\"b c d", "a\\\"b", "c", "d"); + ok_argsA("test.exe a\\\\\\\\\"b c\" d e", "a\\\\b c", "d", "e"); + ok_argsA("test.exe a b \"\"", "a", "b", ""); + ok_argsA("test.exe a \"\" b", "a", "", "b"); + ok_argsA("test.exe a \"b\"\" c", "a", "b\"", "c"); + ok_argsA("test.exe a \"b\\\"\" c", "a", "b\"", "c"); + ok_argsA("test.exe a \" b\\ \"\" c", "a", " b\\ \"", "c"); + ok_argsA("test.exe a \"b\\ \"\"\" c\" d", "a", "b\\ \" c", "d"); + ok_argsA("test.exe a \"b\\ \"\"\" \"c \"\"\"\" d", "a", "b\\ \" c", "\" d"); + + ok_argsW(L"test.exe \"a b c\" d e", L"a b c", L"d", L"e"); + ok_argsW(L"test.exe \"ab\\\"c\" \"\\\\\" d", L"ab\"c", L"\\", L"d"); + ok_argsW(L"test.exe a\\\\\\b d\"e f\"g h", L"a\\\\\\b", L"de fg", L"h"); + ok_argsW(L"test.exe a\\\\\\\"b c d", L"a\\\"b", L"c", L"d"); + ok_argsW(L"test.exe a\\\\\\\\\"b c\" d e", L"a\\\\b c", L"d", L"e"); + ok_argsW(L"test.exe a b \"\"", L"a", L"b", L""); + ok_argsW(L"test.exe a \"\" b", L"a", L"", L"b"); + ok_argsW(L"test.exe a \"b\"\" c", L"a", L"b\"", L"c"); + ok_argsW(L"test.exe a \"b\\\"\" c", L"a", L"b\"", L"c"); + ok_argsW(L"test.exe a \" b\\ \"\" c", L"a", L" b\\ \"", L"c"); + ok_argsW(L"test.exe a \"b\\ \"\"\" c\" d", L"a", L"b\\ \" c", L"d"); + ok_argsW(L"test.exe a \"b\\ \"\"\" \"c \"\"\"\" d", L"a", L"b\\ \" c", L"\" d"); +} Propchange: trunk/rostests/apitests/crt/__getmainargs.c ------------------------------------------------------------------------------ svn:eol-style = native Modified: trunk/rostests/apitests/crt/msvcrt_crt_apitest.cmake URL: http://svn.reactos.org/svn/reactos/trunk/rostests/apitests/crt/msvcrt_crt_apitest.cmake?rev=72214&r1=72213&r2=72214&view=diff ============================================================================== --- trunk/rostests/apitests/crt/msvcrt_crt_apitest.cmake [iso-8859-1] (original) +++ trunk/rostests/apitests/crt/msvcrt_crt_apitest.cmake [iso-8859-1] Fri Aug 12 21:31:32 2016 @@ -66,7 +66,7 @@ # __doserrno.c # __fpecode.c # __get_app_type.c -# __getmainargs.c + __getmainargs.c # __initenv # __iob_func.c # __isascii.c Modified: trunk/rostests/apitests/crt/testlist.c URL: http://svn.reactos.org/svn/reactos/trunk/rostests/apitests/crt/testlist.c?rev=72214&r1=72213&r2=72214&view=diff ============================================================================== --- trunk/rostests/apitests/crt/testlist.c [iso-8859-1] (original) +++ trunk/rostests/apitests/crt/testlist.c [iso-8859-1] Fri Aug 12 21:31:32 2016 @@ -25,6 +25,7 @@ extern void func_wcsnlen(void); extern void func_wcstombs(void); extern void func_wcstoul(void); +extern void func___getmainargs(void); extern void func_static_construct(void); extern void func_static_init(void); @@ -50,6 +51,7 @@ #endif #if defined(TEST_STATIC_CRT) #elif defined(TEST_MSVCRT) + { "__getmainargs", func___getmainargs }, { "_vscprintf", func__vscprintf }, { "_vscwprintf", func__vscwprintf },