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