1 /* -*-c-*- */
2 /* This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or
5  * (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, see: <http://www.gnu.org/licenses/>
14  */
15 
16 /*
17  *  MODULE OF       fvwm
18  *
19  *  DESCRIPTION     Routines to expand environment-variables into strings.
20  *                  Will understand both $ENV and ${ENV} -type variables.
21  *
22  *  WRITTEN BY      Sverre H. Huseby
23  *                  sverrehu@ifi.uio.no
24  *
25  *  CREATED         1995/10/3
26  *
27  *  UPDATED         migo - 21/Jun/1999 - added getFirstEnv, some changes
28  *
29  */
30 
31 /* ---------------------------- included header files ---------------------- */
32 
33 #include "config.h"
34 #include <stdio.h>
35 #include <ctype.h>
36 
37 #include "fvwmlib.h"
38 #include "envvar.h"
39 
40 /* ---------------------------- local definitions -------------------------- */
41 
42 #ifdef HAVE_UNSETENV
43 #define FHaveUnsetenv 1
44 #else
45 #define unsetenv(x) do { } while (0)
46 #define FHaveUnsetenv 0
47 #endif
48 
49 /* ---------------------------- local macros ------------------------------- */
50 
51 #define ENV_LIST_INC 10
52 #ifndef NULL
53 #define NULL 0
54 #endif
55 
56 /* ---------------------------- imports ------------------------------------ */
57 
58 /* ---------------------------- included code files ------------------------ */
59 
60 /* ---------------------------- local types -------------------------------- */
61 
62 typedef struct
63 {
64 	char *var;
65 	char *env;
66 } env_list_item;
67 
68 /* ---------------------------- forward declarations ----------------------- */
69 
70 /* ---------------------------- local variables ---------------------------- */
71 
72 /* ---------------------------- exported variables (globals) --------------- */
73 
74 /* ---------------------------- local functions ---------------------------- */
75 
76 /*-------------------------------------------------------------------------
77  *
78  *  NAME          strDel
79  *
80  *  FUNCTION      Delete characters from a string.
81  *
82  *  INPUT         s       the string to delete characters from.
83  *                idx     index of first character to delete.
84  *                n       number of characters to delete.
85  *
86  *  OUTPUT        s       string with characters deleted.
87  *
88  *  DESCRIPTION   Deletes characters from a string by moving following
89  *                characters back.
90  *
91  */
strDel(char * s,int idx,int n)92 static void strDel(char *s, int idx, int n)
93 {
94 	int  l;
95 	char *p;
96 
97 	if (idx >= (l = strlen(s)))
98 		return;
99 	if (idx + n > l)
100 		n = l - idx;
101 	s += idx;
102 	p = s + n;
103 	do {
104 		*s++ = *p;
105 	} while (*p++);
106 }
107 
108 /*-------------------------------------------------------------------------
109  *
110  *  NAME          strIns
111  *
112  *  FUNCTION      Insert a string into a string.
113  *
114  *  INPUT         s       the string to insert into.
115  *                ins     the string to insert.
116  *                idx     index of where to insert the string.
117  *                maxstrlen     max length of s, including '\0'.
118  *
119  *  OUTPUT        s       string with characters inserted.
120  *
121  *  DESCRIPTION   The insertion will be done even if the string gets to
122  *                long, but characters will be sacrificed at the end of s.
123  *                The string is always '\0'-terminated.
124  *
125  */
strIns(char * s,const char * ins,int idx,int maxstrlen)126 static void strIns(char *s, const char *ins, int idx, int maxstrlen)
127 {
128 	int  l, li, move;
129 	char *p1, *p2;
130 
131 	if (idx > (l = strlen(s)))
132 	{
133 		idx = l;
134 	}
135 	li = strlen(ins);
136 	move = l - idx + 1; /* include '\0' in move */
137 	p1 = s + l;
138 	p2 = p1 + li;
139 	while (p2 >= s + maxstrlen) {
140 		--p1;
141 		--p2;
142 		--move;
143 	}
144 	while (move-- > 0)
145 		*p2-- = *p1--;
146 	p1 = s + idx;
147 	if (idx + li >= maxstrlen)
148 	{
149 		li = maxstrlen - idx - 1;
150 	}
151 	while (li-- > 0)
152 		*p1++ = *ins++;
153 	s[maxstrlen - 1] = '\0';
154 }
155 
156 /*-------------------------------------------------------------------------
157  *
158  *  NAME          findEnvVar
159  *
160  *  FUNCTION      Find first environment variable in a string.
161  *
162  *  INPUT         s       the string to scan.
163  *
164  *  OUTPUT        len     length of variable, including $ and { }.
165  *
166  *  RETURNS       Pointer to the $ that introduces the variable, or NULL
167  *                if no variable is found.
168  *
169  *  DESCRIPTION   Searches for matches like $NAME and ${NAME}, where NAME is
170  *                a sequence of characters, digits and underscores, of which
171  *                the first can not be a digit.
172  *
173  *  NOTE          This function will only return `legal' variables. There
174  *                may be $'s in the string that are not followed by what
175  *                is considered a legal variable name introducer. Such
176  *                occurrences are skipped.
177  *
178  */
findEnvVar(const char * s,int * len)179 static char *findEnvVar(const char *s, int *len)
180 {
181 	int   brace = 0;
182 	char  *ret = NULL;
183 	const char *next;
184 
185 	if (!s)
186 		return NULL;
187 	while (*s) {
188 		next = s + 1;
189 		if (*s == '$' &&
190 		    (isalpha(*next) || *next == '_' || *next == '{')) {
191 			ret = (char *) s++;
192 			if (*s == '{') {
193 				brace = 1;
194 				++s;
195 			}
196 			while (*s && (isalnum(*s) || *s == '_'))
197 				++s;
198 			*len = s - ret;
199 			if (brace) {
200 				if (*s == '}') {
201 					++*len;
202 					break;
203 				}
204 				ret = NULL;
205 			} else
206 				break;
207 		}
208 		++s;
209 	}
210 	return ret;
211 }
212 
213 /*-------------------------------------------------------------------------
214  *
215  *  FUNCTION      Look up environment variable.
216  *
217  *  INPUT         name    name of environment variable to look up. This
218  *                        may include $ and { }.
219  *                len     length for environment variable name (0 - ignore).
220  *
221  *  RETURNS       The variable contents, or "" if not found.
222  *
223  */
getEnv(const char * name,int len)224 static const char *getEnv(const char *name, int len)
225 {
226 	static char *empty = "";
227 	char   *ret = NULL, *tmp, *p, *p2;
228 
229 	if ((tmp = fxstrdup(name)) == NULL)
230 		return empty;  /* better than no test at all. */
231 	p = tmp;
232 	if (*p == '$')
233 		++p;
234 	if (*p == '{') {
235 		++p;
236 		if ((p2 = strchr(p, '}')) != NULL)
237 			*p2 = '\0';
238 	}
239 	if (len > 0 && len < strlen(tmp)) tmp[len] = '\0';
240 	if ((ret = getenv(p)) == NULL)
241 		ret = empty;
242 	free(tmp);
243 	return ret;
244 }
245 
246 /* ---------------------------- interface functions ------------------------ */
247 
248 /*
249  *  FUNCTION      Expand environment variables in a string.
250  *
251  *  SYNOPSIS      #include "envvar.h"
252  *                int envExpand(char *s, int maxstrlen);
253  *
254  *  INPUT         s       string to expand environment variables in.
255  *                maxstrlen     max length of string, including '\0'.
256  *
257  *  OUTPUT        s       the string with environment variables expanded.
258  *
259  *  RETURNS       Number of changes done.
260  *
261  *  NOTES         A non-existing variable is substituted with the empty
262  *                string.
263  *
264  */
envExpand(char * s,int maxstrlen)265 int envExpand(char *s, int maxstrlen)
266 {
267 	char  *var, *s2;
268 	const char *env;
269 	int   len, ret = 0;
270 
271 	s2 = s;
272 	while ((var = findEnvVar(s2, &len)) != NULL) {
273 		++ret;
274 		env = getEnv(var, len);
275 		strDel(s, var - s, len);
276 		strIns(s, env, var - s, maxstrlen);
277 		s2 = var + strlen(env);
278 	}
279 	return ret;
280 }
281 
282 /*
283  *  FUNCTION      Expand environment variables into a new string.
284  *
285  *  SYNOPSIS      #include "envvar.h"
286  *                char *envDupExpand(const char *s, int extra);
287  *
288  *  INPUT         s       string to expand environment variables in.
289  *                extra   number of extra bytes to allocate in the
290  *                        string, in addition to the string contents
291  *                        and the terminating '\0'.
292  *
293  *  RETURNS       A dynamically allocated string with environment
294  *                variables expanded.
295  *                Use free() to deallocate the buffer when it is no
296  *                longer needed.
297  *                NULL is returned if there is not enough memory.
298  *
299  *  NOTES         A non-existing variable is substituted with the empty
300  *                string.
301  *
302  */
envDupExpand(const char * s,int extra)303 char *envDupExpand(const char *s, int extra)
304 {
305 	char  *var, *ret;
306 	const char *env, *s2;
307 	int   len, slen, elen, bufflen;
308 
309 	/*
310 	 *  calculate length needed.
311 	 */
312 	s2 = s;
313 	slen = strlen(s);
314 	bufflen = slen + 1 + extra;
315 	while ((var = findEnvVar(s2, &len)) != NULL) {
316 		env = getEnv(var, len);
317 		elen = strlen(env);
318 		/* need to make a buffer the maximum possible size, else we
319 		 * may get trouble while expanding. */
320 		bufflen += len > elen ? len : elen;
321 		s2 = var + len;
322 	}
323 	if (bufflen < slen + 1)
324 		bufflen = slen + 1;
325 
326 	ret = fxmalloc(bufflen);
327 	/* TA:  FIXME!  xasprintf() */
328 
329 	/*
330 	 *  now do the real expansion.
331 	 */
332 	strcpy(ret, s);
333 	envExpand(ret, bufflen - extra);
334 
335 	return ret;
336 }
337 
338 /*
339  *  FUNCTION      Search for the first environment variable and return
340  *                its contents and coordinates in the given string.
341  *
342  *  INPUT         s       the string to scan.
343  *                        may include $ and { } that introduce variable.
344  *
345  *  OUTPUT        beg     index in the string of matching $.
346  *                end     index in the string, first after matching var.
347  *
348  *  RETURNS       The variable contents; "" if env variable has legal name,
349  *                but does not exist; or NULL if no env variables found.
350  *                Returned constant string must not be deallocated.
351  *
352  *  NOTE          This function will only return `legal' variables. There
353  *                may be $'s in the string that are not followed by what
354  *                is considered a legal variable name introducer. Such
355  *                occurrences are skipped.
356  *                If nothing is found returns NULL and sets beg and end to 0.
357  *
358  *  EXAMPLE       getFirstEnv("echo $HOME/.fvwm/config", &beg, &end)
359  *                returns "/home/username" and beg=5, end=10.
360  *
361  */
getFirstEnv(const char * s,int * beg,int * end)362 const char* getFirstEnv(const char *s, int *beg, int *end)
363 {
364 	char *var;
365 	const char *env;
366 	int len;
367 
368 	*beg = *end = 0;
369 	if ((var = findEnvVar(s, &len)) == NULL) return NULL;
370 	env = getEnv(var, len);
371 
372 	*beg = var - s;
373 	*end = *beg + len;
374 
375 	return env;
376 }
377 
378 /* If env is NULL, var is removed from the environment list */
add_to_envlist(char * var,char * env)379 static void add_to_envlist(char *var, char *env)
380 {
381 	static env_list_item *env_list = NULL;
382 	static unsigned int env_len = 0;
383 	static unsigned int env_len_allocated = 0;
384 	unsigned int i;
385 
386 	/* find string in list */
387 	if (env_list && env_len)
388 	{
389 		for (i = 0; i < env_len; i++)
390 		{
391 			if (strcmp(var, env_list[i].var) != 0)
392 			{
393 				continue;
394 			}
395 			/* found it - replace old string */
396 			free(env_list[i].var);
397 			free(env_list[i].env);
398 			if (env == NULL)
399 			{
400 				/* delete */
401 				env_len--;
402 				env_list[i].var =
403 					env_list[env_len].var;
404 				env_list[i].env =
405 					env_list[env_len].env;
406 			}
407 			else
408 			{
409 				/* replace */
410 				env_list[i].var = var;
411 				env_list[i].env = env;
412 			}
413 
414 			return;
415 		}
416 	}
417 	if (env == NULL)
418 	{
419 		return;
420 	}
421 	/* not found */
422 	if (env_list == NULL)
423 	{
424 		/* list is still empty */
425 		env_len_allocated = ENV_LIST_INC;
426 		env_list = fxcalloc(sizeof(env_list_item), env_len_allocated);
427 	}
428 	else if (env_len >= env_len_allocated && env != NULL)
429 	{
430 		/* need more memory */
431 		env_len_allocated = env_len + ENV_LIST_INC;
432 		env_list = fxrealloc((void *)env_list, (env_len_allocated),
433 				sizeof(env_list_item));
434 	}
435 	env_list[env_len].var = var;
436 	env_list[env_len].env = env;
437 	env_len++;
438 
439 	return;
440 }
441 
442 /* This function keeps a list of all strings that were set in the environment.
443  * If a variable is written again, the old memory is freed.  This function
444  * should be called instead of putenv().
445  *
446  *   var - environement variable name
447  *   env - environment string ("variable=value")
448  *
449  * Both arguments are copied internally and should be freed after calling this
450  * function.
451  */
flib_putenv(char * var,char * env)452 void flib_putenv(char *var, char *env)
453 {
454 	char *s;
455 
456 	s = fxstrdup(var);
457 	var = s;
458 	s = fxstrdup(env);
459 	env = s;
460 	putenv(env);
461 	add_to_envlist(var, env);
462 
463 	return;
464 }
465 
flib_unsetenv(const char * name)466 void flib_unsetenv(const char *name)
467 {
468 	if (FHaveUnsetenv)
469 	{
470 		unsetenv(name);
471 	}
472 	else
473 	{
474 		int rc;
475 
476 		/* try putenv without '=' */
477 		rc = putenv((char *)name);
478 		if (rc == 0 || getenv(name) != NULL)
479 		{
480 			/* failed, write empty string */
481 			flib_putenv((char *)name, "");
482 			return;
483 		}
484 	}
485 	add_to_envlist((char *)name, NULL);
486 
487 	return;
488 }
489