xref: /curl/src/tool_cb_dbg.c (revision 3829759b)
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