blob: 2789ee20bf44a451386b03dc3a26d454d01499cb [file] [log] [blame]
xf.li6c8fc1e2023-08-12 00:11:09 -07001/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2022, 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
32#include "memdebug.h" /* keep this as LAST include */
33
34static int writeTime(FILE *stream, const struct writeoutvar *wovar,
35 struct per_transfer *per, CURLcode per_result,
36 bool use_json);
37
38static int writeString(FILE *stream, const struct writeoutvar *wovar,
39 struct per_transfer *per, CURLcode per_result,
40 bool use_json);
41
42static int writeLong(FILE *stream, const struct writeoutvar *wovar,
43 struct per_transfer *per, CURLcode per_result,
44 bool use_json);
45
46static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
47 struct per_transfer *per, CURLcode per_result,
48 bool use_json);
49
50struct httpmap {
51 const char *str;
52 int num;
53};
54
55static const struct httpmap http_version[] = {
56 { "0", CURL_HTTP_VERSION_NONE},
57 { "1", CURL_HTTP_VERSION_1_0},
58 { "1.1", CURL_HTTP_VERSION_1_1},
59 { "2", CURL_HTTP_VERSION_2},
60 { "3", CURL_HTTP_VERSION_3},
61 { NULL, 0} /* end of list */
62};
63
64/* The designated write function should be the same as the CURLINFO return type
65 with exceptions special cased in the respective function. For example,
66 http_version uses CURLINFO_HTTP_VERSION which returns the version as a long,
67 however it is output as a string and therefore is handled in writeString.
68
69 Yes: "http_version": "1.1"
70 No: "http_version": 1.1
71
72 Variable names should be in alphabetical order.
73 */
74static const struct writeoutvar variables[] = {
75 {"content_type", VAR_CONTENT_TYPE, CURLINFO_CONTENT_TYPE, writeString},
76 {"errormsg", VAR_ERRORMSG, CURLINFO_NONE, writeString},
77 {"exitcode", VAR_EXITCODE, CURLINFO_NONE, writeLong},
78 {"filename_effective", VAR_EFFECTIVE_FILENAME, CURLINFO_NONE, writeString},
79 {"ftp_entry_path", VAR_FTP_ENTRY_PATH, CURLINFO_FTP_ENTRY_PATH, writeString},
80 {"header_json", VAR_HEADER_JSON, CURLINFO_NONE, NULL},
81 {"http_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong},
82 {"http_connect", VAR_HTTP_CODE_PROXY, CURLINFO_HTTP_CONNECTCODE, writeLong},
83 {"http_version", VAR_HTTP_VERSION, CURLINFO_HTTP_VERSION, writeString},
84 {"json", VAR_JSON, CURLINFO_NONE, NULL},
85 {"local_ip", VAR_LOCAL_IP, CURLINFO_LOCAL_IP, writeString},
86 {"local_port", VAR_LOCAL_PORT, CURLINFO_LOCAL_PORT, writeLong},
87 {"method", VAR_EFFECTIVE_METHOD, CURLINFO_EFFECTIVE_METHOD, writeString},
88 {"num_connects", VAR_NUM_CONNECTS, CURLINFO_NUM_CONNECTS, writeLong},
89 {"num_headers", VAR_NUM_HEADERS, CURLINFO_NONE, writeLong},
90 {"num_redirects", VAR_REDIRECT_COUNT, CURLINFO_REDIRECT_COUNT, writeLong},
91 {"onerror", VAR_ONERROR, CURLINFO_NONE, NULL},
92 {"proxy_ssl_verify_result", VAR_PROXY_SSL_VERIFY_RESULT,
93 CURLINFO_PROXY_SSL_VERIFYRESULT, writeLong},
94 {"redirect_url", VAR_REDIRECT_URL, CURLINFO_REDIRECT_URL, writeString},
95 {"referer", VAR_REFERER, CURLINFO_REFERER, writeString},
96 {"remote_ip", VAR_PRIMARY_IP, CURLINFO_PRIMARY_IP, writeString},
97 {"remote_port", VAR_PRIMARY_PORT, CURLINFO_PRIMARY_PORT, writeLong},
98 {"response_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong},
99 {"scheme", VAR_SCHEME, CURLINFO_SCHEME, writeString},
100 {"size_download", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, writeOffset},
101 {"size_header", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writeLong},
102 {"size_request", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writeLong},
103 {"size_upload", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writeOffset},
104 {"speed_download", VAR_SPEED_DOWNLOAD, CURLINFO_SPEED_DOWNLOAD_T,
105 writeOffset},
106 {"speed_upload", VAR_SPEED_UPLOAD, CURLINFO_SPEED_UPLOAD_T, writeOffset},
107 {"ssl_verify_result", VAR_SSL_VERIFY_RESULT, CURLINFO_SSL_VERIFYRESULT,
108 writeLong},
109 {"stderr", VAR_STDERR, CURLINFO_NONE, NULL},
110 {"stdout", VAR_STDOUT, CURLINFO_NONE, NULL},
111 {"time_appconnect", VAR_APPCONNECT_TIME, CURLINFO_APPCONNECT_TIME_T,
112 writeTime},
113 {"time_connect", VAR_CONNECT_TIME, CURLINFO_CONNECT_TIME_T, writeTime},
114 {"time_namelookup", VAR_NAMELOOKUP_TIME, CURLINFO_NAMELOOKUP_TIME_T,
115 writeTime},
116 {"time_pretransfer", VAR_PRETRANSFER_TIME, CURLINFO_PRETRANSFER_TIME_T,
117 writeTime},
118 {"time_redirect", VAR_REDIRECT_TIME, CURLINFO_REDIRECT_TIME_T, writeTime},
119 {"time_starttransfer", VAR_STARTTRANSFER_TIME, CURLINFO_STARTTRANSFER_TIME_T,
120 writeTime},
121 {"time_total", VAR_TOTAL_TIME, CURLINFO_TOTAL_TIME_T, writeTime},
122 {"url", VAR_INPUT_URL, CURLINFO_NONE, writeString},
123 {"url_effective", VAR_EFFECTIVE_URL, CURLINFO_EFFECTIVE_URL, writeString},
124 {"urlnum", VAR_URLNUM, CURLINFO_NONE, writeLong},
125 {NULL, VAR_NONE, CURLINFO_NONE, NULL}
126};
127
128static int writeTime(FILE *stream, const struct writeoutvar *wovar,
129 struct per_transfer *per, CURLcode per_result,
130 bool use_json)
131{
132 bool valid = false;
133 curl_off_t us = 0;
134
135 (void)per;
136 (void)per_result;
137 DEBUGASSERT(wovar->writefunc == writeTime);
138
139 if(wovar->ci) {
140 if(!curl_easy_getinfo(per->curl, wovar->ci, &us))
141 valid = true;
142 }
143 else {
144 DEBUGASSERT(0);
145 }
146
147 if(valid) {
148 curl_off_t secs = us / 1000000;
149 us %= 1000000;
150
151 if(use_json)
152 fprintf(stream, "\"%s\":", wovar->name);
153
154 fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU
155 ".%06" CURL_FORMAT_CURL_OFF_TU, secs, us);
156 }
157 else {
158 if(use_json)
159 fprintf(stream, "\"%s\":null", wovar->name);
160 }
161
162 return 1; /* return 1 if anything was written */
163}
164
165static int writeString(FILE *stream, const struct writeoutvar *wovar,
166 struct per_transfer *per, CURLcode per_result,
167 bool use_json)
168{
169 bool valid = false;
170 const char *strinfo = NULL;
171
172 DEBUGASSERT(wovar->writefunc == writeString);
173
174 if(wovar->ci) {
175 if(wovar->ci == CURLINFO_HTTP_VERSION) {
176 long version = 0;
177 if(!curl_easy_getinfo(per->curl, CURLINFO_HTTP_VERSION, &version)) {
178 const struct httpmap *m = &http_version[0];
179 while(m->str) {
180 if(m->num == version) {
181 strinfo = m->str;
182 valid = true;
183 break;
184 }
185 m++;
186 }
187 }
188 }
189 else {
190 if(!curl_easy_getinfo(per->curl, wovar->ci, &strinfo) && strinfo)
191 valid = true;
192 }
193 }
194 else {
195 switch(wovar->id) {
196 case VAR_ERRORMSG:
197 if(per_result) {
198 strinfo = (per->errorbuffer && per->errorbuffer[0]) ?
199 per->errorbuffer : curl_easy_strerror(per_result);
200 valid = true;
201 }
202 break;
203 case VAR_EFFECTIVE_FILENAME:
204 if(per->outs.filename) {
205 strinfo = per->outs.filename;
206 valid = true;
207 }
208 break;
209 case VAR_INPUT_URL:
210 if(per->this_url) {
211 strinfo = per->this_url;
212 valid = true;
213 }
214 break;
215 default:
216 DEBUGASSERT(0);
217 break;
218 }
219 }
220
221 if(valid) {
222 DEBUGASSERT(strinfo);
223 if(use_json) {
224 fprintf(stream, "\"%s\":", wovar->name);
225 jsonWriteString(stream, strinfo, FALSE);
226 }
227 else
228 fputs(strinfo, stream);
229 }
230 else {
231 if(use_json)
232 fprintf(stream, "\"%s\":null", wovar->name);
233 }
234
235 return 1; /* return 1 if anything was written */
236}
237
238static int writeLong(FILE *stream, const struct writeoutvar *wovar,
239 struct per_transfer *per, CURLcode per_result,
240 bool use_json)
241{
242 bool valid = false;
243 long longinfo = 0;
244
245 DEBUGASSERT(wovar->writefunc == writeLong);
246
247 if(wovar->ci) {
248 if(!curl_easy_getinfo(per->curl, wovar->ci, &longinfo))
249 valid = true;
250 }
251 else {
252 switch(wovar->id) {
253 case VAR_NUM_HEADERS:
254 longinfo = per->num_headers;
255 valid = true;
256 break;
257 case VAR_EXITCODE:
258 longinfo = per_result;
259 valid = true;
260 break;
261 case VAR_URLNUM:
262 if(per->urlnum <= INT_MAX) {
263 longinfo = (long)per->urlnum;
264 valid = true;
265 }
266 break;
267 default:
268 DEBUGASSERT(0);
269 break;
270 }
271 }
272
273 if(valid) {
274 if(use_json)
275 fprintf(stream, "\"%s\":%ld", wovar->name, longinfo);
276 else {
277 if(wovar->id == VAR_HTTP_CODE || wovar->id == VAR_HTTP_CODE_PROXY)
278 fprintf(stream, "%03ld", longinfo);
279 else
280 fprintf(stream, "%ld", longinfo);
281 }
282 }
283 else {
284 if(use_json)
285 fprintf(stream, "\"%s\":null", wovar->name);
286 }
287
288 return 1; /* return 1 if anything was written */
289}
290
291static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
292 struct per_transfer *per, CURLcode per_result,
293 bool use_json)
294{
295 bool valid = false;
296 curl_off_t offinfo = 0;
297
298 (void)per;
299 (void)per_result;
300 DEBUGASSERT(wovar->writefunc == writeOffset);
301
302 if(wovar->ci) {
303 if(!curl_easy_getinfo(per->curl, wovar->ci, &offinfo))
304 valid = true;
305 }
306 else {
307 DEBUGASSERT(0);
308 }
309
310 if(valid) {
311 if(use_json)
312 fprintf(stream, "\"%s\":", wovar->name);
313
314 fprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo);
315 }
316 else {
317 if(use_json)
318 fprintf(stream, "\"%s\":null", wovar->name);
319 }
320
321 return 1; /* return 1 if anything was written */
322}
323
324void ourWriteOut(const char *writeinfo, struct per_transfer *per,
325 CURLcode per_result)
326{
327 FILE *stream = stdout;
328 const char *ptr = writeinfo;
329 bool done = FALSE;
330
331 while(ptr && *ptr && !done) {
332 if('%' == *ptr && ptr[1]) {
333 if('%' == ptr[1]) {
334 /* an escaped %-letter */
335 fputc('%', stream);
336 ptr += 2;
337 }
338 else {
339 /* this is meant as a variable to output */
340 char *end;
341 size_t vlen;
342 if('{' == ptr[1]) {
343 int i;
344 bool match = FALSE;
345 end = strchr(ptr, '}');
346 ptr += 2; /* pass the % and the { */
347 if(!end) {
348 fputs("%{", stream);
349 continue;
350 }
351 vlen = end - ptr;
352 for(i = 0; variables[i].name; i++) {
353 if((strlen(variables[i].name) == vlen) &&
354 curl_strnequal(ptr, variables[i].name, vlen)) {
355 match = TRUE;
356 switch(variables[i].id) {
357 case VAR_ONERROR:
358 if(per_result == CURLE_OK)
359 /* this isn't error so skip the rest */
360 done = TRUE;
361 break;
362 case VAR_STDOUT:
363 stream = stdout;
364 break;
365 case VAR_STDERR:
366 stream = stderr;
367 break;
368 case VAR_JSON:
369 ourWriteOutJSON(stream, variables, per, per_result);
370 break;
371 case VAR_HEADER_JSON:
372 headerJSON(stream, per);
373 break;
374 default:
375 (void)variables[i].writefunc(stream, &variables[i],
376 per, per_result, false);
377 break;
378 }
379 break;
380 }
381 }
382 if(!match) {
383 fprintf(stderr, "curl: unknown --write-out variable: '%.*s'\n",
384 (int)vlen, ptr);
385 }
386 ptr = end + 1; /* pass the end */
387 }
388 else if(!strncmp("header{", &ptr[1], 7)) {
389 ptr += 8;
390 end = strchr(ptr, '}');
391 if(end) {
392 char hname[256]; /* holds the longest header field name */
393 struct curl_header *header;
394 vlen = end - ptr;
395 if(vlen < sizeof(hname)) {
396 memcpy(hname, ptr, vlen);
397 hname[vlen] = 0;
398 if(CURLHE_OK == curl_easy_header(per->curl, hname, 0,
399 CURLH_HEADER, -1, &header))
400 fputs(header->value, stream);
401 }
402 ptr = end + 1;
403 }
404 else
405 fputs("%header{", stream);
406 }
407 else {
408 /* illegal syntax, then just output the characters that are used */
409 fputc('%', stream);
410 fputc(ptr[1], stream);
411 ptr += 2;
412 }
413 }
414 }
415 else if('\\' == *ptr && ptr[1]) {
416 switch(ptr[1]) {
417 case 'r':
418 fputc('\r', stream);
419 break;
420 case 'n':
421 fputc('\n', stream);
422 break;
423 case 't':
424 fputc('\t', stream);
425 break;
426 default:
427 /* unknown, just output this */
428 fputc(*ptr, stream);
429 fputc(ptr[1], stream);
430 break;
431 }
432 ptr += 2;
433 }
434 else {
435 fputc(*ptr, stream);
436 ptr++;
437 }
438 }
439}