tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

har-exporter.js (8063B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
      8 const clipboardHelper = require("resource://devtools/shared/platform/clipboard.js");
      9 const {
     10  HarUtils,
     11 } = require("resource://devtools/client/netmonitor/src/har/har-utils.js");
     12 const {
     13  HarBuilder,
     14 } = require("resource://devtools/client/netmonitor/src/har/har-builder.js");
     15 
     16 const lazy = {};
     17 
     18 ChromeUtils.defineESModuleGetters(lazy, {
     19  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
     20 });
     21 
     22 var uid = 1;
     23 
     24 // Helper tracer. Should be generic sharable by other modules (bug 1171927)
     25 const trace = {
     26  log() {},
     27 };
     28 
     29 /**
     30 * This object represents the main public API designed to access
     31 * Network export logic. Clients, such as the Network panel itself,
     32 * should use this API to export collected HTTP data from the panel.
     33 */
     34 const HarExporter = {
     35  // Public API
     36 
     37  /**
     38   * Save collected HTTP data from the Network panel into HAR file.
     39   *
     40   * @param Object options
     41   *        Configuration object
     42   *
     43   * The following options are supported:
     44   *
     45   * - includeResponseBodies {Boolean}: If set to true, HTTP response bodies
     46   *   are also included in the HAR file (can produce significantly bigger
     47   *   amount of data).
     48   *
     49   * - items {Array}: List of Network requests to be exported.
     50   *
     51   * - jsonp {Boolean}: If set to true the export format is HARP (support
     52   *   for JSONP syntax).
     53   *
     54   * - jsonpCallback {String}: Default name of JSONP callback (used for
     55   *   HARP format).
     56   *
     57   * - compress {Boolean}: If set to true the final HAR file is zipped.
     58   *   This represents great disk-space optimization.
     59   *
     60   * - defaultFileName {String}: Default name of the target HAR file.
     61   *   The default file name supports the format specifier %date to output the
     62   *   current date/time.
     63   *
     64   * - defaultLogDir {String}: Default log directory for automated logs.
     65   *
     66   * - id {String}: ID of the page (used in the HAR file).
     67   *
     68   * - title {String}: Title of the page (used in the HAR file).
     69   *
     70   * - forceExport {Boolean}: The result HAR file is created even if
     71   *   there are no HTTP entries.
     72   *
     73   * - isSingleRequest {Boolean}: Set to true if only a single request.
     74   */
     75  async save(options) {
     76    // Set default options related to save operation.
     77    const defaultFileName = Services.prefs.getCharPref(
     78      "devtools.netmonitor.har.defaultFileName"
     79    );
     80    const compress = Services.prefs.getBoolPref(
     81      "devtools.netmonitor.har.compress"
     82    );
     83 
     84    trace.log("HarExporter.save; " + defaultFileName, options);
     85 
     86    let data = await this.fetchHarData(options);
     87 
     88    const host = new URL(options.connector.currentTarget.url);
     89 
     90    if (typeof options.isSingleRequest != "boolean") {
     91      options.isSingleRequest = false;
     92    }
     93 
     94    const fileName = HarUtils.getHarFileName(
     95      defaultFileName,
     96      options.jsonp,
     97      compress,
     98      host.hostname,
     99      options.isSingleRequest && options.items.length
    100        ? new URL(options.items[0].url).pathname
    101        : ""
    102    );
    103 
    104    data = new TextEncoder().encode(data);
    105 
    106    if (compress) {
    107      const file = await DevToolsUtils.showSaveFileDialog(window, fileName, [
    108        "*.zip",
    109      ]);
    110      this._zip(file, fileName.replace(/\.zip$/, ""), data.buffer);
    111    } else {
    112      DevToolsUtils.saveAs(window, data, fileName);
    113    }
    114  },
    115 
    116  /**
    117   * Helper to save the har file into a .zip file
    118   *
    119   * @param {nsIFIle} file The final zip file
    120   * @param {string} fileName Name of the har file within the zip file
    121   * @param {ArrayBuffer} buffer Content of the har file
    122   */
    123  _zip(file, fileName, buffer) {
    124    const ZipWriter = Components.Constructor(
    125      "@mozilla.org/zipwriter;1",
    126      "nsIZipWriter"
    127    );
    128    const zipW = new ZipWriter();
    129 
    130    file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, lazy.FileUtils.PERMS_FILE);
    131 
    132    // Open the file in write mode only and reset the size of any existing file
    133    const MODE_WRONLY = 0x02;
    134    const MODE_TRUNCATE = 0x20;
    135    zipW.open(file, MODE_WRONLY | MODE_TRUNCATE);
    136 
    137    const stream = Cc[
    138      "@mozilla.org/io/arraybuffer-input-stream;1"
    139    ].createInstance(Ci.nsIArrayBufferInputStream);
    140    stream.setData(buffer, 0, buffer.byteLength);
    141 
    142    // Needs to be in microseconds for some reason.
    143    const time = Date.now() * 1000;
    144    zipW.addEntryStream(fileName, time, 0, stream, false);
    145    zipW.close();
    146  },
    147 
    148  /**
    149   * Copy HAR string into the clipboard.
    150   *
    151   * @param Object options
    152   *        Configuration object, see save() for detailed description.
    153   */
    154  copy(options) {
    155    return this.fetchHarData(options).then(jsonString => {
    156      clipboardHelper.copyString(jsonString);
    157      return jsonString;
    158    });
    159  },
    160 
    161  /**
    162   * Get HAR data as JSON object.
    163   *
    164   * @param Object options
    165   *        Configuration object, see save() for detailed description.
    166   */
    167  getHar(options) {
    168    return this.fetchHarData(options).then(data => {
    169      return data ? JSON.parse(data) : null;
    170    });
    171  },
    172 
    173  // Helpers
    174 
    175  fetchHarData(options) {
    176    // Generate page ID
    177    options.id = options.id || uid++;
    178 
    179    // Set default generic HAR export options.
    180    if (typeof options.jsonp != "boolean") {
    181      options.jsonp = Services.prefs.getBoolPref(
    182        "devtools.netmonitor.har.jsonp"
    183      );
    184    }
    185    if (typeof options.includeResponseBodies != "boolean") {
    186      options.includeResponseBodies = Services.prefs.getBoolPref(
    187        "devtools.netmonitor.har.includeResponseBodies"
    188      );
    189    }
    190    if (typeof options.jsonpCallback != "boolean") {
    191      options.jsonpCallback = Services.prefs.getCharPref(
    192        "devtools.netmonitor.har.jsonpCallback"
    193      );
    194    }
    195    if (typeof options.forceExport != "boolean") {
    196      options.forceExport = Services.prefs.getBoolPref(
    197        "devtools.netmonitor.har.forceExport"
    198      );
    199    }
    200    if (typeof options.supportsMultiplePages != "boolean") {
    201      options.supportsMultiplePages = Services.prefs.getBoolPref(
    202        "devtools.netmonitor.har.multiple-pages"
    203      );
    204    }
    205 
    206    // Build HAR object.
    207    return this.buildHarData(options)
    208      .then(har => {
    209        // Do not export an empty HAR file, unless the user
    210        // explicitly says so (using the forceExport option).
    211        if (!har.log.entries.length && !options.forceExport) {
    212          return Promise.resolve();
    213        }
    214 
    215        let jsonString = this.stringify(har);
    216        if (!jsonString) {
    217          return Promise.resolve();
    218        }
    219 
    220        // If JSONP is wanted, wrap the string in a function call
    221        if (options.jsonp) {
    222          // This callback name is also used in HAR Viewer by default.
    223          // http://www.softwareishard.com/har/viewer/
    224          const callbackName = options.jsonpCallback || "onInputData";
    225          jsonString = callbackName + "(" + jsonString + ");";
    226        }
    227 
    228        return jsonString;
    229      })
    230      .catch(function onError(err) {
    231        console.error(err);
    232      });
    233  },
    234 
    235  /**
    236   * Build HAR data object. This object contains all HTTP data
    237   * collected by the Network panel. The process is asynchronous
    238   * since it can involve additional RDP communication (e.g. resolving
    239   * long strings).
    240   */
    241  async buildHarData(options) {
    242    // Disconnect from redux actions/store.
    243    options.connector.enableActions(false);
    244 
    245    // Build HAR object from collected data.
    246    const builder = new HarBuilder(options);
    247    const result = await builder.build();
    248 
    249    // Connect to redux actions again.
    250    options.connector.enableActions(true);
    251 
    252    return result;
    253  },
    254 
    255  /**
    256   * Build JSON string from the HAR data object.
    257   */
    258  stringify(har) {
    259    if (!har) {
    260      return null;
    261    }
    262 
    263    try {
    264      return JSON.stringify(har, null, "  ");
    265    } catch (err) {
    266      console.error(err);
    267      return undefined;
    268    }
    269  },
    270 };
    271 
    272 // Exports from this module
    273 exports.HarExporter = HarExporter;