1 /* tilde.c -- Tilde expansion code (~/foo := $HOME/foo). */ 2 3 /* Copyright (C) 1988-2020 Free Software Foundation, Inc. 4 5 This file is part of the GNU Readline Library (Readline), a library 6 for reading lines of text with interactive input and history editing. 7 8 Readline is free software: you can redistribute it and/or modify 9 it under the terms of the GNU General Public License as published by 10 the Free Software Foundation, either version 3 of the License, or 11 (at your option) any later version. 12 13 Readline is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with Readline. If not, see <http://www.gnu.org/licenses/>. 20 */ 21 22 #if defined (HAVE_CONFIG_H) 23 # include <config.h> 24 #endif 25 26 #if defined (HAVE_UNISTD_H) 27 # ifdef _MINIX 28 # include <sys/types.h> 29 # endif 30 # include <unistd.h> 31 #endif 32 33 #if defined (HAVE_STRING_H) 34 # include <string.h> 35 #else /* !HAVE_STRING_H */ 36 # include <strings.h> 37 #endif /* !HAVE_STRING_H */ 38 39 #if defined (HAVE_STDLIB_H) 40 # include <stdlib.h> 41 #else 42 # include "ansi_stdlib.h" 43 #endif /* HAVE_STDLIB_H */ 44 45 #include <sys/types.h> 46 #if defined (HAVE_PWD_H) 47 #include <pwd.h> 48 #endif 49 50 #include "tilde.h" 51 52 #if defined (TEST) || defined (STATIC_MALLOC) 53 static void *xmalloc (), *xrealloc (); 54 #else 55 # include "xmalloc.h" 56 #endif /* TEST || STATIC_MALLOC */ 57 58 #if !defined (HAVE_GETPW_DECLS) 59 # if defined (HAVE_GETPWUID) 60 extern struct passwd *getpwuid (uid_t); 61 # endif 62 # if defined (HAVE_GETPWNAM) 63 extern struct passwd *getpwnam (const char *); 64 # endif 65 #endif /* !HAVE_GETPW_DECLS */ 66 67 #if !defined (savestring) 68 #define savestring(x) strcpy ((char *)xmalloc (1 + strlen (x)), (x)) 69 #endif /* !savestring */ 70 71 #if !defined (NULL) 72 # if defined (__STDC__) 73 # define NULL ((void *) 0) 74 # else 75 # define NULL 0x0 76 # endif /* !__STDC__ */ 77 #endif /* !NULL */ 78 79 /* If being compiled as part of bash, these will be satisfied from 80 variables.o. If being compiled as part of readline, they will 81 be satisfied from shell.o. */ 82 extern char *sh_get_home_dir (void); 83 extern char *sh_get_env_value (const char *); 84 85 /* The default value of tilde_additional_prefixes. This is set to 86 whitespace preceding a tilde so that simple programs which do not 87 perform any word separation get desired behaviour. */ 88 static const char *default_prefixes[] = 89 { " ~", "\t~", (const char *)NULL }; 90 91 /* The default value of tilde_additional_suffixes. This is set to 92 whitespace or newline so that simple programs which do not 93 perform any word separation get desired behaviour. */ 94 static const char *default_suffixes[] = 95 { " ", "\n", (const char *)NULL }; 96 97 /* If non-null, this contains the address of a function that the application 98 wants called before trying the standard tilde expansions. The function 99 is called with the text sans tilde, and returns a malloc()'ed string 100 which is the expansion, or a NULL pointer if the expansion fails. */ 101 tilde_hook_func_t *tilde_expansion_preexpansion_hook = (tilde_hook_func_t *)NULL; 102 103 /* If non-null, this contains the address of a function to call if the 104 standard meaning for expanding a tilde fails. The function is called 105 with the text (sans tilde, as in "foo"), and returns a malloc()'ed string 106 which is the expansion, or a NULL pointer if there is no expansion. */ 107 tilde_hook_func_t *tilde_expansion_failure_hook = (tilde_hook_func_t *)NULL; 108 109 /* When non-null, this is a NULL terminated array of strings which 110 are duplicates for a tilde prefix. Bash uses this to expand 111 `=~' and `:~'. */ 112 char **tilde_additional_prefixes = (char **)default_prefixes; 113 114 /* When non-null, this is a NULL terminated array of strings which match 115 the end of a username, instead of just "/". Bash sets this to 116 `:' and `=~'. */ 117 char **tilde_additional_suffixes = (char **)default_suffixes; 118 119 static int tilde_find_prefix (const char *, int *); 120 static int tilde_find_suffix (const char *); 121 static char *isolate_tilde_prefix (const char *, int *); 122 static char *glue_prefix_and_suffix (char *, const char *, int); 123 124 /* Find the start of a tilde expansion in STRING, and return the index of 125 the tilde which starts the expansion. Place the length of the text 126 which identified this tilde starter in LEN, excluding the tilde itself. */ 127 static int 128 tilde_find_prefix (const char *string, int *len) 129 { 130 register int i, j, string_len; 131 register char **prefixes; 132 133 prefixes = tilde_additional_prefixes; 134 135 string_len = strlen (string); 136 *len = 0; 137 138 if (*string == '\0' || *string == '~') 139 return (0); 140 141 if (prefixes) 142 { 143 for (i = 0; i < string_len; i++) 144 { 145 for (j = 0; prefixes[j]; j++) 146 { 147 if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0) 148 { 149 *len = strlen (prefixes[j]) - 1; 150 return (i + *len); 151 } 152 } 153 } 154 } 155 return (string_len); 156 } 157 158 /* Find the end of a tilde expansion in STRING, and return the index of 159 the character which ends the tilde definition. */ 160 static int 161 tilde_find_suffix (const char *string) 162 { 163 register int i, j, string_len; 164 register char **suffixes; 165 166 suffixes = tilde_additional_suffixes; 167 string_len = strlen (string); 168 169 for (i = 0; i < string_len; i++) 170 { 171 #if defined (__MSDOS__) 172 if (string[i] == '/' || string[i] == '\\' /* || !string[i] */) 173 #else 174 if (string[i] == '/' /* || !string[i] */) 175 #endif 176 break; 177 178 for (j = 0; suffixes && suffixes[j]; j++) 179 { 180 if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0) 181 return (i); 182 } 183 } 184 return (i); 185 } 186 187 /* Return a new string which is the result of tilde expanding STRING. */ 188 char * 189 tilde_expand (const char *string) 190 { 191 char *result; 192 int result_size, result_index; 193 194 result_index = result_size = 0; 195 if (result = strchr (string, '~')) 196 result = (char *)xmalloc (result_size = (strlen (string) + 16)); 197 else 198 result = (char *)xmalloc (result_size = (strlen (string) + 1)); 199 200 /* Scan through STRING expanding tildes as we come to them. */ 201 while (1) 202 { 203 register int start, end; 204 char *tilde_word, *expansion; 205 int len; 206 207 /* Make START point to the tilde which starts the expansion. */ 208 start = tilde_find_prefix (string, &len); 209 210 /* Copy the skipped text into the result. */ 211 if ((result_index + start + 1) > result_size) 212 result = (char *)xrealloc (result, 1 + (result_size += (start + 20))); 213 214 strncpy (result + result_index, string, start); 215 result_index += start; 216 217 /* Advance STRING to the starting tilde. */ 218 string += start; 219 220 /* Make END be the index of one after the last character of the 221 username. */ 222 end = tilde_find_suffix (string); 223 224 /* If both START and END are zero, we are all done. */ 225 if (!start && !end) 226 break; 227 228 /* Expand the entire tilde word, and copy it into RESULT. */ 229 tilde_word = (char *)xmalloc (1 + end); 230 strncpy (tilde_word, string, end); 231 tilde_word[end] = '\0'; 232 string += end; 233 234 expansion = tilde_expand_word (tilde_word); 235 236 if (expansion == 0) 237 expansion = tilde_word; 238 else 239 xfree (tilde_word); 240 241 len = strlen (expansion); 242 #ifdef __CYGWIN__ 243 /* Fix for Cygwin to prevent ~user/xxx from expanding to //xxx when 244 $HOME for `user' is /. On cygwin, // denotes a network drive. */ 245 if (len > 1 || *expansion != '/' || *string != '/') 246 #endif 247 { 248 if ((result_index + len + 1) > result_size) 249 result = (char *)xrealloc (result, 1 + (result_size += (len + 20))); 250 251 strcpy (result + result_index, expansion); 252 result_index += len; 253 } 254 xfree (expansion); 255 } 256 257 result[result_index] = '\0'; 258 259 return (result); 260 } 261 262 /* Take FNAME and return the tilde prefix we want expanded. If LENP is 263 non-null, the index of the end of the prefix into FNAME is returned in 264 the location it points to. */ 265 static char * 266 isolate_tilde_prefix (const char *fname, int *lenp) 267 { 268 char *ret; 269 int i; 270 271 ret = (char *)xmalloc (strlen (fname)); 272 #if defined (__MSDOS__) 273 for (i = 1; fname[i] && fname[i] != '/' && fname[i] != '\\'; i++) 274 #else 275 for (i = 1; fname[i] && fname[i] != '/'; i++) 276 #endif 277 ret[i - 1] = fname[i]; 278 ret[i - 1] = '\0'; 279 if (lenp) 280 *lenp = i; 281 return ret; 282 } 283 284 #if 0 285 /* Public function to scan a string (FNAME) beginning with a tilde and find 286 the portion of the string that should be passed to the tilde expansion 287 function. Right now, it just calls tilde_find_suffix and allocates new 288 memory, but it can be expanded to do different things later. */ 289 char * 290 tilde_find_word (const char *fname, int flags, int *lenp) 291 { 292 int x; 293 char *r; 294 295 x = tilde_find_suffix (fname); 296 if (x == 0) 297 { 298 r = savestring (fname); 299 if (lenp) 300 *lenp = 0; 301 } 302 else 303 { 304 r = (char *)xmalloc (1 + x); 305 strncpy (r, fname, x); 306 r[x] = '\0'; 307 if (lenp) 308 *lenp = x; 309 } 310 311 return r; 312 } 313 #endif 314 315 /* Return a string that is PREFIX concatenated with SUFFIX starting at 316 SUFFIND. */ 317 static char * 318 glue_prefix_and_suffix (char *prefix, const char *suffix, int suffind) 319 { 320 char *ret; 321 int plen, slen; 322 323 plen = (prefix && *prefix) ? strlen (prefix) : 0; 324 slen = strlen (suffix + suffind); 325 ret = (char *)xmalloc (plen + slen + 1); 326 if (plen) 327 strcpy (ret, prefix); 328 strcpy (ret + plen, suffix + suffind); 329 return ret; 330 } 331 332 /* Do the work of tilde expansion on FILENAME. FILENAME starts with a 333 tilde. If there is no expansion, call tilde_expansion_failure_hook. 334 This always returns a newly-allocated string, never static storage. */ 335 char * 336 tilde_expand_word (const char *filename) 337 { 338 char *dirname, *expansion, *username; 339 int user_len; 340 struct passwd *user_entry; 341 342 if (filename == 0) 343 return ((char *)NULL); 344 345 if (*filename != '~') 346 return (savestring (filename)); 347 348 /* A leading `~/' or a bare `~' is *always* translated to the value of 349 $HOME or the home directory of the current user, regardless of any 350 preexpansion hook. */ 351 if (filename[1] == '\0' || filename[1] == '/') 352 { 353 /* Prefix $HOME to the rest of the string. */ 354 expansion = sh_get_env_value ("HOME"); 355 #if defined (_WIN32) 356 if (expansion == 0) 357 expansion = sh_get_env_value ("APPDATA"); 358 #endif 359 360 /* If there is no HOME variable, look up the directory in 361 the password database. */ 362 if (expansion == 0) 363 expansion = sh_get_home_dir (); 364 365 return (glue_prefix_and_suffix (expansion, filename, 1)); 366 } 367 368 username = isolate_tilde_prefix (filename, &user_len); 369 370 if (tilde_expansion_preexpansion_hook) 371 { 372 expansion = (*tilde_expansion_preexpansion_hook) (username); 373 if (expansion) 374 { 375 dirname = glue_prefix_and_suffix (expansion, filename, user_len); 376 xfree (username); 377 xfree (expansion); 378 return (dirname); 379 } 380 } 381 382 /* No preexpansion hook, or the preexpansion hook failed. Look in the 383 password database. */ 384 dirname = (char *)NULL; 385 #if defined (HAVE_GETPWNAM) 386 user_entry = getpwnam (username); 387 #else 388 user_entry = 0; 389 #endif 390 if (user_entry == 0) 391 { 392 /* If the calling program has a special syntax for expanding tildes, 393 and we couldn't find a standard expansion, then let them try. */ 394 if (tilde_expansion_failure_hook) 395 { 396 expansion = (*tilde_expansion_failure_hook) (username); 397 if (expansion) 398 { 399 dirname = glue_prefix_and_suffix (expansion, filename, user_len); 400 xfree (expansion); 401 } 402 } 403 /* If we don't have a failure hook, or if the failure hook did not 404 expand the tilde, return a copy of what we were passed. */ 405 if (dirname == 0) 406 dirname = savestring (filename); 407 } 408 #if defined (HAVE_GETPWENT) 409 else 410 dirname = glue_prefix_and_suffix (user_entry->pw_dir, filename, user_len); 411 #endif 412 413 xfree (username); 414 #if defined (HAVE_GETPWENT) 415 endpwent (); 416 #endif 417 return (dirname); 418 } 419 420 421 #if defined (TEST) 423 #undef NULL 424 #include <stdio.h> 425 426 main (int argc, char **argv) 427 { 428 char *result, line[512]; 429 int done = 0; 430 431 while (!done) 432 { 433 printf ("~expand: "); 434 fflush (stdout); 435 436 if (!gets (line)) 437 strcpy (line, "done"); 438 439 if ((strcmp (line, "done") == 0) || 440 (strcmp (line, "quit") == 0) || 441 (strcmp (line, "exit") == 0)) 442 { 443 done = 1; 444 break; 445 } 446 447 result = tilde_expand (line); 448 printf (" --> %s\n", result); 449 free (result); 450 } 451 exit (0); 452 } 453 454 static void memory_error_and_abort (void); 455 456 static void * 457 xmalloc (size_t bytes) 458 { 459 void *temp = (char *)malloc (bytes); 460 461 if (!temp) 462 memory_error_and_abort (); 463 return (temp); 464 } 465 466 static void * 467 xrealloc (void *pointer, int bytes) 468 { 469 void *temp; 470 471 if (!pointer) 472 temp = malloc (bytes); 473 else 474 temp = realloc (pointer, bytes); 475 476 if (!temp) 477 memory_error_and_abort (); 478 479 return (temp); 480 } 481 482 static void 483 memory_error_and_abort (void) 484 { 485 fprintf (stderr, "readline: out of virtual memory\n"); 486 abort (); 487 } 488 489 /* 490 * Local variables: 491 * compile-command: "gcc -g -DTEST -o tilde tilde.c" 492 * end: 493 */ 494 #endif /* TEST */ 495