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