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