Javascript  |  435行  |  13.18 KB

// 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.
})();