diff --git a/examples/cmdline.c b/examples/cmdline.c index abaa542..43f826c 100644 --- a/examples/cmdline.c +++ b/examples/cmdline.c @@ -6,7 +6,7 @@ static const unsigned int ARGV_COUNT_MAX = 100; static const unsigned int ARG_SIZE_MAX = 4096; -static const char *const cmdline = "foo bar\\ car"; +static const char *const cmdline = "foo bar\\ baz \"car cdr\""; int main() { @@ -26,9 +26,10 @@ int main() )); assert(strcmp(error_msg, "") == 0); - assert(argc == 2); + assert(argc == 3); assert(strcmp(argv[0], "foo") == 0); - assert(strcmp(argv[1], "bar car") == 0); + assert(strcmp(argv[1], "bar baz") == 0); + assert(strcmp(argv[2], "car cdr") == 0); return 0; } diff --git a/src/cmdline.c b/src/cmdline.c index cfe044d..a659b31 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -13,6 +13,7 @@ enum State { WHITESPACE, TOKEN, BACKSLASHED, + QUOTED, }; bool kernaux_cmdline_parse( @@ -75,6 +76,15 @@ bool kernaux_cmdline_parse( state = BACKSLASHED; argv[(*argc)++] = buffer; } + else if (cur == '"') { + if (*argc >= argv_count_max) { + kernaux_strncpy(error_msg, "too many args", 13); + goto fail; + } + + state = QUOTED; + argv[(*argc)++] = buffer; + } else { if (*argc >= argv_count_max) { kernaux_strncpy(error_msg, "too many args", 13); @@ -108,6 +118,15 @@ bool kernaux_cmdline_parse( state = BACKSLASHED; argv[(*argc)++] = buffer; } + else if (cur == '"') { + if (*argc >= argv_count_max) { + kernaux_strncpy(error_msg, "too many args", 13); + goto fail; + } + + state = QUOTED; + argv[(*argc)++] = buffer; + } else { if (*argc >= argv_count_max) { kernaux_strncpy(error_msg, "too many args", 13); @@ -150,6 +169,10 @@ bool kernaux_cmdline_parse( else if (cur == '\\') { state = BACKSLASHED; } + else if (cur == '"') { + kernaux_strncpy(error_msg, "unescaped quotation mark", 24); + goto fail; + } else { if (buffer_size >= arg_size_max) { kernaux_strncpy(error_msg, "arg too long", 12); @@ -177,6 +200,32 @@ bool kernaux_cmdline_parse( ++buffer_size; } break; + + case QUOTED: + if (cur == '\0') { + kernaux_strncpy(error_msg, "EOL inside quote", 16); + goto fail; + } + else if (cur == '"') { + if (buffer_size >= arg_size_max) { + kernaux_strncpy(error_msg, "arg too long", 12); + goto fail; + } + + state = WHITESPACE; + *(buffer++) = '\0'; + buffer_size = 0; + } + else { + if (buffer_size >= arg_size_max) { + kernaux_strncpy(error_msg, "arg too long", 12); + goto fail; + } + + *(buffer++) = cur; + ++buffer_size; + } + break; } if (state == FINAL) { diff --git a/tests/test_cmdline.c b/tests/test_cmdline.c index 75c6b1c..2cd68aa 100644 --- a/tests/test_cmdline.c +++ b/tests/test_cmdline.c @@ -25,10 +25,21 @@ static void test( static const char *const argv0[] = {}; +static const char *const argv_empty[] = {""}; +static const char *const argv_empty_X2[] = {"", ""}; +static const char *const argv_empty_X3[] = {"", "", ""}; + static const char *const argv_foo[] = {"foo"}; static const char *const argv_foo_bar[] = {"foo", "bar"}; static const char *const argv_foo_bar_car[] = {"foo", "bar", "car"}; +static const char *const argv_spaceX3_X3[] = {" ", " ", " "}; +static const char *const argv_backslashX3_X3[] = {"\\\\\\", "\\\\\\", "\\\\\\"}; +static const char *const argv_quotmarkX3_X3[] = {"\"\"\"", "\"\"\"", "\"\"\""}; + +static const char *const argv_foospacebar_car[] = {"foo bar", "car"}; +static const char *const argv_foo_barspacecar[] = {"foo", "bar car"}; + static const char *const argv_space[] = {" "}; static const char *const argv_backslash[] = {"\\"}; static const char *const argv_quotmark[] = {"\""}; @@ -63,10 +74,6 @@ static const char *const argv_foo_spacebarspace[] = {"foo", " bar "}; static const char *const argv_foo_backslashbarbackslash[] = {"foo", "\\bar\\"}; static const char *const argv_foo_quotmarkbarquotmark[] = {"foo", "\"bar\""}; -static const char *const argv_spaceX3_X3[] = {" ", " ", " "}; -static const char *const argv_backslashX3_X3[] = {"\\\\\\", "\\\\\\", "\\\\\\"}; -static const char *const argv_quotmarkX3_X3[] = {"\"\"\"", "\"\"\"", "\"\"\""}; - int main() { test("", 0, 0, true, "", 0, argv0); @@ -82,6 +89,20 @@ int main() test(" foo bar ", 0, 0, true, "", 2, argv_foo_bar); test("foo bar car", 0, 0, true, "", 3, argv_foo_bar_car); + test("\"\"", 0, 0, true, "", 1, argv_empty); + test("\"\" \"\"", 0, 0, true, "", 2, argv_empty_X2); + test("\"\" \"\" \"\"", 0, 0, true, "", 3, argv_empty_X3); + test("\"foo\"", 0, 0, true, "", 1, argv_foo); + test("\"foo\" \"bar\"", 0, 0, true, "", 2, argv_foo_bar); + test(" \"foo\" \"bar\"", 0, 0, true, "", 2, argv_foo_bar); + test("\"foo\" \"bar\" ", 0, 0, true, "", 2, argv_foo_bar); + test(" \"foo\" \"bar\" ", 0, 0, true, "", 2, argv_foo_bar); + test("\"foo\" \"bar\"", 0, 0, true, "", 2, argv_foo_bar); + test("\"foo\" \"bar\" ", 0, 0, true, "", 2, argv_foo_bar); + test(" \"foo\" \"bar\"", 0, 0, true, "", 2, argv_foo_bar); + test(" \"foo\" \"bar\" ", 0, 0, true, "", 2, argv_foo_bar); + test("\"foo\" \"bar\" \"car\"", 0, 0, true, "", 3, argv_foo_bar_car); + test("foo bar car", 3, 0, true, "", 3, argv_foo_bar_car); test("foo bar car", 0, 4, true, "", 3, argv_foo_bar_car); test("foo bar car", 3, 4, true, "", 3, argv_foo_bar_car); @@ -90,6 +111,14 @@ int main() test("foo bar car", 0, 3, false, "arg too long", 0, argv0); test("foo bar car", 2, 3, false, "arg too long", 0, argv0); + test("\"foo\" \"bar\" \"car\"", 3, 0, true, "", 3, argv_foo_bar_car); + test("\"foo\" \"bar\" \"car\"", 0, 4, true, "", 3, argv_foo_bar_car); + test("\"foo\" \"bar\" \"car\"", 3, 4, true, "", 3, argv_foo_bar_car); + + test("\"foo\" \"bar\" \"car\"", 2, 0, false, "too many args", 0, argv0); + test("\"foo\" \"bar\" \"car\"", 0, 3, false, "arg too long", 0, argv0); + test("\"foo\" \"bar\" \"car\"", 2, 3, false, "arg too long", 0, argv0); + test("\\ ", 0, 0, true, "", 1, argv_space); test("\\\\", 0, 0, true, "", 1, argv_backslash); test("\\\"", 0, 0, true, "", 1, argv_quotmark); @@ -124,6 +153,13 @@ int main() test("foo \\\\bar\\\\", 0, 0, true, "", 2, argv_foo_backslashbarbackslash); test("foo \\\"bar\\\"", 0, 0, true, "", 2, argv_foo_quotmarkbarquotmark); + test("foo\\ bar car", 0, 0, true, "", 2, argv_foospacebar_car); + test("\"foo bar\" car", 0, 0, true, "", 2, argv_foospacebar_car); + test("\"foo bar\" \"car\"", 0, 0, true, "", 2, argv_foospacebar_car); + test("foo bar\\ car", 0, 0, true, "", 2, argv_foo_barspacecar); + test("foo \"bar car\"", 0, 0, true, "", 2, argv_foo_barspacecar); + test("\"foo\" \"bar car\"", 0, 0, true, "", 2, argv_foo_barspacecar); + test("\\ \\ \\ \\ \\ \\ \\ \\ \\ ", 3, 0, true, "", 3, argv_spaceX3_X3); test("\\\\\\\\\\\\ \\\\\\\\\\\\ \\\\\\\\\\\\", 3, 0, true, "", 3, argv_backslashX3_X3); test("\\\"\\\"\\\" \\\"\\\"\\\" \\\"\\\"\\\"", 3, 0, true, "", 3, argv_quotmarkX3_X3);