1/* tilde.c -- tilde expansion code (~/foo := $HOME/foo).
2   $Id: tilde.c,v 1.3 2004/04/11 17:56:46 karl Exp $
3
4   Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1996, 1998, 1999,
5   2002, 2004 Free Software Foundation, Inc.
6
7   This program is free software; you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation; either version 2, or (at your option)
10   any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program; if not, write to the Free Software
19   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21   Written by Brian Fox (bfox@ai.mit.edu). */
22
23/* Include config.h before doing alloca.  */
24#include "info.h"
25#include "tilde.h"
26
27#if defined (TEST) || defined (STATIC_MALLOC)
28static void *xmalloc (), *xrealloc ();
29#endif /* TEST || STATIC_MALLOC */
30
31/* The default value of tilde_additional_prefixes.  This is set to
32   whitespace preceding a tilde so that simple programs which do not
33   perform any word separation get desired behaviour. */
34static char *default_prefixes[] =
35  { " ~", "\t~", (char *)NULL };
36
37/* The default value of tilde_additional_suffixes.  This is set to
38   whitespace or newline so that simple programs which do not
39   perform any word separation get desired behaviour. */
40static char *default_suffixes[] =
41  { " ", "\n", (char *)NULL };
42
43/* If non-null, this contains the address of a function to call if the
44   standard meaning for expanding a tilde fails.  The function is called
45   with the text (sans tilde, as in "foo"), and returns a malloc()'ed string
46   which is the expansion, or a NULL pointer if there is no expansion. */
47CFunction *tilde_expansion_failure_hook = (CFunction *)NULL;
48
49/* When non-null, this is a NULL terminated array of strings which
50   are duplicates for a tilde prefix.  Bash uses this to expand
51   `=~' and `:~'. */
52char **tilde_additional_prefixes = default_prefixes;
53
54/* When non-null, this is a NULL terminated array of strings which match
55   the end of a username, instead of just "/".  Bash sets this to
56   `:' and `=~'. */
57char **tilde_additional_suffixes = default_suffixes;
58
59/* Find the start of a tilde expansion in STRING, and return the index of
60   the tilde which starts the expansion.  Place the length of the text
61   which identified this tilde starter in LEN, excluding the tilde itself. */
62static int
63tilde_find_prefix (char *string, int *len)
64{
65  register int i, j, string_len;
66  register char **prefixes = tilde_additional_prefixes;
67
68  string_len = strlen (string);
69  *len = 0;
70
71  if (!*string || *string == '~')
72    return (0);
73
74  if (prefixes)
75    {
76      for (i = 0; i < string_len; i++)
77        {
78          for (j = 0; prefixes[j]; j++)
79            {
80              if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0)
81                {
82                  *len = strlen (prefixes[j]) - 1;
83                  return (i + *len);
84                }
85            }
86        }
87    }
88  return (string_len);
89}
90
91/* Find the end of a tilde expansion in STRING, and return the index of
92   the character which ends the tilde definition.  */
93static int
94tilde_find_suffix (char *string)
95{
96  register int i, j, string_len;
97  register char **suffixes = tilde_additional_suffixes;
98
99  string_len = strlen (string);
100
101  for (i = 0; i < string_len; i++)
102    {
103      if (IS_SLASH (string[i]) || !string[i])
104        break;
105
106      for (j = 0; suffixes && suffixes[j]; j++)
107        {
108          if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0)
109            return (i);
110        }
111    }
112  return (i);
113}
114
115/* Return a new string which is the result of tilde expanding STRING. */
116char *
117tilde_expand (char *string)
118{
119  char *result;
120  int result_size, result_index;
121
122  result_size = result_index = 0;
123  result = (char *)NULL;
124
125  /* Scan through STRING expanding tildes as we come to them. */
126  while (1)
127    {
128      register int start, end;
129      char *tilde_word, *expansion;
130      int len;
131
132      /* Make START point to the tilde which starts the expansion. */
133      start = tilde_find_prefix (string, &len);
134
135      /* Copy the skipped text into the result. */
136      if ((result_index + start + 1) > result_size)
137        result = (char *)xrealloc (result, 1 + (result_size += (start + 20)));
138
139      strncpy (result + result_index, string, start);
140      result_index += start;
141
142      /* Advance STRING to the starting tilde. */
143      string += start;
144
145      /* Make END be the index of one after the last character of the
146         username. */
147      end = tilde_find_suffix (string);
148
149      /* If both START and END are zero, we are all done. */
150      if (!start && !end)
151        break;
152
153      /* Expand the entire tilde word, and copy it into RESULT. */
154      tilde_word = (char *)xmalloc (1 + end);
155      strncpy (tilde_word, string, end);
156      tilde_word[end] = '\0';
157      string += end;
158
159      expansion = tilde_expand_word (tilde_word);
160      free (tilde_word);
161
162      len = strlen (expansion);
163      if ((result_index + len + 1) > result_size)
164        result = (char *)xrealloc (result, 1 + (result_size += (len + 20)));
165
166      strcpy (result + result_index, expansion);
167      result_index += len;
168      free (expansion);
169    }
170
171  result[result_index] = '\0';
172
173  return (result);
174}
175
176/* Do the work of tilde expansion on FILENAME.  FILENAME starts with a
177   tilde.  If there is no expansion, call tilde_expansion_failure_hook. */
178char *
179tilde_expand_word (char *filename)
180{
181  char *dirname = filename ? xstrdup (filename) : NULL;
182
183  if (dirname && *dirname == '~')
184    {
185      char *temp_name;
186      if (!dirname[1] || IS_SLASH (dirname[1]))
187        {
188          /* Prepend $HOME to the rest of the string. */
189          char *temp_home = getenv ("HOME");
190
191          /* If there is no HOME variable, look up the directory in
192             the password database. */
193          if (!temp_home)
194            {
195              struct passwd *entry;
196
197              entry = (struct passwd *) getpwuid (getuid ());
198              if (entry)
199                temp_home = entry->pw_dir;
200            }
201
202          temp_name = xmalloc (1 + strlen (&dirname[1])
203                               + (temp_home ? strlen (temp_home) : 0));
204          if (temp_home)
205            strcpy (temp_name, temp_home);
206          else
207            temp_name[0] = 0;
208          strcat (temp_name, &dirname[1]);
209          free (dirname);
210          dirname = xstrdup (temp_name);
211          free (temp_name);
212        }
213      else
214        {
215          struct passwd *user_entry;
216          char *username = xmalloc (257);
217          int i, c;
218
219          for (i = 1; (c = dirname[i]); i++)
220            {
221              if (IS_SLASH (c))
222                break;
223              else
224                username[i - 1] = c;
225            }
226          username[i - 1] = 0;
227
228          if (!(user_entry = (struct passwd *) getpwnam (username)))
229            {
230              /* If the calling program has a special syntax for
231                 expanding tildes, and we couldn't find a standard
232                 expansion, then let them try. */
233              if (tilde_expansion_failure_hook)
234                {
235                  char *expansion = (*tilde_expansion_failure_hook) (username);
236
237                  if (expansion)
238                    {
239                      temp_name = xmalloc (1 + strlen (expansion)
240                                           + strlen (&dirname[i]));
241                      strcpy (temp_name, expansion);
242                      strcat (temp_name, &dirname[i]);
243                      free (expansion);
244                      goto return_name;
245                    }
246                }
247              /* We shouldn't report errors. */
248            }
249          else
250            {
251              temp_name = xmalloc (1 + strlen (user_entry->pw_dir)
252                                   + strlen (&dirname[i]));
253              strcpy (temp_name, user_entry->pw_dir);
254              strcat (temp_name, &dirname[i]);
255
256            return_name:
257              free (dirname);
258              dirname = xstrdup (temp_name);
259              free (temp_name);
260            }
261
262          endpwent ();
263          free (username);
264        }
265    }
266  return dirname;
267}
268
269
270#if defined (TEST)
271#undef NULL
272#include <stdio.h>
273
274main (argc, argv)
275     int argc;
276     char **argv;
277{
278  char *result, line[512];
279  int done = 0;
280
281  while (!done)
282    {
283      printf ("~expand: ");
284      fflush (stdout);
285
286      if (!gets (line))
287        strcpy (line, "done");
288
289      if ((strcmp (line, "done") == 0) ||
290          (strcmp (line, "quit") == 0) ||
291          (strcmp (line, "exit") == 0))
292        {
293          done = 1;
294          break;
295        }
296
297      result = tilde_expand (line);
298      printf ("  --> %s\n", result);
299      free (result);
300    }
301  xexit (0);
302}
303
304static void memory_error_and_abort ();
305
306static void *
307xmalloc (bytes)
308     int bytes;
309{
310  void *temp = (void *)malloc (bytes);
311
312  if (!temp)
313    memory_error_and_abort ();
314  return (temp);
315}
316
317static void *
318xrealloc (pointer, bytes)
319     void *pointer;
320     int bytes;
321{
322  void *temp;
323
324  if (!pointer)
325    temp = (char *)malloc (bytes);
326  else
327    temp = (char *)realloc (pointer, bytes);
328
329  if (!temp)
330    memory_error_and_abort ();
331
332  return (temp);
333}
334
335static void
336memory_error_and_abort ()
337{
338  fprintf (stderr, _("readline: Out of virtual memory!\n"));
339  abort ();
340}
341#endif /* TEST */
342
343