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 <limits.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
63 #include <readline/readline.h>
64 #include <readline/history.h>
66 #include "kami.h"
67 #include "kamiftp.h"
69 struct compl_state {
70 size_t size;
71 size_t len;
72 char **entries;
73 };
75 static struct compl_state compl_state;
76 static char compl_prfx[PATH_MAX];
78 static void
79 compl_state_reset(void)
80 {
81 size_t i;
83 for (i = 0; i < compl_state.len; ++i)
84 free(compl_state.entries[i]);
85 free(compl_state.entries);
87 memset(&compl_state, 0, sizeof(compl_state));
88 }
90 static int
91 compl_add_entry(const struct np_stat *st)
92 {
93 const char *sufx = "";
94 char *dup;
96 if (compl_state.len == compl_state.size) {
97 size_t newsz = compl_state.size * 1.5;
98 void *t;
100 if (newsz == 0)
101 newsz = 16;
103 /* one for the NULL entry at the end */
104 t = recallocarray(compl_state.entries, compl_state.size,
105 newsz + 1, sizeof(char *));
106 if (t == NULL)
107 return -1;
108 compl_state.entries = t;
109 compl_state.size = newsz;
112 if (st->qid.type & QTDIR)
113 sufx = "/";
115 if (asprintf(&dup, "%s%s%s", compl_prfx, st->name, sufx) == -1)
116 return -1;
117 compl_state.entries[compl_state.len++] = dup;
118 return 0;
121 static void
122 cleanword(char *buf, int brkspc)
124 char *cmd;
125 int escape, quote;
127 while (brkspc && isspace((unsigned char)*buf))
128 memmove(buf, buf + 1, strlen(buf));
130 escape = quote = 0;
131 for (cmd = buf; *cmd != '\0'; ++cmd) {
132 if (escape) {
133 escape = 0;
134 continue;
136 if (*cmd == '\\')
137 goto skip;
138 if (*cmd == quote) {
139 quote = 0;
140 goto skip;
142 if (*cmd == '\'' || *cmd == '"') {
143 quote = *cmd;
144 goto skip;
146 if (quote)
147 continue;
148 if (brkspc && isspace((unsigned char)*cmd))
149 break;
150 continue;
152 skip:
153 memmove(cmd, cmd + 1, strlen(cmd));
154 cmd--;
156 *cmd = '\0';
159 static int
160 tellcmd(char *buf)
162 size_t i;
164 cleanword(buf, 1);
165 for (i = 0; i < nitems(cmds); ++i) {
166 if (!strcmp(cmds[i].name, buf))
167 return cmds[i].cmdtype;
170 return CMD_UNKNOWN;
173 static int
174 tell_argno(const char *cmd, int *cmdtype)
176 char cmd0[64]; /* plenty of space */
177 const char *start = cmd;
178 int escape, quote;
179 int argno = 0;
181 *cmdtype = CMD_UNKNOWN;
183 /* find which argument needs to be completed */
184 while (*cmd) {
185 while (isspace((unsigned char)*cmd))
186 cmd++;
187 if (*cmd == '\0')
188 break;
190 escape = quote = 0;
191 for (; *cmd; ++cmd) {
192 if (escape) {
193 escape = 0;
194 continue;
196 if (*cmd == '\\') {
197 escape = 1;
198 continue;
200 if (*cmd == quote) {
201 quote = 0;
202 continue;
204 if (*cmd == '\'' || *cmd == '\"') {
205 quote = *cmd;
206 continue;
208 if (quote)
209 continue;
210 if (isspace((unsigned char)*cmd))
211 break;
213 if (isspace((unsigned char)*cmd))
214 argno++;
216 if (argno == 1 && strlcpy(cmd0, start, sizeof(cmd0)) <
217 sizeof(cmd0))
218 *cmdtype = tellcmd(cmd0);
221 return argno;
224 static char *
225 ftp_cmdname_generator(const char *text, int state)
227 static size_t i, len;
228 struct cmd *cmd;
230 if (state == 0) {
231 i = 0;
232 len = strlen(text);
235 while (i < nitems(cmds)) {
236 cmd = &cmds[i++];
237 if (strncmp(text, cmd->name, len) == 0)
238 return strdup(cmd->name);
241 return NULL;
244 static char *
245 ftp_bool_generator(const char *text, int state)
247 static const char *toks[] = { "on", "off" };
248 static size_t i, len;
249 const char *tok;
251 if (state == 0) {
252 i = 0;
253 len = strlen(text);
256 while ((tok = toks[i++]) != NULL) {
257 if (strncmp(text, tok, len) == 0)
258 return strdup(tok);
260 return NULL;
263 static char *
264 ftp_dirent_generator(const char *text, int state)
266 static size_t i, len;
267 const char *entry;
269 if (state == 0) {
270 i = 0;
271 len = strlen(text);
274 while (i < compl_state.len) {
275 entry = compl_state.entries[i++];
276 if (strncmp(text, entry, len) == 0)
277 return strdup(entry);
279 return NULL;
282 static char **
283 ftp_remote_files(const char *text, int start, int end)
285 const char *dir;
286 char t[PATH_MAX];
287 char *s, *e;
289 strlcpy(t, text, sizeof(t));
290 cleanword(t, 0);
292 if (!strcmp(t, "..")) {
293 char **cs;
294 if ((cs = calloc(2, sizeof(*cs))) == NULL)
295 return NULL;
296 cs[0] = strdup("../");
297 return cs;
300 s = t;
301 if (!strncmp(s, "./", 2)) {
302 s++;
303 while (*s == '/')
304 s++;
307 if ((e = strrchr(s, '/')) != NULL)
308 e[1] = '\0';
309 dir = t;
311 if (!strcmp(dir, "."))
312 strlcpy(compl_prfx, "", sizeof(compl_prfx));
313 else
314 strlcpy(compl_prfx, dir, sizeof(compl_prfx));
316 compl_state_reset();
317 if (dir_listing(dir, compl_add_entry, 0) == -1)
318 return NULL;
319 return rl_completion_matches(text, ftp_dirent_generator);
322 static char **
323 ftp_completion(const char *text, int start, int end)
325 int argno, cmdtype;
326 char *line;
328 /* don't fall back on the default completion system by default */
329 rl_attempted_completion_over = 1;
331 /* append a space after selecting a command */
332 rl_completion_append_character = ' ';
334 if ((line = rl_copy_text(0, start)) == NULL)
335 return NULL;
337 argno = tell_argno(line, &cmdtype);
338 free(line);
339 if (argno == 0)
340 return rl_completion_matches(text, ftp_cmdname_generator);
342 /* but not for file completions */
343 rl_completion_append_character = '\0';
345 switch (cmdtype) {
346 case CMD_BELL:
347 case CMD_HEXDUMP:
348 case CMD_VERBOSE:
349 if (argno != 1)
350 return NULL;
351 return rl_completion_matches(text, ftp_bool_generator);
353 case CMD_BYE:
354 case CMD_LPWD:
355 /* no args */
356 return NULL;
358 case CMD_CD:
359 case CMD_EDIT:
360 case CMD_LS:
361 case CMD_PAGE:
362 if (argno != 1)
363 return NULL;
364 /* fallthrough */
365 case CMD_RM:
366 return ftp_remote_files(text, start, end);
368 case CMD_GET:
369 if (argno > 2)
370 return NULL;
371 if (argno == 2)
372 return ftp_remote_files(text, start, end);
373 /* try local */
374 rl_attempted_completion_over = 0;
375 return NULL;
377 case CMD_LCD:
378 if (argno != 1)
379 return NULL;
380 /* try local */
381 rl_attempted_completion_over = 0;
382 return NULL;
384 case CMD_PIPE:
385 if (argno > 2)
386 return NULL;
387 if (argno == 1)
388 return ftp_remote_files(text, start, end);
389 /* try local */
390 rl_attempted_completion_over = 0;
391 return NULL;
393 case CMD_PUT:
394 if (argno > 2)
395 return NULL;
396 if (argno == 1) {
397 /* try local */
398 rl_attempted_completion_over = 0;
399 return NULL;
401 return ftp_remote_files(text, start, end);
403 case CMD_RENAME:
404 if (argno > 2)
405 return NULL;
406 return ftp_remote_files(text, start, end);
409 return NULL;
412 static int
413 ftp_quoted(char *line, int index)
415 if (index > 0 && line[index - 1] == '\\')
416 return !ftp_quoted(line, index - 1);
417 return 0;
420 void
421 compl_setup(void)
423 rl_attempted_completion_function = ftp_completion;
424 rl_completer_word_break_characters = "\t ";
425 rl_completer_quote_characters = "\"'";
426 rl_char_is_quoted_p = ftp_quoted;
429 #endif