1 /* $NetBSD: filecomplete.c,v 1.51 2018/05/04 20:38:26 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 1997 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jaromir Dolecek. 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 "config.h" 33 34 #if !defined(lint) && !defined(SCCSID) 35 __RCSID("$NetBSD: filecomplete.c,v 1.51 2018/05/04 20:38:26 christos Exp $"); 36 #endif /* not lint && not SCCSID */ 37 38 #include <sys/types.h> 39 #include <sys/stat.h> 40 #include <dirent.h> 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <limits.h> 44 #include <pwd.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 50 #include "el.h" 51 #include "filecomplete.h" 52 53 static const wchar_t break_chars[] = L" \t\n\"\\'`@$><=;|&{("; 54 55 /********************************/ 56 /* completion functions */ 57 58 /* 59 * does tilde expansion of strings of type ``~user/foo'' 60 * if ``user'' isn't valid user name or ``txt'' doesn't start 61 * w/ '~', returns pointer to strdup()ed copy of ``txt'' 62 * 63 * it's the caller's responsibility to free() the returned string 64 */ 65 char * 66 fn_tilde_expand(const char *txt) 67 { 68 #if defined(HAVE_GETPW_R_POSIX) || defined(HAVE_GETPW_R_DRAFT) 69 struct passwd pwres; 70 char pwbuf[1024]; 71 #endif 72 struct passwd *pass; 73 char *temp; 74 size_t len = 0; 75 76 if (txt[0] != '~') 77 return strdup(txt); 78 79 temp = strchr(txt + 1, '/'); 80 if (temp == NULL) { 81 temp = strdup(txt + 1); 82 if (temp == NULL) 83 return NULL; 84 } else { 85 /* text until string after slash */ 86 len = (size_t)(temp - txt + 1); 87 temp = el_malloc(len * sizeof(*temp)); 88 if (temp == NULL) 89 return NULL; 90 (void)strncpy(temp, txt + 1, len - 2); 91 temp[len - 2] = '\0'; 92 } 93 if (temp[0] == 0) { 94 #ifdef HAVE_GETPW_R_POSIX 95 if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf), 96 &pass) != 0) 97 pass = NULL; 98 #elif HAVE_GETPW_R_DRAFT 99 pass = getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf)); 100 #else 101 pass = getpwuid(getuid()); 102 #endif 103 } else { 104 #ifdef HAVE_GETPW_R_POSIX 105 if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0) 106 pass = NULL; 107 #elif HAVE_GETPW_R_DRAFT 108 pass = getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf)); 109 #else 110 pass = getpwnam(temp); 111 #endif 112 } 113 el_free(temp); /* value no more needed */ 114 if (pass == NULL) 115 return strdup(txt); 116 117 /* update pointer txt to point at string immedially following */ 118 /* first slash */ 119 txt += len; 120 121 len = strlen(pass->pw_dir) + 1 + strlen(txt) + 1; 122 temp = el_malloc(len * sizeof(*temp)); 123 if (temp == NULL) 124 return NULL; 125 (void)snprintf(temp, len, "%s/%s", pass->pw_dir, txt); 126 127 return temp; 128 } 129 130 static int 131 needs_escaping(char c) 132 { 133 switch (c) { 134 case '\'': 135 case '"': 136 case '(': 137 case ')': 138 case '\\': 139 case '<': 140 case '>': 141 case '$': 142 case '#': 143 case ' ': 144 case '\n': 145 case '\t': 146 case '?': 147 case ';': 148 case '`': 149 case '@': 150 case '=': 151 case '|': 152 case '{': 153 case '}': 154 case '&': 155 case '*': 156 case '[': 157 return 1; 158 default: 159 return 0; 160 } 161 } 162 163 static char * 164 escape_filename(EditLine * el, const char *filename) 165 { 166 size_t original_len = 0; 167 size_t escaped_character_count = 0; 168 size_t offset = 0; 169 size_t newlen; 170 const char *s; 171 char c; 172 size_t s_quoted = 0; /* does the input contain a single quote */ 173 size_t d_quoted = 0; /* does the input contain a double quote */ 174 char *escaped_str; 175 wchar_t *temp = el->el_line.buffer; 176 177 while (temp != el->el_line.cursor) { 178 /* 179 * If we see a single quote but have not seen a double quote so far 180 * set/unset s_quote 181 */ 182 if (temp[0] == '\'' && !d_quoted) 183 s_quoted = !s_quoted; 184 /* 185 * vice versa to the above condition 186 */ 187 else if (temp[0] == '"' && !s_quoted) 188 d_quoted = !d_quoted; 189 temp++; 190 } 191 192 /* Count number of special characters so that we can calculate 193 * number of extra bytes needed in the new string 194 */ 195 for (s = filename; *s; s++, original_len++) { 196 c = *s; 197 /* Inside a single quote only single quotes need escaping */ 198 if (s_quoted && c == '\'') { 199 escaped_character_count += 3; 200 continue; 201 } 202 /* Inside double quotes only ", \, ` and $ need escaping */ 203 if (d_quoted && (c == '"' || c == '\\' || c == '`' || c == '$')) { 204 escaped_character_count++; 205 continue; 206 } 207 if (!s_quoted && !d_quoted && needs_escaping(c)) 208 escaped_character_count++; 209 } 210 211 newlen = original_len + escaped_character_count + 1; 212 if ((escaped_str = el_malloc(newlen)) == NULL) 213 return NULL; 214 215 for (s = filename; *s; s++) { 216 c = *s; 217 if (!needs_escaping(c)) { 218 /* no escaping is required continue as usual */ 219 escaped_str[offset++] = c; 220 continue; 221 } 222 223 /* single quotes inside single quotes require special handling */ 224 if (c == '\'' && s_quoted) { 225 escaped_str[offset++] = '\''; 226 escaped_str[offset++] = '\\'; 227 escaped_str[offset++] = '\''; 228 escaped_str[offset++] = '\''; 229 continue; 230 } 231 232 /* Otherwise no escaping needed inside single quotes */ 233 if (s_quoted) { 234 escaped_str[offset++] = c; 235 continue; 236 } 237 238 /* No escaping needed inside a double quoted string either 239 * unless we see a '$', '\', '`', or '"' (itself) 240 */ 241 if (d_quoted && c != '"' && c != '$' && c != '\\' && c != '`') { 242 escaped_str[offset++] = c; 243 continue; 244 } 245 246 /* If we reach here that means escaping is actually needed */ 247 escaped_str[offset++] = '\\'; 248 escaped_str[offset++] = c; 249 } 250 251 /* close the quotes */ 252 if (s_quoted) 253 escaped_str[offset++] = '\''; 254 else if (d_quoted) 255 escaped_str[offset++] = '"'; 256 257 escaped_str[offset] = 0; 258 return escaped_str; 259 } 260 261 /* 262 * return first found file name starting by the ``text'' or NULL if no 263 * such file can be found 264 * value of ``state'' is ignored 265 * 266 * it's the caller's responsibility to free the returned string 267 */ 268 char * 269 fn_filename_completion_function(const char *text, int state) 270 { 271 static DIR *dir = NULL; 272 static char *filename = NULL, *dirname = NULL, *dirpath = NULL; 273 static size_t filename_len = 0; 274 struct dirent *entry; 275 char *temp; 276 size_t len; 277 278 if (state == 0 || dir == NULL) { 279 temp = strrchr(text, '/'); 280 if (temp) { 281 char *nptr; 282 temp++; 283 nptr = el_realloc(filename, (strlen(temp) + 1) * 284 sizeof(*nptr)); 285 if (nptr == NULL) { 286 el_free(filename); 287 filename = NULL; 288 return NULL; 289 } 290 filename = nptr; 291 (void)strcpy(filename, temp); 292 len = (size_t)(temp - text); /* including last slash */ 293 294 nptr = el_realloc(dirname, (len + 1) * 295 sizeof(*nptr)); 296 if (nptr == NULL) { 297 el_free(dirname); 298 dirname = NULL; 299 return NULL; 300 } 301 dirname = nptr; 302 (void)strncpy(dirname, text, len); 303 dirname[len] = '\0'; 304 } else { 305 el_free(filename); 306 if (*text == 0) 307 filename = NULL; 308 else { 309 filename = strdup(text); 310 if (filename == NULL) 311 return NULL; 312 } 313 el_free(dirname); 314 dirname = NULL; 315 } 316 317 if (dir != NULL) { 318 (void)closedir(dir); 319 dir = NULL; 320 } 321 322 /* support for ``~user'' syntax */ 323 324 el_free(dirpath); 325 dirpath = NULL; 326 if (dirname == NULL) { 327 if ((dirname = strdup("")) == NULL) 328 return NULL; 329 dirpath = strdup("./"); 330 } else if (*dirname == '~') 331 dirpath = fn_tilde_expand(dirname); 332 else 333 dirpath = strdup(dirname); 334 335 if (dirpath == NULL) 336 return NULL; 337 338 dir = opendir(dirpath); 339 if (!dir) 340 return NULL; /* cannot open the directory */ 341 342 /* will be used in cycle */ 343 filename_len = filename ? strlen(filename) : 0; 344 } 345 346 /* find the match */ 347 while ((entry = readdir(dir)) != NULL) { 348 /* skip . and .. */ 349 if (entry->d_name[0] == '.' && (!entry->d_name[1] 350 || (entry->d_name[1] == '.' && !entry->d_name[2]))) 351 continue; 352 if (filename_len == 0) 353 break; 354 /* otherwise, get first entry where first */ 355 /* filename_len characters are equal */ 356 if (entry->d_name[0] == filename[0] 357 /* Some dirents have d_namlen, but it is not portable. */ 358 && strlen(entry->d_name) >= filename_len 359 && strncmp(entry->d_name, filename, 360 filename_len) == 0) 361 break; 362 } 363 364 if (entry) { /* match found */ 365 366 /* Some dirents have d_namlen, but it is not portable. */ 367 len = strlen(entry->d_name); 368 369 len = strlen(dirname) + len + 1; 370 temp = el_malloc(len * sizeof(*temp)); 371 if (temp == NULL) 372 return NULL; 373 (void)snprintf(temp, len, "%s%s", dirname, entry->d_name); 374 } else { 375 (void)closedir(dir); 376 dir = NULL; 377 temp = NULL; 378 } 379 380 return temp; 381 } 382 383 384 static const char * 385 append_char_function(const char *name) 386 { 387 struct stat stbuf; 388 char *expname = *name == '~' ? fn_tilde_expand(name) : NULL; 389 const char *rs = " "; 390 391 if (stat(expname ? expname : name, &stbuf) == -1) 392 goto out; 393 if (S_ISDIR(stbuf.st_mode)) 394 rs = "/"; 395 out: 396 if (expname) 397 el_free(expname); 398 return rs; 399 } 400 /* 401 * returns list of completions for text given 402 * non-static for readline. 403 */ 404 char ** completion_matches(const char *, char *(*)(const char *, int)); 405 char ** 406 completion_matches(const char *text, char *(*genfunc)(const char *, int)) 407 { 408 char **match_list = NULL, *retstr, *prevstr; 409 size_t match_list_len, max_equal, which, i; 410 size_t matches; 411 412 matches = 0; 413 match_list_len = 1; 414 while ((retstr = (*genfunc) (text, (int)matches)) != NULL) { 415 /* allow for list terminator here */ 416 if (matches + 3 >= match_list_len) { 417 char **nmatch_list; 418 while (matches + 3 >= match_list_len) 419 match_list_len <<= 1; 420 nmatch_list = el_realloc(match_list, 421 match_list_len * sizeof(*nmatch_list)); 422 if (nmatch_list == NULL) { 423 el_free(match_list); 424 return NULL; 425 } 426 match_list = nmatch_list; 427 428 } 429 match_list[++matches] = retstr; 430 } 431 432 if (!match_list) 433 return NULL; /* nothing found */ 434 435 /* find least denominator and insert it to match_list[0] */ 436 which = 2; 437 prevstr = match_list[1]; 438 max_equal = strlen(prevstr); 439 for (; which <= matches; which++) { 440 for (i = 0; i < max_equal && 441 prevstr[i] == match_list[which][i]; i++) 442 continue; 443 max_equal = i; 444 } 445 446 retstr = el_malloc((max_equal + 1) * sizeof(*retstr)); 447 if (retstr == NULL) { 448 el_free(match_list); 449 return NULL; 450 } 451 (void)strncpy(retstr, match_list[1], max_equal); 452 retstr[max_equal] = '\0'; 453 match_list[0] = retstr; 454 455 /* add NULL as last pointer to the array */ 456 match_list[matches + 1] = NULL; 457 458 return match_list; 459 } 460 461 /* 462 * Sort function for qsort(). Just wrapper around strcasecmp(). 463 */ 464 static int 465 _fn_qsort_string_compare(const void *i1, const void *i2) 466 { 467 const char *s1 = ((const char * const *)i1)[0]; 468 const char *s2 = ((const char * const *)i2)[0]; 469 470 return strcasecmp(s1, s2); 471 } 472 473 /* 474 * Display list of strings in columnar format on readline's output stream. 475 * 'matches' is list of strings, 'num' is number of strings in 'matches', 476 * 'width' is maximum length of string in 'matches'. 477 * 478 * matches[0] is not one of the match strings, but it is counted in 479 * num, so the strings are matches[1] *through* matches[num-1]. 480 */ 481 void 482 fn_display_match_list(EditLine * el, char **matches, size_t num, size_t width, 483 const char *(*app_func) (const char *)) 484 { 485 size_t line, lines, col, cols, thisguy; 486 int screenwidth = el->el_terminal.t_size.h; 487 if (app_func == NULL) 488 app_func = append_char_function; 489 490 /* Ignore matches[0]. Avoid 1-based array logic below. */ 491 matches++; 492 num--; 493 494 /* 495 * Find out how many entries can be put on one line; count 496 * with one space between strings the same way it's printed. 497 */ 498 cols = (size_t)screenwidth / (width + 1); 499 if (cols == 0) 500 cols = 1; 501 502 /* how many lines of output, rounded up */ 503 lines = (num + cols - 1) / cols; 504 505 /* Sort the items. */ 506 qsort(matches, num, sizeof(char *), _fn_qsort_string_compare); 507 508 /* 509 * On the ith line print elements i, i+lines, i+lines*2, etc. 510 */ 511 for (line = 0; line < lines; line++) { 512 for (col = 0; col < cols; col++) { 513 thisguy = line + col * lines; 514 if (thisguy >= num) 515 break; 516 (void)fprintf(el->el_outfile, "%s%s%s", 517 col == 0 ? "" : " ", matches[thisguy], 518 append_char_function(matches[thisguy])); 519 (void)fprintf(el->el_outfile, "%-*s", 520 (int) (width - strlen(matches[thisguy])), ""); 521 } 522 (void)fprintf(el->el_outfile, "\n"); 523 } 524 } 525 526 static wchar_t * 527 find_word_to_complete(const wchar_t * cursor, const wchar_t * buffer, 528 const wchar_t * word_break, const wchar_t * special_prefixes, size_t * length) 529 { 530 /* We now look backwards for the start of a filename/variable word */ 531 const wchar_t *ctemp = cursor; 532 int cursor_at_quote; 533 size_t len; 534 wchar_t *temp; 535 536 /* if the cursor is placed at a slash or a quote, we need to find the 537 * word before it 538 */ 539 if (ctemp > buffer) { 540 switch (ctemp[-1]) { 541 case '\\': 542 case '\'': 543 case '"': 544 cursor_at_quote = 1; 545 ctemp--; 546 break; 547 default: 548 cursor_at_quote = 0; 549 } 550 } else 551 cursor_at_quote = 0; 552 553 while (ctemp > buffer 554 && !wcschr(word_break, ctemp[-1]) 555 && (!special_prefixes || !wcschr(special_prefixes, ctemp[-1]))) 556 ctemp--; 557 558 len = (size_t) (cursor - ctemp - cursor_at_quote); 559 temp = el_malloc((len + 1) * sizeof(*temp)); 560 if (temp == NULL) 561 return NULL; 562 (void) wcsncpy(temp, ctemp, len); 563 temp[len] = '\0'; 564 if (cursor_at_quote) 565 len++; 566 *length = len; 567 return temp; 568 } 569 570 /* 571 * Complete the word at or before point, 572 * 'what_to_do' says what to do with the completion. 573 * \t means do standard completion. 574 * `?' means list the possible completions. 575 * `*' means insert all of the possible completions. 576 * `!' means to do standard completion, and list all possible completions if 577 * there is more than one. 578 * 579 * Note: '*' support is not implemented 580 * '!' could never be invoked 581 */ 582 int 583 fn_complete(EditLine *el, 584 char *(*complet_func)(const char *, int), 585 char **(*attempted_completion_function)(const char *, int, int), 586 const wchar_t *word_break, const wchar_t *special_prefixes, 587 const char *(*app_func)(const char *), size_t query_items, 588 int *completion_type, int *over, int *point, int *end) 589 { 590 const LineInfoW *li; 591 wchar_t *temp; 592 char **matches; 593 size_t len; 594 int what_to_do = '\t'; 595 int retval = CC_NORM; 596 597 if (el->el_state.lastcmd == el->el_state.thiscmd) 598 what_to_do = '?'; 599 600 /* readline's rl_complete() has to be told what we did... */ 601 if (completion_type != NULL) 602 *completion_type = what_to_do; 603 604 if (!complet_func) 605 complet_func = fn_filename_completion_function; 606 if (!app_func) 607 app_func = append_char_function; 608 609 li = el_wline(el); 610 temp = find_word_to_complete(li->cursor, 611 li->buffer, word_break, special_prefixes, &len); 612 if (temp == NULL) 613 goto out; 614 615 /* these can be used by function called in completion_matches() */ 616 /* or (*attempted_completion_function)() */ 617 if (point != NULL) 618 *point = (int)(li->cursor - li->buffer); 619 if (end != NULL) 620 *end = (int)(li->lastchar - li->buffer); 621 622 if (attempted_completion_function) { 623 int cur_off = (int)(li->cursor - li->buffer); 624 matches = (*attempted_completion_function)( 625 ct_encode_string(temp, &el->el_scratch), 626 cur_off - (int)len, cur_off); 627 } else 628 matches = NULL; 629 if (!attempted_completion_function || 630 (over != NULL && !*over && !matches)) 631 matches = completion_matches( 632 ct_encode_string(temp, &el->el_scratch), complet_func); 633 634 if (over != NULL) 635 *over = 0; 636 637 if (matches) { 638 int i; 639 size_t matches_num, maxlen, match_len, match_display=1; 640 int single_match = matches[2] == NULL && 641 (matches[1] == NULL || strcmp(matches[0], matches[1]) == 0); 642 643 retval = CC_REFRESH; 644 645 if (matches[0][0] != '\0') { 646 el_deletestr(el, (int) len); 647 if (single_match) { 648 /* 649 * We found exact match. Add a space after 650 * it, unless we do filename completion and the 651 * object is a directory. Also do necessary escape quoting 652 */ 653 char *escaped_completion = escape_filename(el, matches[0]); 654 if (escaped_completion == NULL) 655 goto out; 656 el_winsertstr(el, 657 ct_decode_string(escaped_completion, &el->el_scratch)); 658 el_winsertstr(el, 659 ct_decode_string((*app_func)(escaped_completion), 660 &el->el_scratch)); 661 free(escaped_completion); 662 } else { 663 /* 664 * Only replace the completed string with common part of 665 * possible matches if there is possible completion. 666 */ 667 el_winsertstr(el, 668 ct_decode_string(matches[0], &el->el_scratch)); 669 } 670 } 671 672 673 if (!single_match && (what_to_do == '!' || what_to_do == '?')) { 674 /* 675 * More than one match and requested to list possible 676 * matches. 677 */ 678 679 for(i = 1, maxlen = 0; matches[i]; i++) { 680 match_len = strlen(matches[i]); 681 if (match_len > maxlen) 682 maxlen = match_len; 683 } 684 /* matches[1] through matches[i-1] are available */ 685 matches_num = (size_t)(i - 1); 686 687 /* newline to get on next line from command line */ 688 (void)fprintf(el->el_outfile, "\n"); 689 690 /* 691 * If there are too many items, ask user for display 692 * confirmation. 693 */ 694 if (matches_num > query_items) { 695 (void)fprintf(el->el_outfile, 696 "Display all %zu possibilities? (y or n) ", 697 matches_num); 698 (void)fflush(el->el_outfile); 699 if (getc(stdin) != 'y') 700 match_display = 0; 701 (void)fprintf(el->el_outfile, "\n"); 702 } 703 704 if (match_display) { 705 /* 706 * Interface of this function requires the 707 * strings be matches[1..num-1] for compat. 708 * We have matches_num strings not counting 709 * the prefix in matches[0], so we need to 710 * add 1 to matches_num for the call. 711 */ 712 fn_display_match_list(el, matches, 713 matches_num+1, maxlen, app_func); 714 } 715 retval = CC_REDISPLAY; 716 } else if (matches[0][0]) { 717 /* 718 * There was some common match, but the name was 719 * not complete enough. Next tab will print possible 720 * completions. 721 */ 722 el_beep(el); 723 } else { 724 /* lcd is not a valid object - further specification */ 725 /* is needed */ 726 el_beep(el); 727 retval = CC_NORM; 728 } 729 730 /* free elements of array and the array itself */ 731 for (i = 0; matches[i]; i++) 732 el_free(matches[i]); 733 el_free(matches); 734 matches = NULL; 735 } 736 737 out: 738 el_free(temp); 739 return retval; 740 } 741 742 /* 743 * el-compatible wrapper around rl_complete; needed for key binding 744 */ 745 /* ARGSUSED */ 746 unsigned char 747 _el_fn_complete(EditLine *el, int ch __attribute__((__unused__))) 748 { 749 return (unsigned char)fn_complete(el, NULL, NULL, 750 break_chars, NULL, NULL, (size_t)100, 751 NULL, NULL, NULL, NULL); 752 } 753