Home | History | Annotate | Line # | Download | only in src
      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
     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
     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 *
    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 *
    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