var.c (14470B)
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 "tool_cfgable.h" 27 #include "tool_getparam.h" 28 #include "tool_helpers.h" 29 #include "tool_findfile.h" 30 #include "tool_msgs.h" 31 #include "tool_parsecfg.h" 32 #include "tool_paramhlp.h" 33 #include "tool_writeout_json.h" 34 #include "var.h" 35 #include "memdebug.h" /* keep this as LAST include */ 36 37 #define MAX_EXPAND_CONTENT 10000000 38 #define MAX_VAR_LEN 128 /* max length of a name */ 39 40 static char *Memdup(const char *data, size_t len) 41 { 42 char *p = malloc(len + 1); 43 if(!p) 44 return NULL; 45 if(len) 46 memcpy(p, data, len); 47 p[len] = 0; 48 return p; 49 } 50 51 /* free everything */ 52 void varcleanup(struct GlobalConfig *global) 53 { 54 struct tool_var *list = global->variables; 55 while(list) { 56 struct tool_var *t = list; 57 list = list->next; 58 free(CURL_UNCONST(t->content)); 59 free(t); 60 } 61 } 62 63 static const struct tool_var *varcontent(struct GlobalConfig *global, 64 const char *name, size_t nlen) 65 { 66 struct tool_var *list = global->variables; 67 while(list) { 68 if((strlen(list->name) == nlen) && 69 !strncmp(name, list->name, nlen)) { 70 return list; 71 } 72 list = list->next; 73 } 74 return NULL; 75 } 76 77 #define ENDOFFUNC(x) (((x) == '}') || ((x) == ':')) 78 #define FUNCMATCH(ptr,name,len) \ 79 (!strncmp(ptr, name, len) && ENDOFFUNC(ptr[len])) 80 81 #define FUNC_TRIM "trim" 82 #define FUNC_TRIM_LEN (sizeof(FUNC_TRIM) - 1) 83 #define FUNC_JSON "json" 84 #define FUNC_JSON_LEN (sizeof(FUNC_JSON) - 1) 85 #define FUNC_URL "url" 86 #define FUNC_URL_LEN (sizeof(FUNC_URL) - 1) 87 #define FUNC_B64 "b64" 88 #define FUNC_B64_LEN (sizeof(FUNC_B64) - 1) 89 #define FUNC_64DEC "64dec" /* base64 decode */ 90 #define FUNC_64DEC_LEN (sizeof(FUNC_64DEC) - 1) 91 92 static ParameterError varfunc(struct GlobalConfig *global, 93 char *c, /* content */ 94 size_t clen, /* content length */ 95 char *f, /* functions */ 96 size_t flen, /* function string length */ 97 struct dynbuf *out) 98 { 99 bool alloc = FALSE; 100 ParameterError err = PARAM_OK; 101 const char *finput = f; 102 103 /* The functions are independent and runs left to right */ 104 while(*f && !err) { 105 if(*f == '}') 106 /* end of functions */ 107 break; 108 /* On entry, this is known to be a colon already. In subsequent laps, it 109 is also known to be a colon since that is part of the FUNCMATCH() 110 checks */ 111 f++; 112 if(FUNCMATCH(f, FUNC_TRIM, FUNC_TRIM_LEN)) { 113 size_t len = clen; 114 f += FUNC_TRIM_LEN; 115 if(clen) { 116 /* skip leading white space, including CRLF */ 117 while(ISSPACE(*c)) { 118 c++; 119 len--; 120 } 121 while(len && ISSPACE(c[len-1])) 122 len--; 123 } 124 /* put it in the output */ 125 curlx_dyn_reset(out); 126 if(curlx_dyn_addn(out, c, len)) { 127 err = PARAM_NO_MEM; 128 break; 129 } 130 } 131 else if(FUNCMATCH(f, FUNC_JSON, FUNC_JSON_LEN)) { 132 f += FUNC_JSON_LEN; 133 curlx_dyn_reset(out); 134 if(clen) { 135 if(jsonquoted(c, clen, out, FALSE)) { 136 err = PARAM_NO_MEM; 137 break; 138 } 139 } 140 } 141 else if(FUNCMATCH(f, FUNC_URL, FUNC_URL_LEN)) { 142 f += FUNC_URL_LEN; 143 curlx_dyn_reset(out); 144 if(clen) { 145 char *enc = curl_easy_escape(NULL, c, (int)clen); 146 if(!enc) { 147 err = PARAM_NO_MEM; 148 break; 149 } 150 151 /* put it in the output */ 152 if(curlx_dyn_add(out, enc)) 153 err = PARAM_NO_MEM; 154 curl_free(enc); 155 if(err) 156 break; 157 } 158 } 159 else if(FUNCMATCH(f, FUNC_B64, FUNC_B64_LEN)) { 160 f += FUNC_B64_LEN; 161 curlx_dyn_reset(out); 162 if(clen) { 163 char *enc; 164 size_t elen; 165 CURLcode result = curlx_base64_encode(c, clen, &enc, &elen); 166 if(result) { 167 err = PARAM_NO_MEM; 168 break; 169 } 170 171 /* put it in the output */ 172 if(curlx_dyn_addn(out, enc, elen)) 173 err = PARAM_NO_MEM; 174 curl_free(enc); 175 if(err) 176 break; 177 } 178 } 179 else if(FUNCMATCH(f, FUNC_64DEC, FUNC_64DEC_LEN)) { 180 f += FUNC_64DEC_LEN; 181 curlx_dyn_reset(out); 182 if(clen) { 183 unsigned char *enc; 184 size_t elen; 185 CURLcode result = curlx_base64_decode(c, &enc, &elen); 186 /* put it in the output */ 187 if(result) { 188 if(curlx_dyn_add(out, "[64dec-fail]")) 189 err = PARAM_NO_MEM; 190 } 191 else { 192 if(curlx_dyn_addn(out, enc, elen)) 193 err = PARAM_NO_MEM; 194 curl_free(enc); 195 } 196 if(err) 197 break; 198 } 199 } 200 else { 201 /* unsupported function */ 202 errorf(global, "unknown variable function in '%.*s'", 203 (int)flen, finput); 204 err = PARAM_EXPAND_ERROR; 205 break; 206 } 207 if(alloc) 208 free(c); 209 210 clen = curlx_dyn_len(out); 211 c = Memdup(curlx_dyn_ptr(out), clen); 212 if(!c) { 213 err = PARAM_NO_MEM; 214 break; 215 } 216 alloc = TRUE; 217 } 218 if(alloc) 219 free(c); 220 if(err) 221 curlx_dyn_free(out); 222 return err; 223 } 224 225 ParameterError varexpand(struct GlobalConfig *global, 226 const char *line, struct dynbuf *out, 227 bool *replaced) 228 { 229 CURLcode result; 230 char *envp; 231 bool added = FALSE; 232 const char *input = line; 233 *replaced = FALSE; 234 curlx_dyn_init(out, MAX_EXPAND_CONTENT); 235 do { 236 envp = strstr(line, "{{"); 237 if((envp > line) && envp[-1] == '\\') { 238 /* preceding backslash, we want this verbatim */ 239 240 /* insert the text up to this point, minus the backslash */ 241 result = curlx_dyn_addn(out, line, envp - line - 1); 242 if(result) 243 return PARAM_NO_MEM; 244 245 /* output '{{' then continue from here */ 246 result = curlx_dyn_addn(out, "{{", 2); 247 if(result) 248 return PARAM_NO_MEM; 249 line = &envp[2]; 250 } 251 else if(envp) { 252 char name[MAX_VAR_LEN]; 253 size_t nlen; 254 size_t i; 255 char *funcp; 256 char *clp = strstr(envp, "}}"); 257 size_t prefix; 258 259 if(!clp) { 260 /* uneven braces */ 261 warnf(global, "missing close '}}' in '%s'", input); 262 break; 263 } 264 265 prefix = 2; 266 envp += 2; /* move over the {{ */ 267 268 /* if there is a function, it ends the name with a colon */ 269 funcp = memchr(envp, ':', clp - envp); 270 if(funcp) 271 nlen = funcp - envp; 272 else 273 nlen = clp - envp; 274 if(!nlen || (nlen >= sizeof(name))) { 275 warnf(global, "bad variable name length '%s'", input); 276 /* insert the text as-is since this is not an env variable */ 277 result = curlx_dyn_addn(out, line, clp - line + prefix); 278 if(result) 279 return PARAM_NO_MEM; 280 } 281 else { 282 /* insert the text up to this point */ 283 result = curlx_dyn_addn(out, line, envp - prefix - line); 284 if(result) 285 return PARAM_NO_MEM; 286 287 /* copy the name to separate buffer */ 288 memcpy(name, envp, nlen); 289 name[nlen] = 0; 290 291 /* verify that the name looks sensible */ 292 for(i = 0; (i < nlen) && 293 (ISALNUM(name[i]) || (name[i] == '_')); i++); 294 if(i != nlen) { 295 warnf(global, "bad variable name: %s", name); 296 /* insert the text as-is since this is not an env variable */ 297 result = curlx_dyn_addn(out, envp - prefix, 298 clp - envp + prefix + 2); 299 if(result) 300 return PARAM_NO_MEM; 301 } 302 else { 303 char *value; 304 size_t vlen = 0; 305 struct dynbuf buf; 306 const struct tool_var *v = varcontent(global, name, nlen); 307 if(v) { 308 value = (char *)CURL_UNCONST(v->content); 309 vlen = v->clen; 310 } 311 else 312 value = NULL; 313 314 curlx_dyn_init(&buf, MAX_EXPAND_CONTENT); 315 if(funcp) { 316 /* apply the list of functions on the value */ 317 size_t flen = clp - funcp; 318 ParameterError err = varfunc(global, value, vlen, funcp, flen, 319 &buf); 320 if(err) 321 return err; 322 value = curlx_dyn_ptr(&buf); 323 vlen = curlx_dyn_len(&buf); 324 } 325 326 if(value && vlen > 0) { 327 /* A variable might contain null bytes. Such bytes cannot be shown 328 using normal means, this is an error. */ 329 char *nb = memchr(value, '\0', vlen); 330 if(nb) { 331 errorf(global, "variable contains null byte"); 332 return PARAM_EXPAND_ERROR; 333 } 334 } 335 /* insert the value */ 336 result = curlx_dyn_addn(out, value, vlen); 337 curlx_dyn_free(&buf); 338 if(result) 339 return PARAM_NO_MEM; 340 341 added = true; 342 } 343 } 344 line = &clp[2]; 345 } 346 347 } while(envp); 348 if(added && *line) { 349 /* add the "suffix" as well */ 350 result = curlx_dyn_add(out, line); 351 if(result) 352 return PARAM_NO_MEM; 353 } 354 *replaced = added; 355 if(!added) 356 curlx_dyn_free(out); 357 return PARAM_OK; 358 } 359 360 /* 361 * Created in a way that is not revealing how variables are actually stored so 362 * that we can improve this if we want better performance when managing many 363 * at a later point. 364 */ 365 static ParameterError addvariable(struct GlobalConfig *global, 366 const char *name, 367 size_t nlen, 368 const char *content, 369 size_t clen, 370 bool contalloc) 371 { 372 struct tool_var *p; 373 const struct tool_var *check = varcontent(global, name, nlen); 374 DEBUGASSERT(nlen); 375 if(check) 376 notef(global, "Overwriting variable '%s'", check->name); 377 378 p = calloc(1, sizeof(struct tool_var) + nlen); 379 if(p) { 380 memcpy(p->name, name, nlen); 381 382 p->content = contalloc ? content : Memdup(content, clen); 383 if(p->content) { 384 p->clen = clen; 385 386 p->next = global->variables; 387 global->variables = p; 388 return PARAM_OK; 389 } 390 free(p); 391 } 392 return PARAM_NO_MEM; 393 } 394 395 #define MAX_FILENAME 10000 396 397 ParameterError setvariable(struct GlobalConfig *global, 398 const char *input) 399 { 400 const char *name; 401 size_t nlen; 402 char *content = NULL; 403 size_t clen = 0; 404 bool contalloc = FALSE; 405 const char *line = input; 406 ParameterError err = PARAM_OK; 407 bool import = FALSE; 408 char *ge = NULL; 409 char buf[MAX_VAR_LEN]; 410 curl_off_t startoffset = 0; 411 curl_off_t endoffset = CURL_OFF_T_MAX; 412 413 if(*input == '%') { 414 import = TRUE; 415 line++; 416 } 417 name = line; 418 while(*line && (ISALNUM(*line) || (*line == '_'))) 419 line++; 420 nlen = line - name; 421 if(!nlen || (nlen >= MAX_VAR_LEN)) { 422 warnf(global, "Bad variable name length (%zd), skipping", nlen); 423 return PARAM_OK; 424 } 425 if(import) { 426 /* this does not use curl_getenv() because we want "" support for blank 427 content */ 428 if(*line) { 429 /* if there is a default action, we need to copy the name */ 430 memcpy(buf, name, nlen); 431 buf[nlen] = 0; 432 name = buf; 433 } 434 ge = getenv(name); 435 if(!*line && !ge) { 436 /* no assign, no variable, fail */ 437 errorf(global, "Variable '%s' import fail, not set", name); 438 return PARAM_EXPAND_ERROR; 439 } 440 else if(ge) { 441 /* there is a value to use */ 442 content = ge; 443 clen = strlen(ge); 444 } 445 } 446 if(*line == '[' && ISDIGIT(line[1])) { 447 /* is there a byte range specified? [num-num] */ 448 line++; 449 if(curlx_str_number(&line, &startoffset, CURL_OFF_T_MAX) || 450 curlx_str_single(&line, '-')) 451 return PARAM_VAR_SYNTAX; 452 if(curlx_str_single(&line, ']')) { 453 if(curlx_str_number(&line, &endoffset, CURL_OFF_T_MAX) || 454 curlx_str_single(&line, ']')) 455 return PARAM_VAR_SYNTAX; 456 } 457 if(startoffset > endoffset) 458 return PARAM_VAR_SYNTAX; 459 } 460 if(content) 461 ; 462 else if(*line == '@') { 463 /* read from file or stdin */ 464 FILE *file; 465 bool use_stdin; 466 struct dynbuf fname; 467 line++; 468 469 curlx_dyn_init(&fname, MAX_FILENAME); 470 471 use_stdin = !strcmp(line, "-"); 472 if(use_stdin) 473 file = stdin; 474 else { 475 file = fopen(line, "rb"); 476 if(!file) { 477 errorf(global, "Failed to open %s: %s", line, 478 strerror(errno)); 479 err = PARAM_READ_ERROR; 480 } 481 } 482 if(!err) { 483 err = file2memory_range(&content, &clen, file, startoffset, endoffset); 484 /* in case of out of memory, this should fail the entire operation */ 485 if(clen) 486 contalloc = TRUE; 487 } 488 curlx_dyn_free(&fname); 489 if(!use_stdin && file) 490 fclose(file); 491 if(err) 492 return err; 493 } 494 else if(*line == '=') { 495 line++; 496 clen = strlen(line); 497 /* this is the exact content */ 498 content = (char *)CURL_UNCONST(line); 499 if(startoffset || (endoffset != CURL_OFF_T_MAX)) { 500 if(startoffset >= (curl_off_t)clen) 501 clen = 0; 502 else { 503 /* make the end offset no larger than the last byte */ 504 if(endoffset >= (curl_off_t)clen) 505 endoffset = clen - 1; 506 clen = (size_t)(endoffset - startoffset) + 1; 507 content += startoffset; 508 } 509 } 510 } 511 else { 512 warnf(global, "Bad --variable syntax, skipping: %s", input); 513 return PARAM_OK; 514 } 515 err = addvariable(global, name, nlen, content, clen, contalloc); 516 if(err) { 517 if(contalloc) 518 free(content); 519 } 520 return err; 521 }