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
377 #define MAX_FILENAME 10000
378
setvariable(struct GlobalConfig * global,const char * input)379 ParameterError setvariable(struct GlobalConfig *global,
380 const char *input)
381 {
382 const char *name;
383 size_t nlen;
384 char *content = NULL;
385 size_t clen = 0;
386 bool contalloc = FALSE;
387 const char *line = input;
388 ParameterError err = PARAM_OK;
389 bool import = FALSE;
390 char *ge = NULL;
391 char buf[MAX_VAR_LEN];
392
393 if(*input == '%') {
394 import = TRUE;
395 line++;
396 }
397 name = line;
398 while(*line && (ISALNUM(*line) || (*line == '_')))
399 line++;
400 nlen = line - name;
401 if(!nlen || (nlen >= MAX_VAR_LEN)) {
402 warnf(global, "Bad variable name length (%zd), skipping", nlen);
403 return PARAM_OK;
404 }
405 if(import) {
406 /* this does not use curl_getenv() because we want "" support for blank
407 content */
408 if(*line) {
409 /* if there is a default action, we need to copy the name */
410 memcpy(buf, name, nlen);
411 buf[nlen] = 0;
412 name = buf;
413 }
414 ge = getenv(name);
415 if(!*line && !ge) {
416 /* no assign, no variable, fail */
417 errorf(global, "Variable '%s' import fail, not set", name);
418 return PARAM_EXPAND_ERROR;
419 }
420 else if(ge) {
421 /* there is a value to use */
422 content = ge;
423 clen = strlen(ge);
424 }
425 }
426 if(content)
427 ;
428 else if(*line == '@') {
429 /* read from file or stdin */
430 FILE *file;
431 bool use_stdin;
432 char *range;
433 struct dynbuf fname;
434 curl_off_t startoffset = 0;
435 curl_off_t endoffset = CURL_OFF_T_MAX;
436 line++;
437
438 Curl_dyn_init(&fname, MAX_FILENAME);
439
440 /* is there a byte range specified? ;[num-num] */
441 range = strstr(line, ";[");
442 if(range && ISDIGIT(range[2])) {
443 char *p = range;
444 char *endp;
445 if(curlx_strtoofft(&p[2], &endp, 10, &startoffset) || (*endp != '-'))
446 return PARAM_VAR_SYNTAX;
447 else {
448 p = endp + 1; /* pass the '-' */
449 if(*p != ']') {
450 if(curlx_strtoofft(p, &endp, 10, &endoffset) || (*endp != ']'))
451 return PARAM_VAR_SYNTAX;
452 }
453 }
454 if(startoffset > endoffset)
455 return PARAM_VAR_SYNTAX;
456 /* create a dynbuf for the filename without the range */
457 if(Curl_dyn_addn(&fname, line, (range - line)))
458 return PARAM_NO_MEM;
459 /* point to the new file name buffer */
460 line = Curl_dyn_ptr(&fname);
461 }
462
463 use_stdin = !strcmp(line, "-");
464 if(use_stdin)
465 file = stdin;
466 else {
467 file = fopen(line, "rb");
468 if(!file) {
469 errorf(global, "Failed to open %s: %s", line,
470 strerror(errno));
471 err = PARAM_READ_ERROR;
472 }
473 }
474 if(!err) {
475 err = file2memory_range(&content, &clen, file, startoffset, endoffset);
476 /* in case of out of memory, this should fail the entire operation */
477 if(clen)
478 contalloc = TRUE;
479 }
480 Curl_dyn_free(&fname);
481 if(!use_stdin && file)
482 fclose(file);
483 if(err)
484 return err;
485 }
486 else if(*line == '=') {
487 line++;
488 /* this is the exact content */
489 content = (char *)line;
490 clen = strlen(line);
491 }
492 else {
493 warnf(global, "Bad --variable syntax, skipping: %s", input);
494 return PARAM_OK;
495 }
496 err = addvariable(global, name, nlen, content, clen, contalloc);
497 if(err) {
498 if(contalloc)
499 free(content);
500 }
501 return err;
502 }
503