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)
51static void *xmalloc (), *xrealloc ();
52#else
53#  include "xmalloc.h"
54#endif /* TEST || STATIC_MALLOC */
55
56#if !defined (HAVE_GETPW_DECLS)
57extern struct passwd *getpwuid PARAMS((uid_t));
58extern 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. */
76extern char *sh_get_home_dir PARAMS((void));
77extern 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. */
82static 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. */
88static 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. */
95tilde_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. */
101tilde_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 `:~'. */
106char **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 `=~'. */
111char **tilde_additional_suffixes = (char **)default_suffixes;
112
113static int tilde_find_prefix PARAMS((const char *, int *));
114static int tilde_find_suffix PARAMS((const char *));
115static char *isolate_tilde_prefix PARAMS((const char *, int *));
116static 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. */
121static int
122tilde_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.  */
156static int
157tilde_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. */
185char *
186tilde_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. */
259static char *
260isolate_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. */
282static char *
283glue_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. */
303char *
304tilde_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
385main (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
415static void memory_error_and_abort ();
416
417static void *
418xmalloc (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
428static void *
429xrealloc (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
446static void
447memory_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