xref: /curl/src/var.c (revision ef4bd8d6)
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 #define MAX_VAR_LEN 128 /* max length of a name */
46 
Memdup(const char * data,size_t len)47 static char *Memdup(const char *data, size_t len)
48 {
49   char *p = malloc(len + 1);
50   if(!p)
51     return NULL;
52   if(len)
53     memcpy(p, data, len);
54   p[len] = 0;
55   return p;
56 }
57 
58 /* free everything */
varcleanup(struct GlobalConfig * global)59 void varcleanup(struct GlobalConfig *global)
60 {
61   struct var *list = global->variables;
62   while(list) {
63     struct var *t = list;
64     list = list->next;
65     free((char *)t->content);
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[MAX_VAR_LEN];
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 are 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   DEBUGASSERT(nlen);
359   if(check)
360     notef(global, "Overwriting variable '%s'", check->name);
361 
362   p = calloc(1, sizeof(struct var) + nlen);
363   if(p) {
364     memcpy(p->name, name, nlen);
365 
366     p->content = contalloc ? content: Memdup(content, clen);
367     if(p->content) {
368       p->clen = clen;
369 
370       p->next = global->variables;
371       global->variables = p;
372       return PARAM_OK;
373     }
374     free(p);
375   }
376   return PARAM_NO_MEM;
377 }
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     line++;
433     use_stdin = !strcmp(line, "-");
434     if(use_stdin)
435       file = stdin;
436     else {
437       file = fopen(line, "rb");
438       if(!file) {
439         errorf(global, "Failed to open %s", line);
440         return PARAM_READ_ERROR;
441       }
442     }
443     err = file2memory(&content, &clen, file);
444     /* in case of out of memory, this should fail the entire operation */
445     contalloc = TRUE;
446     if(!use_stdin)
447       fclose(file);
448     if(err)
449       return err;
450   }
451   else if(*line == '=') {
452     line++;
453     /* this is the exact content */
454     content = (char *)line;
455     clen = strlen(line);
456   }
457   else {
458     warnf(global, "Bad --variable syntax, skipping: %s", input);
459     return PARAM_OK;
460   }
461   err = addvariable(global, name, nlen, content, clen, contalloc);
462   if(err) {
463     if(contalloc)
464       free(content);
465   }
466   return err;
467 }
468