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