// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * TODO(eroman): This needs better presentation, and cleaner code. This * implementation is more of a transitionary step as * the old net-internals is replaced. */ // TODO(eroman): these functions should use lower-case names. var PaintLogView; var PrintSourceEntriesAsText; var proxySettingsToString; // Start of anonymous namespace. (function() { PaintLogView = function(sourceEntries, node) { for (var i = 0; i < sourceEntries.length; ++i) { if (i != 0) addNode(node, 'hr'); addSourceEntry_(node, sourceEntries[i]); } } const INDENTATION_PX = 20; function addSourceEntry_(node, sourceEntry) { var div = addNode(node, 'div'); div.className = 'logSourceEntry'; var p = addNode(div, 'p'); var nobr = addNode(p, 'nobr'); addTextNode(nobr, sourceEntry.getDescription()); var p2 = addNode(div, 'p'); var nobr2 = addNode(p2, 'nobr'); var logEntries = sourceEntry.getLogEntries(); var startDate = g_browser.convertTimeTicksToDate(logEntries[0].time); addTextNode(nobr2, 'Start Time: ' + startDate.toLocaleString()); var pre = addNode(div, 'pre'); addTextNode(pre, PrintSourceEntriesAsText(logEntries)); } function canCollapseBeginWithEnd(beginEntry) { return beginEntry && beginEntry.isBegin() && beginEntry.end && beginEntry.end.index == beginEntry.index + 1 && (!beginEntry.orig.params || !beginEntry.end.orig.params) && beginEntry.orig.wasPassivelyCaptured == beginEntry.end.orig.wasPassivelyCaptured; } PrintSourceEntriesAsText = function(sourceEntries) { var entries = LogGroupEntry.createArrayFrom(sourceEntries); if (entries.length == 0) return ''; var startDate = g_browser.convertTimeTicksToDate(entries[0].orig.time); var startTime = startDate.getTime(); var tablePrinter = new TablePrinter(); for (var i = 0; i < entries.length; ++i) { var entry = entries[i]; // Avoid printing the END for a BEGIN that was immediately before, unless // both have extra parameters. if (!entry.isEnd() || !canCollapseBeginWithEnd(entry.begin)) { tablePrinter.addRow(); // Annotate this entry with "(P)" if it was passively captured. tablePrinter.addCell(entry.orig.wasPassivelyCaptured ? '(P) ' : ''); tablePrinter.addCell('t='); var date = g_browser.convertTimeTicksToDate(entry.orig.time) ; var tCell = tablePrinter.addCell(date.getTime()); tCell.alignRight = true; tablePrinter.addCell(' [st='); var stCell = tablePrinter.addCell(date.getTime() - startTime); stCell.alignRight = true; tablePrinter.addCell('] '); var indentationStr = makeRepeatedString(' ', entry.getDepth() * 3); var mainCell = tablePrinter.addCell(indentationStr + getTextForEvent(entry)); tablePrinter.addCell(' '); // Get the elapsed time. if (entry.isBegin()) { tablePrinter.addCell('[dt='); var dt = '?'; // Definite time. if (entry.end) { dt = entry.end.orig.time - entry.orig.time; } var dtCell = tablePrinter.addCell(dt); dtCell.alignRight = true; tablePrinter.addCell(']'); } else { mainCell.allowOverflow = true; } } // Output the extra parameters. if (entry.orig.params != undefined) { // Add a continuation row for each line of text from the extra parameters. var extraParamsText = getTextForExtraParams( entry.orig, g_browser.getSecurityStripping()); var extraParamsTextLines = extraParamsText.split('\n'); for (var j = 0; j < extraParamsTextLines.length; ++j) { tablePrinter.addRow(); tablePrinter.addCell(''); // Empty passive annotation. tablePrinter.addCell(''); // No t=. tablePrinter.addCell(''); tablePrinter.addCell(''); // No st=. tablePrinter.addCell(''); tablePrinter.addCell(' '); var mainExtraCell = tablePrinter.addCell(indentationStr + extraParamsTextLines[j]); mainExtraCell.allowOverflow = true; } } } // Format the table for fixed-width text. return tablePrinter.toText(0); } /** * |hexString| must be a string of hexadecimal characters with no whitespace, * whose length is a multiple of two. Returns a string spanning multiple lines, * with the hexadecimal characters from |hexString| on the left, in groups of * two, and their corresponding ASCII characters on the right. * * |asciiCharsPerLine| specifies how many ASCII characters will be put on each * line of the output string. */ function formatHexString(hexString, asciiCharsPerLine) { // Number of transferred bytes in a line of output. Length of a // line is roughly 4 times larger. var hexCharsPerLine = 2 * asciiCharsPerLine; var out = []; for (var i = 0; i < hexString.length; i += hexCharsPerLine) { var hexLine = ''; var asciiLine = ''; for (var j = i; j < i + hexCharsPerLine && j < hexString.length; j += 2) { var hex = hexString.substr(j, 2); hexLine += hex + ' '; var charCode = parseInt(hex, 16); // For ASCII codes 32 though 126, display the corresponding // characters. Use a space for nulls, and a period for // everything else. if (charCode >= 0x20 && charCode <= 0x7E) { asciiLine += String.fromCharCode(charCode); } else if (charCode == 0x00) { asciiLine += ' '; } else { asciiLine += '.'; } } // Max sure the ASCII text on last line of output lines up with previous // lines. hexLine += makeRepeatedString(' ', 3 * asciiCharsPerLine - hexLine.length); out.push(' ' + hexLine + ' ' + asciiLine); } return out.join('\n'); } function getTextForExtraParams(entry, enableSecurityStripping) { // Format the extra parameters (use a custom formatter for certain types, // but default to displaying as JSON). switch (entry.type) { case LogEventType.HTTP_TRANSACTION_SEND_REQUEST_HEADERS: case LogEventType.HTTP_TRANSACTION_SEND_TUNNEL_HEADERS: return getTextForRequestHeadersExtraParam(entry, enableSecurityStripping); case LogEventType.HTTP_TRANSACTION_READ_RESPONSE_HEADERS: case LogEventType.HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS: return getTextForResponseHeadersExtraParam(entry, enableSecurityStripping); case LogEventType.PROXY_CONFIG_CHANGED: return getTextForProxyConfigChangedExtraParam(entry); default: var out = []; for (var k in entry.params) { if (k == 'headers' && entry.params[k] instanceof Array) { out.push( getTextForResponseHeadersExtraParam(entry, enableSecurityStripping)); continue; } var value = entry.params[k]; // For transferred bytes, display the bytes in hex and ASCII. if (k == 'hex_encoded_bytes') { out.push(' --> ' + k + ' ='); out.push(formatHexString(value, 20)); continue; } var paramStr = ' --> ' + k + ' = ' + JSON.stringify(value); // Append the symbolic name for certain constants. (This relies // on particular naming of event parameters to infer the type). if (typeof value == 'number') { if (k == 'net_error') { paramStr += ' (' + getNetErrorSymbolicString(value) + ')'; } else if (k == 'load_flags') { paramStr += ' (' + getLoadFlagSymbolicString(value) + ')'; } } out.push(paramStr); } return out.join('\n'); } } /** * Returns the name for netError. * * Example: getNetErrorSymbolicString(-105) would return * "NAME_NOT_RESOLVED". */ function getNetErrorSymbolicString(netError) { return getKeyWithValue(NetError, netError); } /** * Returns the set of LoadFlags that make up the integer |loadFlag|. * For example: getLoadFlagSymbolicString( */ function getLoadFlagSymbolicString(loadFlag) { // Load flag of 0 means "NORMAL". Special case this, since and-ing with // 0 is always going to be false. if (loadFlag == 0) return getKeyWithValue(LoadFlag, loadFlag); var matchingLoadFlagNames = []; for (var k in LoadFlag) { if (loadFlag & LoadFlag[k]) matchingLoadFlagNames.push(k); } return matchingLoadFlagNames.join(' | '); } /** * Indent |lines| by |start|. * * For example, if |start| = ' -> ' and |lines| = ['line1', 'line2', 'line3'] * the output will be: * * " -> line1\n" + * " line2\n" + * " line3" */ function indentLines(start, lines) { return start + lines.join('\n' + makeRepeatedString(' ', start.length)); } /** * Removes a cookie or unencrypted login information from a single HTTP header * line, if present, and returns the modified line. Otherwise, just returns * the original line. */ function stripCookieOrLoginInfo(line) { var patterns = [ // Cookie patterns /^set-cookie:/i, /^set-cookie2:/i, /^cookie:/i, // Unencrypted authentication patterns /^authorization: \S*/i, /^proxy-authorization: \S*/i]; for (var i = 0; i < patterns.length; i++) { var match = patterns[i].exec(line); if (match != null) return match + ' [value was stripped]'; } return line; } /** * Removes all cookie and unencrypted login text from a list of HTTP * header lines. */ function stripCookiesAndLoginInfo(headers) { return headers.map(stripCookieOrLoginInfo); } function getTextForRequestHeadersExtraParam(entry, enableSecurityStripping) { var params = entry.params; // Strip the trailing CRLF that params.line contains. var lineWithoutCRLF = params.line.replace(/\r\n$/g, ''); var headers = params.headers; if (enableSecurityStripping) headers = stripCookiesAndLoginInfo(headers); return indentLines(' --> ', [lineWithoutCRLF].concat(headers)); } function getTextForResponseHeadersExtraParam(entry, enableSecurityStripping) { var headers = entry.params.headers; if (enableSecurityStripping) headers = stripCookiesAndLoginInfo(headers); return indentLines(' --> ', headers); } function getTextForProxyConfigChangedExtraParam(entry) { var params = entry.params; var out = ''; var indentation = ' '; if (params.old_config) { var oldConfigString = proxySettingsToString(params.old_config); // The previous configuration may not be present in the case of // the initial proxy settings fetch. out += ' --> old_config =\n' + indentLines(indentation, oldConfigString.split('\n')); out += '\n'; } var newConfigString = proxySettingsToString(params.new_config); out += ' --> new_config =\n' + indentLines(indentation, newConfigString.split('\n')); return out; } function getTextForEvent(entry) { var text = ''; if (entry.isBegin() && canCollapseBeginWithEnd(entry)) { // Don't prefix with '+' if we are going to collapse the END event. text = ' '; } else if (entry.isBegin()) { text = '+' + text; } else if (entry.isEnd()) { text = '-' + text; } else { text = ' '; } text += getKeyWithValue(LogEventType, entry.orig.type); return text; } proxySettingsToString = function(config) { if (!config) return ''; // The proxy settings specify up to three major fallback choices // (auto-detect, custom pac url, or manual settings). // We enumerate these to a list so we can later number them. var modes = []; // Output any automatic settings. if (config.auto_detect) modes.push(['Auto-detect']); if (config.pac_url) modes.push(['PAC script: ' + config.pac_url]); // Output any manual settings. if (config.single_proxy || config.proxy_per_scheme) { var lines = []; if (config.single_proxy) { lines.push('Proxy server: ' + config.single_proxy); } else if (config.proxy_per_scheme) { for (var urlScheme in config.proxy_per_scheme) { if (urlScheme != 'fallback') { lines.push('Proxy server for ' + urlScheme.toUpperCase() + ': ' + config.proxy_per_scheme[urlScheme]); } } if (config.proxy_per_scheme.fallback) { lines.push('Proxy server for everything else: ' + config.proxy_per_scheme.fallback); } } // Output any proxy bypass rules. if (config.bypass_list) { if (config.reverse_bypass) { lines.push('Reversed bypass list: '); } else { lines.push('Bypass list: '); } for (var i = 0; i < config.bypass_list.length; ++i) lines.push(' ' + config.bypass_list[i]); } modes.push(lines); } // If we didn't find any proxy settings modes, we are using DIRECT. if (modes.length < 1) return 'Use DIRECT connections.'; // If there was just one mode, don't bother numbering it. if (modes.length == 1) return modes[0].join('\n'); // Otherwise concatenate all of the modes into a numbered list // (which correspond with the fallback order). var result = []; for (var i = 0; i < modes.length; ++i) result.push(indentLines('(' + (i + 1) + ') ', modes[i])); return result.join('\n'); }; // End of anonymous namespace. })();