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