Home | History | Annotate | Download | only in net_internals
      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