1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * TODO(eroman): This needs better presentation, and cleaner code. This 7 * implementation is more of a transitionary step as 8 * the old net-internals is replaced. 9 */ 10 11 // TODO(eroman): these functions should use lower-case names. 12 var PaintLogView; 13 var PrintSourceEntriesAsText; 14 var proxySettingsToString; 15 16 // Start of anonymous namespace. 17 (function() { 18 19 PaintLogView = function(sourceEntries, node) { 20 for (var i = 0; i < sourceEntries.length; ++i) { 21 if (i != 0) 22 addNode(node, 'hr'); 23 addSourceEntry_(node, sourceEntries[i]); 24 } 25 } 26 27 const INDENTATION_PX = 20; 28 29 function addSourceEntry_(node, sourceEntry) { 30 var div = addNode(node, 'div'); 31 div.className = 'logSourceEntry'; 32 33 var p = addNode(div, 'p'); 34 var nobr = addNode(p, 'nobr'); 35 36 addTextNode(nobr, sourceEntry.getDescription()); 37 38 var p2 = addNode(div, 'p'); 39 var nobr2 = addNode(p2, 'nobr'); 40 41 var logEntries = sourceEntry.getLogEntries(); 42 var startDate = g_browser.convertTimeTicksToDate(logEntries[0].time); 43 addTextNode(nobr2, 'Start Time: ' + startDate.toLocaleString()); 44 45 var pre = addNode(div, 'pre'); 46 addTextNode(pre, PrintSourceEntriesAsText(logEntries)); 47 } 48 49 function canCollapseBeginWithEnd(beginEntry) { 50 return beginEntry && 51 beginEntry.isBegin() && 52 beginEntry.end && 53 beginEntry.end.index == beginEntry.index + 1 && 54 (!beginEntry.orig.params || !beginEntry.end.orig.params) && 55 beginEntry.orig.wasPassivelyCaptured == 56 beginEntry.end.orig.wasPassivelyCaptured; 57 } 58 59 PrintSourceEntriesAsText = function(sourceEntries) { 60 var entries = LogGroupEntry.createArrayFrom(sourceEntries); 61 if (entries.length == 0) 62 return ''; 63 64 var startDate = g_browser.convertTimeTicksToDate(entries[0].orig.time); 65 var startTime = startDate.getTime(); 66 67 var tablePrinter = new TablePrinter(); 68 69 for (var i = 0; i < entries.length; ++i) { 70 var entry = entries[i]; 71 72 // Avoid printing the END for a BEGIN that was immediately before, unless 73 // both have extra parameters. 74 if (!entry.isEnd() || !canCollapseBeginWithEnd(entry.begin)) { 75 tablePrinter.addRow(); 76 77 // Annotate this entry with "(P)" if it was passively captured. 78 tablePrinter.addCell(entry.orig.wasPassivelyCaptured ? '(P) ' : ''); 79 80 tablePrinter.addCell('t='); 81 var date = g_browser.convertTimeTicksToDate(entry.orig.time) ; 82 var tCell = tablePrinter.addCell(date.getTime()); 83 tCell.alignRight = true; 84 tablePrinter.addCell(' [st='); 85 var stCell = tablePrinter.addCell(date.getTime() - startTime); 86 stCell.alignRight = true; 87 tablePrinter.addCell('] '); 88 89 var indentationStr = makeRepeatedString(' ', entry.getDepth() * 3); 90 var mainCell = 91 tablePrinter.addCell(indentationStr + getTextForEvent(entry)); 92 tablePrinter.addCell(' '); 93 94 // Get the elapsed time. 95 if (entry.isBegin()) { 96 tablePrinter.addCell('[dt='); 97 var dt = '?'; 98 // Definite time. 99 if (entry.end) { 100 dt = entry.end.orig.time - entry.orig.time; 101 } 102 var dtCell = tablePrinter.addCell(dt); 103 dtCell.alignRight = true; 104 105 tablePrinter.addCell(']'); 106 } else { 107 mainCell.allowOverflow = true; 108 } 109 } 110 111 // Output the extra parameters. 112 if (entry.orig.params != undefined) { 113 // Add a continuation row for each line of text from the extra parameters. 114 var extraParamsText = getTextForExtraParams( 115 entry.orig, 116 g_browser.getSecurityStripping()); 117 var extraParamsTextLines = extraParamsText.split('\n'); 118 119 for (var j = 0; j < extraParamsTextLines.length; ++j) { 120 tablePrinter.addRow(); 121 tablePrinter.addCell(''); // Empty passive annotation. 122 tablePrinter.addCell(''); // No t=. 123 tablePrinter.addCell(''); 124 tablePrinter.addCell(''); // No st=. 125 tablePrinter.addCell(''); 126 tablePrinter.addCell(' '); 127 128 var mainExtraCell = 129 tablePrinter.addCell(indentationStr + extraParamsTextLines[j]); 130 mainExtraCell.allowOverflow = true; 131 } 132 } 133 } 134 135 // Format the table for fixed-width text. 136 return tablePrinter.toText(0); 137 } 138 139 /** 140 * |hexString| must be a string of hexadecimal characters with no whitespace, 141 * whose length is a multiple of two. Returns a string spanning multiple lines, 142 * with the hexadecimal characters from |hexString| on the left, in groups of 143 * two, and their corresponding ASCII characters on the right. 144 * 145 * |asciiCharsPerLine| specifies how many ASCII characters will be put on each 146 * line of the output string. 147 */ 148 function formatHexString(hexString, asciiCharsPerLine) { 149 // Number of transferred bytes in a line of output. Length of a 150 // line is roughly 4 times larger. 151 var hexCharsPerLine = 2 * asciiCharsPerLine; 152 var out = []; 153 for (var i = 0; i < hexString.length; i += hexCharsPerLine) { 154 var hexLine = ''; 155 var asciiLine = ''; 156 for (var j = i; j < i + hexCharsPerLine && j < hexString.length; j += 2) { 157 var hex = hexString.substr(j, 2); 158 hexLine += hex + ' '; 159 var charCode = parseInt(hex, 16); 160 // For ASCII codes 32 though 126, display the corresponding 161 // characters. Use a space for nulls, and a period for 162 // everything else. 163 if (charCode >= 0x20 && charCode <= 0x7E) { 164 asciiLine += String.fromCharCode(charCode); 165 } else if (charCode == 0x00) { 166 asciiLine += ' '; 167 } else { 168 asciiLine += '.'; 169 } 170 } 171 172 // Max sure the ASCII text on last line of output lines up with previous 173 // lines. 174 hexLine += makeRepeatedString(' ', 3 * asciiCharsPerLine - hexLine.length); 175 out.push(' ' + hexLine + ' ' + asciiLine); 176 } 177 return out.join('\n'); 178 } 179 180 function getTextForExtraParams(entry, enableSecurityStripping) { 181 // Format the extra parameters (use a custom formatter for certain types, 182 // but default to displaying as JSON). 183 switch (entry.type) { 184 case LogEventType.HTTP_TRANSACTION_SEND_REQUEST_HEADERS: 185 case LogEventType.HTTP_TRANSACTION_SEND_TUNNEL_HEADERS: 186 return getTextForRequestHeadersExtraParam(entry, enableSecurityStripping); 187 188 case LogEventType.HTTP_TRANSACTION_READ_RESPONSE_HEADERS: 189 case LogEventType.HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS: 190 return getTextForResponseHeadersExtraParam(entry, 191 enableSecurityStripping); 192 193 case LogEventType.PROXY_CONFIG_CHANGED: 194 return getTextForProxyConfigChangedExtraParam(entry); 195 196 default: 197 var out = []; 198 for (var k in entry.params) { 199 if (k == 'headers' && entry.params[k] instanceof Array) { 200 out.push( 201 getTextForResponseHeadersExtraParam(entry, 202 enableSecurityStripping)); 203 continue; 204 } 205 var value = entry.params[k]; 206 // For transferred bytes, display the bytes in hex and ASCII. 207 if (k == 'hex_encoded_bytes') { 208 out.push(' --> ' + k + ' ='); 209 out.push(formatHexString(value, 20)); 210 continue; 211 } 212 213 var paramStr = ' --> ' + k + ' = ' + JSON.stringify(value); 214 215 // Append the symbolic name for certain constants. (This relies 216 // on particular naming of event parameters to infer the type). 217 if (typeof value == 'number') { 218 if (k == 'net_error') { 219 paramStr += ' (' + getNetErrorSymbolicString(value) + ')'; 220 } else if (k == 'load_flags') { 221 paramStr += ' (' + getLoadFlagSymbolicString(value) + ')'; 222 } 223 } 224 225 out.push(paramStr); 226 } 227 return out.join('\n'); 228 } 229 } 230 231 /** 232 * Returns the name for netError. 233 * 234 * Example: getNetErrorSymbolicString(-105) would return 235 * "NAME_NOT_RESOLVED". 236 */ 237 function getNetErrorSymbolicString(netError) { 238 return getKeyWithValue(NetError, netError); 239 } 240 241 /** 242 * Returns the set of LoadFlags that make up the integer |loadFlag|. 243 * For example: getLoadFlagSymbolicString( 244 */ 245 function getLoadFlagSymbolicString(loadFlag) { 246 // Load flag of 0 means "NORMAL". Special case this, since and-ing with 247 // 0 is always going to be false. 248 if (loadFlag == 0) 249 return getKeyWithValue(LoadFlag, loadFlag); 250 251 var matchingLoadFlagNames = []; 252 253 for (var k in LoadFlag) { 254 if (loadFlag & LoadFlag[k]) 255 matchingLoadFlagNames.push(k); 256 } 257 258 return matchingLoadFlagNames.join(' | '); 259 } 260 261 /** 262 * Indent |lines| by |start|. 263 * 264 * For example, if |start| = ' -> ' and |lines| = ['line1', 'line2', 'line3'] 265 * the output will be: 266 * 267 * " -> line1\n" + 268 * " line2\n" + 269 * " line3" 270 */ 271 function indentLines(start, lines) { 272 return start + lines.join('\n' + makeRepeatedString(' ', start.length)); 273 } 274 275 /** 276 * Removes a cookie or unencrypted login information from a single HTTP header 277 * line, if present, and returns the modified line. Otherwise, just returns 278 * the original line. 279 */ 280 function stripCookieOrLoginInfo(line) { 281 var patterns = [ 282 // Cookie patterns 283 /^set-cookie:/i, 284 /^set-cookie2:/i, 285 /^cookie:/i, 286 287 // Unencrypted authentication patterns 288 /^authorization: \S*/i, 289 /^proxy-authorization: \S*/i]; 290 291 for (var i = 0; i < patterns.length; i++) { 292 var match = patterns[i].exec(line); 293 if (match != null) 294 return match + ' [value was stripped]'; 295 } 296 return line; 297 } 298 299 /** 300 * Removes all cookie and unencrypted login text from a list of HTTP 301 * header lines. 302 */ 303 function stripCookiesAndLoginInfo(headers) { 304 return headers.map(stripCookieOrLoginInfo); 305 } 306 307 function getTextForRequestHeadersExtraParam(entry, enableSecurityStripping) { 308 var params = entry.params; 309 310 // Strip the trailing CRLF that params.line contains. 311 var lineWithoutCRLF = params.line.replace(/\r\n$/g, ''); 312 313 var headers = params.headers; 314 if (enableSecurityStripping) 315 headers = stripCookiesAndLoginInfo(headers); 316 317 return indentLines(' --> ', [lineWithoutCRLF].concat(headers)); 318 } 319 320 function getTextForResponseHeadersExtraParam(entry, enableSecurityStripping) { 321 var headers = entry.params.headers; 322 if (enableSecurityStripping) 323 headers = stripCookiesAndLoginInfo(headers); 324 return indentLines(' --> ', headers); 325 } 326 327 function getTextForProxyConfigChangedExtraParam(entry) { 328 var params = entry.params; 329 var out = ''; 330 var indentation = ' '; 331 332 if (params.old_config) { 333 var oldConfigString = proxySettingsToString(params.old_config); 334 // The previous configuration may not be present in the case of 335 // the initial proxy settings fetch. 336 out += ' --> old_config =\n' + 337 indentLines(indentation, oldConfigString.split('\n')); 338 out += '\n'; 339 } 340 341 var newConfigString = proxySettingsToString(params.new_config); 342 out += ' --> new_config =\n' + 343 indentLines(indentation, newConfigString.split('\n')); 344 345 return out; 346 } 347 348 function getTextForEvent(entry) { 349 var text = ''; 350 351 if (entry.isBegin() && canCollapseBeginWithEnd(entry)) { 352 // Don't prefix with '+' if we are going to collapse the END event. 353 text = ' '; 354 } else if (entry.isBegin()) { 355 text = '+' + text; 356 } else if (entry.isEnd()) { 357 text = '-' + text; 358 } else { 359 text = ' '; 360 } 361 362 text += getKeyWithValue(LogEventType, entry.orig.type); 363 return text; 364 } 365 366 proxySettingsToString = function(config) { 367 if (!config) 368 return ''; 369 370 // The proxy settings specify up to three major fallback choices 371 // (auto-detect, custom pac url, or manual settings). 372 // We enumerate these to a list so we can later number them. 373 var modes = []; 374 375 // Output any automatic settings. 376 if (config.auto_detect) 377 modes.push(['Auto-detect']); 378 if (config.pac_url) 379 modes.push(['PAC script: ' + config.pac_url]); 380 381 // Output any manual settings. 382 if (config.single_proxy || config.proxy_per_scheme) { 383 var lines = []; 384 385 if (config.single_proxy) { 386 lines.push('Proxy server: ' + config.single_proxy); 387 } else if (config.proxy_per_scheme) { 388 for (var urlScheme in config.proxy_per_scheme) { 389 if (urlScheme != 'fallback') { 390 lines.push('Proxy server for ' + urlScheme.toUpperCase() + ': ' + 391 config.proxy_per_scheme[urlScheme]); 392 } 393 } 394 if (config.proxy_per_scheme.fallback) { 395 lines.push('Proxy server for everything else: ' + 396 config.proxy_per_scheme.fallback); 397 } 398 } 399 400 // Output any proxy bypass rules. 401 if (config.bypass_list) { 402 if (config.reverse_bypass) { 403 lines.push('Reversed bypass list: '); 404 } else { 405 lines.push('Bypass list: '); 406 } 407 408 for (var i = 0; i < config.bypass_list.length; ++i) 409 lines.push(' ' + config.bypass_list[i]); 410 } 411 412 modes.push(lines); 413 } 414 415 // If we didn't find any proxy settings modes, we are using DIRECT. 416 if (modes.length < 1) 417 return 'Use DIRECT connections.'; 418 419 // If there was just one mode, don't bother numbering it. 420 if (modes.length == 1) 421 return modes[0].join('\n'); 422 423 // Otherwise concatenate all of the modes into a numbered list 424 // (which correspond with the fallback order). 425 var result = []; 426 for (var i = 0; i < modes.length; ++i) 427 result.push(indentLines('(' + (i + 1) + ') ', modes[i])); 428 429 return result.join('\n'); 430 }; 431 432 // End of anonymous namespace. 433 })(); 434 435