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