1 |
|
|
/* $OpenBSD: complete.c,v 1.31 2017/08/01 15:04:44 anton 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 */ |