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