xref: /curl/src/var.c (revision 40c264db)
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