1 /* $OpenBSD: complete.c,v 1.33 2019/05/16 12:44:17 florian Exp $ */
2 /* $NetBSD: complete.c,v 1.10 1997/08/18 10:20:18 lukem Exp $ */
3
4 /*-
5 * Copyright (c) 1997 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to The NetBSD Foundation
9 * by Luke Mewburn.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #ifndef SMALL
34
35 /*
36 * FTP user program - command and file completion routines
37 */
38
39 #include <ctype.h>
40 #include <err.h>
41 #include <dirent.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45
46 #include "ftp_var.h"
47
48 static int comparstr(const void *, const void *);
49 static unsigned char complete_ambiguous(char *, int, StringList *);
50 static unsigned char complete_command(char *, int);
51 static unsigned char complete_local(char *, int);
52 static unsigned char complete_remote(char *, int);
53 static void ftpvis(char *, size_t, const char *, size_t);
54
55 static int
comparstr(const void * a,const void * b)56 comparstr(const void *a, const void *b)
57 {
58 return (strcmp(*(char **)a, *(char **)b));
59 }
60
61 /*
62 * Determine if complete is ambiguous. If unique, insert.
63 * If no choices, error. If unambiguous prefix, insert that.
64 * Otherwise, list choices. words is assumed to be filtered
65 * to only contain possible choices.
66 * Args:
67 * word word which started the match
68 * list list by default
69 * words stringlist containing possible matches
70 */
71 static unsigned char
complete_ambiguous(char * word,int list,StringList * words)72 complete_ambiguous(char *word, int list, StringList *words)
73 {
74 char insertstr[PATH_MAX * 2];
75 char *lastmatch;
76 int i, j;
77 size_t matchlen, wordlen;
78
79 wordlen = strlen(word);
80 if (words->sl_cur == 0)
81 return (CC_ERROR); /* no choices available */
82
83 if (words->sl_cur == 1) { /* only once choice available */
84 char *p = words->sl_str[0] + wordlen;
85 ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
86 if (el_insertstr(el, insertstr) == -1)
87 return (CC_ERROR);
88 else
89 return (CC_REFRESH);
90 }
91
92 if (!list) {
93 lastmatch = words->sl_str[0];
94 matchlen = strlen(lastmatch);
95 for (i = 1 ; i < words->sl_cur ; i++) {
96 for (j = wordlen ; j < strlen(words->sl_str[i]); j++)
97 if (lastmatch[j] != words->sl_str[i][j])
98 break;
99 if (j < matchlen)
100 matchlen = j;
101 }
102 if (matchlen > wordlen) {
103 ftpvis(insertstr, sizeof(insertstr),
104 lastmatch + wordlen, matchlen - wordlen);
105 if (el_insertstr(el, insertstr) == -1)
106 return (CC_ERROR);
107 else
108 /*
109 * XXX: really want CC_REFRESH_BEEP
110 */
111 return (CC_REFRESH);
112 }
113 }
114
115 putc('\n', ttyout);
116 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
117 list_vertical(words);
118 return (CC_REDISPLAY);
119 }
120
121 /*
122 * Complete a command
123 */
124 static unsigned char
complete_command(char * word,int list)125 complete_command(char *word, int list)
126 {
127 struct cmd *c;
128 StringList *words;
129 size_t wordlen;
130 unsigned char rv;
131
132 words = sl_init();
133 wordlen = strlen(word);
134
135 for (c = cmdtab; c->c_name != NULL; c++) {
136 if (wordlen > strlen(c->c_name))
137 continue;
138 if (strncmp(word, c->c_name, wordlen) == 0)
139 sl_add(words, c->c_name);
140 }
141
142 rv = complete_ambiguous(word, list, words);
143 sl_free(words, 0);
144 return (rv);
145 }
146
147 /*
148 * Complete a local file
149 */
150 static unsigned char
complete_local(char * word,int list)151 complete_local(char *word, int list)
152 {
153 StringList *words;
154 char dir[PATH_MAX];
155 char *file;
156 DIR *dd;
157 struct dirent *dp;
158 unsigned char rv;
159
160 if ((file = strrchr(word, '/')) == NULL) {
161 dir[0] = '.';
162 dir[1] = '\0';
163 file = word;
164 } else {
165 if (file == word) {
166 dir[0] = '/';
167 dir[1] = '\0';
168 } else {
169 (void)strlcpy(dir, word, (size_t)(file - word) + 1);
170 }
171 file++;
172 }
173
174 if ((dd = opendir(dir)) == NULL)
175 return (CC_ERROR);
176
177 words = sl_init();
178
179 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
180 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
181 continue;
182 if (strlen(file) > dp->d_namlen)
183 continue;
184 if (strncmp(file, dp->d_name, strlen(file)) == 0) {
185 char *tcp;
186
187 tcp = strdup(dp->d_name);
188 if (tcp == NULL)
189 errx(1, "Can't allocate memory for local dir");
190 sl_add(words, tcp);
191 }
192 }
193 closedir(dd);
194
195 rv = complete_ambiguous(file, list, words);
196 sl_free(words, 1);
197 return (rv);
198 }
199
200 /*
201 * Complete a remote file
202 */
203 static unsigned char
complete_remote(char * word,int list)204 complete_remote(char *word, int list)
205 {
206 static StringList *dirlist;
207 static char lastdir[PATH_MAX];
208 StringList *words;
209 char dir[PATH_MAX];
210 char *file, *cp;
211 int i;
212 unsigned char rv;
213
214 char *dummyargv[] = { "complete", dir, NULL };
215
216 if ((file = strrchr(word, '/')) == NULL) {
217 dir[0] = '.';
218 dir[1] = '\0';
219 file = word;
220 } else {
221 cp = file;
222 while (*cp == '/' && cp > word)
223 cp--;
224 (void)strlcpy(dir, word, (size_t)(cp - word + 2));
225 file++;
226 }
227
228 if (dirchange || strcmp(dir, lastdir) != 0) { /* dir not cached */
229 char *emesg;
230
231 sl_free(dirlist, 1);
232 dirlist = sl_init();
233
234 mflag = 1;
235 emesg = NULL;
236 if (debug)
237 (void)putc('\n', ttyout);
238 while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) {
239 char *tcp;
240
241 if (!mflag)
242 continue;
243 if (*cp == '\0') {
244 mflag = 0;
245 continue;
246 }
247 tcp = strrchr(cp, '/');
248 if (tcp)
249 tcp++;
250 else
251 tcp = cp;
252 tcp = strdup(tcp);
253 if (tcp == NULL)
254 errx(1, "Can't allocate memory for remote dir");
255 sl_add(dirlist, tcp);
256 }
257 if (emesg != NULL) {
258 fprintf(ttyout, "\n%s\n", emesg);
259 return (CC_REDISPLAY);
260 }
261 (void)strlcpy(lastdir, dir, sizeof lastdir);
262 dirchange = 0;
263 }
264
265 words = sl_init();
266 for (i = 0; i < dirlist->sl_cur; i++) {
267 cp = dirlist->sl_str[i];
268 if (strlen(file) > strlen(cp))
269 continue;
270 if (strncmp(file, cp, strlen(file)) == 0)
271 sl_add(words, cp);
272 }
273 rv = complete_ambiguous(file, list, words);
274 sl_free(words, 0);
275 return (rv);
276 }
277
278 /*
279 * Generic complete routine
280 */
281 unsigned char
complete(EditLine * el,int ch)282 complete(EditLine *el, int ch)
283 {
284 static char word[FTPBUFLEN];
285 static int lastc_argc, lastc_argo;
286 struct cmd *c;
287 const LineInfo *lf;
288 int celems, dolist;
289 size_t len;
290
291 lf = el_line(el);
292 len = lf->lastchar - lf->buffer;
293 if (len >= sizeof(line))
294 return (CC_ERROR);
295 (void)memcpy(line, lf->buffer, len);
296 line[len] = '\0';
297 cursor_pos = line + (lf->cursor - lf->buffer);
298 lastc_argc = cursor_argc; /* remember last cursor pos */
299 lastc_argo = cursor_argo;
300 makeargv(); /* build argc/argv of current line */
301
302 if (cursor_argo >= sizeof(word))
303 return (CC_ERROR);
304
305 dolist = 0;
306 /* if cursor and word is same, list alternatives */
307 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
308 && strncmp(word, margv[cursor_argc], cursor_argo) == 0)
309 dolist = 1;
310 else if (cursor_argo)
311 memcpy(word, margv[cursor_argc], cursor_argo);
312 word[cursor_argo] = '\0';
313
314 if (cursor_argc == 0)
315 return (complete_command(word, dolist));
316
317 c = getcmd(margv[0]);
318 if (c == (struct cmd *)-1 || c == 0)
319 return (CC_ERROR);
320 celems = strlen(c->c_complete);
321
322 /* check for 'continuation' completes (which are uppercase) */
323 if ((cursor_argc > celems) && (celems > 0)
324 && isupper((unsigned char)c->c_complete[celems - 1]))
325 cursor_argc = celems;
326
327 if (cursor_argc > celems)
328 return (CC_ERROR);
329
330 switch (c->c_complete[cursor_argc - 1]) {
331 case 'l': /* local complete */
332 case 'L':
333 return (complete_local(word, dolist));
334 case 'r': /* remote complete */
335 case 'R':
336 if (connected != -1) {
337 fputs("\nMust be logged in to complete.\n", ttyout);
338 return (CC_REDISPLAY);
339 }
340 return (complete_remote(word, dolist));
341 case 'c': /* command complete */
342 case 'C':
343 return (complete_command(word, dolist));
344 case 'n': /* no complete */
345 return (CC_ERROR);
346 }
347
348 return (CC_ERROR);
349 }
350
351 /*
352 * Copy characters from src into dst, \ quoting characters that require it.
353 */
354 static void
ftpvis(char * dst,size_t dstlen,const char * src,size_t srclen)355 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
356 {
357 size_t di, si;
358
359 di = si = 0;
360 while (di + 1 < dstlen && si < srclen && src[si] != '\0') {
361 switch (src[si]) {
362 case '\\':
363 case ' ':
364 case '\t':
365 case '\r':
366 case '\n':
367 case '"':
368 /* Need room for two characters and NUL, avoiding
369 * incomplete escape sequences at end of dst. */
370 if (di + 3 >= dstlen)
371 break;
372 dst[di++] = '\\';
373 /* FALLTHROUGH */
374 default:
375 dst[di++] = src[si++];
376 }
377 }
378 if (dstlen != 0)
379 dst[di] = '\0';
380 }
381 #endif /* !SMALL */
382