1 #include "command.h"
2 #include "debug.h"
3 #include "editor.h"
4 #include "env.h"
5 #include "error.h"
6 #include "util/ascii.h"
7 #include "util/ptr-array.h"
8 #include "util/str-util.h"
9 #include "util/string.h"
10 #include "util/utf8.h"
11 #include "util/xmalloc.h"
12 
parse_sq(const char * cmd,size_t len,String * buf)13 static size_t parse_sq(const char *cmd, size_t len, String *buf)
14 {
15     size_t pos = 0;
16     char ch = '\0';
17     while (pos < len) {
18         ch = cmd[pos];
19         if (ch == '\'') {
20             break;
21         }
22         pos++;
23     }
24     string_add_buf(buf, cmd, pos);
25     if (ch == '\'') {
26         pos++;
27     }
28     return pos;
29 }
30 
unicode_escape(const char * str,size_t count,String * buf)31 static size_t unicode_escape(const char *str, size_t count, String *buf)
32 {
33     if (count == 0) {
34         return 0;
35     }
36 
37     CodePoint u = 0;
38     size_t i;
39     for (i = 0; i < count; i++) {
40         int x = hex_decode(str[i]);
41         if (x < 0) {
42             break;
43         }
44         u = u << 4 | x;
45     }
46     if (u_is_unicode(u)) {
47         string_add_ch(buf, u);
48     }
49     return i;
50 }
51 
min(size_t a,size_t b)52 static size_t min(size_t a, size_t b)
53 {
54     return (a < b) ? a : b;
55 }
56 
parse_dq(const char * cmd,size_t len,String * buf)57 static size_t parse_dq(const char *cmd, size_t len, String *buf)
58 {
59     size_t pos = 0;
60     while (pos < len) {
61         unsigned char ch = cmd[pos++];
62 
63         if (ch == '"') {
64             break;
65         }
66 
67         if (ch == '\\' && pos < len) {
68             ch = cmd[pos++];
69             switch (ch) {
70             case 'a': ch = '\a'; break;
71             case 'b': ch = '\b'; break;
72             case 'f': ch = '\f'; break;
73             case 'n': ch = '\n'; break;
74             case 'r': ch = '\r'; break;
75             case 't': ch = '\t'; break;
76             case 'v': ch = '\v'; break;
77             case '\\':
78             case '"':
79                 break;
80             case 'x':
81                 if (pos < len) {
82                     const int x1 = hex_decode(cmd[pos]);
83                     if (x1 >= 0 && ++pos < len) {
84                         const int x2 = hex_decode(cmd[pos]);
85                         if (x2 >= 0) {
86                             pos++;
87                             ch = x1 << 4 | x2;
88                             break;
89                         }
90                     }
91                 }
92                 continue;
93             case 'u':
94                 pos += unicode_escape(cmd + pos, min(4, len - pos), buf);
95                 continue;
96             case 'U':
97                 pos += unicode_escape(cmd + pos, min(8, len - pos), buf);
98                 continue;
99             default:
100                 string_add_byte(buf, '\\');
101                 break;
102             }
103         }
104 
105         string_add_byte(buf, ch);
106     }
107 
108     return pos;
109 }
110 
parse_var(const char * cmd,size_t len,String * buf)111 static size_t parse_var(const char *cmd, size_t len, String *buf)
112 {
113     if (len == 0 || !is_alpha_or_underscore(cmd[0])) {
114         return 0;
115     }
116 
117     size_t n = 1;
118     while (n < len && is_alnum_or_underscore(cmd[n])) {
119         n++;
120     }
121 
122     char *name = xstrcut(cmd, n);
123     char *value;
124     if (expand_builtin_env(name, &value)) {
125         if (value != NULL) {
126             string_add_str(buf, value);
127             free(value);
128         }
129     } else {
130         const char *val = getenv(name);
131         if (val != NULL) {
132             string_add_str(buf, val);
133         }
134     }
135 
136     free(name);
137     return n;
138 }
139 
parse_command_arg(const char * cmd,size_t len,bool tilde)140 char *parse_command_arg(const char *cmd, size_t len, bool tilde)
141 {
142     String buf;
143     size_t pos = 0;
144 
145     if (tilde && len >= 2 && cmd[0] == '~' && cmd[1] == '/') {
146         const size_t home_dir_len = strlen(editor.home_dir);
147         buf = string_new(len + home_dir_len);
148         string_add_buf(&buf, editor.home_dir, home_dir_len);
149         string_add_byte(&buf, '/');
150         pos += 2;
151     } else {
152         buf = string_new(len);
153     }
154 
155     while (pos < len) {
156         char ch = cmd[pos++];
157         switch (ch) {
158         case '\t':
159         case '\n':
160         case '\r':
161         case ' ':
162         case ';':
163             goto end;
164         case '\'':
165             pos += parse_sq(cmd + pos, len - pos, &buf);
166             break;
167         case '"':
168             pos += parse_dq(cmd + pos, len - pos, &buf);
169             break;
170         case '$':
171             pos += parse_var(cmd + pos, len - pos, &buf);
172             break;
173         case '\\':
174             if (pos == len) {
175                 goto end;
176             }
177             ch = cmd[pos++];
178             // Fallthrough
179         default:
180             string_add_byte(&buf, ch);
181             break;
182         }
183     }
184 
185 end:
186     return string_steal_cstring(&buf);
187 }
188 
find_end(const char * cmd,const size_t startpos,CommandParseError * err)189 size_t find_end(const char *cmd, const size_t startpos, CommandParseError *err)
190 {
191     size_t pos = startpos;
192     while (1) {
193         switch (cmd[pos++]) {
194         case '\'':
195             while (1) {
196                 if (cmd[pos] == '\'') {
197                     pos++;
198                     break;
199                 }
200                 if (cmd[pos] == '\0') {
201                     *err = CMDERR_UNCLOSED_SINGLE_QUOTE;
202                     return 0;
203                 }
204                 pos++;
205             }
206             break;
207         case '"':
208             while (1) {
209                 if (cmd[pos] == '"') {
210                     pos++;
211                     break;
212                 }
213                 if (cmd[pos] == '\0') {
214                     *err = CMDERR_UNCLOSED_DOUBLE_QUOTE;
215                     return 0;
216                 }
217                 if (cmd[pos++] == '\\') {
218                     if (cmd[pos] == '\0') {
219                         goto unexpected_eof;
220                     }
221                     pos++;
222                 }
223             }
224             break;
225         case '\\':
226             if (cmd[pos] == '\0') {
227                 goto unexpected_eof;
228             }
229             pos++;
230             break;
231         case '\0':
232         case '\t':
233         case '\n':
234         case '\r':
235         case ' ':
236         case ';':
237             return pos - 1;
238         }
239     }
240     BUG("Unexpected break of outer loop");
241 unexpected_eof:
242     *err = CMDERR_UNEXPECTED_EOF;
243     return 0;
244 }
245 
parse_commands(PointerArray * array,const char * cmd,CommandParseError * err)246 bool parse_commands(PointerArray *array, const char *cmd, CommandParseError *err)
247 {
248     size_t pos = 0;
249 
250     while (1) {
251         while (ascii_isspace(cmd[pos])) {
252             pos++;
253         }
254 
255         if (cmd[pos] == '\0') {
256             break;
257         }
258 
259         if (cmd[pos] == ';') {
260             ptr_array_add(array, NULL);
261             pos++;
262             continue;
263         }
264 
265         size_t end = find_end(cmd, pos, err);
266         if (*err != CMDERR_NONE) {
267             return false;
268         }
269 
270         ptr_array_add(array, parse_command_arg(cmd + pos, end - pos, true));
271         pos = end;
272     }
273     ptr_array_add(array, NULL);
274     return true;
275 }
276 
command_parse_error_to_string(CommandParseError err)277 const char *command_parse_error_to_string(CommandParseError err)
278 {
279     switch (err) {
280     case CMDERR_UNCLOSED_SINGLE_QUOTE:
281         return "Missing '";
282     case CMDERR_UNCLOSED_DOUBLE_QUOTE:
283         return "Missing \"";
284     case CMDERR_UNEXPECTED_EOF:
285         return "Unexpected EOF";
286     case CMDERR_NONE:
287         break;
288     }
289     return NULL;
290 }
291