1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel (at) 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.haxx.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 ***************************************************************************/ 22 #include "tool_setup.h" 23 24 #define ENABLE_CURLX_PRINTF 25 /* use our own printf() functions */ 26 #include "curlx.h" 27 28 #include "tool_cfgable.h" 29 #include "tool_convert.h" 30 #include "tool_msgs.h" 31 #include "tool_cb_dbg.h" 32 #include "tool_util.h" 33 34 #include "memdebug.h" /* keep this as LAST include */ 35 36 static void dump(const char *timebuf, const char *text, 37 FILE *stream, const unsigned char *ptr, size_t size, 38 trace tracetype, curl_infotype infotype); 39 40 /* 41 ** callback for CURLOPT_DEBUGFUNCTION 42 */ 43 44 int tool_debug_cb(CURL *handle, curl_infotype type, 45 char *data, size_t size, 46 void *userdata) 47 { 48 struct OperationConfig *operation = userdata; 49 struct GlobalConfig *config = operation->global; 50 FILE *output = config->errors; 51 const char *text; 52 struct timeval tv; 53 struct tm *now; 54 char timebuf[20]; 55 time_t secs; 56 static time_t epoch_offset; 57 static int known_offset; 58 59 (void)handle; /* not used */ 60 61 if(config->tracetime) { 62 tv = tvnow(); 63 if(!known_offset) { 64 epoch_offset = time(NULL) - tv.tv_sec; 65 known_offset = 1; 66 } 67 secs = epoch_offset + tv.tv_sec; 68 now = localtime(&secs); /* not thread safe but we don't care */ 69 snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d.%06ld ", 70 now->tm_hour, now->tm_min, now->tm_sec, (long)tv.tv_usec); 71 } 72 else 73 timebuf[0] = 0; 74 75 if(!config->trace_stream) { 76 /* open for append */ 77 if(!strcmp("-", config->trace_dump)) 78 config->trace_stream = stdout; 79 else if(!strcmp("%", config->trace_dump)) 80 /* Ok, this is somewhat hackish but we do it undocumented for now */ 81 config->trace_stream = config->errors; /* aka stderr */ 82 else { 83 config->trace_stream = fopen(config->trace_dump, FOPEN_WRITETEXT); 84 config->trace_fopened = TRUE; 85 } 86 } 87 88 if(config->trace_stream) 89 output = config->trace_stream; 90 91 if(!output) { 92 warnf(config, "Failed to create/open output"); 93 return 0; 94 } 95 96 if(config->tracetype == TRACE_PLAIN) { 97 /* 98 * This is the trace look that is similar to what libcurl makes on its 99 * own. 100 */ 101 static const char * const s_infotype[] = { 102 "*", "<", ">", "{", "}", "{", "}" 103 }; 104 size_t i; 105 size_t st = 0; 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 for(i = 0; i < size - 1; i++) { 113 if(data[i] == '\n') { /* LF */ 114 if(!newl) { 115 fprintf(output, "%s%s ", timebuf, s_infotype[type]); 116 } 117 (void)fwrite(data + st, i - st + 1, 1, output); 118 st = i + 1; 119 newl = FALSE; 120 } 121 } 122 if(!newl) 123 fprintf(output, "%s%s ", timebuf, s_infotype[type]); 124 (void)fwrite(data + st, i - st + 1, 1, output); 125 } 126 newl = (size && (data[size - 1] != '\n')) ? TRUE : FALSE; 127 traced_data = FALSE; 128 break; 129 case CURLINFO_TEXT: 130 case CURLINFO_HEADER_IN: 131 if(!newl) 132 fprintf(output, "%s%s ", timebuf, s_infotype[type]); 133 (void)fwrite(data, size, 1, output); 134 newl = (size && (data[size - 1] != '\n')) ? TRUE : FALSE; 135 traced_data = FALSE; 136 break; 137 case CURLINFO_DATA_OUT: 138 case CURLINFO_DATA_IN: 139 case CURLINFO_SSL_DATA_IN: 140 case CURLINFO_SSL_DATA_OUT: 141 if(!traced_data) { 142 /* if the data is output to a tty and we're sending this debug trace 143 to stderr or stdout, we don't display the alert about the data not 144 being shown as the data _is_ shown then just not via this 145 function */ 146 if(!config->isatty || ((output != stderr) && (output != stdout))) { 147 if(!newl) 148 fprintf(output, "%s%s ", timebuf, s_infotype[type]); 149 fprintf(output, "[%zd bytes data]\n", size); 150 newl = FALSE; 151 traced_data = TRUE; 152 } 153 } 154 break; 155 default: /* nada */ 156 newl = FALSE; 157 traced_data = FALSE; 158 break; 159 } 160 161 return 0; 162 } 163 164 #ifdef CURL_DOES_CONVERSIONS 165 /* Special processing is needed for CURLINFO_HEADER_OUT blocks 166 * if they contain both headers and data (separated by CRLFCRLF). 167 * We dump the header text and then switch type to CURLINFO_DATA_OUT. 168 */ 169 if((type == CURLINFO_HEADER_OUT) && (size > 4)) { 170 size_t i; 171 for(i = 0; i < size - 4; i++) { 172 if(memcmp(&data[i], "\r\n\r\n", 4) == 0) { 173 /* dump everything through the CRLFCRLF as a sent header */ 174 text = "=> Send header"; 175 dump(timebuf, text, output, (unsigned char *)data, i + 4, 176 config->tracetype, type); 177 data += i + 3; 178 size -= i + 4; 179 type = CURLINFO_DATA_OUT; 180 data += 1; 181 break; 182 } 183 } 184 } 185 #endif /* CURL_DOES_CONVERSIONS */ 186 187 switch(type) { 188 case CURLINFO_TEXT: 189 fprintf(output, "%s== Info: %s", timebuf, data); 190 /* FALLTHROUGH */ 191 default: /* in case a new one is introduced to shock us */ 192 return 0; 193 194 case CURLINFO_HEADER_OUT: 195 text = "=> Send header"; 196 break; 197 case CURLINFO_DATA_OUT: 198 text = "=> Send data"; 199 break; 200 case CURLINFO_HEADER_IN: 201 text = "<= Recv header"; 202 break; 203 case CURLINFO_DATA_IN: 204 text = "<= Recv data"; 205 break; 206 case CURLINFO_SSL_DATA_IN: 207 text = "<= Recv SSL data"; 208 break; 209 case CURLINFO_SSL_DATA_OUT: 210 text = "=> Send SSL data"; 211 break; 212 } 213 214 dump(timebuf, text, output, (unsigned char *) data, size, config->tracetype, 215 type); 216 return 0; 217 } 218 219 static void dump(const char *timebuf, const char *text, 220 FILE *stream, const unsigned char *ptr, size_t size, 221 trace tracetype, curl_infotype infotype) 222 { 223 size_t i; 224 size_t c; 225 226 unsigned int width = 0x10; 227 228 if(tracetype == TRACE_ASCII) 229 /* without the hex output, we can fit more on screen */ 230 width = 0x40; 231 232 fprintf(stream, "%s%s, %zd bytes (0x%zx)\n", timebuf, text, size, size); 233 234 for(i = 0; i < size; i += width) { 235 236 fprintf(stream, "%04zx: ", i); 237 238 if(tracetype == TRACE_BIN) { 239 /* hex not disabled, show it */ 240 for(c = 0; c < width; c++) 241 if(i + c < size) 242 fprintf(stream, "%02x ", ptr[i + c]); 243 else 244 fputs(" ", stream); 245 } 246 247 for(c = 0; (c < width) && (i + c < size); c++) { 248 /* check for 0D0A; if found, skip past and start a new line of output */ 249 if((tracetype == TRACE_ASCII) && 250 (i + c + 1 < size) && (ptr[i + c] == 0x0D) && 251 (ptr[i + c + 1] == 0x0A)) { 252 i += (c + 2 - width); 253 break; 254 } 255 #ifdef CURL_DOES_CONVERSIONS 256 /* repeat the 0D0A check above but use the host encoding for CRLF */ 257 if((tracetype == TRACE_ASCII) && 258 (i + c + 1 < size) && (ptr[i + c] == '\r') && 259 (ptr[i + c + 1] == '\n')) { 260 i += (c + 2 - width); 261 break; 262 } 263 /* convert to host encoding and print this character */ 264 fprintf(stream, "%c", convert_char(infotype, ptr[i + c])); 265 #else 266 (void)infotype; 267 fprintf(stream, "%c", ((ptr[i + c] >= 0x20) && (ptr[i + c] < 0x80)) ? 268 ptr[i + c] : UNPRINTABLE_CHAR); 269 #endif /* CURL_DOES_CONVERSIONS */ 270 /* check again for 0D0A, to avoid an extra \n if it's at width */ 271 if((tracetype == TRACE_ASCII) && 272 (i + c + 2 < size) && (ptr[i + c + 1] == 0x0D) && 273 (ptr[i + c + 2] == 0x0A)) { 274 i += (c + 3 - width); 275 break; 276 } 277 } 278 fputc('\n', stream); /* newline */ 279 } 280 fflush(stream); 281 } 282 283