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.
12var PaintLogView;
13var PrintSourceEntriesAsText;
14var proxySettingsToString;
15
16// Start of anonymous namespace.
17(function() {
18
19PaintLogView = 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
27const INDENTATION_PX = 20;
28
29function 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
49function 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
59PrintSourceEntriesAsText = 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 */
148function 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
180function 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 */
237function 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 */
245function 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 */
271function 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 */
280function 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 */
303function stripCookiesAndLoginInfo(headers) {
304  return headers.map(stripCookieOrLoginInfo);
305}
306
307function 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
320function getTextForResponseHeadersExtraParam(entry, enableSecurityStripping) {
321  var headers = entry.params.headers;
322  if (enableSecurityStripping)
323    headers = stripCookiesAndLoginInfo(headers);
324  return indentLines(' --> ', headers);
325}
326
327function 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
348function 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
366proxySettingsToString = 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