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