Home | History | Annotate | Line # | Download | only in readline
      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