xref: /third_party/curl/src/var.c (revision 13498266)
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
46static 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 */
58void 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
70static 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
97static 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
209ParameterError 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 */
349static 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(1, sizeof(struct var));
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;
377err:
378  free((char *)p->content);
379  free((char *)p->name);
380  free(p);
381  return PARAM_NO_MEM;
382}
383
384ParameterError 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