Blob


1 /*
2 * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include "compat.h"
19 #if !HAVE_READLINE
21 #include <stdio.h>
22 #include <string.h>
24 char *
25 readline(const char *prompt)
26 {
27 char *ch, *line = NULL;
28 size_t linesize = 0;
29 ssize_t linelen;
31 printf("%s", prompt);
32 fflush(stdout);
34 linelen = getline(&line, &linesize, stdin);
35 if (linelen == -1)
36 return NULL;
38 if ((ch = strchr(line, '\n')) != NULL)
39 *ch = '\0';
40 return line;
41 }
43 void
44 add_history(const char *line)
45 {
46 return;
47 }
49 void
50 compl_setup(void)
51 {
52 return;
53 }
55 #else /* HAVE_READLINE */
57 #include <ctype.h>
58 #include <libgen.h>
59 #include <limits.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
64 #include <readline/readline.h>
65 #include <readline/history.h>
67 #include "kami.h"
68 #include "kamiftp.h"
70 struct compl_state {
71 size_t size;
72 size_t len;
73 char **entries;
74 };
76 static struct compl_state compl_state;
77 static char compl_prfx[PATH_MAX];
79 static void
80 compl_state_reset(void)
81 {
82 size_t i;
84 for (i = 0; i < compl_state.len; ++i)
85 free(compl_state.entries[i]);
86 free(compl_state.entries);
88 memset(&compl_state, 0, sizeof(compl_state));
89 }
91 static int
92 compl_add_entry(const struct np_stat *st)
93 {
94 const char *sufx = "";
95 char *dup;
97 if (compl_state.len == compl_state.size) {
98 size_t newsz = compl_state.size * 1.5;
99 void *t;
101 if (newsz == 0)
102 newsz = 16;
104 /* one for the NULL entry at the end */
105 t = recallocarray(compl_state.entries, compl_state.size,
106 newsz + 1, sizeof(char *));
107 if (t == NULL)
108 return -1;
109 compl_state.entries = t;
110 compl_state.size = newsz;
113 if (st->qid.type & QTDIR)
114 sufx = "/";
116 if (asprintf(&dup, "%s%s%s", compl_prfx, st->name, sufx) == -1)
117 return -1;
118 compl_state.entries[compl_state.len++] = dup;
119 return 0;
122 static void
123 cleanword(char *buf, int brkspc)
125 char *cmd;
126 int escape, quote;
128 while (brkspc && isspace((unsigned char)*buf))
129 memmove(buf, buf + 1, strlen(buf));
131 escape = quote = 0;
132 for (cmd = buf; *cmd != '\0'; ++cmd) {
133 if (escape) {
134 escape = 0;
135 continue;
137 if (*cmd == '\\')
138 goto skip;
139 if (*cmd == quote) {
140 quote = 0;
141 goto skip;
143 if (*cmd == '\'' || *cmd == '"') {
144 quote = *cmd;
145 goto skip;
147 if (quote)
148 continue;
149 if (brkspc && isspace((unsigned char)*cmd))
150 break;
151 continue;
153 skip:
154 memmove(cmd, cmd + 1, strlen(cmd));
155 cmd--;
157 *cmd = '\0';
160 static int
161 tellcmd(char *buf)
163 size_t i;
165 cleanword(buf, 1);
166 for (i = 0; i < nitems(cmds); ++i) {
167 if (!strcmp(cmds[i].name, buf))
168 return cmds[i].cmdtype;
171 return CMD_UNKNOWN;
174 static int
175 tell_argno(const char *cmd, int *cmdtype)
177 char cmd0[64]; /* plenty of space */
178 const char *start = cmd;
179 int escape, quote;
180 int argno = 0;
182 *cmdtype = CMD_UNKNOWN;
184 /* find which argument needs to be completed */
185 while (*cmd) {
186 while (isspace((unsigned char)*cmd))
187 cmd++;
188 if (*cmd == '\0')
189 break;
191 escape = quote = 0;
192 for (; *cmd; ++cmd) {
193 if (escape) {
194 escape = 0;
195 continue;
197 if (*cmd == '\\') {
198 escape = 1;
199 continue;
201 if (*cmd == quote) {
202 quote = 0;
203 continue;
205 if (*cmd == '\'' || *cmd == '\"') {
206 quote = *cmd;
207 continue;
209 if (quote)
210 continue;
211 if (isspace((unsigned char)*cmd))
212 break;
214 if (isspace((unsigned char)*cmd))
215 argno++;
217 if (argno == 1 && strlcpy(cmd0, start, sizeof(cmd0)) <
218 sizeof(cmd0))
219 *cmdtype = tellcmd(cmd0);
222 return argno;
225 static char *
226 ftp_cmdname_generator(const char *text, int state)
228 static size_t i, len;
229 struct cmd *cmd;
231 if (state == 0) {
232 i = 0;
233 len = strlen(text);
236 while (i < nitems(cmds)) {
237 cmd = &cmds[i++];
238 if (strncmp(text, cmd->name, len) == 0)
239 return strdup(cmd->name);
242 return NULL;
245 static char *
246 ftp_bool_generator(const char *text, int state)
248 static const char *toks[] = { "on", "off" };
249 static size_t i, len;
250 const char *tok;
252 if (state == 0) {
253 i = 0;
254 len = strlen(text);
257 while ((tok = toks[i++]) != NULL) {
258 if (strncmp(text, tok, len) == 0)
259 return strdup(tok);
261 return NULL;
264 static char *
265 ftp_dirent_generator(const char *text, int state)
267 static size_t i, len;
268 const char *entry;
270 if (state == 0) {
271 i = 0;
272 len = strlen(text);
275 while (i < compl_state.len) {
276 entry = compl_state.entries[i++];
277 if (strncmp(text, entry, len) == 0)
278 return strdup(entry);
280 return NULL;
283 static inline void
284 set_compl_prfx(const char *path)
286 if (!strcmp(path, "."))
287 strlcpy(compl_prfx, "", sizeof(compl_prfx));
288 else
289 strlcpy(compl_prfx, path, sizeof(compl_prfx));
292 static inline void
293 inplace_dirname(char *path, size_t len)
295 char *t;
297 if ((t = strrchr(path, '/')) == NULL)
298 strlcpy(path, ".", len);
299 else
300 t[1] = '\0';
303 static char **
304 ftp_remote_files(const char *text, int start, int end)
306 char *t, dir[PATH_MAX];
308 strlcpy(dir, text, sizeof(dir));
309 cleanword(dir, 0);
311 if (!strcmp(dir, "..")) {
312 char **cs;
313 if ((cs = calloc(2, sizeof(*cs))) == NULL)
314 return NULL;
315 cs[0] = strdup("../");
316 return cs;
319 t = dir;
320 if (!strncmp(t, "./", 2)) {
321 t++;
322 while (*t == '/')
323 t++;
324 memmove(dir, t, strlen(t) + 1);
327 if (*dir && dir[strlen(dir) - 1] != '/')
328 strlcat(dir, "/", sizeof(dir));
330 set_compl_prfx(dir);
331 compl_state_reset();
332 if (dir_listing(dir, compl_add_entry, 0) == -1) {
333 if (*dir)
334 dir[strlen(dir) - 1] = '\0';
335 inplace_dirname(dir, sizeof(dir));
336 set_compl_prfx(dir);
338 compl_state_reset();
339 if (dir_listing(dir, compl_add_entry, 0) == -1)
340 return NULL;
343 return rl_completion_matches(text, ftp_dirent_generator);
346 static char **
347 ftp_completion(const char *text, int start, int end)
349 int argno, cmdtype;
350 char *line;
352 /* don't fall back on the default completion system by default */
353 rl_attempted_completion_over = 1;
355 /* append a space after selecting a command */
356 rl_completion_append_character = ' ';
358 if ((line = rl_copy_text(0, start)) == NULL)
359 return NULL;
361 argno = tell_argno(line, &cmdtype);
362 free(line);
363 if (argno == 0)
364 return rl_completion_matches(text, ftp_cmdname_generator);
366 /* but not for file completions */
367 rl_completion_append_character = '\0';
369 switch (cmdtype) {
370 case CMD_BELL:
371 case CMD_HEXDUMP:
372 case CMD_VERBOSE:
373 if (argno != 1)
374 return NULL;
375 return rl_completion_matches(text, ftp_bool_generator);
377 case CMD_BYE:
378 case CMD_LPWD:
379 /* no args */
380 return NULL;
382 case CMD_CD:
383 case CMD_EDIT:
384 case CMD_LS:
385 case CMD_PAGE:
386 if (argno != 1)
387 return NULL;
388 /* fallthrough */
389 case CMD_RM:
390 return ftp_remote_files(text, start, end);
392 case CMD_GET:
393 if (argno > 2)
394 return NULL;
395 if (argno == 2)
396 return ftp_remote_files(text, start, end);
397 /* try local */
398 rl_attempted_completion_over = 0;
399 return NULL;
401 case CMD_LCD:
402 if (argno != 1)
403 return NULL;
404 /* try local */
405 rl_attempted_completion_over = 0;
406 return NULL;
408 case CMD_PIPE:
409 if (argno > 2)
410 return NULL;
411 if (argno == 1)
412 return ftp_remote_files(text, start, end);
413 /* try local */
414 rl_attempted_completion_over = 0;
415 return NULL;
417 case CMD_PUT:
418 if (argno > 2)
419 return NULL;
420 if (argno == 1) {
421 /* try local */
422 rl_attempted_completion_over = 0;
423 return NULL;
425 return ftp_remote_files(text, start, end);
427 case CMD_RENAME:
428 if (argno > 2)
429 return NULL;
430 return ftp_remote_files(text, start, end);
433 return NULL;
436 static int
437 ftp_quoted(char *line, int index)
439 if (index > 0 && line[index - 1] == '\\')
440 return !ftp_quoted(line, index - 1);
441 return 0;
444 void
445 compl_setup(void)
447 rl_attempted_completion_function = ftp_completion;
448 rl_completer_word_break_characters = "\t ";
449 rl_completer_quote_characters = "\"'";
450 rl_char_is_quoted_p = ftp_quoted;
453 #endif