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