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