1 /* $NetBSD: complete.c,v 1.11 2020/07/04 09:59:07 lukem Exp $ */ 2 /* from NetBSD: complete.c,v 1.47 2019/01/28 12:04:16 christos Exp */ 3 4 /*- 5 * Copyright (c) 1997-2009 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 #include "tnftp.h" 34 35 #if 0 /* tnftp */ 36 37 #include <sys/cdefs.h> 38 #ifndef lint 39 __RCSID(" NetBSD: complete.c,v 1.47 2019/01/28 12:04:16 christos Exp "); 40 #endif /* not lint */ 41 42 /* 43 * FTP user program - command and file completion routines 44 */ 45 46 #include <sys/stat.h> 47 48 #include <ctype.h> 49 #include <err.h> 50 #include <dirent.h> 51 #include <stdio.h> 52 #include <stdlib.h> 53 #include <string.h> 54 55 #endif /* tnftp */ 56 57 #include "ftp_var.h" 58 59 #ifndef NO_EDITCOMPLETE 60 61 static int comparstr (const void *, const void *); 62 static unsigned char complete_ambiguous (char *, int, StringList *); 63 static unsigned char complete_command (char *, int); 64 static unsigned char complete_local (char *, int); 65 static unsigned char complete_option (char *, int); 66 static unsigned char complete_remote (char *, int); 67 68 static int 69 comparstr(const void *a, const void *b) 70 { 71 return (strcmp(*(const char * const *)a, *(const char * const *)b)); 72 } 73 74 /* 75 * Determine if complete is ambiguous. If unique, insert. 76 * If no choices, error. If unambiguous prefix, insert that. 77 * Otherwise, list choices. words is assumed to be filtered 78 * to only contain possible choices. 79 * Args: 80 * word word which started the match 81 * list list by default 82 * words stringlist containing possible matches 83 * Returns a result as per el_set(EL_ADDFN, ...) 84 */ 85 static unsigned char 86 complete_ambiguous(char *word, int list, StringList *words) 87 { 88 char insertstr[MAXPATHLEN]; 89 char *lastmatch, *p; 90 size_t i, j; 91 size_t matchlen, wordlen; 92 93 wordlen = strlen(word); 94 if (words->sl_cur == 0) 95 return (CC_ERROR); /* no choices available */ 96 97 if (words->sl_cur == 1) { /* only once choice available */ 98 p = words->sl_str[0] + wordlen; 99 if (*p == '\0') /* at end of word? */ 100 return (CC_REFRESH); 101 ftpvis(insertstr, sizeof(insertstr), p, strlen(p)); 102 if (el_insertstr(el, insertstr) == -1) 103 return (CC_ERROR); 104 else 105 return (CC_REFRESH); 106 } 107 108 if (!list) { 109 lastmatch = words->sl_str[0]; 110 matchlen = strlen(lastmatch); 111 for (i = 1 ; i < words->sl_cur ; i++) { 112 for (j = wordlen; j < strlen(words->sl_str[i]); j++) 113 if (lastmatch[j] != words->sl_str[i][j]) 114 break; 115 if (j < matchlen) 116 matchlen = j; 117 } 118 if (matchlen > wordlen) { 119 ftpvis(insertstr, sizeof(insertstr), 120 lastmatch + wordlen, matchlen - wordlen); 121 if (el_insertstr(el, insertstr) == -1) 122 return (CC_ERROR); 123 else 124 return (CC_REFRESH_BEEP); 125 } 126 } 127 128 putc('\n', ttyout); 129 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr); 130 list_vertical(words); 131 return (CC_REDISPLAY); 132 } 133 134 /* 135 * Complete a command 136 */ 137 static unsigned char 138 complete_command(char *word, int list) 139 { 140 struct cmd *c; 141 StringList *words; 142 size_t wordlen; 143 unsigned char rv; 144 145 words = ftp_sl_init(); 146 wordlen = strlen(word); 147 148 for (c = cmdtab; c->c_name != NULL; c++) { 149 if (wordlen > strlen(c->c_name)) 150 continue; 151 if (strncmp(word, c->c_name, wordlen) == 0) 152 ftp_sl_add(words, ftp_strdup(c->c_name)); 153 } 154 155 rv = complete_ambiguous(word, list, words); 156 if (rv == CC_REFRESH) { 157 if (el_insertstr(el, " ") == -1) 158 rv = CC_ERROR; 159 } 160 sl_free(words, 1); 161 return (rv); 162 } 163 164 /* 165 * Complete a local file 166 */ 167 static unsigned char 168 complete_local(char *word, int list) 169 { 170 StringList *words; 171 char dir[MAXPATHLEN]; 172 char *file; 173 DIR *dd; 174 struct dirent *dp; 175 unsigned char rv; 176 size_t len; 177 178 if ((file = strrchr(word, '/')) == NULL) { 179 dir[0] = '.'; 180 dir[1] = '\0'; 181 file = word; 182 } else { 183 if (file == word) { 184 dir[0] = '/'; 185 dir[1] = '\0'; 186 } else 187 (void)strlcpy(dir, word, file - word + 1); 188 file++; 189 } 190 if (dir[0] == '~') { 191 char *p; 192 193 if ((p = globulize(dir)) == NULL) 194 return (CC_ERROR); 195 (void)strlcpy(dir, p, sizeof(dir)); 196 free(p); 197 } 198 199 if ((dd = opendir(dir)) == NULL) 200 return (CC_ERROR); 201 202 words = ftp_sl_init(); 203 len = strlen(file); 204 205 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 206 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 207 continue; 208 209 #if defined(DIRENT_MISSING_D_NAMLEN) 210 if (len > strlen(dp->d_name)) 211 continue; 212 #else 213 if (len > dp->d_namlen) 214 continue; 215 #endif 216 if (strncmp(file, dp->d_name, len) == 0) { 217 char *tcp; 218 219 tcp = ftp_strdup(dp->d_name); 220 ftp_sl_add(words, tcp); 221 } 222 } 223 closedir(dd); 224 225 rv = complete_ambiguous(file, list, words); 226 if (rv == CC_REFRESH) { 227 struct stat sb; 228 char path[MAXPATHLEN]; 229 230 (void)strlcpy(path, dir, sizeof(path)); 231 (void)strlcat(path, "/", sizeof(path)); 232 (void)strlcat(path, words->sl_str[0], sizeof(path)); 233 234 if (stat(path, &sb) >= 0) { 235 char suffix[2] = " "; 236 237 if (S_ISDIR(sb.st_mode)) 238 suffix[0] = '/'; 239 if (el_insertstr(el, suffix) == -1) 240 rv = CC_ERROR; 241 } 242 } 243 sl_free(words, 1); 244 return (rv); 245 } 246 /* 247 * Complete an option 248 */ 249 static unsigned char 250 complete_option(char *word, int list) 251 { 252 struct option *o; 253 StringList *words; 254 size_t wordlen; 255 unsigned char rv; 256 257 words = ftp_sl_init(); 258 wordlen = strlen(word); 259 260 for (o = optiontab; o->name != NULL; o++) { 261 if (wordlen > strlen(o->name)) 262 continue; 263 if (strncmp(word, o->name, wordlen) == 0) 264 ftp_sl_add(words, ftp_strdup(o->name)); 265 } 266 267 rv = complete_ambiguous(word, list, words); 268 if (rv == CC_REFRESH) { 269 if (el_insertstr(el, " ") == -1) 270 rv = CC_ERROR; 271 } 272 sl_free(words, 1); 273 return (rv); 274 } 275 276 /* 277 * Complete a remote file 278 */ 279 static unsigned char 280 complete_remote(char *word, int list) 281 { 282 static StringList *dirlist; 283 static char lastdir[MAXPATHLEN]; 284 StringList *words; 285 char dir[MAXPATHLEN]; 286 char *file, *cp; 287 size_t i; 288 unsigned char rv; 289 char cmdbuf[MAX_C_NAME]; 290 char *dummyargv[3] = { NULL, NULL, NULL }; 291 292 (void)strlcpy(cmdbuf, "complete", sizeof(cmdbuf)); 293 dummyargv[0] = cmdbuf; 294 dummyargv[1] = dir; 295 296 if ((file = strrchr(word, '/')) == NULL) { 297 dir[0] = '\0'; 298 file = word; 299 } else { 300 cp = file; 301 while (*cp == '/' && cp > word) 302 cp--; 303 (void)strlcpy(dir, word, cp - word + 2); 304 file++; 305 } 306 307 if (dirchange || dirlist == NULL || 308 strcmp(dir, lastdir) != 0) { /* dir not cached */ 309 const char *emesg; 310 311 if (dirlist != NULL) 312 sl_free(dirlist, 1); 313 dirlist = ftp_sl_init(); 314 315 mflag = 1; 316 emesg = NULL; 317 while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) { 318 char *tcp; 319 320 if (!mflag) 321 continue; 322 if (*cp == '\0') { 323 mflag = 0; 324 continue; 325 } 326 tcp = strrchr(cp, '/'); 327 if (tcp) 328 tcp++; 329 else 330 tcp = cp; 331 tcp = ftp_strdup(tcp); 332 ftp_sl_add(dirlist, tcp); 333 } 334 if (emesg != NULL) { 335 fprintf(ttyout, "\n%s\n", emesg); 336 return (CC_REDISPLAY); 337 } 338 (void)strlcpy(lastdir, dir, sizeof(lastdir)); 339 dirchange = 0; 340 } 341 342 words = ftp_sl_init(); 343 for (i = 0; i < dirlist->sl_cur; i++) { 344 cp = dirlist->sl_str[i]; 345 if (strlen(file) > strlen(cp)) 346 continue; 347 if (strncmp(file, cp, strlen(file)) == 0) 348 ftp_sl_add(words, cp); 349 } 350 rv = complete_ambiguous(file, list, words); 351 sl_free(words, 0); 352 return (rv); 353 } 354 355 /* 356 * Generic complete routine 357 */ 358 unsigned char 359 complete(EditLine *cel, int ch) 360 { 361 static char word[FTPBUFLEN]; 362 static size_t lastc_argc, lastc_argo; 363 364 struct cmd *c; 365 const LineInfo *lf; 366 int dolist, cmpltype; 367 size_t celems, len; 368 369 lf = el_line(cel); 370 len = lf->lastchar - lf->buffer; 371 if (len >= sizeof(line)) 372 return (CC_ERROR); 373 (void)strlcpy(line, lf->buffer, len + 1); 374 cursor_pos = line + (lf->cursor - lf->buffer); 375 lastc_argc = cursor_argc; /* remember last cursor pos */ 376 lastc_argo = cursor_argo; 377 makeargv(); /* build argc/argv of current line */ 378 379 if (cursor_argo >= sizeof(word)) 380 return (CC_ERROR); 381 382 dolist = 0; 383 /* if cursor and word is same, list alternatives */ 384 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo 385 && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "", 386 cursor_argo) == 0) 387 dolist = 1; 388 else if (cursor_argc < (size_t)margc) 389 (void)strlcpy(word, margv[cursor_argc], cursor_argo + 1); 390 word[cursor_argo] = '\0'; 391 392 if (cursor_argc == 0) 393 return (complete_command(word, dolist)); 394 395 c = getcmd(margv[0]); 396 if (c == (struct cmd *)-1 || c == 0) 397 return (CC_ERROR); 398 celems = strlen(c->c_complete); 399 400 /* check for 'continuation' completes (which are uppercase) */ 401 if ((cursor_argc > celems) && (celems > 0) 402 && isupper((unsigned char) c->c_complete[celems-1])) 403 cursor_argc = celems; 404 405 if (cursor_argc > celems) 406 return (CC_ERROR); 407 408 cmpltype = c->c_complete[cursor_argc - 1]; 409 switch (cmpltype) { 410 case 'c': /* command complete */ 411 case 'C': 412 return (complete_command(word, dolist)); 413 case 'l': /* local complete */ 414 case 'L': 415 return (complete_local(word, dolist)); 416 case 'n': /* no complete */ 417 case 'N': /* no complete */ 418 return (CC_ERROR); 419 case 'o': /* local complete */ 420 case 'O': 421 return (complete_option(word, dolist)); 422 case 'r': /* remote complete */ 423 case 'R': 424 if (connected != -1) { 425 fputs("\nMust be logged in to complete.\n", 426 ttyout); 427 return (CC_REDISPLAY); 428 } 429 return (complete_remote(word, dolist)); 430 default: 431 errx(1, "complete: unknown complete type `%c'", 432 cmpltype); 433 return (CC_ERROR); 434 } 435 /* NOTREACHED */ 436 } 437 438 #endif /* !NO_EDITCOMPLETE */ 439