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