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
35 static int writeTime(FILE *stream, const struct writeoutvar *wovar,
36 struct per_transfer *per, CURLcode per_result,
37 bool use_json);
38
39 static int writeString(FILE *stream, const struct writeoutvar *wovar,
40 struct per_transfer *per, CURLcode per_result,
41 bool use_json);
42
43 static int writeLong(FILE *stream, const struct writeoutvar *wovar,
44 struct per_transfer *per, CURLcode per_result,
45 bool use_json);
46
47 static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
48 struct per_transfer *per, CURLcode per_result,
49 bool use_json);
50
51 struct httpmap {
52 const char *str;
53 int num;
54 };
55
56 static 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 */
75 static 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
writeTime(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)153 static 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
urlpart(struct per_transfer * per,writeoutid vid,const char ** contentp)190 static 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
writeString(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)271 static 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
writeLong(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)421 static 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
writeOffset(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)478 static 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
ourWriteOut(struct OperationConfig * config,struct per_transfer * per,CURLcode per_result)511 void 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