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
26 #define ENABLE_CURLX_PRINTF
27 /* use our own printf() functions */
28 #include "curlx.h"
29
30 #include "tool_cfgable.h"
31 #include "tool_msgs.h"
32 #include "tool_cb_dbg.h"
33 #include "tool_util.h"
34
35 #include "memdebug.h" /* keep this as LAST include */
36
37 static void dump(const char *timebuf, const char *idsbuf, const char *text,
38 FILE *stream, const unsigned char *ptr, size_t size,
39 trace tracetype, curl_infotype infotype);
40
41 /*
42 * Return the formatted HH:MM:SS for the tv_sec given.
43 * NOT thread safe.
44 */
hms_for_sec(time_t tv_sec)45 static const char *hms_for_sec(time_t tv_sec)
46 {
47 static time_t cached_tv_sec;
48 static char hms_buf[12];
49 static time_t epoch_offset;
50 static int known_epoch;
51
52 if(tv_sec != cached_tv_sec) {
53 struct tm *now;
54 time_t secs;
55 /* recalculate */
56 if(!known_epoch) {
57 epoch_offset = time(NULL) - tv_sec;
58 known_epoch = 1;
59 }
60 secs = epoch_offset + tv_sec;
61 /* !checksrc! disable BANNEDFUNC 1 */
62 now = localtime(&secs); /* not thread safe but we don't care */
63 msnprintf(hms_buf, sizeof(hms_buf), "%02d:%02d:%02d",
64 now->tm_hour, now->tm_min, now->tm_sec);
65 cached_tv_sec = tv_sec;
66 }
67 return hms_buf;
68 }
69
log_line_start(FILE * log,const char * timebuf,const char * idsbuf,curl_infotype type)70 static void log_line_start(FILE *log, const char *timebuf,
71 const char *idsbuf, curl_infotype type)
72 {
73 /*
74 * This is the trace look that is similar to what libcurl makes on its
75 * own.
76 */
77 static const char * const s_infotype[] = {
78 "* ", "< ", "> ", "{ ", "} ", "{ ", "} "
79 };
80 if((timebuf && *timebuf) || (idsbuf && *idsbuf))
81 fprintf(log, "%s%s%s", timebuf, idsbuf, s_infotype[type]);
82 else
83 fputs(s_infotype[type], log);
84 }
85
86 #define TRC_IDS_FORMAT_IDS_1 "[%" CURL_FORMAT_CURL_OFF_T "-x] "
87 #define TRC_IDS_FORMAT_IDS_2 "[%" CURL_FORMAT_CURL_OFF_T "-%" \
88 CURL_FORMAT_CURL_OFF_T "] "
89 /*
90 ** callback for CURLOPT_DEBUGFUNCTION
91 */
tool_debug_cb(CURL * handle,curl_infotype type,char * data,size_t size,void * userdata)92 int tool_debug_cb(CURL *handle, curl_infotype type,
93 char *data, size_t size,
94 void *userdata)
95 {
96 struct OperationConfig *operation = userdata;
97 struct GlobalConfig *config = operation->global;
98 FILE *output = tool_stderr;
99 const char *text;
100 struct timeval tv;
101 char timebuf[20];
102 /* largest signed 64bit is: 9,223,372,036,854,775,807
103 * max length in decimal: 1 + (6*3) = 19
104 * formatted via TRC_IDS_FORMAT_IDS_2 this becomes 2 + 19 + 1 + 19 + 2 = 43
105 * negative xfer-id are not printed, negative conn-ids use TRC_IDS_FORMAT_1
106 */
107 char idsbuf[60];
108 curl_off_t xfer_id, conn_id;
109
110 (void)handle; /* not used */
111
112 if(config->tracetime) {
113 tv = tvnow();
114 msnprintf(timebuf, sizeof(timebuf), "%s.%06ld ",
115 hms_for_sec(tv.tv_sec), (long)tv.tv_usec);
116 }
117 else
118 timebuf[0] = 0;
119
120 if(handle && config->traceids &&
121 !curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
122 if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
123 conn_id >= 0) {
124 msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2,
125 xfer_id, conn_id);
126 }
127 else {
128 msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
129 }
130 }
131 else
132 idsbuf[0] = 0;
133
134 if(!config->trace_stream) {
135 /* open for append */
136 if(!strcmp("-", config->trace_dump))
137 config->trace_stream = stdout;
138 else if(!strcmp("%", config->trace_dump))
139 /* Ok, this is somewhat hackish but we do it undocumented for now */
140 config->trace_stream = tool_stderr;
141 else {
142 config->trace_stream = fopen(config->trace_dump, FOPEN_WRITETEXT);
143 config->trace_fopened = TRUE;
144 }
145 }
146
147 if(config->trace_stream)
148 output = config->trace_stream;
149
150 if(!output) {
151 warnf(config, "Failed to create/open output");
152 return 0;
153 }
154
155 if(config->tracetype == TRACE_PLAIN) {
156 static bool newl = FALSE;
157 static bool traced_data = FALSE;
158
159 switch(type) {
160 case CURLINFO_HEADER_OUT:
161 if(size > 0) {
162 size_t st = 0;
163 size_t i;
164 for(i = 0; i < size - 1; i++) {
165 if(data[i] == '\n') { /* LF */
166 if(!newl) {
167 log_line_start(output, timebuf, idsbuf, type);
168 }
169 (void)fwrite(data + st, i - st + 1, 1, output);
170 st = i + 1;
171 newl = FALSE;
172 }
173 }
174 if(!newl)
175 log_line_start(output, timebuf, idsbuf, type);
176 (void)fwrite(data + st, i - st + 1, 1, output);
177 }
178 newl = (size && (data[size - 1] != '\n')) ? TRUE : FALSE;
179 traced_data = FALSE;
180 break;
181 case CURLINFO_TEXT:
182 case CURLINFO_HEADER_IN:
183 if(!newl)
184 log_line_start(output, timebuf, idsbuf, type);
185 (void)fwrite(data, size, 1, output);
186 newl = (size && (data[size - 1] != '\n')) ? TRUE : FALSE;
187 traced_data = FALSE;
188 break;
189 case CURLINFO_DATA_OUT:
190 case CURLINFO_DATA_IN:
191 case CURLINFO_SSL_DATA_IN:
192 case CURLINFO_SSL_DATA_OUT:
193 if(!traced_data) {
194 /* if the data is output to a tty and we're sending this debug trace
195 to stderr or stdout, we don't display the alert about the data not
196 being shown as the data _is_ shown then just not via this
197 function */
198 if(!config->isatty ||
199 ((output != tool_stderr) && (output != stdout))) {
200 if(!newl)
201 log_line_start(output, timebuf, idsbuf, type);
202 fprintf(output, "[%zu bytes data]\n", size);
203 newl = FALSE;
204 traced_data = TRUE;
205 }
206 }
207 break;
208 default: /* nada */
209 newl = FALSE;
210 traced_data = FALSE;
211 break;
212 }
213
214 return 0;
215 }
216
217 switch(type) {
218 case CURLINFO_TEXT:
219 fprintf(output, "%s%s== Info: %.*s", timebuf, idsbuf, (int)size, data);
220 FALLTHROUGH();
221 default: /* in case a new one is introduced to shock us */
222 return 0;
223
224 case CURLINFO_HEADER_OUT:
225 text = "=> Send header";
226 break;
227 case CURLINFO_DATA_OUT:
228 text = "=> Send data";
229 break;
230 case CURLINFO_HEADER_IN:
231 text = "<= Recv header";
232 break;
233 case CURLINFO_DATA_IN:
234 text = "<= Recv data";
235 break;
236 case CURLINFO_SSL_DATA_IN:
237 text = "<= Recv SSL data";
238 break;
239 case CURLINFO_SSL_DATA_OUT:
240 text = "=> Send SSL data";
241 break;
242 }
243
244 dump(timebuf, idsbuf, text, output, (unsigned char *) data, size,
245 config->tracetype, type);
246 return 0;
247 }
248
dump(const char * timebuf,const char * idsbuf,const char * text,FILE * stream,const unsigned char * ptr,size_t size,trace tracetype,curl_infotype infotype)249 static void dump(const char *timebuf, const char *idsbuf, const char *text,
250 FILE *stream, const unsigned char *ptr, size_t size,
251 trace tracetype, curl_infotype infotype)
252 {
253 size_t i;
254 size_t c;
255
256 unsigned int width = 0x10;
257
258 if(tracetype == TRACE_ASCII)
259 /* without the hex output, we can fit more on screen */
260 width = 0x40;
261
262 fprintf(stream, "%s%s%s, %zu bytes (0x%zx)\n", timebuf, idsbuf,
263 text, size, size);
264
265 for(i = 0; i < size; i += width) {
266
267 fprintf(stream, "%04zx: ", i);
268
269 if(tracetype == TRACE_BIN) {
270 /* hex not disabled, show it */
271 for(c = 0; c < width; c++)
272 if(i + c < size)
273 fprintf(stream, "%02x ", ptr[i + c]);
274 else
275 fputs(" ", stream);
276 }
277
278 for(c = 0; (c < width) && (i + c < size); c++) {
279 /* check for 0D0A; if found, skip past and start a new line of output */
280 if((tracetype == TRACE_ASCII) &&
281 (i + c + 1 < size) && (ptr[i + c] == 0x0D) &&
282 (ptr[i + c + 1] == 0x0A)) {
283 i += (c + 2 - width);
284 break;
285 }
286 (void)infotype;
287 fprintf(stream, "%c", ((ptr[i + c] >= 0x20) && (ptr[i + c] < 0x7F)) ?
288 ptr[i + c] : UNPRINTABLE_CHAR);
289 /* check again for 0D0A, to avoid an extra \n if it's at width */
290 if((tracetype == TRACE_ASCII) &&
291 (i + c + 2 < size) && (ptr[i + c + 1] == 0x0D) &&
292 (ptr[i + c + 2] == 0x0A)) {
293 i += (c + 3 - width);
294 break;
295 }
296 }
297 fputc('\n', stream); /* newline */
298 }
299 fflush(stream);
300 }
301