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