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