1 /*
2 * Copyright (C) 1984-2025 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 */
skipsl(constant char * buf,size_t len,size_t e)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] == '\\' && buf[e+1] != '\0');
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 */
make_replaces(mutable char * buf,size_t len,size_t * pe,char term)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 */
free_replaces(struct replace * replaces)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 */
evar_match(constant char * str,constant char * pat)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 */
find_replace(constant struct replace * repl,constant char * evar,size_t * pv)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 */
add_evar(struct xbuffer * xbuf,mutable char * buf,size_t len,size_t e,constant char * evar,char term)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] == '\\' && repl[r+1] != '\0') ++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 */
expand_evars(mutable char * buf,size_t len,struct xbuffer * xbuf)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