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