1 /* expand_path.c -- expand environmental variables in passed in string
2  *
3  * Copyright (C) 1995-2005 The Free Software Foundation, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * The main routine is expand_path(), it is the routine that handles
16  * the '~' character in four forms:
17  *     ~name
18  *     ~name/
19  *     ~/
20  *     ~
21  * and handles environment variables contained within the pathname
22  * which are defined by:
23  *     ${var_name}   (var_name is the name of the environ variable)
24  *     $var_name     (var_name ends w/ non-alphanumeric char other than '_')
25  */
26 
27 #include "cvs.h"
28 #include <sys/types.h>
29 
30 /* User variables.  */
31 
32 List *variable_list;
33 
34 static void variable_delproc (Node *);
35 
36 static void
37 variable_delproc (Node *node)
38 {
39     free (node->data);
40 }
41 
42 /* Currently used by -s option; we might want a way to set user
43    variables in a file in the $CVSROOT/CVSROOT directory too.  */
44 
45 void
46 variable_set (char *nameval)
47 {
48     char *p;
49     char *name;
50     Node *node;
51 
52     p = nameval;
53     while (isalnum ((unsigned char) *p) || *p == '_')
54 	++p;
55     if (*p != '=')
56 	error (1, 0, "invalid character in user variable name in %s", nameval);
57     if (p == nameval)
58 	error (1, 0, "empty user variable name in %s", nameval);
59     name = xmalloc (p - nameval + 1);
60     strncpy (name, nameval, p - nameval);
61     name[p - nameval] = '\0';
62     /* Make p point to the value.  */
63     ++p;
64     if (strchr (p, '\012'))
65 	error (1, 0, "linefeed in user variable value in %s", nameval);
66 
67     if (!variable_list)
68 	variable_list = getlist ();
69 
70     node = findnode (variable_list, name);
71     if (!node)
72     {
73 	node = getnode ();
74 	node->type = VARIABLE;
75 	node->delproc = variable_delproc;
76 	node->key = name;
77 	node->data = xstrdup (p);
78 	(void) addnode (variable_list, node);
79     }
80     else
81     {
82 	/* Replace the old value.  For example, this means that -s
83 	   options on the command line override ones from .cvsrc.  */
84 	free (node->data);
85 	node->data = xstrdup (p);
86 	free (name);
87     }
88 }
89 
90 
91 
92 /* Expand variable NAME into its contents, per the rules above.
93  *
94  * CVSROOT is used to expanding $CVSROOT.
95  *
96  * RETURNS
97  *   A pointer to the requested variable contents or NULL when the requested
98  *   variable is not found.
99  *
100  * ERRORS
101  *   None, though this function may generate warning messages when NAME is not
102  *   found.
103  */
104 static const char *
105 expand_variable (const char *name, const char *cvsroot,
106 		 const char *file, int line)
107 {
108     if (!strcmp (name, CVSROOT_ENV))
109 	return cvsroot;
110     else if (!strcmp (name, "RCSBIN"))
111     {
112 	error (0, 0, "RCSBIN internal variable is no longer supported");
113 	return NULL;
114     }
115     else if (!strcmp (name, EDITOR1_ENV))
116 	return Editor;
117     else if (!strcmp (name, EDITOR2_ENV))
118 	return Editor;
119     else if (!strcmp (name, EDITOR3_ENV))
120 	return Editor;
121     else if (!strcmp (name, "USER"))
122 	return getcaller ();
123     else if (!strcmp (name, "SESSIONID")
124 	     || !strcmp (name, "COMMITID"))
125 	return global_session_id;
126     else if (isalpha (name[0]))
127     {
128 	/* These names are reserved for future versions of CVS,
129 	   so that is why it is an error.  */
130 	if (line)
131 	    error (0, 0, "%s:%d: no such internal variable $%s",
132 		   file, line, name);
133 	else
134 	    error (0, 0, "%s: no such internal variable $%s",
135 		   file, name);
136 	return NULL;
137     }
138     else if (name[0] == '=')
139     {
140 	Node *node;
141 	/* Crazy syntax for a user variable.  But we want
142 	   *something* that lets the user name a user variable
143 	   anything he wants, without interference from
144 	   (existing or future) internal variables.  */
145 	node = findnode (variable_list, name + 1);
146 	if (!node)
147 	{
148 	    if (line)
149 		error (0, 0, "%s:%d: no such user variable ${%s}",
150 		       file, line, name);
151 	    else
152 		error (0, 0, "%s: no such user variable ${%s}",
153 		       file, name);
154 	    return NULL;
155 	}
156 	return node->data;
157     }
158     else
159     {
160 	/* It is an unrecognized character.  We return an error to
161 	   reserve these for future versions of CVS; it is plausible
162 	   that various crazy syntaxes might be invented for inserting
163 	   information about revisions, branches, etc.  */
164 	if (line)
165 	    error (0, 0, "%s:%d: unrecognized variable syntax %s",
166 		   file, line, name);
167 	else
168 	    error (0, 0, "%s: unrecognized variable syntax %s",
169 		   file, name);
170 	return NULL;
171     }
172 }
173 
174 
175 
176 /* This routine will expand the pathname to account for ~ and $
177  * characters as described above.  Returns a pointer to a newly
178  * malloc'd string.  If an error occurs, an error message is printed
179  * via error() and NULL is returned.  FILE and LINE are the filename
180  * and linenumber to include in the error message.  FILE must point
181  * to something; LINE can be zero to indicate the line number is not
182  * known.
183  *
184  * When FORMATSAFE is set, percent signs (`%') in variable contents are doubled
185  * to prevent later expansion by format_cmdline.
186  *
187  * CVSROOT is used to expanding $CVSROOT.
188  */
189 char *
190 expand_path (const char *name, const char *cvsroot, bool formatsafe,
191 	     const char *file, int line)
192 {
193     size_t s, d, p;
194     const char *e;
195 
196     char *mybuf = NULL;
197     size_t mybuf_size = 0;
198     char *buf = NULL;
199     size_t buf_size = 0;
200 
201     char inquotes = '\0';
202 
203     char *result;
204 
205     /* Sorry this routine is so ugly; it is a head-on collision
206        between the `traditional' unix *d++ style and the need to
207        dynamically allocate.  It would be much cleaner (and probably
208        faster, not that this is a bottleneck for CVS) with more use of
209        strcpy & friends, but I haven't taken the effort to rewrite it
210        thusly.  */
211 
212     /* First copy from NAME to MYBUF, expanding $<foo> as we go.  */
213     s = d = 0;
214     expand_string (&mybuf, &mybuf_size, d + 1);
215     while ((mybuf[d++] = name[s]) != '\0')
216     {
217 	if (name[s] == '\\')
218 	{
219 	    /* The next character is a literal.  Leave the \ in the string
220 	     * since it will be needed again when the string is split into
221 	     * arguments.
222 	     */
223 	    /* if we have a \ as the last character of the string, just leave
224 	     * it there - this is where we would set the escape flag to tell
225 	     * our parent we want another line if we cared.
226 	     */
227 	    if (name[++s])
228 	    {
229 		expand_string (&mybuf, &mybuf_size, d + 1);
230 		mybuf[d++] = name[s++];
231 	    }
232 	}
233 	/* skip $ variable processing for text inside single quotes */
234 	else if (inquotes == '\'')
235 	{
236 	    if (name[s++] == '\'')
237 	    {
238 		inquotes = '\0';
239 	    }
240 	}
241 	else if (name[s] == '\'')
242 	{
243 	    s++;
244 	    inquotes = '\'';
245 	}
246 	else if (name[s] == '"')
247 	{
248 	    s++;
249 	    if (inquotes) inquotes = '\0';
250 	    else inquotes = '"';
251 	}
252 	else if (name[s++] == '$')
253 	{
254 	    int flag = (name[s] == '{');
255 	    p = d;
256 
257 	    expand_string (&mybuf, &mybuf_size, d + 1);
258 	    for (; (mybuf[d++] = name[s]); s++)
259 	    {
260 		if (flag
261 		    ? name[s] =='}'
262 		    : !isalnum (name[s]) && name[s] != '_')
263 		    break;
264 		expand_string (&mybuf, &mybuf_size, d + 1);
265 	    }
266 	    mybuf[--d] = '\0';
267 	    e = expand_variable (&mybuf[p+flag], cvsroot, file, line);
268 
269 	    if (e)
270 	    {
271 		expand_string (&mybuf, &mybuf_size, d + 1);
272 		for (d = p - 1; (mybuf[d++] = *e++); )
273 		{
274 		    expand_string (&mybuf, &mybuf_size, d + 1);
275 		    if (mybuf[d-1] == '"')
276 		    {
277 			/* escape the double quotes if we're between a matched
278 			 * pair of double quotes so that this sub will be
279 			 * passed inside as or as part of a single argument
280 			 * during the argument split later.
281 			 */
282 			if (inquotes)
283 			{
284 			    mybuf[d-1] = '\\';
285 			    expand_string (&mybuf, &mybuf_size, d + 1);
286 			    mybuf[d++] = '"';
287 			}
288 		    }
289 		    else if (formatsafe && mybuf[d-1] == '%')
290 		    {
291 			/* escape '%' to get past printf style format strings
292 			 * later (in make_cmdline).
293 			 */
294 			expand_string (&mybuf, &mybuf_size, d + 1);
295 			mybuf[d] = '%';
296 			d++;
297 		    }
298 		}
299 		--d;
300 		if (flag && name[s])
301 		    s++;
302 	    }
303 	    else
304 		/* expand_variable has already printed an error message.  */
305 		goto error_exit;
306 	}
307 	expand_string (&mybuf, &mybuf_size, d + 1);
308     }
309     expand_string (&mybuf, &mybuf_size, d + 1);
310     mybuf[d] = '\0';
311 
312     /* Then copy from MYBUF to BUF, expanding ~.  */
313     s = d = 0;
314     /* If you don't want ~username ~/ to be expanded simply remove
315      * This entire if statement including the else portion
316      */
317     if (mybuf[s] == '~')
318     {
319 	p = d;
320 	while (mybuf[++s] != '/' && mybuf[s] != '\0')
321 	{
322 	    expand_string (&buf, &buf_size, p + 1);
323 	    buf[p++] = name[s];
324 	}
325 	expand_string (&buf, &buf_size, p + 1);
326 	buf[p] = '\0';
327 
328 	if (p == d)
329 	    e = get_homedir ();
330 	else
331 	{
332 #ifdef GETPWNAM_MISSING
333 	    if (line)
334 		error (0, 0,
335 		       "%s:%d:tilde expansion not supported on this system",
336 		       file, line);
337 	    else
338 		error (0, 0, "%s:tilde expansion not supported on this system",
339 		       file);
340 	    goto error_exit;
341 #else
342 	    struct passwd *ps;
343 	    ps = getpwnam (buf + d);
344 	    if (ps == NULL)
345 	    {
346 		if (line)
347 		    error (0, 0, "%s:%d: no such user %s",
348 			   file, line, buf + d);
349 		else
350 		    error (0, 0, "%s: no such user %s", file, buf + d);
351 		goto error_exit;
352 	    }
353 	    e = ps->pw_dir;
354 #endif
355 	}
356 	if (!e)
357 	    error (1, 0, "cannot find home directory");
358 
359 	p = strlen (e);
360 	expand_string (&buf, &buf_size, d + p);
361 	memcpy (buf + d, e, p);
362 	d += p;
363     }
364     /* Kill up to here */
365     p = strlen (mybuf + s) + 1;
366     expand_string (&buf, &buf_size, d + p);
367     memcpy (buf + d, mybuf + s, p);
368 
369     /* OK, buf contains the value we want to return.  Clean up and return
370        it.  */
371     free (mybuf);
372     /* Save a little memory with xstrdup; buf will tend to allocate
373        more than it needs to.  */
374     result = xstrdup (buf);
375     free (buf);
376     return result;
377 
378  error_exit:
379     if (mybuf) free (mybuf);
380     if (buf) free (buf);
381     return NULL;
382 }
383