blob: 388d45592f2d27c27a6e1c2f3442b8808054e8e8 [file] [log] [blame]
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "tool_setup.h"
#define ENABLE_CURLX_PRINTF
/* use our own printf() functions */
#include "curlx.h"
#include "tool_cfgable.h"
#include "tool_getparam.h"
#include "tool_helpers.h"
#include "tool_findfile.h"
#include "tool_msgs.h"
#include "tool_parsecfg.h"
#include "dynbuf.h"
#include "curl_base64.h"
#include "tool_paramhlp.h"
#include "tool_writeout_json.h"
#include "var.h"
#include "memdebug.h" /* keep this as LAST include */
#define MAX_EXPAND_CONTENT 10000000
static char *Memdup(const char *data, size_t len)
{
char *p = malloc(len + 1);
if(!p)
return NULL;
if(len)
memcpy(p, data, len);
p[len] = 0;
return p;
}
/* free everything */
void varcleanup(struct GlobalConfig *global)
{
struct var *list = global->variables;
while(list) {
struct var *t = list;
list = list->next;
free((char *)t->content);
free((char *)t->name);
free(t);
}
}
static const struct var *varcontent(struct GlobalConfig *global,
const char *name, size_t nlen)
{
struct var *list = global->variables;
while(list) {
if((strlen(list->name) == nlen) &&
!strncmp(name, list->name, nlen)) {
return list;
}
list = list->next;
}
return NULL;
}
#define ENDOFFUNC(x) (((x) == '}') || ((x) == ':'))
#define FUNCMATCH(ptr,name,len) \
(!strncmp(ptr, name, len) && ENDOFFUNC(ptr[len]))
#define FUNC_TRIM "trim"
#define FUNC_TRIM_LEN (sizeof(FUNC_TRIM) - 1)
#define FUNC_JSON "json"
#define FUNC_JSON_LEN (sizeof(FUNC_JSON) - 1)
#define FUNC_URL "url"
#define FUNC_URL_LEN (sizeof(FUNC_URL) - 1)
#define FUNC_B64 "b64"
#define FUNC_B64_LEN (sizeof(FUNC_B64) - 1)
static ParameterError varfunc(struct GlobalConfig *global,
char *c, /* content */
size_t clen, /* content length */
char *f, /* functions */
size_t flen, /* function string length */
struct curlx_dynbuf *out)
{
bool alloc = FALSE;
ParameterError err = PARAM_OK;
const char *finput = f;
/* The functions are independent and runs left to right */
while(*f && !err) {
if(*f == '}')
/* end of functions */
break;
/* On entry, this is known to be a colon already. In subsequent laps, it
is also known to be a colon since that is part of the FUNCMATCH()
checks */
f++;
if(FUNCMATCH(f, FUNC_TRIM, FUNC_TRIM_LEN)) {
size_t len = clen;
f += FUNC_TRIM_LEN;
if(clen) {
/* skip leading white space, including CRLF */
while(*c && ISSPACE(*c)) {
c++;
len--;
}
while(len && ISSPACE(c[len-1]))
len--;
}
/* put it in the output */
curlx_dyn_reset(out);
if(curlx_dyn_addn(out, c, len)) {
err = PARAM_NO_MEM;
break;
}
}
else if(FUNCMATCH(f, FUNC_JSON, FUNC_JSON_LEN)) {
f += FUNC_JSON_LEN;
curlx_dyn_reset(out);
if(clen) {
if(jsonquoted(c, clen, out, FALSE)) {
err = PARAM_NO_MEM;
break;
}
}
}
else if(FUNCMATCH(f, FUNC_URL, FUNC_URL_LEN)) {
f += FUNC_URL_LEN;
curlx_dyn_reset(out);
if(clen) {
char *enc = curl_easy_escape(NULL, c, (int)clen);
if(!enc) {
err = PARAM_NO_MEM;
break;
}
/* put it in the output */
if(curlx_dyn_add(out, enc))
err = PARAM_NO_MEM;
curl_free(enc);
if(err)
break;
}
}
else if(FUNCMATCH(f, FUNC_B64, FUNC_B64_LEN)) {
f += FUNC_B64_LEN;
curlx_dyn_reset(out);
if(clen) {
char *enc;
size_t elen;
CURLcode result = curlx_base64_encode(c, clen, &enc, &elen);
if(result) {
err = PARAM_NO_MEM;
break;
}
/* put it in the output */
if(curlx_dyn_addn(out, enc, elen))
err = PARAM_NO_MEM;
curl_free(enc);
if(err)
break;
}
}
else {
/* unsupported function */
errorf(global, "unknown variable function in '%.*s'",
(int)flen, finput);
err = PARAM_EXPAND_ERROR;
break;
}
if(alloc)
free(c);
clen = curlx_dyn_len(out);
c = Memdup(curlx_dyn_ptr(out), clen);
if(!c) {
err = PARAM_NO_MEM;
break;
}
alloc = TRUE;
}
if(alloc)
free(c);
if(err)
curlx_dyn_free(out);
return err;
}
ParameterError varexpand(struct GlobalConfig *global,
const char *line, struct curlx_dynbuf *out,
bool *replaced)
{
CURLcode result;
char *envp;
bool added = FALSE;
const char *input = line;
*replaced = FALSE;
curlx_dyn_init(out, MAX_EXPAND_CONTENT);
do {
envp = strstr(line, "{{");
if((envp > line) && envp[-1] == '\\') {
/* preceding backslash, we want this verbatim */
/* insert the text up to this point, minus the backslash */
result = curlx_dyn_addn(out, line, envp - line - 1);
if(result)
return PARAM_NO_MEM;
/* output '{{' then continue from here */
result = curlx_dyn_addn(out, "{{", 2);
if(result)
return PARAM_NO_MEM;
line = &envp[2];
}
else if(envp) {
char name[128];
size_t nlen;
size_t i;
char *funcp;
char *clp = strstr(envp, "}}");
size_t prefix;
if(!clp) {
/* uneven braces */
warnf(global, "missing close '}}' in '%s'", input);
break;
}
prefix = 2;
envp += 2; /* move over the {{ */
/* if there is a function, it ends the name with a colon */
funcp = memchr(envp, ':', clp - envp);
if(funcp)
nlen = funcp - envp;
else
nlen = clp - envp;
if(!nlen || (nlen >= sizeof(name))) {
warnf(global, "bad variable name length '%s'", input);
/* insert the text as-is since this is not an env variable */
result = curlx_dyn_addn(out, line, clp - line + prefix);
if(result)
return PARAM_NO_MEM;
}
else {
/* insert the text up to this point */
result = curlx_dyn_addn(out, line, envp - prefix - line);
if(result)
return PARAM_NO_MEM;
/* copy the name to separate buffer */
memcpy(name, envp, nlen);
name[nlen] = 0;
/* verify that the name looks sensible */
for(i = 0; (i < nlen) &&
(ISALNUM(name[i]) || (name[i] == '_')); i++);
if(i != nlen) {
warnf(global, "bad variable name: %s", name);
/* insert the text as-is since this is not an env variable */
result = curlx_dyn_addn(out, envp - prefix,
clp - envp + prefix + 2);
if(result)
return PARAM_NO_MEM;
}
else {
char *value;
size_t vlen = 0;
struct curlx_dynbuf buf;
const struct var *v = varcontent(global, name, nlen);
if(v) {
value = (char *)v->content;
vlen = v->clen;
}
else
value = NULL;
curlx_dyn_init(&buf, MAX_EXPAND_CONTENT);
if(funcp) {
/* apply the list of functions on the value */
size_t flen = clp - funcp;
ParameterError err = varfunc(global, value, vlen, funcp, flen,
&buf);
if(err)
return err;
value = curlx_dyn_ptr(&buf);
vlen = curlx_dyn_len(&buf);
}
if(value && vlen > 0) {
/* A variable might contain null bytes. Such bytes cannot be shown
using normal means, this is an error. */
char *nb = memchr(value, '\0', vlen);
if(nb) {
errorf(global, "variable contains null byte");
return PARAM_EXPAND_ERROR;
}
}
/* insert the value */
result = curlx_dyn_addn(out, value, vlen);
curlx_dyn_free(&buf);
if(result)
return PARAM_NO_MEM;
added = true;
}
}
line = &clp[2];
}
} while(envp);
if(added && *line) {
/* add the "suffix" as well */
result = curlx_dyn_add(out, line);
if(result)
return PARAM_NO_MEM;
}
*replaced = added;
if(!added)
curlx_dyn_free(out);
return PARAM_OK;
}
/*
* Created in a way that is not revealing how variables is actually stored so
* that we can improve this if we want better performance when managing many
* at a later point.
*/
static ParameterError addvariable(struct GlobalConfig *global,
const char *name,
size_t nlen,
const char *content,
size_t clen,
bool contalloc)
{
struct var *p;
const struct var *check = varcontent(global, name, nlen);
if(check)
notef(global, "Overwriting variable '%s'", check->name);
p = calloc(1, sizeof(struct var));
if(!p)
return PARAM_NO_MEM;
p->name = Memdup(name, nlen);
if(!p->name)
goto err;
p->content = contalloc ? content: Memdup(content, clen);
if(!p->content)
goto err;
p->clen = clen;
p->next = global->variables;
global->variables = p;
return PARAM_OK;
err:
free((char *)p->content);
free((char *)p->name);
free(p);
return PARAM_NO_MEM;
}
ParameterError setvariable(struct GlobalConfig *global,
const char *input)
{
const char *name;
size_t nlen;
char *content = NULL;
size_t clen = 0;
bool contalloc = FALSE;
const char *line = input;
ParameterError err = PARAM_OK;
bool import = FALSE;
char *ge = NULL;
if(*input == '%') {
import = TRUE;
line++;
}
name = line;
while(*line && (ISALNUM(*line) || (*line == '_')))
line++;
nlen = line - name;
if(!nlen || (nlen > 128)) {
warnf(global, "Bad variable name length (%zd), skipping", nlen);
return PARAM_OK;
}
if(import) {
ge = curl_getenv(name);
if(!*line && !ge) {
/* no assign, no variable, fail */
errorf(global, "Variable '%s' import fail, not set", name);
return PARAM_EXPAND_ERROR;
}
else if(ge) {
/* there is a value to use */
content = ge;
clen = strlen(ge);
}
}
if(content)
;
else if(*line == '@') {
/* read from file or stdin */
FILE *file;
bool use_stdin;
line++;
use_stdin = !strcmp(line, "-");
if(use_stdin)
file = stdin;
else {
file = fopen(line, "rb");
if(!file) {
errorf(global, "Failed to open %s", line);
return PARAM_READ_ERROR;
}
}
err = file2memory(&content, &clen, file);
/* in case of out of memory, this should fail the entire operation */
contalloc = TRUE;
if(!use_stdin)
fclose(file);
if(err)
return err;
}
else if(*line == '=') {
line++;
/* this is the exact content */
content = (char *)line;
clen = strlen(line);
}
else {
warnf(global, "Bad --variable syntax, skipping: %s", input);
return PARAM_OK;
}
err = addvariable(global, name, nlen, content, clen, contalloc);
if(err) {
if(contalloc)
free(content);
}
curl_free(ge);
return err;
}