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