1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | |
3 | #include <mos/lib/cmdline.h> |
4 | #include <mos_stdlib.h> |
5 | #include <mos_string.h> |
6 | |
7 | // (result array, cmdline, max cmdlines) -> a new result array |
8 | typedef const char **(*cmdline_insert_fn_t)(const char **result, size_t result_capacity, char *cmdline, size_t *result_count); |
9 | |
10 | static const char **cmdline_static_array_insert(const char **result, size_t result_capacity, char *cmdline, size_t *result_count) |
11 | { |
12 | if (*result_count == result_capacity) |
13 | return NULL; |
14 | |
15 | result[*result_count] = cmdline; |
16 | (*result_count)++; |
17 | return result; |
18 | } |
19 | |
20 | static const char **cmdline_dynamic_array_insert(const char **argv, size_t result_capacity, char *cmdline, size_t *result_count) |
21 | { |
22 | MOS_UNUSED(result_capacity); // unused because we always realloc |
23 | argv = krealloc(ptr: argv, size: sizeof(char *) * (*result_count + 1)); |
24 | argv[*result_count] = strdup(src: cmdline); |
25 | (*result_count)++; |
26 | return argv; |
27 | } |
28 | |
29 | static bool cmdline_parse_generic(char *start, size_t length, size_t cmdline_max, size_t *out_count, const char ***argv_ptr, cmdline_insert_fn_t insert) |
30 | { |
31 | char *buf_start = start; |
32 | |
33 | if (length == 0) |
34 | return true; |
35 | |
36 | // replace all spaces with null terminators, except for the ones quoted, and also handle escaped quotes |
37 | bool escaped = false; |
38 | enum |
39 | { |
40 | Q_SINGLE, |
41 | Q_DOUBLE, |
42 | Q_NONE, |
43 | } quote_type = Q_NONE; |
44 | |
45 | for (char *c = start; c != &buf_start[length + 1]; c++) |
46 | { |
47 | if (escaped) |
48 | { |
49 | escaped = false; |
50 | continue; |
51 | } |
52 | |
53 | if (*c == '\\') |
54 | { |
55 | escaped = true; |
56 | continue; |
57 | } |
58 | |
59 | if (*c == '\'' && quote_type != Q_DOUBLE) |
60 | { |
61 | quote_type = quote_type == Q_SINGLE ? Q_NONE : Q_SINGLE; |
62 | continue; |
63 | } |
64 | |
65 | if (*c == '"' && quote_type != Q_SINGLE) |
66 | { |
67 | quote_type = quote_type == Q_DOUBLE ? Q_NONE : Q_DOUBLE; |
68 | continue; |
69 | } |
70 | |
71 | if (*c == ' ' && quote_type == Q_NONE) |
72 | *c = '\0'; |
73 | |
74 | if (*c == '\0' && quote_type != Q_NONE) |
75 | *c = ' '; // we are in a quoted string, so replace null terminator with space |
76 | |
77 | if (*c == '\0') |
78 | { |
79 | if (strlen(str: start) > 0) |
80 | { |
81 | *argv_ptr = insert(*argv_ptr, cmdline_max, start, out_count); |
82 | if (!*argv_ptr) |
83 | return false; |
84 | } |
85 | |
86 | start = c + 1; |
87 | } |
88 | } |
89 | |
90 | return true; |
91 | } |
92 | |
93 | bool cmdline_parse_inplace(char *inbuf, size_t length, size_t cmdline_max, size_t *out_count, const char **out_cmdlines) |
94 | { |
95 | return cmdline_parse_generic(start: inbuf, length, cmdline_max, out_count, argv_ptr: &out_cmdlines, insert: cmdline_static_array_insert); |
96 | } |
97 | |
98 | const char **cmdline_parse(const char **inargv, char *inbuf, size_t length, size_t *out_count) |
99 | { |
100 | if (!cmdline_parse_generic(start: inbuf, length, cmdline_max: 0, out_count, argv_ptr: &inargv, insert: cmdline_dynamic_array_insert)) |
101 | { |
102 | return NULL; |
103 | } |
104 | return inargv; |
105 | } |
106 | |
107 | void string_unquote(char *str) |
108 | { |
109 | size_t len = strlen(str); |
110 | if (len < 2) |
111 | return; |
112 | |
113 | char quote = str[0]; |
114 | if (quote != '\'' && quote != '"') |
115 | return; |
116 | |
117 | if (str[len - 1] != quote) |
118 | return; // unbalanced quotes |
119 | |
120 | for (size_t i = 1; i < len - 1; i++) |
121 | { |
122 | if (str[i] == '\\') |
123 | { |
124 | if (str[i + 1] == quote) |
125 | { |
126 | memmove(dest: &str[i], src: &str[i + 1], n: len - i); |
127 | len--; |
128 | } |
129 | else if (str[i + 1] == '\\') |
130 | { |
131 | memmove(dest: &str[i], src: &str[i + 1], n: len - i); |
132 | len--; |
133 | } |
134 | } |
135 | } |
136 | |
137 | str[len - 1] = '\0'; |
138 | memmove(dest: str, src: &str[1], n: len - 1); |
139 | } |
140 | |