xref: /freebsd/contrib/less/evar.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
1 /*
2  * Copyright (C) 1984-2024  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9 
10 /*
11  * Code to support expanding environment variables in text.
12  */
13 
14 #include "less.h"
15 #include "xbuf.h"
16 
17 struct replace {
18 	struct replace *r_next;
19 	char *r_fm;
20 	char *r_to;
21 };
22 
23 /*
24  * Skip to the next unescaped slash or right curly bracket in a string.
25  */
26 static size_t skipsl(constant char *buf, size_t len, size_t e)
27 {
28 	lbool esc = FALSE;
29 	while (e < len && buf[e] != '\0' && (esc || (buf[e] != '/' && buf[e] != '}')))
30 	{
31 		esc = (!esc && buf[e] == '\\');
32 		++e;
33 	}
34 	return e;
35 }
36 
37 /*
38  * Parse a replacement string: one or more instances of
39  * (slash, pattern, slash, replacement), followed by right curly bracket.
40  * Replacement may be empty in which case the second slash is optional.
41  */
42 static struct replace * make_replaces(mutable char *buf, size_t len, size_t *pe, char term)
43 {
44 	size_t e = *pe;
45 	struct replace *replaces = NULL;
46 
47 	while (term == '/')
48 	{
49 		struct replace *repl;
50 		size_t to;
51 		size_t fm = e;
52 		e = skipsl(buf, len, e);
53 		if (e >= len) break;
54 		if (e == fm) /* missing fm string; we're done */
55 		{
56 			while (e < len)
57 				if (buf[e++] == '}') break;
58 			break;
59 		}
60 		term = buf[e];
61 		buf[e++] = '\0'; /* terminate the fm string */
62 		if (term != '/') /* missing to string */
63 		{
64 			to = e-1;
65 		} else
66 		{
67 			to = e;
68 			e = skipsl(buf, len, e);
69 			if (e >= len) break;
70 			term = buf[e];
71 			buf[e++] = '\0'; /* terminate the to string */
72 		}
73 		repl = ecalloc(1, sizeof(struct replace));
74 		repl->r_fm = &buf[fm];
75 		repl->r_to = &buf[to];
76 		repl->r_next = replaces;
77 		replaces = repl;
78 	}
79 	*pe = e;
80 	return replaces;
81 }
82 
83 /*
84  * Free a list of replace structs.
85  */
86 static void free_replaces(struct replace *replaces)
87 {
88 	while (replaces != NULL)
89 	{
90 		struct replace *r = replaces;
91 		replaces = r->r_next;
92 		free(r);
93 	}
94 }
95 
96 /*
97  * See if the initial substring of a string matches a pattern.
98  * Backslash escapes in the pattern are ignored.
99  * Return the length of the matched substring, or 0 if no match.
100  */
101 static size_t evar_match(constant char *str, constant char *pat)
102 {
103 	size_t len = 0;
104 	while (*pat != '\0')
105 	{
106 		if (*pat == '\\') ++pat;
107 		if (*str++ != *pat++) return 0;
108 		++len;
109 	}
110 	return len;
111 }
112 
113 /*
114  * Find the replacement for a string (&evar[*pv]),
115  * given a list of replace structs.
116  */
117 static constant char * find_replace(constant struct replace *repl, constant char *evar, size_t *pv)
118 {
119 	for (;  repl != NULL;  repl = repl->r_next)
120 	{
121 		size_t len = evar_match(&evar[*pv], repl->r_fm);
122 		if (len > 0)
123 		{
124 			*pv += len;
125 			return repl->r_to;
126 		}
127 	}
128 	return NULL;
129 }
130 
131 /*
132  * With buf[e] positioned just after NAME in "${NAME" and
133  * term containing the character at that point, parse the rest
134  * of the environment var string (including the final right curly bracket).
135  * Write evar to xbuf, performing any specified text replacements.
136  * Return the new value of e to point just after the final right curly bracket.
137  */
138 static size_t add_evar(struct xbuffer *xbuf, mutable char *buf, size_t len, size_t e, constant char *evar, char term)
139 {
140 	struct replace *replaces = make_replaces(buf, len, &e, term);
141 	size_t v;
142 
143 	for (v = 0;  evar[v] != '\0'; )
144 	{
145 		constant char *repl = find_replace(replaces, evar, &v);
146 		if (repl == NULL)
147 			xbuf_add_char(xbuf, evar[v++]);
148 		else
149 		{
150 			size_t r;
151 			for (r = 0;  repl[r] != '\0';  r++)
152 			{
153 				if (repl[r] == '\\') ++r;
154 				xbuf_add_char(xbuf, repl[r]);
155 			}
156 		}
157 	}
158 	free_replaces(replaces);
159 	return e;
160 }
161 
162 /*
163  * Expand env variables in a string.
164  * Writes expanded output to xbuf. Corrupts buf.
165  */
166 public void expand_evars(mutable char *buf, size_t len, struct xbuffer *xbuf)
167 {
168 	size_t i;
169 	for (i = 0;  i < len; )
170 	{
171 		if (i+1 < len && buf[i] == '$' && buf[i+1] == '{')
172 		{
173 			constant char *evar;
174 			char term;
175 			size_t e;
176 			i += 2; /* skip "${" */
177 			for (e = i;  e < len;  e++)
178 				if (buf[e] == '\0' || buf[e] == '}' || buf[e] == '/')
179 					break;
180 			if (e >= len || buf[e] == '\0')
181 				break; /* missing right curly bracket; ignore var */
182 			term = buf[e];
183 			buf[e++] = '\0';
184 			evar = lgetenv_ext(&buf[i], xbuf->data, xbuf->end);
185 			if (evar == NULL) evar = "";
186 			i = add_evar(xbuf, buf, len, e, evar, term);
187 		} else
188 		{
189 			xbuf_add_char(xbuf, buf[i++]);
190 		}
191 	}
192 }
193