1/* tilde.c -- Tilde expansion code (~/foo := $HOME/foo). */
2
3/* Copyright (C) 1988-2009 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)
53static 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)
60extern struct passwd *getpwuid PARAMS((uid_t));
61#  endif
62#  if defined (HAVE_GETPWNAM)
63extern struct passwd *getpwnam PARAMS((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. */
82extern char *sh_get_home_dir PARAMS((void));
83extern char *sh_get_env_value PARAMS((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. */
88static 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. */
94static 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. */
101tilde_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. */
107tilde_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 `:~'. */
112char **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 `=~'. */
117char **tilde_additional_suffixes = (char **)default_suffixes;
118
119static int tilde_find_prefix PARAMS((const char *, int *));
120static int tilde_find_suffix PARAMS((const char *));
121static char *isolate_tilde_prefix PARAMS((const char *, int *));
122static char *glue_prefix_and_suffix PARAMS((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. */
127static int
128tilde_find_prefix (string, len)
129     const char *string;
130     int *len;
131{
132  register int i, j, string_len;
133  register char **prefixes;
134
135  prefixes = tilde_additional_prefixes;
136
137  string_len = strlen (string);
138  *len = 0;
139
140  if (*string == '\0' || *string == '~')
141    return (0);
142
143  if (prefixes)
144    {
145      for (i = 0; i < string_len; i++)
146	{
147	  for (j = 0; prefixes[j]; j++)
148	    {
149	      if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0)
150		{
151		  *len = strlen (prefixes[j]) - 1;
152		  return (i + *len);
153		}
154	    }
155	}
156    }
157  return (string_len);
158}
159
160/* Find the end of a tilde expansion in STRING, and return the index of
161   the character which ends the tilde definition.  */
162static int
163tilde_find_suffix (string)
164     const char *string;
165{
166  register int i, j, string_len;
167  register char **suffixes;
168
169  suffixes = tilde_additional_suffixes;
170  string_len = strlen (string);
171
172  for (i = 0; i < string_len; i++)
173    {
174#if defined (__MSDOS__)
175      if (string[i] == '/' || string[i] == '\\' /* || !string[i] */)
176#else
177      if (string[i] == '/' /* || !string[i] */)
178#endif
179	break;
180
181      for (j = 0; suffixes && suffixes[j]; j++)
182	{
183	  if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0)
184	    return (i);
185	}
186    }
187  return (i);
188}
189
190/* Return a new string which is the result of tilde expanding STRING. */
191char *
192tilde_expand (string)
193     const char *string;
194{
195  char *result;
196  int result_size, result_index;
197
198  result_index = result_size = 0;
199  if (result = strchr (string, '~'))
200    result = (char *)xmalloc (result_size = (strlen (string) + 16));
201  else
202    result = (char *)xmalloc (result_size = (strlen (string) + 1));
203
204  /* Scan through STRING expanding tildes as we come to them. */
205  while (1)
206    {
207      register int start, end;
208      char *tilde_word, *expansion;
209      int len;
210
211      /* Make START point to the tilde which starts the expansion. */
212      start = tilde_find_prefix (string, &len);
213
214      /* Copy the skipped text into the result. */
215      if ((result_index + start + 1) > result_size)
216	result = (char *)xrealloc (result, 1 + (result_size += (start + 20)));
217
218      strncpy (result + result_index, string, start);
219      result_index += start;
220
221      /* Advance STRING to the starting tilde. */
222      string += start;
223
224      /* Make END be the index of one after the last character of the
225	 username. */
226      end = tilde_find_suffix (string);
227
228      /* If both START and END are zero, we are all done. */
229      if (!start && !end)
230	break;
231
232      /* Expand the entire tilde word, and copy it into RESULT. */
233      tilde_word = (char *)xmalloc (1 + end);
234      strncpy (tilde_word, string, end);
235      tilde_word[end] = '\0';
236      string += end;
237
238      expansion = tilde_expand_word (tilde_word);
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. */
265static char *
266isolate_tilde_prefix (fname, lenp)
267     const char *fname;
268     int *lenp;
269{
270  char *ret;
271  int i;
272
273  ret = (char *)xmalloc (strlen (fname));
274#if defined (__MSDOS__)
275  for (i = 1; fname[i] && fname[i] != '/' && fname[i] != '\\'; i++)
276#else
277  for (i = 1; fname[i] && fname[i] != '/'; i++)
278#endif
279    ret[i - 1] = fname[i];
280  ret[i - 1] = '\0';
281  if (lenp)
282    *lenp = i;
283  return ret;
284}
285
286#if 0
287/* Public function to scan a string (FNAME) beginning with a tilde and find
288   the portion of the string that should be passed to the tilde expansion
289   function.  Right now, it just calls tilde_find_suffix and allocates new
290   memory, but it can be expanded to do different things later. */
291char *
292tilde_find_word (fname, flags, lenp)
293     const char *fname;
294     int flags, *lenp;
295{
296  int x;
297  char *r;
298
299  x = tilde_find_suffix (fname);
300  if (x == 0)
301    {
302      r = savestring (fname);
303      if (lenp)
304	*lenp = 0;
305    }
306  else
307    {
308      r = (char *)xmalloc (1 + x);
309      strncpy (r, fname, x);
310      r[x] = '\0';
311      if (lenp)
312	*lenp = x;
313    }
314
315  return r;
316}
317#endif
318
319/* Return a string that is PREFIX concatenated with SUFFIX starting at
320   SUFFIND. */
321static char *
322glue_prefix_and_suffix (prefix, suffix, suffind)
323     char *prefix;
324     const char *suffix;
325     int suffind;
326{
327  char *ret;
328  int plen, slen;
329
330  plen = (prefix && *prefix) ? strlen (prefix) : 0;
331  slen = strlen (suffix + suffind);
332  ret = (char *)xmalloc (plen + slen + 1);
333  if (plen)
334    strcpy (ret, prefix);
335  strcpy (ret + plen, suffix + suffind);
336  return ret;
337}
338
339/* Do the work of tilde expansion on FILENAME.  FILENAME starts with a
340   tilde.  If there is no expansion, call tilde_expansion_failure_hook.
341   This always returns a newly-allocated string, never static storage. */
342char *
343tilde_expand_word (filename)
344     const char *filename;
345{
346  char *dirname, *expansion, *username;
347  int user_len;
348  struct passwd *user_entry;
349
350  if (filename == 0)
351    return ((char *)NULL);
352
353  if (*filename != '~')
354    return (savestring (filename));
355
356  /* A leading `~/' or a bare `~' is *always* translated to the value of
357     $HOME or the home directory of the current user, regardless of any
358     preexpansion hook. */
359  if (filename[1] == '\0' || filename[1] == '/')
360    {
361      /* Prefix $HOME to the rest of the string. */
362      expansion = sh_get_env_value ("HOME");
363
364      /* If there is no HOME variable, look up the directory in
365	 the password database. */
366      if (expansion == 0)
367	expansion = sh_get_home_dir ();
368
369      return (glue_prefix_and_suffix (expansion, filename, 1));
370    }
371
372  username = isolate_tilde_prefix (filename, &user_len);
373
374  if (tilde_expansion_preexpansion_hook)
375    {
376      expansion = (*tilde_expansion_preexpansion_hook) (username);
377      if (expansion)
378	{
379	  dirname = glue_prefix_and_suffix (expansion, filename, user_len);
380	  xfree (username);
381	  free (expansion);
382	  return (dirname);
383	}
384    }
385
386  /* No preexpansion hook, or the preexpansion hook failed.  Look in the
387     password database. */
388  dirname = (char *)NULL;
389#if defined (HAVE_GETPWNAM)
390  user_entry = getpwnam (username);
391#else
392  user_entry = 0;
393#endif
394  if (user_entry == 0)
395    {
396      /* If the calling program has a special syntax for expanding tildes,
397	 and we couldn't find a standard expansion, then let them try. */
398      if (tilde_expansion_failure_hook)
399	{
400	  expansion = (*tilde_expansion_failure_hook) (username);
401	  if (expansion)
402	    {
403	      dirname = glue_prefix_and_suffix (expansion, filename, user_len);
404	      free (expansion);
405	    }
406	}
407      /* If we don't have a failure hook, or if the failure hook did not
408	 expand the tilde, return a copy of what we were passed. */
409      if (dirname == 0)
410	dirname = savestring (filename);
411    }
412#if defined (HAVE_GETPWENT)
413  else
414    dirname = glue_prefix_and_suffix (user_entry->pw_dir, filename, user_len);
415#endif
416
417  xfree (username);
418#if defined (HAVE_GETPWENT)
419  endpwent ();
420#endif
421  return (dirname);
422}
423
424
425#if defined (TEST)
426#undef NULL
427#include <stdio.h>
428
429main (argc, argv)
430     int argc;
431     char **argv;
432{
433  char *result, line[512];
434  int done = 0;
435
436  while (!done)
437    {
438      printf ("~expand: ");
439      fflush (stdout);
440
441      if (!gets (line))
442	strcpy (line, "done");
443
444      if ((strcmp (line, "done") == 0) ||
445	  (strcmp (line, "quit") == 0) ||
446	  (strcmp (line, "exit") == 0))
447	{
448	  done = 1;
449	  break;
450	}
451
452      result = tilde_expand (line);
453      printf ("  --> %s\n", result);
454      free (result);
455    }
456  exit (0);
457}
458
459static void memory_error_and_abort ();
460
461static void *
462xmalloc (bytes)
463     size_t bytes;
464{
465  void *temp = (char *)malloc (bytes);
466
467  if (!temp)
468    memory_error_and_abort ();
469  return (temp);
470}
471
472static void *
473xrealloc (pointer, bytes)
474     void *pointer;
475     int bytes;
476{
477  void *temp;
478
479  if (!pointer)
480    temp = malloc (bytes);
481  else
482    temp = realloc (pointer, bytes);
483
484  if (!temp)
485    memory_error_and_abort ();
486
487  return (temp);
488}
489
490static void
491memory_error_and_abort ()
492{
493  fprintf (stderr, "readline: out of virtual memory\n");
494  abort ();
495}
496
497/*
498 * Local variables:
499 * compile-command: "gcc -g -DTEST -o tilde tilde.c"
500 * end:
501 */
502#endif /* TEST */
503