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 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 */ 382