1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24 #include "tool_setup.h"
25
26 #include "curlx.h"
27
28 #include "tool_cfgable.h"
29 #include "tool_getparam.h"
30 #include "tool_helpers.h"
31 #include "tool_findfile.h"
32 #include "tool_msgs.h"
33 #include "tool_parsecfg.h"
34 #include "dynbuf.h"
35 #include "curl_base64.h"
36 #include "tool_paramhlp.h"
37 #include "tool_writeout_json.h"
38 #include "var.h"
39
40 #include "memdebug.h" /* keep this as LAST include */
41
42 #define MAX_EXPAND_CONTENT 10000000
43 #define MAX_VAR_LEN 128 /* max length of a name */
44
Memdup(const char * data,size_t len)45 static char *Memdup(const char *data, size_t len)
46 {
47 char *p = malloc(len + 1);
48 if(!p)
49 return NULL;
50 if(len)
51 memcpy(p, data, len);
52 p[len] = 0;
53 return p;
54 }
55
56 /* free everything */
varcleanup(struct GlobalConfig * global)57 void varcleanup(struct GlobalConfig *global)
58 {
59 struct tool_var *list = global->variables;
60 while(list) {
61 struct tool_var *t = list;
62 list = list->next;
63 free((char *)t->content);
64 free(t);
65 }
66 }
67
varcontent(struct GlobalConfig * global,const char * name,size_t nlen)68 static const struct tool_var *varcontent(struct GlobalConfig *global,
69 const char *name, size_t nlen)
70 {
71 struct tool_var *list = global->variables;
72 while(list) {
73 if((strlen(list->name) == nlen) &&
74 !strncmp(name, list->name, nlen)) {
75 return list;
76 }
77 list = list->next;
78 }
79 return NULL;
80 }
81
82 #define ENDOFFUNC(x) (((x) == '}') || ((x) == ':'))
83 #define FUNCMATCH(ptr,name,len) \
84 (!strncmp(ptr, name, len) && ENDOFFUNC(ptr[len]))
85
86 #define FUNC_TRIM "trim"
87 #define FUNC_TRIM_LEN (sizeof(FUNC_TRIM) - 1)
88 #define FUNC_JSON "json"
89 #define FUNC_JSON_LEN (sizeof(FUNC_JSON) - 1)
90 #define FUNC_URL "url"
91 #define FUNC_URL_LEN (sizeof(FUNC_URL) - 1)
92 #define FUNC_B64 "b64"
93 #define FUNC_B64_LEN (sizeof(FUNC_B64) - 1)
94
varfunc(struct GlobalConfig * global,char * c,size_t clen,char * f,size_t flen,struct curlx_dynbuf * out)95 static ParameterError varfunc(struct GlobalConfig *global,
96 char *c, /* content */
97 size_t clen, /* content length */
98 char *f, /* functions */
99 size_t flen, /* function string length */
100 struct curlx_dynbuf *out)
101 {
102 bool alloc = FALSE;
103 ParameterError err = PARAM_OK;
104 const char *finput = f;
105
106 /* The functions are independent and runs left to right */
107 while(*f && !err) {
108 if(*f == '}')
109 /* end of functions */
110 break;
111 /* On entry, this is known to be a colon already. In subsequent laps, it
112 is also known to be a colon since that is part of the FUNCMATCH()
113 checks */
114 f++;
115 if(FUNCMATCH(f, FUNC_TRIM, FUNC_TRIM_LEN)) {
116 size_t len = clen;
117 f += FUNC_TRIM_LEN;
118 if(clen) {
119 /* skip leading white space, including CRLF */
120 while(*c && ISSPACE(*c)) {
121 c++;
122 len--;
123 }
124 while(len && ISSPACE(c[len-1]))
125 len--;
126 }
127 /* put it in the output */
128 curlx_dyn_reset(out);
129 if(curlx_dyn_addn(out, c, len)) {
130 err = PARAM_NO_MEM;
131 break;
132 }
133 }
134 else if(FUNCMATCH(f, FUNC_JSON, FUNC_JSON_LEN)) {
135 f += FUNC_JSON_LEN;
136 curlx_dyn_reset(out);
137 if(clen) {
138 if(jsonquoted(c, clen, out, FALSE)) {
139 err = PARAM_NO_MEM;
140 break;
141 }
142 }
143 }
144 else if(FUNCMATCH(f, FUNC_URL, FUNC_URL_LEN)) {
145 f += FUNC_URL_LEN;
146 curlx_dyn_reset(out);
147 if(clen) {
148 char *enc = curl_easy_escape(NULL, c, (int)clen);
149 if(!enc) {
150 err = PARAM_NO_MEM;
151 break;
152 }
153
154 /* put it in the output */
155 if(curlx_dyn_add(out, enc))
156 err = PARAM_NO_MEM;
157 curl_free(enc);
158 if(err)
159 break;
160 }
161 }
162 else if(FUNCMATCH(f, FUNC_B64, FUNC_B64_LEN)) {
163 f += FUNC_B64_LEN;
164 curlx_dyn_reset(out);
165 if(clen) {
166 char *enc;
167 size_t elen;
168 CURLcode result = curlx_base64_encode(c, clen, &enc, &elen);
169 if(result) {
170 err = PARAM_NO_MEM;
171 break;
172 }
173
174 /* put it in the output */
175 if(curlx_dyn_addn(out, enc, elen))
176 err = PARAM_NO_MEM;
177 curl_free(enc);
178 if(err)
179 break;
180 }
181 }
182 else {
183 /* unsupported function */
184 errorf(global, "unknown variable function in '%.*s'",
185 (int)flen, finput);
186 err = PARAM_EXPAND_ERROR;
187 break;
188 }
189 if(alloc)
190 free(c);
191
192 clen = curlx_dyn_len(out);
193 c = Memdup(curlx_dyn_ptr(out), clen);
194 if(!c) {
195 err = PARAM_NO_MEM;
196 break;
197 }
198 alloc = TRUE;
199 }
200 if(alloc)
201 free(c);
202 if(err)
203 curlx_dyn_free(out);
204 return err;
205 }
206
varexpand(struct GlobalConfig * global,const char * line,struct curlx_dynbuf * out,bool * replaced)207 ParameterError varexpand(struct GlobalConfig *global,
208 const char *line, struct curlx_dynbuf *out,
209 bool *replaced)
210 {
211 CURLcode result;
212 char *envp;
213 bool added = FALSE;
214 const char *input = line;
215 *replaced = FALSE;
216 curlx_dyn_init(out, MAX_EXPAND_CONTENT);
217 do {
218 envp = strstr(line, "{{");
219 if((envp > line) && envp[-1] == '\\') {
220 /* preceding backslash, we want this verbatim */
221
222 /* insert the text up to this point, minus the backslash */
223 result = curlx_dyn_addn(out, line, envp - line - 1);
224 if(result)
225 return PARAM_NO_MEM;
226
227 /* output '{{' then continue from here */
228 result = curlx_dyn_addn(out, "{{", 2);
229 if(result)
230 return PARAM_NO_MEM;
231 line = &envp[2];
232 }
233 else if(envp) {
234 char name[MAX_VAR_LEN];
235 size_t nlen;
236 size_t i;
237 char *funcp;
238 char *clp = strstr(envp, "}}");
239 size_t prefix;
240
241 if(!clp) {
242 /* uneven braces */
243 warnf(global, "missing close '}}' in '%s'", input);
244 break;
245 }
246
247 prefix = 2;
248 envp += 2; /* move over the {{ */
249
250 /* if there is a function, it ends the name with a colon */
251 funcp = memchr(envp, ':', clp - envp);
252 if(funcp)
253 nlen = funcp - envp;
254 else
255 nlen = clp - envp;
256 if(!nlen || (nlen >= sizeof(name))) {
257 warnf(global, "bad variable name length '%s'", input);
258 /* insert the text as-is since this is not an env variable */
259 result = curlx_dyn_addn(out, line, clp - line + prefix);
260 if(result)
261 return PARAM_NO_MEM;
262 }
263 else {
264 /* insert the text up to this point */
265 result = curlx_dyn_addn(out, line, envp - prefix - line);
266 if(result)
267 return PARAM_NO_MEM;
268
269 /* copy the name to separate buffer */
270 memcpy(name, envp, nlen);
271 name[nlen] = 0;
272
273 /* verify that the name looks sensible */
274 for(i = 0; (i < nlen) &&
275 (ISALNUM(name[i]) || (name[i] == '_')); i++);
276 if(i != nlen) {
277 warnf(global, "bad variable name: %s", name);
278 /* insert the text as-is since this is not an env variable */
279 result = curlx_dyn_addn(out, envp - prefix,
280 clp - envp + prefix + 2);
281 if(result)
282 return PARAM_NO_MEM;
283 }
284 else {
285 char *value;
286 size_t vlen = 0;
287 struct curlx_dynbuf buf;
288 const struct tool_var *v = varcontent(global, name, nlen);
289 if(v) {
290 value = (char *)v->content;
291 vlen = v->clen;
292 }
293 else
294 value = NULL;
295
296 curlx_dyn_init(&buf, MAX_EXPAND_CONTENT);
297 if(funcp) {
298 /* apply the list of functions on the value */
299 size_t flen = clp - funcp;
300 ParameterError err = varfunc(global, value, vlen, funcp, flen,
301 &buf);
302 if(err)
303 return err;
304 value = curlx_dyn_ptr(&buf);
305 vlen = curlx_dyn_len(&buf);
306 }
307
308 if(value && vlen > 0) {
309 /* A variable might contain null bytes. Such bytes cannot be shown
310 using normal means, this is an error. */
311 char *nb = memchr(value, '\0', vlen);
312 if(nb) {
313 errorf(global, "variable contains null byte");
314 return PARAM_EXPAND_ERROR;
315 }
316 }
317 /* insert the value */
318 result = curlx_dyn_addn(out, value, vlen);
319 curlx_dyn_free(&buf);
320 if(result)
321 return PARAM_NO_MEM;
322
323 added = true;
324 }
325 }
326 line = &clp[2];
327 }
328
329 } while(envp);
330 if(added && *line) {
331 /* add the "suffix" as well */
332 result = curlx_dyn_add(out, line);
333 if(result)
334 return PARAM_NO_MEM;
335 }
336 *replaced = added;
337 if(!added)
338 curlx_dyn_free(out);
339 return PARAM_OK;
340 }
341
342 /*
343 * Created in a way that is not revealing how variables are actually stored so
344 * that we can improve this if we want better performance when managing many
345 * at a later point.
346 */
addvariable(struct GlobalConfig * global,const char * name,size_t nlen,const char * content,size_t clen,bool contalloc)347 static ParameterError addvariable(struct GlobalConfig *global,
348 const char *name,
349 size_t nlen,
350 const char *content,
351 size_t clen,
352 bool contalloc)
353 {
354 struct tool_var *p;
355 const struct tool_var *check = varcontent(global, name, nlen);
356 DEBUGASSERT(nlen);
357 if(check)
358 notef(global, "Overwriting variable '%s'", check->name);
359
360 p = calloc(1, sizeof(struct tool_var) + nlen);
361 if(p) {
362 memcpy(p->name, name, nlen);
363
364 p->content = contalloc ? content : Memdup(content, clen);
365 if(p->content) {
366 p->clen = clen;
367
368 p->next = global->variables;
369 global->variables = p;
370 return PARAM_OK;
371 }
372 free(p);
373 }
374 return PARAM_NO_MEM;
375 }
376
setvariable(struct GlobalConfig * global,const char * input)377 ParameterError setvariable(struct GlobalConfig *global,
378 const char *input)
379 {
380 const char *name;
381 size_t nlen;
382 char *content = NULL;
383 size_t clen = 0;
384 bool contalloc = FALSE;
385 const char *line = input;
386 ParameterError err = PARAM_OK;
387 bool import = FALSE;
388 char *ge = NULL;
389 char buf[MAX_VAR_LEN];
390
391 if(*input == '%') {
392 import = TRUE;
393 line++;
394 }
395 name = line;
396 while(*line && (ISALNUM(*line) || (*line == '_')))
397 line++;
398 nlen = line - name;
399 if(!nlen || (nlen >= MAX_VAR_LEN)) {
400 warnf(global, "Bad variable name length (%zd), skipping", nlen);
401 return PARAM_OK;
402 }
403 if(import) {
404 /* this does not use curl_getenv() because we want "" support for blank
405 content */
406 if(*line) {
407 /* if there is a default action, we need to copy the name */
408 memcpy(buf, name, nlen);
409 buf[nlen] = 0;
410 name = buf;
411 }
412 ge = getenv(name);
413 if(!*line && !ge) {
414 /* no assign, no variable, fail */
415 errorf(global, "Variable '%s' import fail, not set", name);
416 return PARAM_EXPAND_ERROR;
417 }
418 else if(ge) {
419 /* there is a value to use */
420 content = ge;
421 clen = strlen(ge);
422 }
423 }
424 if(content)
425 ;
426 else if(*line == '@') {
427 /* read from file or stdin */
428 FILE *file;
429 bool use_stdin;
430 line++;
431 use_stdin = !strcmp(line, "-");
432 if(use_stdin)
433 file = stdin;
434 else {
435 file = fopen(line, "rb");
436 if(!file) {
437 errorf(global, "Failed to open %s", line);
438 return PARAM_READ_ERROR;
439 }
440 }
441 err = file2memory(&content, &clen, file);
442 /* in case of out of memory, this should fail the entire operation */
443 contalloc = TRUE;
444 if(!use_stdin)
445 fclose(file);
446 if(err)
447 return err;
448 }
449 else if(*line == '=') {
450 line++;
451 /* this is the exact content */
452 content = (char *)line;
453 clen = strlen(line);
454 }
455 else {
456 warnf(global, "Bad --variable syntax, skipping: %s", input);
457 return PARAM_OK;
458 }
459 err = addvariable(global, name, nlen, content, clen, contalloc);
460 if(err) {
461 if(contalloc)
462 free(content);
463 }
464 return err;
465 }
466