toolkit/devtools/server/transport.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 // TODO: Get rid of this code once the marionette server loads transport.js as
     8 // an SDK module (see bug 1000814)
     9 (function (factory) { // Module boilerplate
    10   if (this.module && module.id.indexOf("transport") >= 0) { // require
    11     factory(require, exports);
    12   } else { // loadSubScript
    13     if (this.require) {
    14       factory(require, this);
    15     } else {
    16       const Cu = Components.utils;
    17       const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
    18       factory(devtools.require, this);
    19     }
    20   }
    21 }).call(this, function (require, exports) {
    23 "use strict";
    25 const { Cc, Ci, Cr, Cu } = require("chrome");
    26 const Services = require("Services");
    27 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
    28 const { dumpn } = DevToolsUtils;
    30 Cu.import("resource://gre/modules/NetUtil.jsm");
    32 /**
    33  * An adapter that handles data transfers between the debugger client and
    34  * server. It can work with both nsIPipe and nsIServerSocket transports so
    35  * long as the properly created input and output streams are specified.
    36  * (However, for intra-process connections, LocalDebuggerTransport, below,
    37  * is more efficient than using an nsIPipe pair with DebuggerTransport.)
    38  *
    39  * @param aInput nsIInputStream
    40  *        The input stream.
    41  * @param aOutput nsIAsyncOutputStream
    42  *        The output stream.
    43  *
    44  * Given a DebuggerTransport instance dt:
    45  * 1) Set dt.hooks to a packet handler object (described below).
    46  * 2) Call dt.ready() to begin watching for input packets.
    47  * 3) Call dt.send() to send packets as you please, and handle incoming
    48  *    packets passed to hook.onPacket.
    49  * 4) Call dt.close() to close the connection, and disengage from the event
    50  *    loop.
    51  *
    52  * A packet handler is an object with two methods:
    53  *
    54  * - onPacket(packet) - called when we have received a complete packet.
    55  *   |Packet| is the parsed form of the packet --- a JavaScript value, not
    56  *   a JSON-syntax string.
    57  *
    58  * - onClosed(status) - called when the connection is closed. |Status| is
    59  *   an nsresult, of the sort passed to nsIRequestObserver.
    60  *
    61  * Data is transferred as a JSON packet serialized into a string, with the
    62  * string length prepended to the packet, followed by a colon
    63  * ([length]:[packet]). The contents of the JSON packet are specified in
    64  * the Remote Debugging Protocol specification.
    65  */
    66 function DebuggerTransport(aInput, aOutput)
    67 {
    68   this._input = aInput;
    69   this._output = aOutput;
    71   this._converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
    72     .createInstance(Ci.nsIScriptableUnicodeConverter);
    73   this._converter.charset = "UTF-8";
    75   this._outgoing = "";
    76   this._incoming = "";
    78   this.hooks = null;
    79 }
    81 DebuggerTransport.prototype = {
    82   /**
    83    * Transmit a packet.
    84    *
    85    * This method returns immediately, without waiting for the entire
    86    * packet to be transmitted, registering event handlers as needed to
    87    * transmit the entire packet. Packets are transmitted in the order
    88    * they are passed to this method.
    89    */
    90   send: function DT_send(aPacket) {
    91     let data = JSON.stringify(aPacket);
    92     data = this._converter.ConvertFromUnicode(data);
    93     data = data.length + ':' + data;
    94     this._outgoing += data;
    95     this._flushOutgoing();
    96   },
    98   /**
    99    * Close the transport.
   100    */
   101   close: function DT_close() {
   102     this._input.close();
   103     this._output.close();
   104   },
   106   /**
   107    * Flush the outgoing stream.
   108    */
   109   _flushOutgoing: function DT_flushOutgoing() {
   110     if (this._outgoing.length > 0) {
   111       var threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
   112       this._output.asyncWait(this, 0, 0, threadManager.currentThread);
   113     }
   114   },
   116   onOutputStreamReady:
   117   DevToolsUtils.makeInfallible(function DT_onOutputStreamReady(aStream) {
   118     let written = 0;
   119     try {
   120       written = aStream.write(this._outgoing, this._outgoing.length);
   121     } catch(e if e.result == Cr.NS_BASE_STREAM_CLOSED) {
   122       dumpn("Connection closed.");
   123       this.close();
   124       return;
   125     }
   126     this._outgoing = this._outgoing.slice(written);
   127     this._flushOutgoing();
   128   }, "DebuggerTransport.prototype.onOutputStreamReady"),
   130   /**
   131    * Initialize the input stream for reading. Once this method has been
   132    * called, we watch for packets on the input stream, and pass them to
   133    * this.hook.onPacket.
   134    */
   135   ready: function DT_ready() {
   136     let pump = Cc["@mozilla.org/network/input-stream-pump;1"]
   137       .createInstance(Ci.nsIInputStreamPump);
   138     pump.init(this._input, -1, -1, 0, 0, false);
   139     pump.asyncRead(this, null);
   140   },
   142   // nsIStreamListener
   143   onStartRequest:
   144   DevToolsUtils.makeInfallible(function DT_onStartRequest(aRequest, aContext) {},
   145                  "DebuggerTransport.prototype.onStartRequest"),
   147   onStopRequest:
   148   DevToolsUtils.makeInfallible(function DT_onStopRequest(aRequest, aContext, aStatus) {
   149     this.close();
   150     if (this.hooks) {
   151       this.hooks.onClosed(aStatus);
   152       this.hooks = null;
   153     }
   154   }, "DebuggerTransport.prototype.onStopRequest"),
   156   onDataAvailable:
   157   DevToolsUtils.makeInfallible(function DT_onDataAvailable(aRequest, aContext,
   158                                              aStream, aOffset, aCount) {
   159     this._incoming += NetUtil.readInputStreamToString(aStream,
   160                                                       aStream.available());
   161     while (this._processIncoming()) {};
   162   }, "DebuggerTransport.prototype.onDataAvailable"),
   164   /**
   165    * Process incoming packets. Returns true if a packet has been received, either
   166    * if it was properly parsed or not. Returns false if the incoming stream does
   167    * not contain a full packet yet. After a proper packet is parsed, the dispatch
   168    * handler DebuggerTransport.hooks.onPacket is called with the packet as a
   169    * parameter.
   170    */
   171   _processIncoming: function DT__processIncoming() {
   172     // Well this is ugly.
   173     let sep = this._incoming.indexOf(':');
   174     if (sep < 0) {
   175       // Incoming packet length is too big anyway - drop the connection.
   176       if (this._incoming.length > 20) {
   177         this.close();
   178       }
   180       return false;
   181     }
   183     let count = this._incoming.substring(0, sep);
   184     // Check for a positive number with no garbage afterwards.
   185     if (!/^[0-9]+$/.exec(count)) {
   186       this.close();
   187       return false;
   188     }
   190     count = +count;
   191     if (this._incoming.length - (sep + 1) < count) {
   192       // Don't have a complete request yet.
   193       return false;
   194     }
   196     // We have a complete request, pluck it out of the data and parse it.
   197     this._incoming = this._incoming.substring(sep + 1);
   198     let packet = this._incoming.substring(0, count);
   199     this._incoming = this._incoming.substring(count);
   201     try {
   202       packet = this._converter.ConvertToUnicode(packet);
   203       var parsed = JSON.parse(packet);
   204     } catch(e) {
   205       let msg = "Error parsing incoming packet: " + packet + " (" + e + " - " + e.stack + ")";
   206       if (Cu.reportError) {
   207         Cu.reportError(msg);
   208       }
   209       dump(msg + "\n");
   210       return true;
   211     }
   213     if (dumpn.wantLogging) {
   214       dumpn("Got: " + JSON.stringify(parsed, null, 2));
   215     }
   216     let self = this;
   217     Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(function() {
   218       // Ensure the hooks are still around by the time this runs (they will go
   219       // away when the transport is closed).
   220       if (self.hooks) {
   221         self.hooks.onPacket(parsed);
   222       }
   223     }, "DebuggerTransport instance's this.hooks.onPacket"), 0);
   225     return true;
   226   }
   227 }
   229 exports.DebuggerTransport = DebuggerTransport;
   231 /**
   232  * An adapter that handles data transfers between the debugger client and
   233  * server when they both run in the same process. It presents the same API as
   234  * DebuggerTransport, but instead of transmitting serialized messages across a
   235  * connection it merely calls the packet dispatcher of the other side.
   236  *
   237  * @param aOther LocalDebuggerTransport
   238  *        The other endpoint for this debugger connection.
   239  *
   240  * @see DebuggerTransport
   241  */
   242 function LocalDebuggerTransport(aOther)
   243 {
   244   this.other = aOther;
   245   this.hooks = null;
   247   /*
   248    * A packet number, shared between this and this.other. This isn't used
   249    * by the protocol at all, but it makes the packet traces a lot easier to
   250    * follow.
   251    */
   252   this._serial = this.other ? this.other._serial : { count: 0 };
   253 }
   255 LocalDebuggerTransport.prototype = {
   256   /**
   257    * Transmit a message by directly calling the onPacket handler of the other
   258    * endpoint.
   259    */
   260   send: function LDT_send(aPacket) {
   261     let serial = this._serial.count++;
   262     if (dumpn.wantLogging) {
   263       /* Check 'from' first, as 'echo' packets have both. */
   264       if (aPacket.from) {
   265         dumpn("Packet " + serial + " sent from " + uneval(aPacket.from));
   266       } else if (aPacket.to) {
   267         dumpn("Packet " + serial + " sent to " + uneval(aPacket.to));
   268       }
   269     }
   270     this._deepFreeze(aPacket);
   271     let other = this.other;
   272     if (other) {
   273       Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(function() {
   274         // Avoid the cost of JSON.stringify() when logging is disabled.
   275         if (dumpn.wantLogging) {
   276           dumpn("Received packet " + serial + ": " + JSON.stringify(aPacket, null, 2));
   277         }
   278         if (other.hooks) {
   279           other.hooks.onPacket(aPacket);
   280         }
   281       }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"), 0);
   282     }
   283   },
   285   /**
   286    * Close the transport.
   287    */
   288   close: function LDT_close() {
   289     if (this.other) {
   290       // Remove the reference to the other endpoint before calling close(), to
   291       // avoid infinite recursion.
   292       let other = this.other;
   293       this.other = null;
   294       other.close();
   295     }
   296     if (this.hooks) {
   297       try {
   298         this.hooks.onClosed();
   299       } catch(ex) {
   300         Cu.reportError(ex);
   301       }
   302       this.hooks = null;
   303     }
   304   },
   306   /**
   307    * An empty method for emulating the DebuggerTransport API.
   308    */
   309   ready: function LDT_ready() {},
   311   /**
   312    * Helper function that makes an object fully immutable.
   313    */
   314   _deepFreeze: function LDT_deepFreeze(aObject) {
   315     Object.freeze(aObject);
   316     for (let prop in aObject) {
   317       // Freeze the properties that are objects, not on the prototype, and not
   318       // already frozen. Note that this might leave an unfrozen reference
   319       // somewhere in the object if there is an already frozen object containing
   320       // an unfrozen object.
   321       if (aObject.hasOwnProperty(prop) && typeof aObject === "object" &&
   322           !Object.isFrozen(aObject)) {
   323         this._deepFreeze(o[prop]);
   324       }
   325     }
   326   }
   327 };
   329 exports.LocalDebuggerTransport = LocalDebuggerTransport;
   331 /**
   332  * A transport for the debugging protocol that uses nsIMessageSenders to
   333  * exchange packets with servers running in child processes.
   334  *
   335  * In the parent process, |aSender| should be the nsIMessageSender for the
   336  * child process. In a child process, |aSender| should be the child process
   337  * message manager, which sends packets to the parent.
   338  *
   339  * aPrefix is a string included in the message names, to distinguish
   340  * multiple servers running in the same child process.
   341  *
   342  * This transport exchanges messages named 'debug:<prefix>:packet', where
   343  * <prefix> is |aPrefix|, whose data is the protocol packet.
   344  */
   345 function ChildDebuggerTransport(aSender, aPrefix) {
   346   this._sender = aSender.QueryInterface(Ci.nsIMessageSender);
   347   this._messageName = "debug:" + aPrefix + ":packet";
   348 }
   350 /*
   351  * To avoid confusion, we use 'message' to mean something that
   352  * nsIMessageSender conveys, and 'packet' to mean a remote debugging
   353  * protocol packet.
   354  */
   355 ChildDebuggerTransport.prototype = {
   356   constructor: ChildDebuggerTransport,
   358   hooks: null,
   360   ready: function () {
   361     this._sender.addMessageListener(this._messageName, this);
   362   },
   364   close: function () {
   365     this._sender.removeMessageListener(this._messageName, this);
   366     this.hooks.onClosed();
   367   },
   369   receiveMessage: function ({data}) {
   370     this.hooks.onPacket(data);
   371   },
   373   send: function (packet) {
   374     this._sender.sendAsyncMessage(this._messageName, packet);
   375   }
   376 };
   378 exports.ChildDebuggerTransport = ChildDebuggerTransport;
   380 });

mercurial