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 *text,
38                  FILE *stream, const unsigned char *ptr, size_t size,
39                  trace tracetype, curl_infotype infotype);
40 
41 /*
42 ** callback for CURLOPT_DEBUGFUNCTION
43 */
44 
tool_debug_cb(CURL * handle,curl_infotype type,char * data,size_t size,void * userdata)45 int tool_debug_cb(CURL *handle, curl_infotype type,
46                   char *data, size_t size,
47                   void *userdata)
48 {
49   struct OperationConfig *operation = userdata;
50   struct GlobalConfig *config = operation->global;
51   FILE *output = stderr;
52   const char *text;
53   struct timeval tv;
54   char timebuf[20];
55   time_t secs;
56 
57   (void)handle; /* not used */
58 
59   if(config->tracetime) {
60     struct tm *now;
61     static time_t epoch_offset;
62     static int    known_offset;
63     tv = tvnow();
64     if(!known_offset) {
65       epoch_offset = time(NULL) - tv.tv_sec;
66       known_offset = 1;
67     }
68     secs = epoch_offset + tv.tv_sec;
69     /* !checksrc! disable BANNEDFUNC 1 */
70     now = localtime(&secs);  /* not thread safe but we don't care */
71     msnprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d.%06ld ",
72               now->tm_hour, now->tm_min, now->tm_sec, (long)tv.tv_usec);
73   }
74   else
75     timebuf[0] = 0;
76 
77   if(!config->trace_stream) {
78     /* open for append */
79     if(!strcmp("-", config->trace_dump))
80       config->trace_stream = stdout;
81     else if(!strcmp("%", config->trace_dump))
82       /* Ok, this is somewhat hackish but we do it undocumented for now */
83       config->trace_stream = stderr;
84     else {
85       config->trace_stream = fopen(config->trace_dump, FOPEN_WRITETEXT);
86       config->trace_fopened = TRUE;
87     }
88   }
89 
90   if(config->trace_stream)
91     output = config->trace_stream;
92 
93   if(!output) {
94     warnf(config, "Failed to create/open output");
95     return 0;
96   }
97 
98   if(config->tracetype == TRACE_PLAIN) {
99     /*
100      * This is the trace look that is similar to what libcurl makes on its
101      * own.
102      */
103     static const char * const s_infotype[] = {
104       "*", "<", ">", "{", "}", "{", "}"
105     };
106     static bool newl = FALSE;
107     static bool traced_data = FALSE;
108 
109     switch(type) {
110     case CURLINFO_HEADER_OUT:
111       if(size > 0) {
112         size_t st = 0;
113         size_t i;
114         for(i = 0; i < size - 1; i++) {
115           if(data[i] == '\n') { /* LF */
116             if(!newl) {
117               fprintf(output, "%s%s ", timebuf, s_infotype[type]);
118             }
119             (void)fwrite(data + st, i - st + 1, 1, output);
120             st = i + 1;
121             newl = FALSE;
122           }
123         }
124         if(!newl)
125           fprintf(output, "%s%s ", timebuf, s_infotype[type]);
126         (void)fwrite(data + st, i - st + 1, 1, output);
127       }
128       newl = (size && (data[size - 1] != '\n')) ? TRUE : FALSE;
129       traced_data = FALSE;
130       break;
131     case CURLINFO_TEXT:
132     case CURLINFO_HEADER_IN:
133       if(!newl)
134         fprintf(output, "%s%s ", timebuf, s_infotype[type]);
135       (void)fwrite(data, size, 1, output);
136       newl = (size && (data[size - 1] != '\n')) ? TRUE : FALSE;
137       traced_data = FALSE;
138       break;
139     case CURLINFO_DATA_OUT:
140     case CURLINFO_DATA_IN:
141     case CURLINFO_SSL_DATA_IN:
142     case CURLINFO_SSL_DATA_OUT:
143       if(!traced_data) {
144         /* if the data is output to a tty and we're sending this debug trace
145            to stderr or stdout, we don't display the alert about the data not
146            being shown as the data _is_ shown then just not via this
147            function */
148         if(!config->isatty || ((output != stderr) && (output != stdout))) {
149           if(!newl)
150             fprintf(output, "%s%s ", timebuf, s_infotype[type]);
151           fprintf(output, "[%zu bytes data]\n", size);
152           newl = FALSE;
153           traced_data = TRUE;
154         }
155       }
156       break;
157     default: /* nada */
158       newl = FALSE;
159       traced_data = FALSE;
160       break;
161     }
162 
163     return 0;
164   }
165 
166   switch(type) {
167   case CURLINFO_TEXT:
168     fprintf(output, "%s== Info: %.*s", timebuf, (int)size, data);
169     /* FALLTHROUGH */
170   default: /* in case a new one is introduced to shock us */
171     return 0;
172 
173   case CURLINFO_HEADER_OUT:
174     text = "=> Send header";
175     break;
176   case CURLINFO_DATA_OUT:
177     text = "=> Send data";
178     break;
179   case CURLINFO_HEADER_IN:
180     text = "<= Recv header";
181     break;
182   case CURLINFO_DATA_IN:
183     text = "<= Recv data";
184     break;
185   case CURLINFO_SSL_DATA_IN:
186     text = "<= Recv SSL data";
187     break;
188   case CURLINFO_SSL_DATA_OUT:
189     text = "=> Send SSL data";
190     break;
191   }
192 
193   dump(timebuf, text, output, (unsigned char *) data, size, config->tracetype,
194        type);
195   return 0;
196 }
197 
dump(const char * timebuf,const char * text,FILE * stream,const unsigned char * ptr,size_t size,trace tracetype,curl_infotype infotype)198 static void dump(const char *timebuf, const char *text,
199                  FILE *stream, const unsigned char *ptr, size_t size,
200                  trace tracetype, curl_infotype infotype)
201 {
202   size_t i;
203   size_t c;
204 
205   unsigned int width = 0x10;
206 
207   if(tracetype == TRACE_ASCII)
208     /* without the hex output, we can fit more on screen */
209     width = 0x40;
210 
211   fprintf(stream, "%s%s, %zu bytes (0x%zx)\n", timebuf, text, size, size);
212 
213   for(i = 0; i < size; i += width) {
214 
215     fprintf(stream, "%04zx: ", i);
216 
217     if(tracetype == TRACE_BIN) {
218       /* hex not disabled, show it */
219       for(c = 0; c < width; c++)
220         if(i + c < size)
221           fprintf(stream, "%02x ", ptr[i + c]);
222         else
223           fputs("   ", stream);
224     }
225 
226     for(c = 0; (c < width) && (i + c < size); c++) {
227       /* check for 0D0A; if found, skip past and start a new line of output */
228       if((tracetype == TRACE_ASCII) &&
229          (i + c + 1 < size) && (ptr[i + c] == 0x0D) &&
230          (ptr[i + c + 1] == 0x0A)) {
231         i += (c + 2 - width);
232         break;
233       }
234       (void)infotype;
235       fprintf(stream, "%c", ((ptr[i + c] >= 0x20) && (ptr[i + c] < 0x7F)) ?
236               ptr[i + c] : UNPRINTABLE_CHAR);
237       /* check again for 0D0A, to avoid an extra \n if it's at width */
238       if((tracetype == TRACE_ASCII) &&
239          (i + c + 2 < size) && (ptr[i + c + 1] == 0x0D) &&
240          (ptr[i + c + 2] == 0x0A)) {
241         i += (c + 3 - width);
242         break;
243       }
244     }
245     fputc('\n', stream); /* newline */
246   }
247   fflush(stream);
248 }
249