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#define ENABLE_CURLX_PRINTF 26/* use our own printf() functions */ 27#include "curlx.h" 28#include "tool_cfgable.h" 29#include "tool_writeout.h" 30#include "tool_writeout_json.h" 31#include "dynbuf.h" 32 33#include "memdebug.h" /* keep this as LAST include */ 34 35static int writeTime(FILE *stream, const struct writeoutvar *wovar, 36 struct per_transfer *per, CURLcode per_result, 37 bool use_json); 38 39static int writeString(FILE *stream, const struct writeoutvar *wovar, 40 struct per_transfer *per, CURLcode per_result, 41 bool use_json); 42 43static int writeLong(FILE *stream, const struct writeoutvar *wovar, 44 struct per_transfer *per, CURLcode per_result, 45 bool use_json); 46 47static int writeOffset(FILE *stream, const struct writeoutvar *wovar, 48 struct per_transfer *per, CURLcode per_result, 49 bool use_json); 50 51struct httpmap { 52 const char *str; 53 int num; 54}; 55 56static const struct httpmap http_version[] = { 57 { "0", CURL_HTTP_VERSION_NONE}, 58 { "1", CURL_HTTP_VERSION_1_0}, 59 { "1.1", CURL_HTTP_VERSION_1_1}, 60 { "2", CURL_HTTP_VERSION_2}, 61 { "3", CURL_HTTP_VERSION_3}, 62 { NULL, 0} /* end of list */ 63}; 64 65/* The designated write function should be the same as the CURLINFO return type 66 with exceptions special cased in the respective function. For example, 67 http_version uses CURLINFO_HTTP_VERSION which returns the version as a long, 68 however it is output as a string and therefore is handled in writeString. 69 70 Yes: "http_version": "1.1" 71 No: "http_version": 1.1 72 73 Variable names should be in alphabetical order. 74 */ 75static const struct writeoutvar variables[] = { 76 {"certs", VAR_CERT, CURLINFO_NONE, writeString}, 77 {"content_type", VAR_CONTENT_TYPE, CURLINFO_CONTENT_TYPE, writeString}, 78 {"conn_id", VAR_CONN_ID, CURLINFO_CONN_ID, writeOffset}, 79 {"errormsg", VAR_ERRORMSG, CURLINFO_NONE, writeString}, 80 {"exitcode", VAR_EXITCODE, CURLINFO_NONE, writeLong}, 81 {"filename_effective", VAR_EFFECTIVE_FILENAME, CURLINFO_NONE, writeString}, 82 {"ftp_entry_path", VAR_FTP_ENTRY_PATH, CURLINFO_FTP_ENTRY_PATH, writeString}, 83 {"header_json", VAR_HEADER_JSON, CURLINFO_NONE, NULL}, 84 {"http_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong}, 85 {"http_connect", VAR_HTTP_CODE_PROXY, CURLINFO_HTTP_CONNECTCODE, writeLong}, 86 {"http_version", VAR_HTTP_VERSION, CURLINFO_HTTP_VERSION, writeString}, 87 {"json", VAR_JSON, CURLINFO_NONE, NULL}, 88 {"local_ip", VAR_LOCAL_IP, CURLINFO_LOCAL_IP, writeString}, 89 {"local_port", VAR_LOCAL_PORT, CURLINFO_LOCAL_PORT, writeLong}, 90 {"method", VAR_EFFECTIVE_METHOD, CURLINFO_EFFECTIVE_METHOD, writeString}, 91 {"num_certs", VAR_NUM_CERTS, CURLINFO_NONE, writeLong}, 92 {"num_connects", VAR_NUM_CONNECTS, CURLINFO_NUM_CONNECTS, writeLong}, 93 {"num_headers", VAR_NUM_HEADERS, CURLINFO_NONE, writeLong}, 94 {"num_redirects", VAR_REDIRECT_COUNT, CURLINFO_REDIRECT_COUNT, writeLong}, 95 {"onerror", VAR_ONERROR, CURLINFO_NONE, NULL}, 96 {"proxy_ssl_verify_result", VAR_PROXY_SSL_VERIFY_RESULT, 97 CURLINFO_PROXY_SSL_VERIFYRESULT, writeLong}, 98 {"redirect_url", VAR_REDIRECT_URL, CURLINFO_REDIRECT_URL, writeString}, 99 {"referer", VAR_REFERER, CURLINFO_REFERER, writeString}, 100 {"remote_ip", VAR_PRIMARY_IP, CURLINFO_PRIMARY_IP, writeString}, 101 {"remote_port", VAR_PRIMARY_PORT, CURLINFO_PRIMARY_PORT, writeLong}, 102 {"response_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong}, 103 {"scheme", VAR_SCHEME, CURLINFO_SCHEME, writeString}, 104 {"size_download", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, writeOffset}, 105 {"size_header", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writeLong}, 106 {"size_request", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writeLong}, 107 {"size_upload", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writeOffset}, 108 {"speed_download", VAR_SPEED_DOWNLOAD, CURLINFO_SPEED_DOWNLOAD_T, 109 writeOffset}, 110 {"speed_upload", VAR_SPEED_UPLOAD, CURLINFO_SPEED_UPLOAD_T, writeOffset}, 111 {"ssl_verify_result", VAR_SSL_VERIFY_RESULT, CURLINFO_SSL_VERIFYRESULT, 112 writeLong}, 113 {"stderr", VAR_STDERR, CURLINFO_NONE, NULL}, 114 {"stdout", VAR_STDOUT, CURLINFO_NONE, NULL}, 115 {"time_appconnect", VAR_APPCONNECT_TIME, CURLINFO_APPCONNECT_TIME_T, 116 writeTime}, 117 {"time_connect", VAR_CONNECT_TIME, CURLINFO_CONNECT_TIME_T, writeTime}, 118 {"time_namelookup", VAR_NAMELOOKUP_TIME, CURLINFO_NAMELOOKUP_TIME_T, 119 writeTime}, 120 {"time_pretransfer", VAR_PRETRANSFER_TIME, CURLINFO_PRETRANSFER_TIME_T, 121 writeTime}, 122 {"time_redirect", VAR_REDIRECT_TIME, CURLINFO_REDIRECT_TIME_T, writeTime}, 123 {"time_starttransfer", VAR_STARTTRANSFER_TIME, CURLINFO_STARTTRANSFER_TIME_T, 124 writeTime}, 125 {"time_total", VAR_TOTAL_TIME, CURLINFO_TOTAL_TIME_T, writeTime}, 126 {"url", VAR_INPUT_URL, CURLINFO_NONE, writeString}, 127 {"url.scheme", VAR_INPUT_URLSCHEME, CURLINFO_NONE, writeString}, 128 {"url.user", VAR_INPUT_URLUSER, CURLINFO_NONE, writeString}, 129 {"url.password", VAR_INPUT_URLPASSWORD, CURLINFO_NONE, writeString}, 130 {"url.options", VAR_INPUT_URLOPTIONS, CURLINFO_NONE, writeString}, 131 {"url.host", VAR_INPUT_URLHOST, CURLINFO_NONE, writeString}, 132 {"url.port", VAR_INPUT_URLPORT, CURLINFO_NONE, writeString}, 133 {"url.path", VAR_INPUT_URLPATH, CURLINFO_NONE, writeString}, 134 {"url.query", VAR_INPUT_URLQUERY, CURLINFO_NONE, writeString}, 135 {"url.fragment", VAR_INPUT_URLFRAGMENT, CURLINFO_NONE, writeString}, 136 {"url.zoneid", VAR_INPUT_URLZONEID, CURLINFO_NONE, writeString}, 137 {"urle.scheme", VAR_INPUT_URLESCHEME, CURLINFO_NONE, writeString}, 138 {"urle.user", VAR_INPUT_URLEUSER, CURLINFO_NONE, writeString}, 139 {"urle.password", VAR_INPUT_URLEPASSWORD, CURLINFO_NONE, writeString}, 140 {"urle.options", VAR_INPUT_URLEOPTIONS, CURLINFO_NONE, writeString}, 141 {"urle.host", VAR_INPUT_URLEHOST, CURLINFO_NONE, writeString}, 142 {"urle.port", VAR_INPUT_URLEPORT, CURLINFO_NONE, writeString}, 143 {"urle.path", VAR_INPUT_URLEPATH, CURLINFO_NONE, writeString}, 144 {"urle.query", VAR_INPUT_URLEQUERY, CURLINFO_NONE, writeString}, 145 {"urle.fragment", VAR_INPUT_URLEFRAGMENT, CURLINFO_NONE, writeString}, 146 {"urle.zoneid", VAR_INPUT_URLEZONEID, CURLINFO_NONE, writeString}, 147 {"url_effective", VAR_EFFECTIVE_URL, CURLINFO_EFFECTIVE_URL, writeString}, 148 {"urlnum", VAR_URLNUM, CURLINFO_NONE, writeLong}, 149 {"xfer_id", VAR_EASY_ID, CURLINFO_XFER_ID, writeOffset}, 150 {NULL, VAR_NONE, CURLINFO_NONE, NULL} 151}; 152 153static int writeTime(FILE *stream, const struct writeoutvar *wovar, 154 struct per_transfer *per, CURLcode per_result, 155 bool use_json) 156{ 157 bool valid = false; 158 curl_off_t us = 0; 159 160 (void)per; 161 (void)per_result; 162 DEBUGASSERT(wovar->writefunc == writeTime); 163 164 if(wovar->ci) { 165 if(!curl_easy_getinfo(per->curl, wovar->ci, &us)) 166 valid = true; 167 } 168 else { 169 DEBUGASSERT(0); 170 } 171 172 if(valid) { 173 curl_off_t secs = us / 1000000; 174 us %= 1000000; 175 176 if(use_json) 177 fprintf(stream, "\"%s\":", wovar->name); 178 179 fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU 180 ".%06" CURL_FORMAT_CURL_OFF_TU, secs, us); 181 } 182 else { 183 if(use_json) 184 fprintf(stream, "\"%s\":null", wovar->name); 185 } 186 187 return 1; /* return 1 if anything was written */ 188} 189 190static int urlpart(struct per_transfer *per, writeoutid vid, 191 const char **contentp) 192{ 193 CURLU *uh = curl_url(); 194 int rc = 0; 195 if(uh) { 196 CURLUPart cpart = CURLUPART_HOST; 197 char *part = NULL; 198 const char *url = NULL; 199 200 if(vid >= VAR_INPUT_URLEHOST) { 201 if(curl_easy_getinfo(per->curl, CURLINFO_EFFECTIVE_URL, &url)) 202 rc = 5; 203 } 204 else 205 url = per->this_url; 206 207 if(!rc) { 208 switch(vid) { 209 case VAR_INPUT_URLSCHEME: 210 case VAR_INPUT_URLESCHEME: 211 cpart = CURLUPART_SCHEME; 212 break; 213 case VAR_INPUT_URLUSER: 214 case VAR_INPUT_URLEUSER: 215 cpart = CURLUPART_USER; 216 break; 217 case VAR_INPUT_URLPASSWORD: 218 case VAR_INPUT_URLEPASSWORD: 219 cpart = CURLUPART_PASSWORD; 220 break; 221 case VAR_INPUT_URLOPTIONS: 222 case VAR_INPUT_URLEOPTIONS: 223 cpart = CURLUPART_OPTIONS; 224 break; 225 case VAR_INPUT_URLHOST: 226 case VAR_INPUT_URLEHOST: 227 cpart = CURLUPART_HOST; 228 break; 229 case VAR_INPUT_URLPORT: 230 case VAR_INPUT_URLEPORT: 231 cpart = CURLUPART_PORT; 232 break; 233 case VAR_INPUT_URLPATH: 234 case VAR_INPUT_URLEPATH: 235 cpart = CURLUPART_PATH; 236 break; 237 case VAR_INPUT_URLQUERY: 238 case VAR_INPUT_URLEQUERY: 239 cpart = CURLUPART_QUERY; 240 break; 241 case VAR_INPUT_URLFRAGMENT: 242 case VAR_INPUT_URLEFRAGMENT: 243 cpart = CURLUPART_FRAGMENT; 244 break; 245 case VAR_INPUT_URLZONEID: 246 case VAR_INPUT_URLEZONEID: 247 cpart = CURLUPART_ZONEID; 248 break; 249 default: 250 /* not implemented */ 251 rc = 4; 252 break; 253 } 254 } 255 if(!rc && curl_url_set(uh, CURLUPART_URL, url, 256 CURLU_GUESS_SCHEME|CURLU_NON_SUPPORT_SCHEME)) 257 rc = 2; 258 259 if(!rc && curl_url_get(uh, cpart, &part, CURLU_DEFAULT_PORT)) 260 rc = 3; 261 262 if(!rc && part) 263 *contentp = part; 264 curl_url_cleanup(uh); 265 } 266 else 267 return 1; 268 return rc; 269} 270 271static int writeString(FILE *stream, const struct writeoutvar *wovar, 272 struct per_transfer *per, CURLcode per_result, 273 bool use_json) 274{ 275 bool valid = false; 276 const char *strinfo = NULL; 277 const char *freestr = NULL; 278 struct dynbuf buf; 279 curlx_dyn_init(&buf, 256*1024); 280 281 DEBUGASSERT(wovar->writefunc == writeString); 282 283 if(wovar->ci) { 284 if(wovar->ci == CURLINFO_HTTP_VERSION) { 285 long version = 0; 286 if(!curl_easy_getinfo(per->curl, CURLINFO_HTTP_VERSION, &version)) { 287 const struct httpmap *m = &http_version[0]; 288 while(m->str) { 289 if(m->num == version) { 290 strinfo = m->str; 291 valid = true; 292 break; 293 } 294 m++; 295 } 296 } 297 } 298 else { 299 if(!curl_easy_getinfo(per->curl, wovar->ci, &strinfo) && strinfo) 300 valid = true; 301 } 302 } 303 else { 304 switch(wovar->id) { 305 case VAR_CERT: 306 if(per->certinfo) { 307 int i; 308 bool error = FALSE; 309 for(i = 0; (i < per->certinfo->num_of_certs) && !error; i++) { 310 struct curl_slist *slist; 311 312 for(slist = per->certinfo->certinfo[i]; slist; slist = slist->next) { 313 size_t len; 314 if(curl_strnequal(slist->data, "cert:", 5)) { 315 if(curlx_dyn_add(&buf, &slist->data[5])) { 316 error = TRUE; 317 break; 318 } 319 } 320 else { 321 if(curlx_dyn_add(&buf, slist->data)) { 322 error = TRUE; 323 break; 324 } 325 } 326 len = curlx_dyn_len(&buf); 327 if(len) { 328 char *ptr = curlx_dyn_ptr(&buf); 329 if(ptr[len -1] != '\n') { 330 /* add a newline to make things look better */ 331 if(curlx_dyn_addn(&buf, "\n", 1)) { 332 error = TRUE; 333 break; 334 } 335 } 336 } 337 } 338 } 339 if(!error) { 340 strinfo = curlx_dyn_ptr(&buf); 341 if(!strinfo) 342 /* maybe not a TLS protocol */ 343 strinfo = ""; 344 valid = true; 345 } 346 } 347 else 348 strinfo = ""; /* no cert info */ 349 break; 350 case VAR_ERRORMSG: 351 if(per_result) { 352 strinfo = (per->errorbuffer && per->errorbuffer[0]) ? 353 per->errorbuffer : curl_easy_strerror(per_result); 354 valid = true; 355 } 356 break; 357 case VAR_EFFECTIVE_FILENAME: 358 if(per->outs.filename) { 359 strinfo = per->outs.filename; 360 valid = true; 361 } 362 break; 363 case VAR_INPUT_URL: 364 if(per->this_url) { 365 strinfo = per->this_url; 366 valid = true; 367 } 368 break; 369 case VAR_INPUT_URLSCHEME: 370 case VAR_INPUT_URLUSER: 371 case VAR_INPUT_URLPASSWORD: 372 case VAR_INPUT_URLOPTIONS: 373 case VAR_INPUT_URLHOST: 374 case VAR_INPUT_URLPORT: 375 case VAR_INPUT_URLPATH: 376 case VAR_INPUT_URLQUERY: 377 case VAR_INPUT_URLFRAGMENT: 378 case VAR_INPUT_URLZONEID: 379 case VAR_INPUT_URLESCHEME: 380 case VAR_INPUT_URLEUSER: 381 case VAR_INPUT_URLEPASSWORD: 382 case VAR_INPUT_URLEOPTIONS: 383 case VAR_INPUT_URLEHOST: 384 case VAR_INPUT_URLEPORT: 385 case VAR_INPUT_URLEPATH: 386 case VAR_INPUT_URLEQUERY: 387 case VAR_INPUT_URLEFRAGMENT: 388 case VAR_INPUT_URLEZONEID: 389 if(per->this_url) { 390 if(!urlpart(per, wovar->id, &strinfo)) { 391 freestr = strinfo; 392 valid = true; 393 } 394 } 395 break; 396 default: 397 DEBUGASSERT(0); 398 break; 399 } 400 } 401 402 if(valid) { 403 DEBUGASSERT(strinfo); 404 if(use_json) { 405 fprintf(stream, "\"%s\":", wovar->name); 406 jsonWriteString(stream, strinfo, FALSE); 407 } 408 else 409 fputs(strinfo, stream); 410 } 411 else { 412 if(use_json) 413 fprintf(stream, "\"%s\":null", wovar->name); 414 } 415 curl_free((char *)freestr); 416 417 curlx_dyn_free(&buf); 418 return 1; /* return 1 if anything was written */ 419} 420 421static int writeLong(FILE *stream, const struct writeoutvar *wovar, 422 struct per_transfer *per, CURLcode per_result, 423 bool use_json) 424{ 425 bool valid = false; 426 long longinfo = 0; 427 428 DEBUGASSERT(wovar->writefunc == writeLong); 429 430 if(wovar->ci) { 431 if(!curl_easy_getinfo(per->curl, wovar->ci, &longinfo)) 432 valid = true; 433 } 434 else { 435 switch(wovar->id) { 436 case VAR_NUM_CERTS: 437 longinfo = per->certinfo ? per->certinfo->num_of_certs : 0; 438 valid = true; 439 break; 440 case VAR_NUM_HEADERS: 441 longinfo = per->num_headers; 442 valid = true; 443 break; 444 case VAR_EXITCODE: 445 longinfo = per_result; 446 valid = true; 447 break; 448 case VAR_URLNUM: 449 if(per->urlnum <= INT_MAX) { 450 longinfo = (long)per->urlnum; 451 valid = true; 452 } 453 break; 454 default: 455 DEBUGASSERT(0); 456 break; 457 } 458 } 459 460 if(valid) { 461 if(use_json) 462 fprintf(stream, "\"%s\":%ld", wovar->name, longinfo); 463 else { 464 if(wovar->id == VAR_HTTP_CODE || wovar->id == VAR_HTTP_CODE_PROXY) 465 fprintf(stream, "%03ld", longinfo); 466 else 467 fprintf(stream, "%ld", longinfo); 468 } 469 } 470 else { 471 if(use_json) 472 fprintf(stream, "\"%s\":null", wovar->name); 473 } 474 475 return 1; /* return 1 if anything was written */ 476} 477 478static int writeOffset(FILE *stream, const struct writeoutvar *wovar, 479 struct per_transfer *per, CURLcode per_result, 480 bool use_json) 481{ 482 bool valid = false; 483 curl_off_t offinfo = 0; 484 485 (void)per; 486 (void)per_result; 487 DEBUGASSERT(wovar->writefunc == writeOffset); 488 489 if(wovar->ci) { 490 if(!curl_easy_getinfo(per->curl, wovar->ci, &offinfo)) 491 valid = true; 492 } 493 else { 494 DEBUGASSERT(0); 495 } 496 497 if(valid) { 498 if(use_json) 499 fprintf(stream, "\"%s\":", wovar->name); 500 501 fprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo); 502 } 503 else { 504 if(use_json) 505 fprintf(stream, "\"%s\":null", wovar->name); 506 } 507 508 return 1; /* return 1 if anything was written */ 509} 510 511void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, 512 CURLcode per_result) 513{ 514 FILE *stream = stdout; 515 const char *writeinfo = config->writeout; 516 const char *ptr = writeinfo; 517 bool done = FALSE; 518 struct curl_certinfo *certinfo; 519 CURLcode res = curl_easy_getinfo(per->curl, CURLINFO_CERTINFO, &certinfo); 520 bool fclose_stream = FALSE; 521 522 if(!writeinfo) 523 return; 524 525 if(!res && certinfo) 526 per->certinfo = certinfo; 527 528 while(ptr && *ptr && !done) { 529 if('%' == *ptr && ptr[1]) { 530 if('%' == ptr[1]) { 531 /* an escaped %-letter */ 532 fputc('%', stream); 533 ptr += 2; 534 } 535 else { 536 /* this is meant as a variable to output */ 537 char *end; 538 size_t vlen; 539 if('{' == ptr[1]) { 540 int i; 541 bool match = FALSE; 542 end = strchr(ptr, '}'); 543 ptr += 2; /* pass the % and the { */ 544 if(!end) { 545 fputs("%{", stream); 546 continue; 547 } 548 vlen = end - ptr; 549 for(i = 0; variables[i].name; i++) { 550 if((strlen(variables[i].name) == vlen) && 551 curl_strnequal(ptr, variables[i].name, vlen)) { 552 match = TRUE; 553 switch(variables[i].id) { 554 case VAR_ONERROR: 555 if(per_result == CURLE_OK) 556 /* this isn't error so skip the rest */ 557 done = TRUE; 558 break; 559 case VAR_STDOUT: 560 if(fclose_stream) 561 fclose(stream); 562 fclose_stream = FALSE; 563 stream = stdout; 564 break; 565 case VAR_STDERR: 566 if(fclose_stream) 567 fclose(stream); 568 fclose_stream = FALSE; 569 stream = tool_stderr; 570 break; 571 case VAR_JSON: 572 ourWriteOutJSON(stream, variables, per, per_result); 573 break; 574 case VAR_HEADER_JSON: 575 headerJSON(stream, per); 576 break; 577 default: 578 (void)variables[i].writefunc(stream, &variables[i], 579 per, per_result, false); 580 break; 581 } 582 break; 583 } 584 } 585 if(!match) { 586 fprintf(tool_stderr, 587 "curl: unknown --write-out variable: '%.*s'\n", 588 (int)vlen, ptr); 589 } 590 ptr = end + 1; /* pass the end */ 591 } 592 else if(!strncmp("header{", &ptr[1], 7)) { 593 ptr += 8; 594 end = strchr(ptr, '}'); 595 if(end) { 596 char hname[256]; /* holds the longest header field name */ 597 struct curl_header *header; 598 vlen = end - ptr; 599 if(vlen < sizeof(hname)) { 600 memcpy(hname, ptr, vlen); 601 hname[vlen] = 0; 602 if(CURLHE_OK == curl_easy_header(per->curl, hname, 0, 603 CURLH_HEADER, -1, &header)) 604 fputs(header->value, stream); 605 } 606 ptr = end + 1; 607 } 608 else 609 fputs("%header{", stream); 610 } 611 else if(!strncmp("output{", &ptr[1], 7)) { 612 bool append = FALSE; 613 ptr += 8; 614 if((ptr[0] == '>') && (ptr[1] == '>')) { 615 append = TRUE; 616 ptr += 2; 617 } 618 end = strchr(ptr, '}'); 619 if(end) { 620 char fname[512]; /* holds the longest file name */ 621 size_t flen = end - ptr; 622 if(flen < sizeof(fname)) { 623 FILE *stream2; 624 memcpy(fname, ptr, flen); 625 fname[flen] = 0; 626 stream2 = fopen(fname, append? FOPEN_APPENDTEXT : 627 FOPEN_WRITETEXT); 628 if(stream2) { 629 /* only change if the open worked */ 630 if(fclose_stream) 631 fclose(stream); 632 stream = stream2; 633 fclose_stream = TRUE; 634 } 635 } 636 ptr = end + 1; 637 } 638 else 639 fputs("%output{", stream); 640 } 641 else { 642 /* illegal syntax, then just output the characters that are used */ 643 fputc('%', stream); 644 fputc(ptr[1], stream); 645 ptr += 2; 646 } 647 } 648 } 649 else if('\\' == *ptr && ptr[1]) { 650 switch(ptr[1]) { 651 case 'r': 652 fputc('\r', stream); 653 break; 654 case 'n': 655 fputc('\n', stream); 656 break; 657 case 't': 658 fputc('\t', stream); 659 break; 660 default: 661 /* unknown, just output this */ 662 fputc(*ptr, stream); 663 fputc(ptr[1], stream); 664 break; 665 } 666 ptr += 2; 667 } 668 else { 669 fputc(*ptr, stream); 670 ptr++; 671 } 672 } 673 if(fclose_stream) 674 fclose(stream); 675} 676