2 d3e1ab0c 2022-11-24 op * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
4 d3e1ab0c 2022-11-24 op * Permission to use, copy, modify, and distribute this software for any
5 d3e1ab0c 2022-11-24 op * purpose with or without fee is hereby granted, provided that the above
6 d3e1ab0c 2022-11-24 op * copyright notice and this permission notice appear in all copies.
8 d3e1ab0c 2022-11-24 op * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 d3e1ab0c 2022-11-24 op * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 d3e1ab0c 2022-11-24 op * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 d3e1ab0c 2022-11-24 op * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 d3e1ab0c 2022-11-24 op * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 d3e1ab0c 2022-11-24 op * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 d3e1ab0c 2022-11-24 op * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 d3e1ab0c 2022-11-24 op #include "compat.h"
19 d3e1ab0c 2022-11-24 op #if !HAVE_READLINE
21 d3e1ab0c 2022-11-24 op #include <stdio.h>
22 d3e1ab0c 2022-11-24 op #include <string.h>
25 d3e1ab0c 2022-11-24 op readline(const char *prompt)
27 d3e1ab0c 2022-11-24 op char *ch, *line = NULL;
28 d3e1ab0c 2022-11-24 op size_t linesize = 0;
29 d3e1ab0c 2022-11-24 op ssize_t linelen;
31 d3e1ab0c 2022-11-24 op printf("%s", prompt);
32 d3e1ab0c 2022-11-24 op fflush(stdout);
34 d3e1ab0c 2022-11-24 op linelen = getline(&line, &linesize, stdin);
35 d3e1ab0c 2022-11-24 op if (linelen == -1)
38 d3e1ab0c 2022-11-24 op if ((ch = strchr(line, '\n')) != NULL)
44 d3e1ab0c 2022-11-24 op add_history(const char *line)
50 d3e1ab0c 2022-11-24 op compl_setup(void)
55 d3e1ab0c 2022-11-24 op #else /* HAVE_READLINE */
57 d3e1ab0c 2022-11-24 op #include <ctype.h>
58 d3e1ab0c 2022-11-24 op #include <limits.h>
59 d3e1ab0c 2022-11-24 op #include <stdio.h>
60 d3e1ab0c 2022-11-24 op #include <stdlib.h>
61 d3e1ab0c 2022-11-24 op #include <string.h>
63 d3e1ab0c 2022-11-24 op #include <readline/readline.h>
64 d3e1ab0c 2022-11-24 op #include <readline/history.h>
66 d3e1ab0c 2022-11-24 op #include "kami.h"
67 d3e1ab0c 2022-11-24 op #include "kamiftp.h"
69 d3e1ab0c 2022-11-24 op struct compl_state {
72 d3e1ab0c 2022-11-24 op char **entries;
75 d3e1ab0c 2022-11-24 op static struct compl_state compl_state;
76 d3e1ab0c 2022-11-24 op static char compl_prfx[PATH_MAX];
79 d3e1ab0c 2022-11-24 op compl_state_reset(void)
83 d3e1ab0c 2022-11-24 op for (i = 0; i < compl_state.len; ++i)
84 d3e1ab0c 2022-11-24 op free(compl_state.entries[i]);
85 d3e1ab0c 2022-11-24 op free(compl_state.entries);
87 e3644194 2022-12-21 op memset(&compl_state, 0, sizeof(compl_state));
91 d3e1ab0c 2022-11-24 op compl_add_entry(const struct np_stat *st)
93 d3e1ab0c 2022-11-24 op const char *sufx = "";
96 d3e1ab0c 2022-11-24 op if (compl_state.len == compl_state.size) {
97 d3e1ab0c 2022-11-24 op size_t newsz = compl_state.size * 1.5;
100 e3644194 2022-12-21 op if (newsz == 0)
103 e3644194 2022-12-21 op /* one for the NULL entry at the end */
104 d3e1ab0c 2022-11-24 op t = recallocarray(compl_state.entries, compl_state.size,
105 e3644194 2022-12-21 op newsz + 1, sizeof(char *));
106 d3e1ab0c 2022-11-24 op if (t == NULL)
108 d3e1ab0c 2022-11-24 op compl_state.entries = t;
109 d3e1ab0c 2022-11-24 op compl_state.size = newsz;
112 d3e1ab0c 2022-11-24 op if (st->qid.type & QTDIR)
115 d3e1ab0c 2022-11-24 op if (asprintf(&dup, "%s%s%s", compl_prfx, st->name, sufx) == -1)
117 d3e1ab0c 2022-11-24 op compl_state.entries[compl_state.len++] = dup;
122 d3e1ab0c 2022-11-24 op cleanword(char *buf, int brkspc)
125 d3e1ab0c 2022-11-24 op int escape, quote;
127 d3e1ab0c 2022-11-24 op while (brkspc && isspace((unsigned char)*buf))
128 d3e1ab0c 2022-11-24 op memmove(buf, buf + 1, strlen(buf));
130 d3e1ab0c 2022-11-24 op escape = quote = 0;
131 d3e1ab0c 2022-11-24 op for (cmd = buf; *cmd != '\0'; ++cmd) {
132 d3e1ab0c 2022-11-24 op if (escape) {
136 d3e1ab0c 2022-11-24 op if (*cmd == '\\')
138 d3e1ab0c 2022-11-24 op if (*cmd == quote) {
142 d3e1ab0c 2022-11-24 op if (*cmd == '\'' || *cmd == '"') {
143 d3e1ab0c 2022-11-24 op quote = *cmd;
148 d3e1ab0c 2022-11-24 op if (brkspc && isspace((unsigned char)*cmd))
153 d3e1ab0c 2022-11-24 op memmove(cmd, cmd + 1, strlen(cmd));
160 d3e1ab0c 2022-11-24 op tellcmd(char *buf)
164 d3e1ab0c 2022-11-24 op cleanword(buf, 1);
165 d3e1ab0c 2022-11-24 op for (i = 0; i < nitems(cmds); ++i) {
166 d3e1ab0c 2022-11-24 op if (!strcmp(cmds[i].name, buf))
167 d3e1ab0c 2022-11-24 op return cmds[i].cmdtype;
170 d3e1ab0c 2022-11-24 op return CMD_UNKNOWN;
174 d3e1ab0c 2022-11-24 op tell_argno(const char *cmd, int *cmdtype)
176 d3e1ab0c 2022-11-24 op char cmd0[64]; /* plenty of space */
177 d3e1ab0c 2022-11-24 op const char *start = cmd;
178 d3e1ab0c 2022-11-24 op int escape, quote;
179 d3e1ab0c 2022-11-24 op int argno = 0;
181 d3e1ab0c 2022-11-24 op *cmdtype = CMD_UNKNOWN;
183 d3e1ab0c 2022-11-24 op /* find which argument needs to be completed */
184 d3e1ab0c 2022-11-24 op while (*cmd) {
185 d3e1ab0c 2022-11-24 op while (isspace((unsigned char)*cmd))
187 d3e1ab0c 2022-11-24 op if (*cmd == '\0')
190 d3e1ab0c 2022-11-24 op escape = quote = 0;
191 d3e1ab0c 2022-11-24 op for (; *cmd; ++cmd) {
192 d3e1ab0c 2022-11-24 op if (escape) {
196 d3e1ab0c 2022-11-24 op if (*cmd == '\\') {
200 d3e1ab0c 2022-11-24 op if (*cmd == quote) {
204 d3e1ab0c 2022-11-24 op if (*cmd == '\'' || *cmd == '\"') {
205 d3e1ab0c 2022-11-24 op quote = *cmd;
210 d3e1ab0c 2022-11-24 op if (isspace((unsigned char)*cmd))
213 d3e1ab0c 2022-11-24 op if (isspace((unsigned char)*cmd))
216 d3e1ab0c 2022-11-24 op if (argno == 1 && strlcpy(cmd0, start, sizeof(cmd0)) <
217 d3e1ab0c 2022-11-24 op sizeof(cmd0))
218 d3e1ab0c 2022-11-24 op *cmdtype = tellcmd(cmd0);
221 d3e1ab0c 2022-11-24 op return argno;
224 d3e1ab0c 2022-11-24 op static char *
225 d3e1ab0c 2022-11-24 op ftp_cmdname_generator(const char *text, int state)
227 d3e1ab0c 2022-11-24 op static size_t i, len;
228 d3e1ab0c 2022-11-24 op struct cmd *cmd;
230 d3e1ab0c 2022-11-24 op if (state == 0) {
232 d3e1ab0c 2022-11-24 op len = strlen(text);
235 d3e1ab0c 2022-11-24 op while (i < nitems(cmds)) {
236 d3e1ab0c 2022-11-24 op cmd = &cmds[i++];
237 d3e1ab0c 2022-11-24 op if (strncmp(text, cmd->name, len) == 0)
238 d3e1ab0c 2022-11-24 op return strdup(cmd->name);
244 d3e1ab0c 2022-11-24 op static char *
245 d3e1ab0c 2022-11-24 op ftp_bool_generator(const char *text, int state)
247 d3e1ab0c 2022-11-24 op static const char *toks[] = { "on", "off" };
248 d3e1ab0c 2022-11-24 op static size_t i, len;
249 d3e1ab0c 2022-11-24 op const char *tok;
251 d3e1ab0c 2022-11-24 op if (state == 0) {
253 d3e1ab0c 2022-11-24 op len = strlen(text);
256 d3e1ab0c 2022-11-24 op while ((tok = toks[i++]) != NULL) {
257 d3e1ab0c 2022-11-24 op if (strncmp(text, tok, len) == 0)
258 d3e1ab0c 2022-11-24 op return strdup(tok);
263 d3e1ab0c 2022-11-24 op static char *
264 d3e1ab0c 2022-11-24 op ftp_dirent_generator(const char *text, int state)
266 d3e1ab0c 2022-11-24 op static size_t i, len;
267 d3e1ab0c 2022-11-24 op const char *entry;
269 d3e1ab0c 2022-11-24 op if (state == 0) {
271 d3e1ab0c 2022-11-24 op len = strlen(text);
274 d3e1ab0c 2022-11-24 op while (i < compl_state.len) {
275 d3e1ab0c 2022-11-24 op entry = compl_state.entries[i++];
276 d3e1ab0c 2022-11-24 op if (strncmp(text, entry, len) == 0)
277 d3e1ab0c 2022-11-24 op return strdup(entry);
282 d3e1ab0c 2022-11-24 op static char **
283 d3e1ab0c 2022-11-24 op ftp_remote_files(const char *text, int start, int end)
285 d3e1ab0c 2022-11-24 op const char *dir;
286 d3e1ab0c 2022-11-24 op char t[PATH_MAX];
289 d3e1ab0c 2022-11-24 op strlcpy(t, text, sizeof(t));
290 d3e1ab0c 2022-11-24 op cleanword(t, 0);
292 d3e1ab0c 2022-11-24 op if (!strcmp(t, "..")) {
294 d3e1ab0c 2022-11-24 op if ((cs = calloc(2, sizeof(*cs))) == NULL)
296 d3e1ab0c 2022-11-24 op cs[0] = strdup("../");
301 d3e1ab0c 2022-11-24 op if (!strncmp(s, "./", 2)) {
303 d3e1ab0c 2022-11-24 op while (*s == '/')
307 d3e1ab0c 2022-11-24 op if ((e = strrchr(s, '/')) != NULL)
311 d3e1ab0c 2022-11-24 op if (!strcmp(dir, "."))
312 d3e1ab0c 2022-11-24 op strlcpy(compl_prfx, "", sizeof(compl_prfx));
314 d3e1ab0c 2022-11-24 op strlcpy(compl_prfx, dir, sizeof(compl_prfx));
316 d3e1ab0c 2022-11-24 op compl_state_reset();
317 d3e1ab0c 2022-11-24 op if (dir_listing(dir, compl_add_entry, 0) == -1)
319 d3e1ab0c 2022-11-24 op return rl_completion_matches(text, ftp_dirent_generator);
322 d3e1ab0c 2022-11-24 op static char **
323 d3e1ab0c 2022-11-24 op ftp_completion(const char *text, int start, int end)
325 d3e1ab0c 2022-11-24 op int argno, cmdtype;
328 d3e1ab0c 2022-11-24 op /* don't fall back on the default completion system by default */
329 d3e1ab0c 2022-11-24 op rl_attempted_completion_over = 1;
331 d3e1ab0c 2022-11-24 op if ((line = rl_copy_text(0, start)) == NULL)
334 d3e1ab0c 2022-11-24 op argno = tell_argno(line, &cmdtype);
336 d3e1ab0c 2022-11-24 op if (argno == 0)
337 d3e1ab0c 2022-11-24 op return rl_completion_matches(text, ftp_cmdname_generator);
339 d3e1ab0c 2022-11-24 op switch (cmdtype) {
340 d3e1ab0c 2022-11-24 op case CMD_BELL:
341 d3e1ab0c 2022-11-24 op case CMD_HEXDUMP:
342 d3e1ab0c 2022-11-24 op case CMD_VERBOSE:
343 d3e1ab0c 2022-11-24 op if (argno != 1)
345 d3e1ab0c 2022-11-24 op return rl_completion_matches(text, ftp_bool_generator);
347 d3e1ab0c 2022-11-24 op case CMD_BYE:
348 d3e1ab0c 2022-11-24 op case CMD_LPWD:
349 d3e1ab0c 2022-11-24 op /* no args */
353 d3e1ab0c 2022-11-24 op case CMD_EDIT:
355 d3e1ab0c 2022-11-24 op case CMD_PAGE:
356 d3e1ab0c 2022-11-24 op if (argno != 1)
358 d3e1ab0c 2022-11-24 op /* fallthrough */
360 d3e1ab0c 2022-11-24 op return ftp_remote_files(text, start, end);
362 d3e1ab0c 2022-11-24 op case CMD_GET:
363 d3e1ab0c 2022-11-24 op if (argno > 2)
365 d3e1ab0c 2022-11-24 op if (argno == 2)
366 d3e1ab0c 2022-11-24 op return ftp_remote_files(text, start, end);
367 d3e1ab0c 2022-11-24 op /* try local */
368 d3e1ab0c 2022-11-24 op rl_attempted_completion_over = 0;
371 d3e1ab0c 2022-11-24 op case CMD_LCD:
372 d3e1ab0c 2022-11-24 op if (argno != 1)
374 d3e1ab0c 2022-11-24 op /* try local */
375 d3e1ab0c 2022-11-24 op rl_attempted_completion_over = 0;
378 d3e1ab0c 2022-11-24 op case CMD_PIPE:
379 d3e1ab0c 2022-11-24 op if (argno > 2)
381 d3e1ab0c 2022-11-24 op if (argno == 1)
382 d3e1ab0c 2022-11-24 op return ftp_remote_files(text, start, end);
383 d3e1ab0c 2022-11-24 op /* try local */
384 d3e1ab0c 2022-11-24 op rl_attempted_completion_over = 0;
387 d3e1ab0c 2022-11-24 op case CMD_PUT:
388 d3e1ab0c 2022-11-24 op if (argno > 2)
390 d3e1ab0c 2022-11-24 op if (argno == 1) {
391 d3e1ab0c 2022-11-24 op /* try local */
392 d3e1ab0c 2022-11-24 op rl_attempted_completion_over = 0;
395 d3e1ab0c 2022-11-24 op return ftp_remote_files(text, start, end);
397 d3e1ab0c 2022-11-24 op case CMD_RENAME:
398 d3e1ab0c 2022-11-24 op if (argno > 2)
400 d3e1ab0c 2022-11-24 op return ftp_remote_files(text, start, end);
407 d3e1ab0c 2022-11-24 op ftp_quoted(char *line, int index)
409 d3e1ab0c 2022-11-24 op if (index > 0 && line[index - 1] == '\\')
410 d3e1ab0c 2022-11-24 op return !ftp_quoted(line, index - 1);
415 d3e1ab0c 2022-11-24 op compl_setup(void)
417 d3e1ab0c 2022-11-24 op rl_attempted_completion_function = ftp_completion;
418 d3e1ab0c 2022-11-24 op rl_completer_word_break_characters = "\t ";
419 d3e1ab0c 2022-11-24 op rl_completer_quote_characters = "\"'";
420 d3e1ab0c 2022-11-24 op rl_char_is_quoted_p = ftp_quoted;