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
8typedef const char **(*cmdline_insert_fn_t)(const char **result, size_t result_capacity, char *cmdline, size_t *result_count);
9
10static 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
20static 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
29static 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
93bool 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
98const 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
107void 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