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