www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

httpd.js (154638B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et: */
      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/. */
      6 
      7 /*
      8  * An implementation of an HTTP server both as a loadable script and as an XPCOM
      9  * component.  See the accompanying README file for user documentation on
     10  * httpd.js.
     11  */
     12 
     13 this.EXPORTED_SYMBOLS = [
     14   "HTTP_400",
     15   "HTTP_401",
     16   "HTTP_402",
     17   "HTTP_403",
     18   "HTTP_404",
     19   "HTTP_405",
     20   "HTTP_406",
     21   "HTTP_407",
     22   "HTTP_408",
     23   "HTTP_409",
     24   "HTTP_410",
     25   "HTTP_411",
     26   "HTTP_412",
     27   "HTTP_413",
     28   "HTTP_414",
     29   "HTTP_415",
     30   "HTTP_417",
     31   "HTTP_500",
     32   "HTTP_501",
     33   "HTTP_502",
     34   "HTTP_503",
     35   "HTTP_504",
     36   "HTTP_505",
     37   "HttpError",
     38   "HttpServer",
     39 ];
     40 
     41 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
     42 
     43 const Cc = Components.classes;
     44 const Ci = Components.interfaces;
     45 const Cr = Components.results;
     46 const Cu = Components.utils;
     47 const CC = Components.Constructor;
     48 
     49 const PR_UINT32_MAX = Math.pow(2, 32) - 1;
     50 
     51 /** True if debugging output is enabled, false otherwise. */
     52 var DEBUG = false; // non-const *only* so tweakable in server tests
     53 
     54 /** True if debugging output should be timestamped. */
     55 var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests
     56 
     57 var gGlobalObject = this;
     58 
     59 /**
     60  * Asserts that the given condition holds.  If it doesn't, the given message is
     61  * dumped, a stack trace is printed, and an exception is thrown to attempt to
     62  * stop execution (which unfortunately must rely upon the exception not being
     63  * accidentally swallowed by the code that uses it).
     64  */
     65 function NS_ASSERT(cond, msg)
     66 {
     67   if (DEBUG && !cond)
     68   {
     69     dumpn("###!!!");
     70     dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!"));
     71     dumpn("###!!! Stack follows:");
     72 
     73     var stack = new Error().stack.split(/\n/);
     74     dumpn(stack.map(function(val) { return "###!!!   " + val; }).join("\n"));
     75 
     76     throw Cr.NS_ERROR_ABORT;
     77   }
     78 }
     79 
     80 /** Constructs an HTTP error object. */
     81 this.HttpError = function HttpError(code, description)
     82 {
     83   this.code = code;
     84   this.description = description;
     85 }
     86 HttpError.prototype =
     87 {
     88   toString: function()
     89   {
     90     return this.code + " " + this.description;
     91   }
     92 };
     93 
     94 /**
     95  * Errors thrown to trigger specific HTTP server responses.
     96  */
     97 this.HTTP_400 = new HttpError(400, "Bad Request");
     98 this.HTTP_401 = new HttpError(401, "Unauthorized");
     99 this.HTTP_402 = new HttpError(402, "Payment Required");
    100 this.HTTP_403 = new HttpError(403, "Forbidden");
    101 this.HTTP_404 = new HttpError(404, "Not Found");
    102 this.HTTP_405 = new HttpError(405, "Method Not Allowed");
    103 this.HTTP_406 = new HttpError(406, "Not Acceptable");
    104 this.HTTP_407 = new HttpError(407, "Proxy Authentication Required");
    105 this.HTTP_408 = new HttpError(408, "Request Timeout");
    106 this.HTTP_409 = new HttpError(409, "Conflict");
    107 this.HTTP_410 = new HttpError(410, "Gone");
    108 this.HTTP_411 = new HttpError(411, "Length Required");
    109 this.HTTP_412 = new HttpError(412, "Precondition Failed");
    110 this.HTTP_413 = new HttpError(413, "Request Entity Too Large");
    111 this.HTTP_414 = new HttpError(414, "Request-URI Too Long");
    112 this.HTTP_415 = new HttpError(415, "Unsupported Media Type");
    113 this.HTTP_417 = new HttpError(417, "Expectation Failed");
    114 
    115 this.HTTP_500 = new HttpError(500, "Internal Server Error");
    116 this.HTTP_501 = new HttpError(501, "Not Implemented");
    117 this.HTTP_502 = new HttpError(502, "Bad Gateway");
    118 this.HTTP_503 = new HttpError(503, "Service Unavailable");
    119 this.HTTP_504 = new HttpError(504, "Gateway Timeout");
    120 this.HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
    121 
    122 /** Creates a hash with fields corresponding to the values in arr. */
    123 function array2obj(arr)
    124 {
    125   var obj = {};
    126   for (var i = 0; i < arr.length; i++)
    127     obj[arr[i]] = arr[i];
    128   return obj;
    129 }
    130 
    131 /** Returns an array of the integers x through y, inclusive. */
    132 function range(x, y)
    133 {
    134   var arr = [];
    135   for (var i = x; i <= y; i++)
    136     arr.push(i);
    137   return arr;
    138 }
    139 
    140 /** An object (hash) whose fields are the numbers of all HTTP error codes. */
    141 const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505)));
    142 
    143 
    144 /**
    145  * The character used to distinguish hidden files from non-hidden files, a la
    146  * the leading dot in Apache.  Since that mechanism also hides files from
    147  * easy display in LXR, ls output, etc. however, we choose instead to use a
    148  * suffix character.  If a requested file ends with it, we append another
    149  * when getting the file on the server.  If it doesn't, we just look up that
    150  * file.  Therefore, any file whose name ends with exactly one of the character
    151  * is "hidden" and available for use by the server.
    152  */
    153 const HIDDEN_CHAR = "^";
    154 
    155 /**
    156  * The file name suffix indicating the file containing overridden headers for
    157  * a requested file.
    158  */
    159 const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR;
    160 
    161 /** Type used to denote SJS scripts for CGI-like functionality. */
    162 const SJS_TYPE = "sjs";
    163 
    164 /** Base for relative timestamps produced by dumpn(). */
    165 var firstStamp = 0;
    166 
    167 /** dump(str) with a trailing "\n" -- only outputs if DEBUG. */
    168 function dumpn(str)
    169 {
    170   if (DEBUG)
    171   {
    172     var prefix = "HTTPD-INFO | ";
    173     if (DEBUG_TIMESTAMP)
    174     {
    175       if (firstStamp === 0)
    176         firstStamp = Date.now();
    177 
    178       var elapsed = Date.now() - firstStamp; // milliseconds
    179       var min = Math.floor(elapsed / 60000);
    180       var sec = (elapsed % 60000) / 1000;
    181 
    182       if (sec < 10)
    183         prefix += min + ":0" + sec.toFixed(3) + " | ";
    184       else
    185         prefix += min + ":" + sec.toFixed(3) + " | ";
    186     }
    187 
    188     dump(prefix + str + "\n");
    189   }
    190 }
    191 
    192 /** Dumps the current JS stack if DEBUG. */
    193 function dumpStack()
    194 {
    195   // peel off the frames for dumpStack() and Error()
    196   var stack = new Error().stack.split(/\n/).slice(2);
    197   stack.forEach(dumpn);
    198 }
    199 
    200 
    201 /** The XPCOM thread manager. */
    202 var gThreadManager = null;
    203 
    204 /** The XPCOM prefs service. */
    205 var gRootPrefBranch = null;
    206 function getRootPrefBranch()
    207 {
    208   if (!gRootPrefBranch)
    209   {
    210     gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"]
    211                         .getService(Ci.nsIPrefBranch);
    212   }
    213   return gRootPrefBranch;
    214 }
    215 
    216 /**
    217  * JavaScript constructors for commonly-used classes; precreating these is a
    218  * speedup over doing the same from base principles.  See the docs at
    219  * http://developer.mozilla.org/en/docs/Components.Constructor for details.
    220  */
    221 const ServerSocket = CC("@mozilla.org/network/server-socket;1",
    222                         "nsIServerSocket",
    223                         "init");
    224 const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
    225                                  "nsIScriptableInputStream",
    226                                  "init");
    227 const Pipe = CC("@mozilla.org/pipe;1",
    228                 "nsIPipe",
    229                 "init");
    230 const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
    231                            "nsIFileInputStream",
    232                            "init");
    233 const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1",
    234                                 "nsIConverterInputStream",
    235                                 "init");
    236 const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1",
    237                                "nsIWritablePropertyBag2");
    238 const SupportsString = CC("@mozilla.org/supports-string;1",
    239                           "nsISupportsString");
    240 
    241 /* These two are non-const only so a test can overwrite them. */
    242 var BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
    243                            "nsIBinaryInputStream",
    244                            "setInputStream");
    245 var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
    246                             "nsIBinaryOutputStream",
    247                             "setOutputStream");
    248 
    249 /**
    250  * Returns the RFC 822/1123 representation of a date.
    251  *
    252  * @param date : Number
    253  *   the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT
    254  * @returns string
    255  *   the representation of the given date
    256  */
    257 function toDateString(date)
    258 {
    259   //
    260   // rfc1123-date = wkday "," SP date1 SP time SP "GMT"
    261   // date1        = 2DIGIT SP month SP 4DIGIT
    262   //                ; day month year (e.g., 02 Jun 1982)
    263   // time         = 2DIGIT ":" 2DIGIT ":" 2DIGIT
    264   //                ; 00:00:00 - 23:59:59
    265   // wkday        = "Mon" | "Tue" | "Wed"
    266   //              | "Thu" | "Fri" | "Sat" | "Sun"
    267   // month        = "Jan" | "Feb" | "Mar" | "Apr"
    268   //              | "May" | "Jun" | "Jul" | "Aug"
    269   //              | "Sep" | "Oct" | "Nov" | "Dec"
    270   //
    271 
    272   const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    273   const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
    274                         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
    275 
    276   /**
    277    * Processes a date and returns the encoded UTC time as a string according to
    278    * the format specified in RFC 2616.
    279    *
    280    * @param date : Date
    281    *   the date to process
    282    * @returns string
    283    *   a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
    284    */
    285   function toTime(date)
    286   {
    287     var hrs = date.getUTCHours();
    288     var rv  = (hrs < 10) ? "0" + hrs : hrs;
    289     
    290     var mins = date.getUTCMinutes();
    291     rv += ":";
    292     rv += (mins < 10) ? "0" + mins : mins;
    293 
    294     var secs = date.getUTCSeconds();
    295     rv += ":";
    296     rv += (secs < 10) ? "0" + secs : secs;
    297 
    298     return rv;
    299   }
    300 
    301   /**
    302    * Processes a date and returns the encoded UTC date as a string according to
    303    * the date1 format specified in RFC 2616.
    304    *
    305    * @param date : Date
    306    *   the date to process
    307    * @returns string
    308    *   a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
    309    */
    310   function toDate1(date)
    311   {
    312     var day = date.getUTCDate();
    313     var month = date.getUTCMonth();
    314     var year = date.getUTCFullYear();
    315 
    316     var rv = (day < 10) ? "0" + day : day;
    317     rv += " " + monthStrings[month];
    318     rv += " " + year;
    319 
    320     return rv;
    321   }
    322 
    323   date = new Date(date);
    324 
    325   const fmtString = "%wkday%, %date1% %time% GMT";
    326   var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]);
    327   rv = rv.replace("%time%", toTime(date));
    328   return rv.replace("%date1%", toDate1(date));
    329 }
    330 
    331 /**
    332  * Prints out a human-readable representation of the object o and its fields,
    333  * omitting those whose names begin with "_" if showMembers != true (to ignore
    334  * "private" properties exposed via getters/setters).
    335  */
    336 function printObj(o, showMembers)
    337 {
    338   var s = "******************************\n";
    339   s +=    "o = {\n";
    340   for (var i in o)
    341   {
    342     if (typeof(i) != "string" ||
    343         (showMembers || (i.length > 0 && i[0] != "_")))
    344       s+= "      " + i + ": " + o[i] + ",\n";
    345   }
    346   s +=    "    };\n";
    347   s +=    "******************************";
    348   dumpn(s);
    349 }
    350 
    351 /**
    352  * Instantiates a new HTTP server.
    353  */
    354 function nsHttpServer()
    355 {
    356   if (!gThreadManager)
    357     gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService();
    358 
    359   /** The port on which this server listens. */
    360   this._port = undefined;
    361 
    362   /** The socket associated with this. */
    363   this._socket = null;
    364 
    365   /** The handler used to process requests to this server. */
    366   this._handler = new ServerHandler(this);
    367 
    368   /** Naming information for this server. */
    369   this._identity = new ServerIdentity();
    370 
    371   /**
    372    * Indicates when the server is to be shut down at the end of the request.
    373    */
    374   this._doQuit = false;
    375 
    376   /**
    377    * True if the socket in this is closed (and closure notifications have been
    378    * sent and processed if the socket was ever opened), false otherwise.
    379    */
    380   this._socketClosed = true;
    381 
    382   /**
    383    * Used for tracking existing connections and ensuring that all connections
    384    * are properly cleaned up before server shutdown; increases by 1 for every
    385    * new incoming connection.
    386    */
    387   this._connectionGen = 0;
    388 
    389   /**
    390    * Hash of all open connections, indexed by connection number at time of
    391    * creation.
    392    */
    393   this._connections = {};
    394 }
    395 nsHttpServer.prototype =
    396 {
    397   classID: Components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"),
    398 
    399   // NSISERVERSOCKETLISTENER
    400 
    401   /**
    402    * Processes an incoming request coming in on the given socket and contained
    403    * in the given transport.
    404    *
    405    * @param socket : nsIServerSocket
    406    *   the socket through which the request was served
    407    * @param trans : nsISocketTransport
    408    *   the transport for the request/response
    409    * @see nsIServerSocketListener.onSocketAccepted
    410    */
    411   onSocketAccepted: function(socket, trans)
    412   {
    413     dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")");
    414 
    415     dumpn(">>> new connection on " + trans.host + ":" + trans.port);
    416 
    417     const SEGMENT_SIZE = 8192;
    418     const SEGMENT_COUNT = 1024;
    419     try
    420     {
    421       var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
    422                        .QueryInterface(Ci.nsIAsyncInputStream);
    423       var output = trans.openOutputStream(0, 0, 0);
    424     }
    425     catch (e)
    426     {
    427       dumpn("*** error opening transport streams: " + e);
    428       trans.close(Cr.NS_BINDING_ABORTED);
    429       return;
    430     }
    431 
    432     var connectionNumber = ++this._connectionGen;
    433 
    434     try
    435     {
    436       var conn = new Connection(input, output, this, socket.port, trans.port,
    437                                 connectionNumber);
    438       var reader = new RequestReader(conn);
    439 
    440       // XXX add request timeout functionality here!
    441 
    442       // Note: must use main thread here, or we might get a GC that will cause
    443       //       threadsafety assertions.  We really need to fix XPConnect so that
    444       //       you can actually do things in multi-threaded JS.  :-(
    445       input.asyncWait(reader, 0, 0, gThreadManager.mainThread);
    446     }
    447     catch (e)
    448     {
    449       // Assume this connection can't be salvaged and bail on it completely;
    450       // don't attempt to close it so that we can assert that any connection
    451       // being closed is in this._connections.
    452       dumpn("*** error in initial request-processing stages: " + e);
    453       trans.close(Cr.NS_BINDING_ABORTED);
    454       return;
    455     }
    456 
    457     this._connections[connectionNumber] = conn;
    458     dumpn("*** starting connection " + connectionNumber);
    459   },
    460 
    461   /**
    462    * Called when the socket associated with this is closed.
    463    *
    464    * @param socket : nsIServerSocket
    465    *   the socket being closed
    466    * @param status : nsresult
    467    *   the reason the socket stopped listening (NS_BINDING_ABORTED if the server
    468    *   was stopped using nsIHttpServer.stop)
    469    * @see nsIServerSocketListener.onStopListening
    470    */
    471   onStopListening: function(socket, status)
    472   {
    473     dumpn(">>> shutting down server on port " + socket.port);
    474     for (var n in this._connections) {
    475       if (!this._connections[n]._requestStarted) {
    476         this._connections[n].close();
    477       }
    478     }
    479     this._socketClosed = true;
    480     if (this._hasOpenConnections()) {
    481       dumpn("*** open connections!!!");
    482     }
    483     if (!this._hasOpenConnections())
    484     {
    485       dumpn("*** no open connections, notifying async from onStopListening");
    486 
    487       // Notify asynchronously so that any pending teardown in stop() has a
    488       // chance to run first.
    489       var self = this;
    490       var stopEvent =
    491         {
    492           run: function()
    493           {
    494             dumpn("*** _notifyStopped async callback");
    495             self._notifyStopped();
    496           }
    497         };
    498       gThreadManager.currentThread
    499                     .dispatch(stopEvent, Ci.nsIThread.DISPATCH_NORMAL);
    500     }
    501   },
    502 
    503   // NSIHTTPSERVER
    504 
    505   //
    506   // see nsIHttpServer.start
    507   //
    508   start: function(port)
    509   {
    510     this._start(port, "localhost")
    511   },
    512 
    513   _start: function(port, host)
    514   {
    515     if (this._socket)
    516       throw Cr.NS_ERROR_ALREADY_INITIALIZED;
    517 
    518     this._port = port;
    519     this._doQuit = this._socketClosed = false;
    520 
    521     this._host = host;
    522 
    523     // The listen queue needs to be long enough to handle
    524     // network.http.max-persistent-connections-per-server or
    525     // network.http.max-persistent-connections-per-proxy concurrent
    526     // connections, plus a safety margin in case some other process is
    527     // talking to the server as well.
    528     var prefs = getRootPrefBranch();
    529     var maxConnections = 5 + Math.max(
    530       prefs.getIntPref("network.http.max-persistent-connections-per-server"),
    531       prefs.getIntPref("network.http.max-persistent-connections-per-proxy"));
    532 
    533     try
    534     {
    535       var loopback = true;
    536       if (this._host != "127.0.0.1" && this._host != "localhost") {
    537         var loopback = false;
    538       }
    539 
    540       // When automatically selecting a port, sometimes the chosen port is
    541       // "blocked" from clients. We don't want to use these ports because
    542       // tests will intermittently fail. So, we simply keep trying to to
    543       // get a server socket until a valid port is obtained. We limit
    544       // ourselves to finite attempts just so we don't loop forever.
    545       var ios = Cc["@mozilla.org/network/io-service;1"]
    546                   .getService(Ci.nsIIOService);
    547       var socket;
    548       for (var i = 100; i; i--)
    549       {
    550         var temp = new ServerSocket(this._port,
    551                                     loopback, // true = localhost, false = everybody
    552                                     maxConnections);
    553 
    554         var allowed = ios.allowPort(temp.port, "http");
    555         if (!allowed)
    556         {
    557           dumpn(">>>Warning: obtained ServerSocket listens on a blocked " +
    558                 "port: " + temp.port);
    559         }
    560 
    561         if (!allowed && this._port == -1)
    562         {
    563           dumpn(">>>Throwing away ServerSocket with bad port.");
    564           temp.close();
    565           continue;
    566         }
    567 
    568         socket = temp;
    569         break;
    570       }
    571 
    572       if (!socket) {
    573         throw new Error("No socket server available. Are there no available ports?");
    574       }
    575 
    576       dumpn(">>> listening on port " + socket.port + ", " + maxConnections +
    577             " pending connections");
    578       socket.asyncListen(this);
    579       this._port = socket.port;
    580       this._identity._initialize(socket.port, host, true);
    581       this._socket = socket;
    582     }
    583     catch (e)
    584     {
    585       dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
    586       throw Cr.NS_ERROR_NOT_AVAILABLE;
    587     }
    588   },
    589 
    590   //
    591   // see nsIHttpServer.stop
    592   //
    593   stop: function(callback)
    594   {
    595     if (!callback)
    596       throw Cr.NS_ERROR_NULL_POINTER;
    597     if (!this._socket)
    598       throw Cr.NS_ERROR_UNEXPECTED;
    599 
    600     this._stopCallback = typeof callback === "function"
    601                        ? callback
    602                        : function() { callback.onStopped(); };
    603 
    604     dumpn(">>> stopping listening on port " + this._socket.port);
    605     this._socket.close();
    606     this._socket = null;
    607 
    608     // We can't have this identity any more, and the port on which we're running
    609     // this server now could be meaningless the next time around.
    610     this._identity._teardown();
    611 
    612     this._doQuit = false;
    613 
    614     // socket-close notification and pending request completion happen async
    615   },
    616 
    617   //
    618   // see nsIHttpServer.registerFile
    619   //
    620   registerFile: function(path, file)
    621   {
    622     if (file && (!file.exists() || file.isDirectory()))
    623       throw Cr.NS_ERROR_INVALID_ARG;
    624 
    625     this._handler.registerFile(path, file);
    626   },
    627 
    628   //
    629   // see nsIHttpServer.registerDirectory
    630   //
    631   registerDirectory: function(path, directory)
    632   {
    633     // XXX true path validation!
    634     if (path.charAt(0) != "/" ||
    635         path.charAt(path.length - 1) != "/" ||
    636         (directory &&
    637          (!directory.exists() || !directory.isDirectory())))
    638       throw Cr.NS_ERROR_INVALID_ARG;
    639 
    640     // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping
    641     //     exists!
    642 
    643     this._handler.registerDirectory(path, directory);
    644   },
    645 
    646   //
    647   // see nsIHttpServer.registerPathHandler
    648   //
    649   registerPathHandler: function(path, handler)
    650   {
    651     this._handler.registerPathHandler(path, handler);
    652   },
    653 
    654   //
    655   // see nsIHttpServer.registerPrefixHandler
    656   //
    657   registerPrefixHandler: function(prefix, handler)
    658   {
    659     this._handler.registerPrefixHandler(prefix, handler);
    660   },
    661 
    662   //
    663   // see nsIHttpServer.registerErrorHandler
    664   //
    665   registerErrorHandler: function(code, handler)
    666   {
    667     this._handler.registerErrorHandler(code, handler);
    668   },
    669 
    670   //
    671   // see nsIHttpServer.setIndexHandler
    672   //
    673   setIndexHandler: function(handler)
    674   {
    675     this._handler.setIndexHandler(handler);
    676   },
    677 
    678   //
    679   // see nsIHttpServer.registerContentType
    680   //
    681   registerContentType: function(ext, type)
    682   {
    683     this._handler.registerContentType(ext, type);
    684   },
    685 
    686   //
    687   // see nsIHttpServer.serverIdentity
    688   //
    689   get identity()
    690   {
    691     return this._identity;
    692   },
    693 
    694   //
    695   // see nsIHttpServer.getState
    696   //
    697   getState: function(path, k)
    698   {
    699     return this._handler._getState(path, k);
    700   },
    701 
    702   //
    703   // see nsIHttpServer.setState
    704   //
    705   setState: function(path, k, v)
    706   {
    707     return this._handler._setState(path, k, v);
    708   },
    709 
    710   //
    711   // see nsIHttpServer.getSharedState
    712   //
    713   getSharedState: function(k)
    714   {
    715     return this._handler._getSharedState(k);
    716   },
    717 
    718   //
    719   // see nsIHttpServer.setSharedState
    720   //
    721   setSharedState: function(k, v)
    722   {
    723     return this._handler._setSharedState(k, v);
    724   },
    725 
    726   //
    727   // see nsIHttpServer.getObjectState
    728   //
    729   getObjectState: function(k)
    730   {
    731     return this._handler._getObjectState(k);
    732   },
    733 
    734   //
    735   // see nsIHttpServer.setObjectState
    736   //
    737   setObjectState: function(k, v)
    738   {
    739     return this._handler._setObjectState(k, v);
    740   },
    741 
    742 
    743   // NSISUPPORTS
    744 
    745   //
    746   // see nsISupports.QueryInterface
    747   //
    748   QueryInterface: function(iid)
    749   {
    750     if (iid.equals(Ci.nsIHttpServer) ||
    751         iid.equals(Ci.nsIServerSocketListener) ||
    752         iid.equals(Ci.nsISupports))
    753       return this;
    754 
    755     throw Cr.NS_ERROR_NO_INTERFACE;
    756   },
    757 
    758 
    759   // NON-XPCOM PUBLIC API
    760 
    761   /**
    762    * Returns true iff this server is not running (and is not in the process of
    763    * serving any requests still to be processed when the server was last
    764    * stopped after being run).
    765    */
    766   isStopped: function()
    767   {
    768     return this._socketClosed && !this._hasOpenConnections();
    769   },
    770 
    771   // PRIVATE IMPLEMENTATION
    772 
    773   /** True if this server has any open connections to it, false otherwise. */
    774   _hasOpenConnections: function()
    775   {
    776     //
    777     // If we have any open connections, they're tracked as numeric properties on
    778     // |this._connections|.  The non-standard __count__ property could be used
    779     // to check whether there are any properties, but standard-wise, even
    780     // looking forward to ES5, there's no less ugly yet still O(1) way to do
    781     // this.
    782     //
    783     for (var n in this._connections)
    784       return true;
    785     return false;
    786   },
    787 
    788   /** Calls the server-stopped callback provided when stop() was called. */
    789   _notifyStopped: function()
    790   {
    791     NS_ASSERT(this._stopCallback !== null, "double-notifying?");
    792     NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now");
    793 
    794     //
    795     // NB: We have to grab this now, null out the member, *then* call the
    796     //     callback here, or otherwise the callback could (indirectly) futz with
    797     //     this._stopCallback by starting and immediately stopping this, at
    798     //     which point we'd be nulling out a field we no longer have a right to
    799     //     modify.
    800     //
    801     var callback = this._stopCallback;
    802     this._stopCallback = null;
    803     try
    804     {
    805       callback();
    806     }
    807     catch (e)
    808     {
    809       // not throwing because this is specified as being usually (but not
    810       // always) asynchronous
    811       dump("!!! error running onStopped callback: " + e + "\n");
    812     }
    813   },
    814 
    815   /**
    816    * Notifies this server that the given connection has been closed.
    817    *
    818    * @param connection : Connection
    819    *   the connection that was closed
    820    */
    821   _connectionClosed: function(connection)
    822   {
    823     NS_ASSERT(connection.number in this._connections,
    824               "closing a connection " + this + " that we never added to the " +
    825               "set of open connections?");
    826     NS_ASSERT(this._connections[connection.number] === connection,
    827               "connection number mismatch?  " +
    828               this._connections[connection.number]);
    829     delete this._connections[connection.number];
    830 
    831     // Fire a pending server-stopped notification if it's our responsibility.
    832     if (!this._hasOpenConnections() && this._socketClosed)
    833       this._notifyStopped();
    834     // Bug 508125: Add a GC here else we'll use gigabytes of memory running
    835     // mochitests. We can't rely on xpcshell doing an automated GC, as that
    836     // would interfere with testing GC stuff...
    837     Components.utils.forceGC();
    838   },
    839 
    840   /**
    841    * Requests that the server be shut down when possible.
    842    */
    843   _requestQuit: function()
    844   {
    845     dumpn(">>> requesting a quit");
    846     dumpStack();
    847     this._doQuit = true;
    848   }
    849 };
    850 
    851 this.HttpServer = nsHttpServer;
    852 
    853 //
    854 // RFC 2396 section 3.2.2:
    855 //
    856 // host        = hostname | IPv4address
    857 // hostname    = *( domainlabel "." ) toplabel [ "." ]
    858 // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
    859 // toplabel    = alpha | alpha *( alphanum | "-" ) alphanum
    860 // IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
    861 //
    862 
    863 const HOST_REGEX =
    864   new RegExp("^(?:" +
    865                // *( domainlabel "." )
    866                "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" +
    867                // toplabel
    868                "[a-z](?:[a-z0-9-]*[a-z0-9])?" +
    869              "|" +
    870                // IPv4 address 
    871                "\\d+\\.\\d+\\.\\d+\\.\\d+" +
    872              ")$",
    873              "i");
    874 
    875 
    876 /**
    877  * Represents the identity of a server.  An identity consists of a set of
    878  * (scheme, host, port) tuples denoted as locations (allowing a single server to
    879  * serve multiple sites or to be used behind both HTTP and HTTPS proxies for any
    880  * host/port).  Any incoming request must be to one of these locations, or it
    881  * will be rejected with an HTTP 400 error.  One location, denoted as the
    882  * primary location, is the location assigned in contexts where a location
    883  * cannot otherwise be endogenously derived, such as for HTTP/1.0 requests.
    884  *
    885  * A single identity may contain at most one location per unique host/port pair;
    886  * other than that, no restrictions are placed upon what locations may
    887  * constitute an identity.
    888  */
    889 function ServerIdentity()
    890 {
    891   /** The scheme of the primary location. */
    892   this._primaryScheme = "http";
    893 
    894   /** The hostname of the primary location. */
    895   this._primaryHost = "127.0.0.1"
    896 
    897   /** The port number of the primary location. */
    898   this._primaryPort = -1;
    899 
    900   /**
    901    * The current port number for the corresponding server, stored so that a new
    902    * primary location can always be set if the current one is removed.
    903    */
    904   this._defaultPort = -1;
    905 
    906   /**
    907    * Maps hosts to maps of ports to schemes, e.g. the following would represent
    908    * https://example.com:789/ and http://example.org/:
    909    *
    910    *   {
    911    *     "xexample.com": { 789: "https" },
    912    *     "xexample.org": { 80: "http" }
    913    *   }
    914    *
    915    * Note the "x" prefix on hostnames, which prevents collisions with special
    916    * JS names like "prototype".
    917    */
    918   this._locations = { "xlocalhost": {} };
    919 }
    920 ServerIdentity.prototype =
    921 {
    922   // NSIHTTPSERVERIDENTITY
    923 
    924   //
    925   // see nsIHttpServerIdentity.primaryScheme
    926   //
    927   get primaryScheme()
    928   {
    929     if (this._primaryPort === -1)
    930       throw Cr.NS_ERROR_NOT_INITIALIZED;
    931     return this._primaryScheme;
    932   },
    933 
    934   //
    935   // see nsIHttpServerIdentity.primaryHost
    936   //
    937   get primaryHost()
    938   {
    939     if (this._primaryPort === -1)
    940       throw Cr.NS_ERROR_NOT_INITIALIZED;
    941     return this._primaryHost;
    942   },
    943 
    944   //
    945   // see nsIHttpServerIdentity.primaryPort
    946   //
    947   get primaryPort()
    948   {
    949     if (this._primaryPort === -1)
    950       throw Cr.NS_ERROR_NOT_INITIALIZED;
    951     return this._primaryPort;
    952   },
    953 
    954   //
    955   // see nsIHttpServerIdentity.add
    956   //
    957   add: function(scheme, host, port)
    958   {
    959     this._validate(scheme, host, port);
    960 
    961     var entry = this._locations["x" + host];
    962     if (!entry)
    963       this._locations["x" + host] = entry = {};
    964 
    965     entry[port] = scheme;
    966   },
    967 
    968   //
    969   // see nsIHttpServerIdentity.remove
    970   //
    971   remove: function(scheme, host, port)
    972   {
    973     this._validate(scheme, host, port);
    974 
    975     var entry = this._locations["x" + host];
    976     if (!entry)
    977       return false;
    978 
    979     var present = port in entry;
    980     delete entry[port];
    981 
    982     if (this._primaryScheme == scheme &&
    983         this._primaryHost == host &&
    984         this._primaryPort == port &&
    985         this._defaultPort !== -1)
    986     {
    987       // Always keep at least one identity in existence at any time, unless
    988       // we're in the process of shutting down (the last condition above).
    989       this._primaryPort = -1;
    990       this._initialize(this._defaultPort, host, false);
    991     }
    992 
    993     return present;
    994   },
    995 
    996   //
    997   // see nsIHttpServerIdentity.has
    998   //
    999   has: function(scheme, host, port)
   1000   {
   1001     this._validate(scheme, host, port);
   1002 
   1003     return "x" + host in this._locations &&
   1004            scheme === this._locations["x" + host][port];
   1005   },
   1006 
   1007   //
   1008   // see nsIHttpServerIdentity.has
   1009   //
   1010   getScheme: function(host, port)
   1011   {
   1012     this._validate("http", host, port);
   1013 
   1014     var entry = this._locations["x" + host];
   1015     if (!entry)
   1016       return "";
   1017 
   1018     return entry[port] || "";
   1019   },
   1020 
   1021   //
   1022   // see nsIHttpServerIdentity.setPrimary
   1023   //
   1024   setPrimary: function(scheme, host, port)
   1025   {
   1026     this._validate(scheme, host, port);
   1027 
   1028     this.add(scheme, host, port);
   1029 
   1030     this._primaryScheme = scheme;
   1031     this._primaryHost = host;
   1032     this._primaryPort = port;
   1033   },
   1034 
   1035 
   1036   // NSISUPPORTS
   1037 
   1038   //
   1039   // see nsISupports.QueryInterface
   1040   //
   1041   QueryInterface: function(iid)
   1042   {
   1043     if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports))
   1044       return this;
   1045 
   1046     throw Cr.NS_ERROR_NO_INTERFACE;
   1047   },
   1048 
   1049 
   1050   // PRIVATE IMPLEMENTATION
   1051 
   1052   /**
   1053    * Initializes the primary name for the corresponding server, based on the
   1054    * provided port number.
   1055    */
   1056   _initialize: function(port, host, addSecondaryDefault)
   1057   {
   1058     this._host = host;
   1059     if (this._primaryPort !== -1)
   1060       this.add("http", host, port);
   1061     else
   1062       this.setPrimary("http", "localhost", port);
   1063     this._defaultPort = port;
   1064 
   1065     // Only add this if we're being called at server startup
   1066     if (addSecondaryDefault && host != "127.0.0.1")
   1067       this.add("http", "127.0.0.1", port);
   1068   },
   1069 
   1070   /**
   1071    * Called at server shutdown time, unsets the primary location only if it was
   1072    * the default-assigned location and removes the default location from the
   1073    * set of locations used.
   1074    */
   1075   _teardown: function()
   1076   {
   1077     if (this._host != "127.0.0.1") {
   1078       // Not the default primary location, nothing special to do here
   1079       this.remove("http", "127.0.0.1", this._defaultPort);
   1080     }
   1081     
   1082     // This is a *very* tricky bit of reasoning here; make absolutely sure the
   1083     // tests for this code pass before you commit changes to it.
   1084     if (this._primaryScheme == "http" &&
   1085         this._primaryHost == this._host &&
   1086         this._primaryPort == this._defaultPort)
   1087     {
   1088       // Make sure we don't trigger the readding logic in .remove(), then remove
   1089       // the default location.
   1090       var port = this._defaultPort;
   1091       this._defaultPort = -1;
   1092       this.remove("http", this._host, port);
   1093 
   1094       // Ensure a server start triggers the setPrimary() path in ._initialize()
   1095       this._primaryPort = -1;
   1096     }
   1097     else
   1098     {
   1099       // No reason not to remove directly as it's not our primary location
   1100       this.remove("http", this._host, this._defaultPort);
   1101     }
   1102   },
   1103 
   1104   /**
   1105    * Ensures scheme, host, and port are all valid with respect to RFC 2396.
   1106    *
   1107    * @throws NS_ERROR_ILLEGAL_VALUE
   1108    *   if any argument doesn't match the corresponding production
   1109    */
   1110   _validate: function(scheme, host, port)
   1111   {
   1112     if (scheme !== "http" && scheme !== "https")
   1113     {
   1114       dumpn("*** server only supports http/https schemes: '" + scheme + "'");
   1115       dumpStack();
   1116       throw Cr.NS_ERROR_ILLEGAL_VALUE;
   1117     }
   1118     if (!HOST_REGEX.test(host))
   1119     {
   1120       dumpn("*** unexpected host: '" + host + "'");
   1121       throw Cr.NS_ERROR_ILLEGAL_VALUE;
   1122     }
   1123     if (port < 0 || port > 65535)
   1124     {
   1125       dumpn("*** unexpected port: '" + port + "'");
   1126       throw Cr.NS_ERROR_ILLEGAL_VALUE;
   1127     }
   1128   }
   1129 };
   1130 
   1131 
   1132 /**
   1133  * Represents a connection to the server (and possibly in the future the thread
   1134  * on which the connection is processed).
   1135  *
   1136  * @param input : nsIInputStream
   1137  *   stream from which incoming data on the connection is read
   1138  * @param output : nsIOutputStream
   1139  *   stream to write data out the connection
   1140  * @param server : nsHttpServer
   1141  *   the server handling the connection
   1142  * @param port : int
   1143  *   the port on which the server is running
   1144  * @param outgoingPort : int
   1145  *   the outgoing port used by this connection
   1146  * @param number : uint
   1147  *   a serial number used to uniquely identify this connection
   1148  */
   1149 function Connection(input, output, server, port, outgoingPort, number)
   1150 {
   1151   dumpn("*** opening new connection " + number + " on port " + outgoingPort);
   1152 
   1153   /** Stream of incoming data. */
   1154   this.input = input;
   1155 
   1156   /** Stream for outgoing data. */
   1157   this.output = output;
   1158 
   1159   /** The server associated with this request. */
   1160   this.server = server;
   1161 
   1162   /** The port on which the server is running. */
   1163   this.port = port;
   1164 
   1165   /** The outgoing poort used by this connection. */
   1166   this._outgoingPort = outgoingPort;
   1167 
   1168   /** The serial number of this connection. */
   1169   this.number = number;
   1170 
   1171   /**
   1172    * The request for which a response is being generated, null if the
   1173    * incoming request has not been fully received or if it had errors.
   1174    */
   1175   this.request = null;
   1176 
   1177   /** This allows a connection to disambiguate between a peer initiating a
   1178    *  close and the socket being forced closed on shutdown.
   1179    */
   1180   this._closed = false;
   1181 
   1182   /** State variable for debugging. */
   1183   this._processed = false;
   1184 
   1185   /** whether or not 1st line of request has been received */
   1186   this._requestStarted = false; 
   1187 }
   1188 Connection.prototype =
   1189 {
   1190   /** Closes this connection's input/output streams. */
   1191   close: function()
   1192   {
   1193     if (this._closed)
   1194         return;
   1195 
   1196     dumpn("*** closing connection " + this.number +
   1197           " on port " + this._outgoingPort);
   1198 
   1199     this.input.close();
   1200     this.output.close();
   1201     this._closed = true;
   1202 
   1203     var server = this.server;
   1204     server._connectionClosed(this);
   1205 
   1206     // If an error triggered a server shutdown, act on it now
   1207     if (server._doQuit)
   1208       server.stop(function() { /* not like we can do anything better */ });
   1209   },
   1210 
   1211   /**
   1212    * Initiates processing of this connection, using the data in the given
   1213    * request.
   1214    *
   1215    * @param request : Request
   1216    *   the request which should be processed
   1217    */
   1218   process: function(request)
   1219   {
   1220     NS_ASSERT(!this._closed && !this._processed);
   1221 
   1222     this._processed = true;
   1223 
   1224     this.request = request;
   1225     this.server._handler.handleResponse(this);
   1226   },
   1227 
   1228   /**
   1229    * Initiates processing of this connection, generating a response with the
   1230    * given HTTP error code.
   1231    *
   1232    * @param code : uint
   1233    *   an HTTP code, so in the range [0, 1000)
   1234    * @param request : Request
   1235    *   incomplete data about the incoming request (since there were errors
   1236    *   during its processing
   1237    */
   1238   processError: function(code, request)
   1239   {
   1240     NS_ASSERT(!this._closed && !this._processed);
   1241 
   1242     this._processed = true;
   1243     this.request = request;
   1244     this.server._handler.handleError(code, this);
   1245   },
   1246 
   1247   /** Converts this to a string for debugging purposes. */
   1248   toString: function()
   1249   {
   1250     return "<Connection(" + this.number +
   1251            (this.request ? ", " + this.request.path : "") +"): " +
   1252            (this._closed ? "closed" : "open") + ">";
   1253   },
   1254 
   1255   requestStarted: function()
   1256   {
   1257     this._requestStarted = true;
   1258   }
   1259 };
   1260 
   1261 
   1262 
   1263 /** Returns an array of count bytes from the given input stream. */
   1264 function readBytes(inputStream, count)
   1265 {
   1266   return new BinaryInputStream(inputStream).readByteArray(count);
   1267 }
   1268 
   1269 
   1270 
   1271 /** Request reader processing states; see RequestReader for details. */
   1272 const READER_IN_REQUEST_LINE = 0;
   1273 const READER_IN_HEADERS      = 1;
   1274 const READER_IN_BODY         = 2;
   1275 const READER_FINISHED        = 3;
   1276 
   1277 
   1278 /**
   1279  * Reads incoming request data asynchronously, does any necessary preprocessing,
   1280  * and forwards it to the request handler.  Processing occurs in three states:
   1281  *
   1282  *   READER_IN_REQUEST_LINE     Reading the request's status line
   1283  *   READER_IN_HEADERS          Reading headers in the request
   1284  *   READER_IN_BODY             Reading the body of the request
   1285  *   READER_FINISHED            Entire request has been read and processed
   1286  *
   1287  * During the first two stages, initial metadata about the request is gathered
   1288  * into a Request object.  Once the status line and headers have been processed,
   1289  * we start processing the body of the request into the Request.  Finally, when
   1290  * the entire body has been read, we create a Response and hand it off to the
   1291  * ServerHandler to be given to the appropriate request handler.
   1292  *
   1293  * @param connection : Connection
   1294  *   the connection for the request being read
   1295  */
   1296 function RequestReader(connection)
   1297 {
   1298   /** Connection metadata for this request. */
   1299   this._connection = connection;
   1300 
   1301   /**
   1302    * A container providing line-by-line access to the raw bytes that make up the
   1303    * data which has been read from the connection but has not yet been acted
   1304    * upon (by passing it to the request handler or by extracting request
   1305    * metadata from it).
   1306    */
   1307   this._data = new LineData();
   1308 
   1309   /**
   1310    * The amount of data remaining to be read from the body of this request.
   1311    * After all headers in the request have been read this is the value in the
   1312    * Content-Length header, but as the body is read its value decreases to zero.
   1313    */
   1314   this._contentLength = 0;
   1315 
   1316   /** The current state of parsing the incoming request. */
   1317   this._state = READER_IN_REQUEST_LINE;
   1318 
   1319   /** Metadata constructed from the incoming request for the request handler. */
   1320   this._metadata = new Request(connection.port);
   1321 
   1322   /**
   1323    * Used to preserve state if we run out of line data midway through a
   1324    * multi-line header.  _lastHeaderName stores the name of the header, while
   1325    * _lastHeaderValue stores the value we've seen so far for the header.
   1326    *
   1327    * These fields are always either both undefined or both strings.
   1328    */
   1329   this._lastHeaderName = this._lastHeaderValue = undefined;
   1330 }
   1331 RequestReader.prototype =
   1332 {
   1333   // NSIINPUTSTREAMCALLBACK
   1334 
   1335   /**
   1336    * Called when more data from the incoming request is available.  This method
   1337    * then reads the available data from input and deals with that data as
   1338    * necessary, depending upon the syntax of already-downloaded data.
   1339    *
   1340    * @param input : nsIAsyncInputStream
   1341    *   the stream of incoming data from the connection
   1342    */
   1343   onInputStreamReady: function(input)
   1344   {
   1345     dumpn("*** onInputStreamReady(input=" + input + ") on thread " +
   1346           gThreadManager.currentThread + " (main is " +
   1347           gThreadManager.mainThread + ")");
   1348     dumpn("*** this._state == " + this._state);
   1349 
   1350     // Handle cases where we get more data after a request error has been
   1351     // discovered but *before* we can close the connection.
   1352     var data = this._data;
   1353     if (!data)
   1354       return;
   1355 
   1356     try
   1357     {
   1358       data.appendBytes(readBytes(input, input.available()));
   1359     }
   1360     catch (e)
   1361     {
   1362       if (streamClosed(e))
   1363       {
   1364         dumpn("*** WARNING: unexpected error when reading from socket; will " +
   1365               "be treated as if the input stream had been closed");
   1366         dumpn("*** WARNING: actual error was: " + e);
   1367       }
   1368 
   1369       // We've lost a race -- input has been closed, but we're still expecting
   1370       // to read more data.  available() will throw in this case, and since
   1371       // we're dead in the water now, destroy the connection.
   1372       dumpn("*** onInputStreamReady called on a closed input, destroying " +
   1373             "connection");
   1374       this._connection.close();
   1375       return;
   1376     }
   1377 
   1378     switch (this._state)
   1379     {
   1380       default:
   1381         NS_ASSERT(false, "invalid state: " + this._state);
   1382         break;
   1383 
   1384       case READER_IN_REQUEST_LINE:
   1385         if (!this._processRequestLine())
   1386           break;
   1387         /* fall through */
   1388 
   1389       case READER_IN_HEADERS:
   1390         if (!this._processHeaders())
   1391           break;
   1392         /* fall through */
   1393 
   1394       case READER_IN_BODY:
   1395         this._processBody();
   1396     }
   1397 
   1398     if (this._state != READER_FINISHED)
   1399       input.asyncWait(this, 0, 0, gThreadManager.currentThread);
   1400   },
   1401 
   1402   //
   1403   // see nsISupports.QueryInterface
   1404   //
   1405   QueryInterface: function(aIID)
   1406   {
   1407     if (aIID.equals(Ci.nsIInputStreamCallback) ||
   1408         aIID.equals(Ci.nsISupports))
   1409       return this;
   1410 
   1411     throw Cr.NS_ERROR_NO_INTERFACE;
   1412   },
   1413 
   1414 
   1415   // PRIVATE API
   1416 
   1417   /**
   1418    * Processes unprocessed, downloaded data as a request line.
   1419    *
   1420    * @returns boolean
   1421    *   true iff the request line has been fully processed
   1422    */
   1423   _processRequestLine: function()
   1424   {
   1425     NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
   1426 
   1427     // Servers SHOULD ignore any empty line(s) received where a Request-Line
   1428     // is expected (section 4.1).
   1429     var data = this._data;
   1430     var line = {};
   1431     var readSuccess;
   1432     while ((readSuccess = data.readLine(line)) && line.value == "")
   1433       dumpn("*** ignoring beginning blank line...");
   1434 
   1435     // if we don't have a full line, wait until we do
   1436     if (!readSuccess)
   1437       return false;
   1438 
   1439     // we have the first non-blank line
   1440     try
   1441     {
   1442       this._parseRequestLine(line.value);
   1443       this._state = READER_IN_HEADERS;
   1444       this._connection.requestStarted();
   1445       return true;
   1446     }
   1447     catch (e)
   1448     {
   1449       this._handleError(e);
   1450       return false;
   1451     }
   1452   },
   1453 
   1454   /**
   1455    * Processes stored data, assuming it is either at the beginning or in
   1456    * the middle of processing request headers.
   1457    *
   1458    * @returns boolean
   1459    *   true iff header data in the request has been fully processed
   1460    */
   1461   _processHeaders: function()
   1462   {
   1463     NS_ASSERT(this._state == READER_IN_HEADERS);
   1464 
   1465     // XXX things to fix here:
   1466     //
   1467     // - need to support RFC 2047-encoded non-US-ASCII characters
   1468 
   1469     try
   1470     {
   1471       var done = this._parseHeaders();
   1472       if (done)
   1473       {
   1474         var request = this._metadata;
   1475 
   1476         // XXX this is wrong for requests with transfer-encodings applied to
   1477         //     them, particularly chunked (which by its nature can have no
   1478         //     meaningful Content-Length header)!
   1479         this._contentLength = request.hasHeader("Content-Length")
   1480                             ? parseInt(request.getHeader("Content-Length"), 10)
   1481                             : 0;
   1482         dumpn("_processHeaders, Content-length=" + this._contentLength);
   1483 
   1484         this._state = READER_IN_BODY;
   1485       }
   1486       return done;
   1487     }
   1488     catch (e)
   1489     {
   1490       this._handleError(e);
   1491       return false;
   1492     }
   1493   },
   1494 
   1495   /**
   1496    * Processes stored data, assuming it is either at the beginning or in
   1497    * the middle of processing the request body.
   1498    *
   1499    * @returns boolean
   1500    *   true iff the request body has been fully processed
   1501    */
   1502   _processBody: function()
   1503   {
   1504     NS_ASSERT(this._state == READER_IN_BODY);
   1505 
   1506     // XXX handle chunked transfer-coding request bodies!
   1507 
   1508     try
   1509     {
   1510       if (this._contentLength > 0)
   1511       {
   1512         var data = this._data.purge();
   1513         var count = Math.min(data.length, this._contentLength);
   1514         dumpn("*** loading data=" + data + " len=" + data.length +
   1515               " excess=" + (data.length - count));
   1516 
   1517         var bos = new BinaryOutputStream(this._metadata._bodyOutputStream);
   1518         bos.writeByteArray(data, count);
   1519         this._contentLength -= count;
   1520       }
   1521 
   1522       dumpn("*** remaining body data len=" + this._contentLength);
   1523       if (this._contentLength == 0)
   1524       {
   1525         this._validateRequest();
   1526         this._state = READER_FINISHED;
   1527         this._handleResponse();
   1528         return true;
   1529       }
   1530       
   1531       return false;
   1532     }
   1533     catch (e)
   1534     {
   1535       this._handleError(e);
   1536       return false;
   1537     }
   1538   },
   1539 
   1540   /**
   1541    * Does various post-header checks on the data in this request.
   1542    *
   1543    * @throws : HttpError
   1544    *   if the request was malformed in some way
   1545    */
   1546   _validateRequest: function()
   1547   {
   1548     NS_ASSERT(this._state == READER_IN_BODY);
   1549 
   1550     dumpn("*** _validateRequest");
   1551 
   1552     var metadata = this._metadata;
   1553     var headers = metadata._headers;
   1554 
   1555     // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header
   1556     var identity = this._connection.server.identity;
   1557     if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
   1558     {
   1559       if (!headers.hasHeader("Host"))
   1560       {
   1561         dumpn("*** malformed HTTP/1.1 or greater request with no Host header!");
   1562         throw HTTP_400;
   1563       }
   1564 
   1565       // If the Request-URI wasn't absolute, then we need to determine our host.
   1566       // We have to determine what scheme was used to access us based on the
   1567       // server identity data at this point, because the request just doesn't
   1568       // contain enough data on its own to do this, sadly.
   1569       if (!metadata._host)
   1570       {
   1571         var host, port;
   1572         var hostPort = headers.getHeader("Host");
   1573         var colon = hostPort.indexOf(":");
   1574         if (colon < 0)
   1575         {
   1576           host = hostPort;
   1577           port = "";
   1578         }
   1579         else
   1580         {
   1581           host = hostPort.substring(0, colon);
   1582           port = hostPort.substring(colon + 1);
   1583         }
   1584 
   1585         // NB: We allow an empty port here because, oddly, a colon may be
   1586         //     present even without a port number, e.g. "example.com:"; in this
   1587         //     case the default port applies.
   1588         if (!HOST_REGEX.test(host) || !/^\d*$/.test(port))
   1589         {
   1590           dumpn("*** malformed hostname (" + hostPort + ") in Host " +
   1591                 "header, 400 time");
   1592           throw HTTP_400;
   1593         }
   1594 
   1595         // If we're not given a port, we're stuck, because we don't know what
   1596         // scheme to use to look up the correct port here, in general.  Since
   1597         // the HTTPS case requires a tunnel/proxy and thus requires that the
   1598         // requested URI be absolute (and thus contain the necessary
   1599         // information), let's assume HTTP will prevail and use that.
   1600         port = +port || 80;
   1601 
   1602         var scheme = identity.getScheme(host, port);
   1603         if (!scheme)
   1604         {
   1605           dumpn("*** unrecognized hostname (" + hostPort + ") in Host " +
   1606                 "header, 400 time");
   1607           throw HTTP_400;
   1608         }
   1609 
   1610         metadata._scheme = scheme;
   1611         metadata._host = host;
   1612         metadata._port = port;
   1613       }
   1614     }
   1615     else
   1616     {
   1617       NS_ASSERT(metadata._host === undefined,
   1618                 "HTTP/1.0 doesn't allow absolute paths in the request line!");
   1619 
   1620       metadata._scheme = identity.primaryScheme;
   1621       metadata._host = identity.primaryHost;
   1622       metadata._port = identity.primaryPort;
   1623     }
   1624 
   1625     NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port),
   1626               "must have a location we recognize by now!");
   1627   },
   1628 
   1629   /**
   1630    * Handles responses in case of error, either in the server or in the request.
   1631    *
   1632    * @param e
   1633    *   the specific error encountered, which is an HttpError in the case where
   1634    *   the request is in some way invalid or cannot be fulfilled; if this isn't
   1635    *   an HttpError we're going to be paranoid and shut down, because that
   1636    *   shouldn't happen, ever
   1637    */
   1638   _handleError: function(e)
   1639   {
   1640     // Don't fall back into normal processing!
   1641     this._state = READER_FINISHED;
   1642 
   1643     var server = this._connection.server;
   1644     if (e instanceof HttpError)
   1645     {
   1646       var code = e.code;
   1647     }
   1648     else
   1649     {
   1650       dumpn("!!! UNEXPECTED ERROR: " + e +
   1651             (e.lineNumber ? ", line " + e.lineNumber : ""));
   1652 
   1653       // no idea what happened -- be paranoid and shut down
   1654       code = 500;
   1655       server._requestQuit();
   1656     }
   1657 
   1658     // make attempted reuse of data an error
   1659     this._data = null;
   1660 
   1661     this._connection.processError(code, this._metadata);
   1662   },
   1663 
   1664   /**
   1665    * Now that we've read the request line and headers, we can actually hand off
   1666    * the request to be handled.
   1667    *
   1668    * This method is called once per request, after the request line and all
   1669    * headers and the body, if any, have been received.
   1670    */
   1671   _handleResponse: function()
   1672   {
   1673     NS_ASSERT(this._state == READER_FINISHED);
   1674 
   1675     // We don't need the line-based data any more, so make attempted reuse an
   1676     // error.
   1677     this._data = null;
   1678 
   1679     this._connection.process(this._metadata);
   1680   },
   1681 
   1682 
   1683   // PARSING
   1684 
   1685   /**
   1686    * Parses the request line for the HTTP request associated with this.
   1687    *
   1688    * @param line : string
   1689    *   the request line
   1690    */
   1691   _parseRequestLine: function(line)
   1692   {
   1693     NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
   1694 
   1695     dumpn("*** _parseRequestLine('" + line + "')");
   1696 
   1697     var metadata = this._metadata;
   1698 
   1699     // clients and servers SHOULD accept any amount of SP or HT characters
   1700     // between fields, even though only a single SP is required (section 19.3)
   1701     var request = line.split(/[ \t]+/);
   1702     if (!request || request.length != 3)
   1703     {
   1704       dumpn("*** No request in line");
   1705       throw HTTP_400;
   1706     }
   1707 
   1708     metadata._method = request[0];
   1709 
   1710     // get the HTTP version
   1711     var ver = request[2];
   1712     var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
   1713     if (!match)
   1714     {
   1715       dumpn("*** No HTTP version in line");
   1716       throw HTTP_400;
   1717     }
   1718 
   1719     // determine HTTP version
   1720     try
   1721     {
   1722       metadata._httpVersion = new nsHttpVersion(match[1]);
   1723       if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0))
   1724         throw "unsupported HTTP version";
   1725     }
   1726     catch (e)
   1727     {
   1728       // we support HTTP/1.0 and HTTP/1.1 only
   1729       throw HTTP_501;
   1730     }
   1731 
   1732 
   1733     var fullPath = request[1];
   1734     var serverIdentity = this._connection.server.identity;
   1735 
   1736     var scheme, host, port;
   1737 
   1738     if (fullPath.charAt(0) != "/")
   1739     {
   1740       // No absolute paths in the request line in HTTP prior to 1.1
   1741       if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
   1742       {
   1743         dumpn("*** Metadata version too low");
   1744         throw HTTP_400;
   1745       }
   1746 
   1747       try
   1748       {
   1749         var uri = Cc["@mozilla.org/network/io-service;1"]
   1750                     .getService(Ci.nsIIOService)
   1751                     .newURI(fullPath, null, null);
   1752         fullPath = uri.path;
   1753         scheme = uri.scheme;
   1754         host = metadata._host = uri.asciiHost;
   1755         port = uri.port;
   1756         if (port === -1)
   1757         {
   1758           if (scheme === "http")
   1759           {
   1760             port = 80;
   1761           }
   1762           else if (scheme === "https")
   1763           {
   1764             port = 443;
   1765           }
   1766           else
   1767           {
   1768             dumpn("*** Unknown scheme: " + scheme);
   1769             throw HTTP_400;
   1770           }
   1771         }
   1772       }
   1773       catch (e)
   1774       {
   1775         // If the host is not a valid host on the server, the response MUST be a
   1776         // 400 (Bad Request) error message (section 5.2).  Alternately, the URI
   1777         // is malformed.
   1778         dumpn("*** Threw when dealing with URI: " + e);
   1779         throw HTTP_400;
   1780       }
   1781 
   1782       if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
   1783       {
   1784         dumpn("*** serverIdentity unknown or path does not start with '/'");
   1785         throw HTTP_400;
   1786       }
   1787     }
   1788 
   1789     var splitter = fullPath.indexOf("?");
   1790     if (splitter < 0)
   1791     {
   1792       // _queryString already set in ctor
   1793       metadata._path = fullPath;
   1794     }
   1795     else
   1796     {
   1797       metadata._path = fullPath.substring(0, splitter);
   1798       metadata._queryString = fullPath.substring(splitter + 1);
   1799     }
   1800 
   1801     metadata._scheme = scheme;
   1802     metadata._host = host;
   1803     metadata._port = port;
   1804   },
   1805 
   1806   /**
   1807    * Parses all available HTTP headers in this until the header-ending CRLFCRLF,
   1808    * adding them to the store of headers in the request.
   1809    *
   1810    * @throws
   1811    *   HTTP_400 if the headers are malformed
   1812    * @returns boolean
   1813    *   true if all headers have now been processed, false otherwise
   1814    */
   1815   _parseHeaders: function()
   1816   {
   1817     NS_ASSERT(this._state == READER_IN_HEADERS);
   1818 
   1819     dumpn("*** _parseHeaders");
   1820 
   1821     var data = this._data;
   1822 
   1823     var headers = this._metadata._headers;
   1824     var lastName = this._lastHeaderName;
   1825     var lastVal = this._lastHeaderValue;
   1826 
   1827     var line = {};
   1828     while (true)
   1829     {
   1830       dumpn("*** Last name: '" + lastName + "'");
   1831       dumpn("*** Last val: '" + lastVal + "'");
   1832       NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),
   1833                 lastName === undefined ?
   1834                   "lastVal without lastName?  lastVal: '" + lastVal + "'" :
   1835                   "lastName without lastVal?  lastName: '" + lastName + "'");
   1836 
   1837       if (!data.readLine(line))
   1838       {
   1839         // save any data we have from the header we might still be processing
   1840         this._lastHeaderName = lastName;
   1841         this._lastHeaderValue = lastVal;
   1842         return false;
   1843       }
   1844 
   1845       var lineText = line.value;
   1846       dumpn("*** Line text: '" + lineText + "'");
   1847       var firstChar = lineText.charAt(0);
   1848 
   1849       // blank line means end of headers
   1850       if (lineText == "")
   1851       {
   1852         // we're finished with the previous header
   1853         if (lastName)
   1854         {
   1855           try
   1856           {
   1857             headers.setHeader(lastName, lastVal, true);
   1858           }
   1859           catch (e)
   1860           {
   1861             dumpn("*** setHeader threw on last header, e == " + e);
   1862             throw HTTP_400;
   1863           }
   1864         }
   1865         else
   1866         {
   1867           // no headers in request -- valid for HTTP/1.0 requests
   1868         }
   1869 
   1870         // either way, we're done processing headers
   1871         this._state = READER_IN_BODY;
   1872         return true;
   1873       }
   1874       else if (firstChar == " " || firstChar == "\t")
   1875       {
   1876         // multi-line header if we've already seen a header line
   1877         if (!lastName)
   1878         {
   1879           dumpn("We don't have a header to continue!");
   1880           throw HTTP_400;
   1881         }
   1882 
   1883         // append this line's text to the value; starts with SP/HT, so no need
   1884         // for separating whitespace
   1885         lastVal += lineText;
   1886       }
   1887       else
   1888       {
   1889         // we have a new header, so set the old one (if one existed)
   1890         if (lastName)
   1891         {
   1892           try
   1893           {
   1894             headers.setHeader(lastName, lastVal, true);
   1895           }
   1896           catch (e)
   1897           {
   1898             dumpn("*** setHeader threw on a header, e == " + e);
   1899             throw HTTP_400;
   1900           }
   1901         }
   1902 
   1903         var colon = lineText.indexOf(":"); // first colon must be splitter
   1904         if (colon < 1)
   1905         {
   1906           dumpn("*** No colon or missing header field-name");
   1907           throw HTTP_400;
   1908         }
   1909 
   1910         // set header name, value (to be set in the next loop, usually)
   1911         lastName = lineText.substring(0, colon);
   1912         lastVal = lineText.substring(colon + 1);
   1913       } // empty, continuation, start of header
   1914     } // while (true)
   1915   }
   1916 };
   1917 
   1918 
   1919 /** The character codes for CR and LF. */
   1920 const CR = 0x0D, LF = 0x0A;
   1921 
   1922 /**
   1923  * Calculates the number of characters before the first CRLF pair in array, or
   1924  * -1 if the array contains no CRLF pair.
   1925  *
   1926  * @param array : Array
   1927  *   an array of numbers in the range [0, 256), each representing a single
   1928  *   character; the first CRLF is the lowest index i where
   1929  *   |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
   1930  *   if such an |i| exists, and -1 otherwise
   1931  * @param start : uint
   1932  *   start index from which to begin searching in array
   1933  * @returns int
   1934  *   the index of the first CRLF if any were present, -1 otherwise
   1935  */
   1936 function findCRLF(array, start)
   1937 {
   1938   for (var i = array.indexOf(CR, start); i >= 0; i = array.indexOf(CR, i + 1))
   1939   {
   1940     if (array[i + 1] == LF)
   1941       return i;
   1942   }
   1943   return -1;
   1944 }
   1945 
   1946 
   1947 /**
   1948  * A container which provides line-by-line access to the arrays of bytes with
   1949  * which it is seeded.
   1950  */
   1951 function LineData()
   1952 {
   1953   /** An array of queued bytes from which to get line-based characters. */
   1954   this._data = [];
   1955 
   1956   /** Start index from which to search for CRLF. */
   1957   this._start = 0;
   1958 }
   1959 LineData.prototype =
   1960 {
   1961   /**
   1962    * Appends the bytes in the given array to the internal data cache maintained
   1963    * by this.
   1964    */
   1965   appendBytes: function(bytes)
   1966   {
   1967     var count = bytes.length;
   1968     var quantum = 262144; // just above half SpiderMonkey's argument-count limit
   1969     if (count < quantum)
   1970     {
   1971       Array.prototype.push.apply(this._data, bytes);
   1972       return;
   1973     }
   1974 
   1975     // Large numbers of bytes may cause Array.prototype.push to be called with
   1976     // more arguments than the JavaScript engine supports.  In that case append
   1977     // bytes in fixed-size amounts until all bytes are appended.
   1978     for (var start = 0; start < count; start += quantum)
   1979     {
   1980       var slice = bytes.slice(start, Math.min(start + quantum, count));
   1981       Array.prototype.push.apply(this._data, slice);
   1982     }
   1983   },
   1984 
   1985   /**
   1986    * Removes and returns a line of data, delimited by CRLF, from this.
   1987    *
   1988    * @param out
   1989    *   an object whose "value" property will be set to the first line of text
   1990    *   present in this, sans CRLF, if this contains a full CRLF-delimited line
   1991    *   of text; if this doesn't contain enough data, the value of the property
   1992    *   is undefined
   1993    * @returns boolean
   1994    *   true if a full line of data could be read from the data in this, false
   1995    *   otherwise
   1996    */
   1997   readLine: function(out)
   1998   {
   1999     var data = this._data;
   2000     var length = findCRLF(data, this._start);
   2001     if (length < 0)
   2002     {
   2003       this._start = data.length;
   2004 
   2005       // But if our data ends in a CR, we have to back up one, because
   2006       // the first byte in the next packet might be an LF and if we
   2007       // start looking at data.length we won't find it.
   2008       if (data.length > 0 && data[data.length - 1] === CR)
   2009         --this._start;
   2010 
   2011       return false;
   2012     }
   2013 
   2014     // Reset for future lines.
   2015     this._start = 0;
   2016 
   2017     //
   2018     // We have the index of the CR, so remove all the characters, including
   2019     // CRLF, from the array with splice, and convert the removed array
   2020     // (excluding the trailing CRLF characters) into the corresponding string.
   2021     //
   2022     var leading = data.splice(0, length + 2);
   2023     var quantum = 262144;
   2024     var line = "";
   2025     for (var start = 0; start < length; start += quantum)
   2026     {
   2027       var slice = leading.slice(start, Math.min(start + quantum, length));
   2028       line += String.fromCharCode.apply(null, slice);
   2029     }
   2030 
   2031     out.value = line;
   2032     return true;
   2033   },
   2034 
   2035   /**
   2036    * Removes the bytes currently within this and returns them in an array.
   2037    *
   2038    * @returns Array
   2039    *   the bytes within this when this method is called
   2040    */
   2041   purge: function()
   2042   {
   2043     var data = this._data;
   2044     this._data = [];
   2045     return data;
   2046   }
   2047 };
   2048 
   2049 
   2050 
   2051 /**
   2052  * Creates a request-handling function for an nsIHttpRequestHandler object.
   2053  */
   2054 function createHandlerFunc(handler)
   2055 {
   2056   return function(metadata, response) { handler.handle(metadata, response); };
   2057 }
   2058 
   2059 
   2060 /**
   2061  * The default handler for directories; writes an HTML response containing a
   2062  * slightly-formatted directory listing.
   2063  */
   2064 function defaultIndexHandler(metadata, response)
   2065 {
   2066   response.setHeader("Content-Type", "text/html;charset=utf-8", false);
   2067 
   2068   var path = htmlEscape(decodeURI(metadata.path));
   2069 
   2070   //
   2071   // Just do a very basic bit of directory listings -- no need for too much
   2072   // fanciness, especially since we don't have a style sheet in which we can
   2073   // stick rules (don't want to pollute the default path-space).
   2074   //
   2075 
   2076   var body = '<html>\
   2077                 <head>\
   2078                   <title>' + path + '</title>\
   2079                 </head>\
   2080                 <body>\
   2081                   <h1>' + path + '</h1>\
   2082                   <ol style="list-style-type: none">';
   2083 
   2084   var directory = metadata.getProperty("directory");
   2085   NS_ASSERT(directory && directory.isDirectory());
   2086 
   2087   var fileList = [];
   2088   var files = directory.directoryEntries;
   2089   while (files.hasMoreElements())
   2090   {
   2091     var f = files.getNext().QueryInterface(Ci.nsIFile);
   2092     var name = f.leafName;
   2093     if (!f.isHidden() &&
   2094         (name.charAt(name.length - 1) != HIDDEN_CHAR ||
   2095          name.charAt(name.length - 2) == HIDDEN_CHAR))
   2096       fileList.push(f);
   2097   }
   2098 
   2099   fileList.sort(fileSort);
   2100 
   2101   for (var i = 0; i < fileList.length; i++)
   2102   {
   2103     var file = fileList[i];
   2104     try
   2105     {
   2106       var name = file.leafName;
   2107       if (name.charAt(name.length - 1) == HIDDEN_CHAR)
   2108         name = name.substring(0, name.length - 1);
   2109       var sep = file.isDirectory() ? "/" : "";
   2110 
   2111       // Note: using " to delimit the attribute here because encodeURIComponent
   2112       //       passes through '.
   2113       var item = '<li><a href="' + encodeURIComponent(name) + sep + '">' +
   2114                    htmlEscape(name) + sep +
   2115                  '</a></li>';
   2116 
   2117       body += item;
   2118     }
   2119     catch (e) { /* some file system error, ignore the file */ }
   2120   }
   2121 
   2122   body    += '    </ol>\
   2123                 </body>\
   2124               </html>';
   2125 
   2126   response.bodyOutputStream.write(body, body.length);
   2127 }
   2128 
   2129 /**
   2130  * Sorts a and b (nsIFile objects) into an aesthetically pleasing order.
   2131  */
   2132 function fileSort(a, b)
   2133 {
   2134   var dira = a.isDirectory(), dirb = b.isDirectory();
   2135 
   2136   if (dira && !dirb)
   2137     return -1;
   2138   if (dirb && !dira)
   2139     return 1;
   2140 
   2141   var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase();
   2142   return nameb > namea ? -1 : 1;
   2143 }
   2144 
   2145 
   2146 /**
   2147  * Converts an externally-provided path into an internal path for use in
   2148  * determining file mappings.
   2149  *
   2150  * @param path
   2151  *   the path to convert
   2152  * @param encoded
   2153  *   true if the given path should be passed through decodeURI prior to
   2154  *   conversion
   2155  * @throws URIError
   2156  *   if path is incorrectly encoded
   2157  */
   2158 function toInternalPath(path, encoded)
   2159 {
   2160   if (encoded)
   2161     path = decodeURI(path);
   2162 
   2163   var comps = path.split("/");
   2164   for (var i = 0, sz = comps.length; i < sz; i++)
   2165   {
   2166     var comp = comps[i];
   2167     if (comp.charAt(comp.length - 1) == HIDDEN_CHAR)
   2168       comps[i] = comp + HIDDEN_CHAR;
   2169   }
   2170   return comps.join("/");
   2171 }
   2172 
   2173 const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;
   2174 
   2175 /**
   2176  * Adds custom-specified headers for the given file to the given response, if
   2177  * any such headers are specified.
   2178  *
   2179  * @param file
   2180  *   the file on the disk which is to be written
   2181  * @param metadata
   2182  *   metadata about the incoming request
   2183  * @param response
   2184  *   the Response to which any specified headers/data should be written
   2185  * @throws HTTP_500
   2186  *   if an error occurred while processing custom-specified headers
   2187  */
   2188 function maybeAddHeaders(file, metadata, response)
   2189 {
   2190   var name = file.leafName;
   2191   if (name.charAt(name.length - 1) == HIDDEN_CHAR)
   2192     name = name.substring(0, name.length - 1);
   2193 
   2194   var headerFile = file.parent;
   2195   headerFile.append(name + HEADERS_SUFFIX);
   2196 
   2197   if (!headerFile.exists())
   2198     return;
   2199 
   2200   const PR_RDONLY = 0x01;
   2201   var fis = new FileInputStream(headerFile, PR_RDONLY, PERMS_READONLY,
   2202                                 Ci.nsIFileInputStream.CLOSE_ON_EOF);
   2203 
   2204   try
   2205   {
   2206     var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
   2207     lis.QueryInterface(Ci.nsIUnicharLineInputStream);
   2208 
   2209     var line = {value: ""};
   2210     var more = lis.readLine(line);
   2211 
   2212     if (!more && line.value == "")
   2213       return;
   2214 
   2215 
   2216     // request line
   2217 
   2218     var status = line.value;
   2219     if (status.indexOf("HTTP ") == 0)
   2220     {
   2221       status = status.substring(5);
   2222       var space = status.indexOf(" ");
   2223       var code, description;
   2224       if (space < 0)
   2225       {
   2226         code = status;
   2227         description = "";
   2228       }
   2229       else
   2230       {
   2231         code = status.substring(0, space);
   2232         description = status.substring(space + 1, status.length);
   2233       }
   2234     
   2235       response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);
   2236 
   2237       line.value = "";
   2238       more = lis.readLine(line);
   2239     }
   2240 
   2241     // headers
   2242     while (more || line.value != "")
   2243     {
   2244       var header = line.value;
   2245       var colon = header.indexOf(":");
   2246 
   2247       response.setHeader(header.substring(0, colon),
   2248                          header.substring(colon + 1, header.length),
   2249                          false); // allow overriding server-set headers
   2250 
   2251       line.value = "";
   2252       more = lis.readLine(line);
   2253     }
   2254   }
   2255   catch (e)
   2256   {
   2257     dumpn("WARNING: error in headers for " + metadata.path + ": " + e);
   2258     throw HTTP_500;
   2259   }
   2260   finally
   2261   {
   2262     fis.close();
   2263   }
   2264 }
   2265 
   2266 
   2267 /**
   2268  * An object which handles requests for a server, executing default and
   2269  * overridden behaviors as instructed by the code which uses and manipulates it.
   2270  * Default behavior includes the paths / and /trace (diagnostics), with some
   2271  * support for HTTP error pages for various codes and fallback to HTTP 500 if
   2272  * those codes fail for any reason.
   2273  *
   2274  * @param server : nsHttpServer
   2275  *   the server in which this handler is being used
   2276  */
   2277 function ServerHandler(server)
   2278 {
   2279   // FIELDS
   2280 
   2281   /**
   2282    * The nsHttpServer instance associated with this handler.
   2283    */
   2284   this._server = server;
   2285 
   2286   /**
   2287    * A FileMap object containing the set of path->nsILocalFile mappings for
   2288    * all directory mappings set in the server (e.g., "/" for /var/www/html/,
   2289    * "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2).
   2290    *
   2291    * Note carefully: the leading and trailing "/" in each path (not file) are
   2292    * removed before insertion to simplify the code which uses this.  You have
   2293    * been warned!
   2294    */
   2295   this._pathDirectoryMap = new FileMap();
   2296 
   2297   /**
   2298    * Custom request handlers for the server in which this resides.  Path-handler
   2299    * pairs are stored as property-value pairs in this property.
   2300    *
   2301    * @see ServerHandler.prototype._defaultPaths
   2302    */
   2303   this._overridePaths = {};
   2304 
   2305   /**
   2306    * Custom request handlers for the path prefixes on the server in which this
   2307    * resides.  Path-handler pairs are stored as property-value pairs in this
   2308    * property.
   2309    *
   2310    * @see ServerHandler.prototype._defaultPaths
   2311    */
   2312   this._overridePrefixes = {};
   2313 
   2314   /**
   2315    * Custom request handlers for the error handlers in the server in which this
   2316    * resides.  Path-handler pairs are stored as property-value pairs in this
   2317    * property.
   2318    *
   2319    * @see ServerHandler.prototype._defaultErrors
   2320    */
   2321   this._overrideErrors = {};
   2322 
   2323   /**
   2324    * Maps file extensions to their MIME types in the server, overriding any
   2325    * mapping that might or might not exist in the MIME service.
   2326    */
   2327   this._mimeMappings = {};
   2328 
   2329   /**
   2330    * The default handler for requests for directories, used to serve directories
   2331    * when no index file is present.
   2332    */
   2333   this._indexHandler = defaultIndexHandler;
   2334 
   2335   /** Per-path state storage for the server. */
   2336   this._state = {};
   2337 
   2338   /** Entire-server state storage. */
   2339   this._sharedState = {};
   2340 
   2341   /** Entire-server state storage for nsISupports values. */
   2342   this._objectState = {};
   2343 }
   2344 ServerHandler.prototype =
   2345 {
   2346   // PUBLIC API
   2347 
   2348   /**
   2349    * Handles a request to this server, responding to the request appropriately
   2350    * and initiating server shutdown if necessary.
   2351    *
   2352    * This method never throws an exception.
   2353    *
   2354    * @param connection : Connection
   2355    *   the connection for this request
   2356    */
   2357   handleResponse: function(connection)
   2358   {
   2359     var request = connection.request;
   2360     var response = new Response(connection);
   2361 
   2362     var path = request.path;
   2363     dumpn("*** path == " + path);
   2364 
   2365     try
   2366     {
   2367       try
   2368       {
   2369         if (path in this._overridePaths)
   2370         {
   2371           // explicit paths first, then files based on existing directory mappings,
   2372           // then (if the file doesn't exist) built-in server default paths
   2373           dumpn("calling override for " + path);
   2374           this._overridePaths[path](request, response);
   2375         }
   2376         else
   2377         {
   2378           var longestPrefix = "";
   2379           for (let prefix in this._overridePrefixes) {
   2380             if (prefix.length > longestPrefix.length &&
   2381                 path.substr(0, prefix.length) == prefix)
   2382             {
   2383               longestPrefix = prefix;
   2384             }
   2385           }
   2386           if (longestPrefix.length > 0)
   2387           {
   2388             dumpn("calling prefix override for " + longestPrefix);
   2389             this._overridePrefixes[longestPrefix](request, response);
   2390           }
   2391           else
   2392           {
   2393             this._handleDefault(request, response);
   2394           }
   2395         }
   2396       }
   2397       catch (e)
   2398       {
   2399         if (response.partiallySent())
   2400         {
   2401           response.abort(e);
   2402           return;
   2403         }
   2404 
   2405         if (!(e instanceof HttpError))
   2406         {
   2407           dumpn("*** unexpected error: e == " + e);
   2408           throw HTTP_500;
   2409         }
   2410         if (e.code !== 404)
   2411           throw e;
   2412 
   2413         dumpn("*** default: " + (path in this._defaultPaths));
   2414 
   2415         response = new Response(connection);
   2416         if (path in this._defaultPaths)
   2417           this._defaultPaths[path](request, response);
   2418         else
   2419           throw HTTP_404;
   2420       }
   2421     }
   2422     catch (e)
   2423     {
   2424       if (response.partiallySent())
   2425       {
   2426         response.abort(e);
   2427         return;
   2428       }
   2429 
   2430       var errorCode = "internal";
   2431 
   2432       try
   2433       {
   2434         if (!(e instanceof HttpError))
   2435           throw e;
   2436 
   2437         errorCode = e.code;
   2438         dumpn("*** errorCode == " + errorCode);
   2439 
   2440         response = new Response(connection);
   2441         if (e.customErrorHandling)
   2442           e.customErrorHandling(response);
   2443         this._handleError(errorCode, request, response);
   2444         return;
   2445       }
   2446       catch (e2)
   2447       {
   2448         dumpn("*** error handling " + errorCode + " error: " +
   2449               "e2 == " + e2 + ", shutting down server");
   2450 
   2451         connection.server._requestQuit();
   2452         response.abort(e2);
   2453         return;
   2454       }
   2455     }
   2456 
   2457     response.complete();
   2458   },
   2459 
   2460   //
   2461   // see nsIHttpServer.registerFile
   2462   //
   2463   registerFile: function(path, file)
   2464   {
   2465     if (!file)
   2466     {
   2467       dumpn("*** unregistering '" + path + "' mapping");
   2468       delete this._overridePaths[path];
   2469       return;
   2470     }
   2471 
   2472     dumpn("*** registering '" + path + "' as mapping to " + file.path);
   2473     file = file.clone();
   2474 
   2475     var self = this;
   2476     this._overridePaths[path] =
   2477       function(request, response)
   2478       {
   2479         if (!file.exists())
   2480           throw HTTP_404;
   2481 
   2482         response.setStatusLine(request.httpVersion, 200, "OK");
   2483         self._writeFileResponse(request, file, response, 0, file.fileSize);
   2484       };
   2485   },
   2486 
   2487   //
   2488   // see nsIHttpServer.registerPathHandler
   2489   //
   2490   registerPathHandler: function(path, handler)
   2491   {
   2492     // XXX true path validation!
   2493     if (path.charAt(0) != "/")
   2494       throw Cr.NS_ERROR_INVALID_ARG;
   2495 
   2496     this._handlerToField(handler, this._overridePaths, path);
   2497   },
   2498 
   2499   //
   2500   // see nsIHttpServer.registerPrefixHandler
   2501   //
   2502   registerPrefixHandler: function(path, handler)
   2503   {
   2504     // XXX true path validation!
   2505     if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/")
   2506       throw Cr.NS_ERROR_INVALID_ARG;
   2507 
   2508     this._handlerToField(handler, this._overridePrefixes, path);
   2509   },
   2510 
   2511   //
   2512   // see nsIHttpServer.registerDirectory
   2513   //
   2514   registerDirectory: function(path, directory)
   2515   {
   2516     // strip off leading and trailing '/' so that we can use lastIndexOf when
   2517     // determining exactly how a path maps onto a mapped directory --
   2518     // conditional is required here to deal with "/".substring(1, 0) being
   2519     // converted to "/".substring(0, 1) per the JS specification
   2520     var key = path.length == 1 ? "" : path.substring(1, path.length - 1);
   2521 
   2522     // the path-to-directory mapping code requires that the first character not
   2523     // be "/", or it will go into an infinite loop
   2524     if (key.charAt(0) == "/")
   2525       throw Cr.NS_ERROR_INVALID_ARG;
   2526 
   2527     key = toInternalPath(key, false);
   2528 
   2529     if (directory)
   2530     {
   2531       dumpn("*** mapping '" + path + "' to the location " + directory.path);
   2532       this._pathDirectoryMap.put(key, directory);
   2533     }
   2534     else
   2535     {
   2536       dumpn("*** removing mapping for '" + path + "'");
   2537       this._pathDirectoryMap.put(key, null);
   2538     }
   2539   },
   2540 
   2541   //
   2542   // see nsIHttpServer.registerErrorHandler
   2543   //
   2544   registerErrorHandler: function(err, handler)
   2545   {
   2546     if (!(err in HTTP_ERROR_CODES))
   2547       dumpn("*** WARNING: registering non-HTTP/1.1 error code " +
   2548             "(" + err + ") handler -- was this intentional?");
   2549 
   2550     this._handlerToField(handler, this._overrideErrors, err);
   2551   },
   2552 
   2553   //
   2554   // see nsIHttpServer.setIndexHandler
   2555   //
   2556   setIndexHandler: function(handler)
   2557   {
   2558     if (!handler)
   2559       handler = defaultIndexHandler;
   2560     else if (typeof(handler) != "function")
   2561       handler = createHandlerFunc(handler);
   2562 
   2563     this._indexHandler = handler;
   2564   },
   2565 
   2566   //
   2567   // see nsIHttpServer.registerContentType
   2568   //
   2569   registerContentType: function(ext, type)
   2570   {
   2571     if (!type)
   2572       delete this._mimeMappings[ext];
   2573     else
   2574       this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type);
   2575   },
   2576 
   2577   // PRIVATE API
   2578 
   2579   /**
   2580    * Sets or remove (if handler is null) a handler in an object with a key.
   2581    *
   2582    * @param handler
   2583    *   a handler, either function or an nsIHttpRequestHandler
   2584    * @param dict
   2585    *   The object to attach the handler to.
   2586    * @param key
   2587    *   The field name of the handler.
   2588    */
   2589   _handlerToField: function(handler, dict, key)
   2590   {
   2591     // for convenience, handler can be a function if this is run from xpcshell
   2592     if (typeof(handler) == "function")
   2593       dict[key] = handler;
   2594     else if (handler)
   2595       dict[key] = createHandlerFunc(handler);
   2596     else
   2597       delete dict[key];
   2598   },
   2599 
   2600   /**
   2601    * Handles a request which maps to a file in the local filesystem (if a base
   2602    * path has already been set; otherwise the 404 error is thrown).
   2603    *
   2604    * @param metadata : Request
   2605    *   metadata for the incoming request
   2606    * @param response : Response
   2607    *   an uninitialized Response to the given request, to be initialized by a
   2608    *   request handler
   2609    * @throws HTTP_###
   2610    *   if an HTTP error occurred (usually HTTP_404); note that in this case the
   2611    *   calling code must handle post-processing of the response
   2612    */
   2613   _handleDefault: function(metadata, response)
   2614   {
   2615     dumpn("*** _handleDefault()");
   2616 
   2617     response.setStatusLine(metadata.httpVersion, 200, "OK");
   2618 
   2619     var path = metadata.path;
   2620     NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">");
   2621 
   2622     // determine the actual on-disk file; this requires finding the deepest
   2623     // path-to-directory mapping in the requested URL
   2624     var file = this._getFileForPath(path);
   2625 
   2626     // the "file" might be a directory, in which case we either serve the
   2627     // contained index.html or make the index handler write the response
   2628     if (file.exists() && file.isDirectory())
   2629     {
   2630       file.append("index.html"); // make configurable?
   2631       if (!file.exists() || file.isDirectory())
   2632       {
   2633         metadata._ensurePropertyBag();
   2634         metadata._bag.setPropertyAsInterface("directory", file.parent);
   2635         this._indexHandler(metadata, response);
   2636         return;
   2637       }
   2638     }
   2639 
   2640     // alternately, the file might not exist
   2641     if (!file.exists())
   2642       throw HTTP_404;
   2643 
   2644     var start, end;
   2645     if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) &&
   2646         metadata.hasHeader("Range") &&
   2647         this._getTypeFromFile(file) !== SJS_TYPE)
   2648     {
   2649       var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
   2650       if (!rangeMatch)
   2651       {
   2652         dumpn("*** Range header bogosity: '" + metadata.getHeader("Range") + "'");
   2653         throw HTTP_400;
   2654       }
   2655 
   2656       if (rangeMatch[1] !== undefined)
   2657         start = parseInt(rangeMatch[1], 10);
   2658 
   2659       if (rangeMatch[2] !== undefined)
   2660         end = parseInt(rangeMatch[2], 10);
   2661 
   2662       if (start === undefined && end === undefined)
   2663       {
   2664         dumpn("*** More Range header bogosity: '" + metadata.getHeader("Range") + "'");
   2665         throw HTTP_400;
   2666       }
   2667 
   2668       // No start given, so the end is really the count of bytes from the
   2669       // end of the file.
   2670       if (start === undefined)
   2671       {
   2672         start = Math.max(0, file.fileSize - end);
   2673         end   = file.fileSize - 1;
   2674       }
   2675 
   2676       // start and end are inclusive
   2677       if (end === undefined || end >= file.fileSize)
   2678         end = file.fileSize - 1;
   2679 
   2680       if (start !== undefined && start >= file.fileSize) {
   2681         var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable");
   2682         HTTP_416.customErrorHandling = function(errorResponse)
   2683         {
   2684           maybeAddHeaders(file, metadata, errorResponse);
   2685         };
   2686         throw HTTP_416;
   2687       }
   2688 
   2689       if (end < start)
   2690       {
   2691         response.setStatusLine(metadata.httpVersion, 200, "OK");
   2692         start = 0;
   2693         end = file.fileSize - 1;
   2694       }
   2695       else
   2696       {
   2697         response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
   2698         var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize;
   2699         response.setHeader("Content-Range", contentRange);
   2700       }
   2701     }
   2702     else
   2703     {
   2704       start = 0;
   2705       end = file.fileSize - 1;
   2706     }
   2707 
   2708     // finally...
   2709     dumpn("*** handling '" + path + "' as mapping to " + file.path + " from " +
   2710           start + " to " + end + " inclusive");
   2711     this._writeFileResponse(metadata, file, response, start, end - start + 1);
   2712   },
   2713 
   2714   /**
   2715    * Writes an HTTP response for the given file, including setting headers for
   2716    * file metadata.
   2717    *
   2718    * @param metadata : Request
   2719    *   the Request for which a response is being generated
   2720    * @param file : nsILocalFile
   2721    *   the file which is to be sent in the response
   2722    * @param response : Response
   2723    *   the response to which the file should be written
   2724    * @param offset: uint
   2725    *   the byte offset to skip to when writing
   2726    * @param count: uint
   2727    *   the number of bytes to write
   2728    */
   2729   _writeFileResponse: function(metadata, file, response, offset, count)
   2730   {
   2731     const PR_RDONLY = 0x01;
   2732 
   2733     var type = this._getTypeFromFile(file);
   2734     if (type === SJS_TYPE)
   2735     {
   2736       var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
   2737                                     Ci.nsIFileInputStream.CLOSE_ON_EOF);
   2738 
   2739       try
   2740       {
   2741         var sis = new ScriptableInputStream(fis);
   2742         var s = Cu.Sandbox(gGlobalObject);
   2743         s.importFunction(dump, "dump");
   2744         s.importFunction(atob, "atob");
   2745         s.importFunction(btoa, "btoa");
   2746 
   2747         // Define a basic key-value state-preservation API across requests, with
   2748         // keys initially corresponding to the empty string.
   2749         var self = this;
   2750         var path = metadata.path;
   2751         s.importFunction(function getState(k)
   2752         {
   2753           return self._getState(path, k);
   2754         });
   2755         s.importFunction(function setState(k, v)
   2756         {
   2757           self._setState(path, k, v);
   2758         });
   2759         s.importFunction(function getSharedState(k)
   2760         {
   2761           return self._getSharedState(k);
   2762         });
   2763         s.importFunction(function setSharedState(k, v)
   2764         {
   2765           self._setSharedState(k, v);
   2766         });
   2767         s.importFunction(function getObjectState(k, callback)
   2768         {
   2769           callback(self._getObjectState(k));
   2770         });
   2771         s.importFunction(function setObjectState(k, v)
   2772         {
   2773           self._setObjectState(k, v);
   2774         });
   2775         s.importFunction(function registerPathHandler(p, h)
   2776         {
   2777           self.registerPathHandler(p, h);
   2778         });
   2779 
   2780         // Make it possible for sjs files to access their location
   2781         this._setState(path, "__LOCATION__", file.path);
   2782 
   2783         try
   2784         {
   2785           // Alas, the line number in errors dumped to console when calling the
   2786           // request handler is simply an offset from where we load the SJS file.
   2787           // Work around this in a reasonably non-fragile way by dynamically
   2788           // getting the line number where we evaluate the SJS file.  Don't
   2789           // separate these two lines!
   2790           var line = new Error().lineNumber;
   2791           Cu.evalInSandbox(sis.read(file.fileSize), s, "latest");
   2792         }
   2793         catch (e)
   2794         {
   2795           dumpn("*** syntax error in SJS at " + file.path + ": " + e);
   2796           throw HTTP_500;
   2797         }
   2798 
   2799         try
   2800         {
   2801           s.handleRequest(metadata, response);
   2802         }
   2803         catch (e)
   2804         {
   2805           dump("*** error running SJS at " + file.path + ": " +
   2806                e + " on line " +
   2807                (e instanceof Error
   2808                ? e.lineNumber + " in httpd.js"
   2809                : (e.lineNumber - line)) + "\n");
   2810           throw HTTP_500;
   2811         }
   2812       }
   2813       finally
   2814       {
   2815         fis.close();
   2816       }
   2817     }
   2818     else
   2819     {
   2820       try
   2821       {
   2822         response.setHeader("Last-Modified",
   2823                            toDateString(file.lastModifiedTime),
   2824                            false);
   2825       }
   2826       catch (e) { /* lastModifiedTime threw, ignore */ }
   2827 
   2828       response.setHeader("Content-Type", type, false);
   2829       maybeAddHeaders(file, metadata, response);
   2830       response.setHeader("Content-Length", "" + count, false);
   2831 
   2832       var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
   2833                                     Ci.nsIFileInputStream.CLOSE_ON_EOF);
   2834 
   2835       offset = offset || 0;
   2836       count  = count || file.fileSize;
   2837       NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset");
   2838       NS_ASSERT(count >= 0, "bad count");
   2839       NS_ASSERT(offset + count <= file.fileSize, "bad total data size");
   2840 
   2841       try
   2842       {
   2843         if (offset !== 0)
   2844         {
   2845           // Seek (or read, if seeking isn't supported) to the correct offset so
   2846           // the data sent to the client matches the requested range.
   2847           if (fis instanceof Ci.nsISeekableStream)
   2848             fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset);
   2849           else
   2850             new ScriptableInputStream(fis).read(offset);
   2851         }
   2852       }
   2853       catch (e)
   2854       {
   2855         fis.close();
   2856         throw e;
   2857       }
   2858 
   2859       let writeMore = function () {
   2860         gThreadManager.currentThread
   2861                       .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL);
   2862       }
   2863 
   2864       var input = new BinaryInputStream(fis);
   2865       var output = new BinaryOutputStream(response.bodyOutputStream);
   2866       var writeData =
   2867         {
   2868           run: function()
   2869           {
   2870             var chunkSize = Math.min(65536, count);
   2871             count -= chunkSize;
   2872             NS_ASSERT(count >= 0, "underflow");
   2873 
   2874             try
   2875             {
   2876               var data = input.readByteArray(chunkSize);
   2877               NS_ASSERT(data.length === chunkSize,
   2878                         "incorrect data returned?  got " + data.length +
   2879                         ", expected " + chunkSize);
   2880               output.writeByteArray(data, data.length);
   2881               if (count === 0)
   2882               {
   2883                 fis.close();
   2884                 response.finish();
   2885               }
   2886               else
   2887               {
   2888                 writeMore();
   2889               }
   2890             }
   2891             catch (e)
   2892             {
   2893               try
   2894               {
   2895                 fis.close();
   2896               }
   2897               finally
   2898               {
   2899                 response.finish();
   2900               }
   2901               throw e;
   2902             }
   2903           }
   2904         };
   2905 
   2906       writeMore();
   2907 
   2908       // Now that we know copying will start, flag the response as async.
   2909       response.processAsync();
   2910     }
   2911   },
   2912 
   2913   /**
   2914    * Get the value corresponding to a given key for the given path for SJS state
   2915    * preservation across requests.
   2916    *
   2917    * @param path : string
   2918    *   the path from which the given state is to be retrieved
   2919    * @param k : string
   2920    *   the key whose corresponding value is to be returned
   2921    * @returns string
   2922    *   the corresponding value, which is initially the empty string
   2923    */
   2924   _getState: function(path, k)
   2925   {
   2926     var state = this._state;
   2927     if (path in state && k in state[path])
   2928       return state[path][k];
   2929     return "";
   2930   },
   2931 
   2932   /**
   2933    * Set the value corresponding to a given key for the given path for SJS state
   2934    * preservation across requests.
   2935    *
   2936    * @param path : string
   2937    *   the path from which the given state is to be retrieved
   2938    * @param k : string
   2939    *   the key whose corresponding value is to be set
   2940    * @param v : string
   2941    *   the value to be set
   2942    */
   2943   _setState: function(path, k, v)
   2944   {
   2945     if (typeof v !== "string")
   2946       throw new Error("non-string value passed");
   2947     var state = this._state;
   2948     if (!(path in state))
   2949       state[path] = {};
   2950     state[path][k] = v;
   2951   },
   2952 
   2953   /**
   2954    * Get the value corresponding to a given key for SJS state preservation
   2955    * across requests.
   2956    *
   2957    * @param k : string
   2958    *   the key whose corresponding value is to be returned
   2959    * @returns string
   2960    *   the corresponding value, which is initially the empty string
   2961    */
   2962   _getSharedState: function(k)
   2963   {
   2964     var state = this._sharedState;
   2965     if (k in state)
   2966       return state[k];
   2967     return "";
   2968   },
   2969 
   2970   /**
   2971    * Set the value corresponding to a given key for SJS state preservation
   2972    * across requests.
   2973    *
   2974    * @param k : string
   2975    *   the key whose corresponding value is to be set
   2976    * @param v : string
   2977    *   the value to be set
   2978    */
   2979   _setSharedState: function(k, v)
   2980   {
   2981     if (typeof v !== "string")
   2982       throw new Error("non-string value passed");
   2983     this._sharedState[k] = v;
   2984   },
   2985 
   2986   /**
   2987    * Returns the object associated with the given key in the server for SJS
   2988    * state preservation across requests.
   2989    *
   2990    * @param k : string
   2991    *  the key whose corresponding object is to be returned
   2992    * @returns nsISupports
   2993    *  the corresponding object, or null if none was present
   2994    */
   2995   _getObjectState: function(k)
   2996   {
   2997     if (typeof k !== "string")
   2998       throw new Error("non-string key passed");
   2999     return this._objectState[k] || null;
   3000   },
   3001 
   3002   /**
   3003    * Sets the object associated with the given key in the server for SJS
   3004    * state preservation across requests.
   3005    *
   3006    * @param k : string
   3007    *  the key whose corresponding object is to be set
   3008    * @param v : nsISupports
   3009    *  the object to be associated with the given key; may be null
   3010    */
   3011   _setObjectState: function(k, v)
   3012   {
   3013     if (typeof k !== "string")
   3014       throw new Error("non-string key passed");
   3015     if (typeof v !== "object")
   3016       throw new Error("non-object value passed");
   3017     if (v && !("QueryInterface" in v))
   3018     {
   3019       throw new Error("must pass an nsISupports; use wrappedJSObject to ease " +
   3020                       "pain when using the server from JS");
   3021     }
   3022 
   3023     this._objectState[k] = v;
   3024   },
   3025 
   3026   /**
   3027    * Gets a content-type for the given file, first by checking for any custom
   3028    * MIME-types registered with this handler for the file's extension, second by
   3029    * asking the global MIME service for a content-type, and finally by failing
   3030    * over to application/octet-stream.
   3031    *
   3032    * @param file : nsIFile
   3033    *   the nsIFile for which to get a file type
   3034    * @returns string
   3035    *   the best content-type which can be determined for the file
   3036    */
   3037   _getTypeFromFile: function(file)
   3038   {
   3039     try
   3040     {
   3041       var name = file.leafName;
   3042       var dot = name.lastIndexOf(".");
   3043       if (dot > 0)
   3044       {
   3045         var ext = name.slice(dot + 1);
   3046         if (ext in this._mimeMappings)
   3047           return this._mimeMappings[ext];
   3048       }
   3049       return Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
   3050                .getService(Ci.nsIMIMEService)
   3051                .getTypeFromFile(file);
   3052     }
   3053     catch (e)
   3054     {
   3055       return "application/octet-stream";
   3056     }
   3057   },
   3058 
   3059   /**
   3060    * Returns the nsILocalFile which corresponds to the path, as determined using
   3061    * all registered path->directory mappings and any paths which are explicitly
   3062    * overridden.
   3063    *
   3064    * @param path : string
   3065    *   the server path for which a file should be retrieved, e.g. "/foo/bar"
   3066    * @throws HttpError
   3067    *   when the correct action is the corresponding HTTP error (i.e., because no
   3068    *   mapping was found for a directory in path, the referenced file doesn't
   3069    *   exist, etc.)
   3070    * @returns nsILocalFile
   3071    *   the file to be sent as the response to a request for the path
   3072    */
   3073   _getFileForPath: function(path)
   3074   {
   3075     // decode and add underscores as necessary
   3076     try
   3077     {
   3078       path = toInternalPath(path, true);
   3079     }
   3080     catch (e)
   3081     {
   3082       dumpn("*** toInternalPath threw " + e);
   3083       throw HTTP_400; // malformed path
   3084     }
   3085 
   3086     // next, get the directory which contains this path
   3087     var pathMap = this._pathDirectoryMap;
   3088 
   3089     // An example progression of tmp for a path "/foo/bar/baz/" might be:
   3090     // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", ""
   3091     var tmp = path.substring(1);
   3092     while (true)
   3093     {
   3094       // do we have a match for current head of the path?
   3095       var file = pathMap.get(tmp);
   3096       if (file)
   3097       {
   3098         // XXX hack; basically disable showing mapping for /foo/bar/ when the
   3099         //     requested path was /foo/bar, because relative links on the page
   3100         //     will all be incorrect -- we really need the ability to easily
   3101         //     redirect here instead
   3102         if (tmp == path.substring(1) &&
   3103             tmp.length != 0 &&
   3104             tmp.charAt(tmp.length - 1) != "/")
   3105           file = null;
   3106         else
   3107           break;
   3108       }
   3109 
   3110       // if we've finished trying all prefixes, exit
   3111       if (tmp == "")
   3112         break;
   3113 
   3114       tmp = tmp.substring(0, tmp.lastIndexOf("/"));
   3115     }
   3116 
   3117     // no mapping applies, so 404
   3118     if (!file)
   3119       throw HTTP_404;
   3120 
   3121 
   3122     // last, get the file for the path within the determined directory
   3123     var parentFolder = file.parent;
   3124     var dirIsRoot = (parentFolder == null);
   3125 
   3126     // Strategy here is to append components individually, making sure we
   3127     // never move above the given directory; this allows paths such as
   3128     // "<file>/foo/../bar" but prevents paths such as "<file>/../base-sibling";
   3129     // this component-wise approach also means the code works even on platforms
   3130     // which don't use "/" as the directory separator, such as Windows
   3131     var leafPath = path.substring(tmp.length + 1);
   3132     var comps = leafPath.split("/");
   3133     for (var i = 0, sz = comps.length; i < sz; i++)
   3134     {
   3135       var comp = comps[i];
   3136 
   3137       if (comp == "..")
   3138         file = file.parent;
   3139       else if (comp == "." || comp == "")
   3140         continue;
   3141       else
   3142         file.append(comp);
   3143 
   3144       if (!dirIsRoot && file.equals(parentFolder))
   3145         throw HTTP_403;
   3146     }
   3147 
   3148     return file;
   3149   },
   3150 
   3151   /**
   3152    * Writes the error page for the given HTTP error code over the given
   3153    * connection.
   3154    *
   3155    * @param errorCode : uint
   3156    *   the HTTP error code to be used
   3157    * @param connection : Connection
   3158    *   the connection on which the error occurred
   3159    */
   3160   handleError: function(errorCode, connection)
   3161   {
   3162     var response = new Response(connection);
   3163 
   3164     dumpn("*** error in request: " + errorCode);
   3165 
   3166     this._handleError(errorCode, new Request(connection.port), response);
   3167   }, 
   3168 
   3169   /**
   3170    * Handles a request which generates the given error code, using the
   3171    * user-defined error handler if one has been set, gracefully falling back to
   3172    * the x00 status code if the code has no handler, and failing to status code
   3173    * 500 if all else fails.
   3174    *
   3175    * @param errorCode : uint
   3176    *   the HTTP error which is to be returned
   3177    * @param metadata : Request
   3178    *   metadata for the request, which will often be incomplete since this is an
   3179    *   error
   3180    * @param response : Response
   3181    *   an uninitialized Response should be initialized when this method
   3182    *   completes with information which represents the desired error code in the
   3183    *   ideal case or a fallback code in abnormal circumstances (i.e., 500 is a
   3184    *   fallback for 505, per HTTP specs)
   3185    */
   3186   _handleError: function(errorCode, metadata, response)
   3187   {
   3188     if (!metadata)
   3189       throw Cr.NS_ERROR_NULL_POINTER;
   3190 
   3191     var errorX00 = errorCode - (errorCode % 100);
   3192 
   3193     try
   3194     {
   3195       if (!(errorCode in HTTP_ERROR_CODES))
   3196         dumpn("*** WARNING: requested invalid error: " + errorCode);
   3197 
   3198       // RFC 2616 says that we should try to handle an error by its class if we
   3199       // can't otherwise handle it -- if that fails, we revert to handling it as
   3200       // a 500 internal server error, and if that fails we throw and shut down
   3201       // the server
   3202 
   3203       // actually handle the error
   3204       try
   3205       {
   3206         if (errorCode in this._overrideErrors)
   3207           this._overrideErrors[errorCode](metadata, response);
   3208         else
   3209           this._defaultErrors[errorCode](metadata, response);
   3210       }
   3211       catch (e)
   3212       {
   3213         if (response.partiallySent())
   3214         {
   3215           response.abort(e);
   3216           return;
   3217         }
   3218 
   3219         // don't retry the handler that threw
   3220         if (errorX00 == errorCode)
   3221           throw HTTP_500;
   3222 
   3223         dumpn("*** error in handling for error code " + errorCode + ", " +
   3224               "falling back to " + errorX00 + "...");
   3225         response = new Response(response._connection);
   3226         if (errorX00 in this._overrideErrors)
   3227           this._overrideErrors[errorX00](metadata, response);
   3228         else if (errorX00 in this._defaultErrors)
   3229           this._defaultErrors[errorX00](metadata, response);
   3230         else
   3231           throw HTTP_500;
   3232       }
   3233     }
   3234     catch (e)
   3235     {
   3236       if (response.partiallySent())
   3237       {
   3238         response.abort();
   3239         return;
   3240       }
   3241 
   3242       // we've tried everything possible for a meaningful error -- now try 500
   3243       dumpn("*** error in handling for error code " + errorX00 + ", falling " +
   3244             "back to 500...");
   3245 
   3246       try
   3247       {
   3248         response = new Response(response._connection);
   3249         if (500 in this._overrideErrors)
   3250           this._overrideErrors[500](metadata, response);
   3251         else
   3252           this._defaultErrors[500](metadata, response);
   3253       }
   3254       catch (e2)
   3255       {
   3256         dumpn("*** multiple errors in default error handlers!");
   3257         dumpn("*** e == " + e + ", e2 == " + e2);
   3258         response.abort(e2);
   3259         return;
   3260       }
   3261     }
   3262 
   3263     response.complete();
   3264   },
   3265 
   3266   // FIELDS
   3267 
   3268   /**
   3269    * This object contains the default handlers for the various HTTP error codes.
   3270    */
   3271   _defaultErrors:
   3272   {
   3273     400: function(metadata, response)
   3274     {
   3275       // none of the data in metadata is reliable, so hard-code everything here
   3276       response.setStatusLine("1.1", 400, "Bad Request");
   3277       response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
   3278 
   3279       var body = "Bad request\n";
   3280       response.bodyOutputStream.write(body, body.length);
   3281     },
   3282     403: function(metadata, response)
   3283     {
   3284       response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
   3285       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
   3286 
   3287       var body = "<html>\
   3288                     <head><title>403 Forbidden</title></head>\
   3289                     <body>\
   3290                       <h1>403 Forbidden</h1>\
   3291                     </body>\
   3292                   </html>";
   3293       response.bodyOutputStream.write(body, body.length);
   3294     },
   3295     404: function(metadata, response)
   3296     {
   3297       response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   3298       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
   3299 
   3300       var body = "<html>\
   3301                     <head><title>404 Not Found</title></head>\
   3302                     <body>\
   3303                       <h1>404 Not Found</h1>\
   3304                       <p>\
   3305                         <span style='font-family: monospace;'>" +
   3306                           htmlEscape(metadata.path) +
   3307                        "</span> was not found.\
   3308                       </p>\
   3309                     </body>\
   3310                   </html>";
   3311       response.bodyOutputStream.write(body, body.length);
   3312     },
   3313     416: function(metadata, response)
   3314     {
   3315       response.setStatusLine(metadata.httpVersion,
   3316                             416,
   3317                             "Requested Range Not Satisfiable");
   3318       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
   3319 
   3320       var body = "<html>\
   3321                    <head>\
   3322                     <title>416 Requested Range Not Satisfiable</title></head>\
   3323                     <body>\
   3324                      <h1>416 Requested Range Not Satisfiable</h1>\
   3325                      <p>The byte range was not valid for the\
   3326                         requested resource.\
   3327                      </p>\
   3328                     </body>\
   3329                   </html>";
   3330       response.bodyOutputStream.write(body, body.length);
   3331     },
   3332     500: function(metadata, response)
   3333     {
   3334       response.setStatusLine(metadata.httpVersion,
   3335                              500,
   3336                              "Internal Server Error");
   3337       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
   3338 
   3339       var body = "<html>\
   3340                     <head><title>500 Internal Server Error</title></head>\
   3341                     <body>\
   3342                       <h1>500 Internal Server Error</h1>\
   3343                       <p>Something's broken in this server and\
   3344                         needs to be fixed.</p>\
   3345                     </body>\
   3346                   </html>";
   3347       response.bodyOutputStream.write(body, body.length);
   3348     },
   3349     501: function(metadata, response)
   3350     {
   3351       response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
   3352       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
   3353 
   3354       var body = "<html>\
   3355                     <head><title>501 Not Implemented</title></head>\
   3356                     <body>\
   3357                       <h1>501 Not Implemented</h1>\
   3358                       <p>This server is not (yet) Apache.</p>\
   3359                     </body>\
   3360                   </html>";
   3361       response.bodyOutputStream.write(body, body.length);
   3362     },
   3363     505: function(metadata, response)
   3364     {
   3365       response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
   3366       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
   3367 
   3368       var body = "<html>\
   3369                     <head><title>505 HTTP Version Not Supported</title></head>\
   3370                     <body>\
   3371                       <h1>505 HTTP Version Not Supported</h1>\
   3372                       <p>This server only supports HTTP/1.0 and HTTP/1.1\
   3373                         connections.</p>\
   3374                     </body>\
   3375                   </html>";
   3376       response.bodyOutputStream.write(body, body.length);
   3377     }
   3378   },
   3379 
   3380   /**
   3381    * Contains handlers for the default set of URIs contained in this server.
   3382    */
   3383   _defaultPaths:
   3384   {
   3385     "/": function(metadata, response)
   3386     {
   3387       response.setStatusLine(metadata.httpVersion, 200, "OK");
   3388       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
   3389 
   3390       var body = "<html>\
   3391                     <head><title>httpd.js</title></head>\
   3392                     <body>\
   3393                       <h1>httpd.js</h1>\
   3394                       <p>If you're seeing this page, httpd.js is up and\
   3395                         serving requests!  Now set a base path and serve some\
   3396                         files!</p>\
   3397                     </body>\
   3398                   </html>";
   3399 
   3400       response.bodyOutputStream.write(body, body.length);
   3401     },
   3402 
   3403     "/trace": function(metadata, response)
   3404     {
   3405       response.setStatusLine(metadata.httpVersion, 200, "OK");
   3406       response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
   3407 
   3408       var body = "Request-URI: " +
   3409                  metadata.scheme + "://" + metadata.host + ":" + metadata.port +
   3410                  metadata.path + "\n\n";
   3411       body += "Request (semantically equivalent, slightly reformatted):\n\n";
   3412       body += metadata.method + " " + metadata.path;
   3413 
   3414       if (metadata.queryString)
   3415         body +=  "?" + metadata.queryString;
   3416         
   3417       body += " HTTP/" + metadata.httpVersion + "\r\n";
   3418 
   3419       var headEnum = metadata.headers;
   3420       while (headEnum.hasMoreElements())
   3421       {
   3422         var fieldName = headEnum.getNext()
   3423                                 .QueryInterface(Ci.nsISupportsString)
   3424                                 .data;
   3425         body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n";
   3426       }
   3427 
   3428       response.bodyOutputStream.write(body, body.length);
   3429     }
   3430   }
   3431 };
   3432 
   3433 
   3434 /**
   3435  * Maps absolute paths to files on the local file system (as nsILocalFiles).
   3436  */
   3437 function FileMap()
   3438 {
   3439   /** Hash which will map paths to nsILocalFiles. */
   3440   this._map = {};
   3441 }
   3442 FileMap.prototype =
   3443 {
   3444   // PUBLIC API
   3445 
   3446   /**
   3447    * Maps key to a clone of the nsILocalFile value if value is non-null;
   3448    * otherwise, removes any extant mapping for key.
   3449    *
   3450    * @param key : string
   3451    *   string to which a clone of value is mapped
   3452    * @param value : nsILocalFile
   3453    *   the file to map to key, or null to remove a mapping
   3454    */
   3455   put: function(key, value)
   3456   {
   3457     if (value)
   3458       this._map[key] = value.clone();
   3459     else
   3460       delete this._map[key];
   3461   },
   3462 
   3463   /**
   3464    * Returns a clone of the nsILocalFile mapped to key, or null if no such
   3465    * mapping exists.
   3466    *
   3467    * @param key : string
   3468    *   key to which the returned file maps
   3469    * @returns nsILocalFile
   3470    *   a clone of the mapped file, or null if no mapping exists
   3471    */
   3472   get: function(key)
   3473   {
   3474     var val = this._map[key];
   3475     return val ? val.clone() : null;
   3476   }
   3477 };
   3478 
   3479 
   3480 // Response CONSTANTS
   3481 
   3482 // token       = *<any CHAR except CTLs or separators>
   3483 // CHAR        = <any US-ASCII character (0-127)>
   3484 // CTL         = <any US-ASCII control character (0-31) and DEL (127)>
   3485 // separators  = "(" | ")" | "<" | ">" | "@"
   3486 //             | "," | ";" | ":" | "\" | <">
   3487 //             | "/" | "[" | "]" | "?" | "="
   3488 //             | "{" | "}" | SP  | HT
   3489 const IS_TOKEN_ARRAY =
   3490   [0, 0, 0, 0, 0, 0, 0, 0, //   0
   3491    0, 0, 0, 0, 0, 0, 0, 0, //   8
   3492    0, 0, 0, 0, 0, 0, 0, 0, //  16
   3493    0, 0, 0, 0, 0, 0, 0, 0, //  24
   3494 
   3495    0, 1, 0, 1, 1, 1, 1, 1, //  32
   3496    0, 0, 1, 1, 0, 1, 1, 0, //  40
   3497    1, 1, 1, 1, 1, 1, 1, 1, //  48
   3498    1, 1, 0, 0, 0, 0, 0, 0, //  56
   3499 
   3500    0, 1, 1, 1, 1, 1, 1, 1, //  64
   3501    1, 1, 1, 1, 1, 1, 1, 1, //  72
   3502    1, 1, 1, 1, 1, 1, 1, 1, //  80
   3503    1, 1, 1, 0, 0, 0, 1, 1, //  88
   3504 
   3505    1, 1, 1, 1, 1, 1, 1, 1, //  96
   3506    1, 1, 1, 1, 1, 1, 1, 1, // 104
   3507    1, 1, 1, 1, 1, 1, 1, 1, // 112
   3508    1, 1, 1, 0, 1, 0, 1];   // 120
   3509 
   3510 
   3511 /**
   3512  * Determines whether the given character code is a CTL.
   3513  *
   3514  * @param code : uint
   3515  *   the character code
   3516  * @returns boolean
   3517  *   true if code is a CTL, false otherwise
   3518  */
   3519 function isCTL(code)
   3520 {
   3521   return (code >= 0 && code <= 31) || (code == 127);
   3522 }
   3523 
   3524 /**
   3525  * Represents a response to an HTTP request, encapsulating all details of that
   3526  * response.  This includes all headers, the HTTP version, status code and
   3527  * explanation, and the entity itself.
   3528  *
   3529  * @param connection : Connection
   3530  *   the connection over which this response is to be written
   3531  */
   3532 function Response(connection)
   3533 {
   3534   /** The connection over which this response will be written. */
   3535   this._connection = connection;
   3536 
   3537   /**
   3538    * The HTTP version of this response; defaults to 1.1 if not set by the
   3539    * handler.
   3540    */
   3541   this._httpVersion = nsHttpVersion.HTTP_1_1;
   3542 
   3543   /**
   3544    * The HTTP code of this response; defaults to 200.
   3545    */
   3546   this._httpCode = 200;
   3547 
   3548   /**
   3549    * The description of the HTTP code in this response; defaults to "OK".
   3550    */
   3551   this._httpDescription = "OK";
   3552 
   3553   /**
   3554    * An nsIHttpHeaders object in which the headers in this response should be
   3555    * stored.  This property is null after the status line and headers have been
   3556    * written to the network, and it may be modified up until it is cleared,
   3557    * except if this._finished is set first (in which case headers are written
   3558    * asynchronously in response to a finish() call not preceded by
   3559    * flushHeaders()).
   3560    */
   3561   this._headers = new nsHttpHeaders();
   3562 
   3563   /**
   3564    * Set to true when this response is ended (completely constructed if possible
   3565    * and the connection closed); further actions on this will then fail.
   3566    */
   3567   this._ended = false;
   3568 
   3569   /**
   3570    * A stream used to hold data written to the body of this response.
   3571    */
   3572   this._bodyOutputStream = null;
   3573 
   3574   /**
   3575    * A stream containing all data that has been written to the body of this
   3576    * response so far.  (Async handlers make the data contained in this
   3577    * unreliable as a way of determining content length in general, but auxiliary
   3578    * saved information can sometimes be used to guarantee reliability.)
   3579    */
   3580   this._bodyInputStream = null;
   3581 
   3582   /**
   3583    * A stream copier which copies data to the network.  It is initially null
   3584    * until replaced with a copier for response headers; when headers have been
   3585    * fully sent it is replaced with a copier for the response body, remaining
   3586    * so for the duration of response processing.
   3587    */
   3588   this._asyncCopier = null;
   3589 
   3590   /**
   3591    * True if this response has been designated as being processed
   3592    * asynchronously rather than for the duration of a single call to
   3593    * nsIHttpRequestHandler.handle.
   3594    */
   3595   this._processAsync = false;
   3596 
   3597   /**
   3598    * True iff finish() has been called on this, signaling that no more changes
   3599    * to this may be made.
   3600    */
   3601   this._finished = false;
   3602 
   3603   /**
   3604    * True iff powerSeized() has been called on this, signaling that this
   3605    * response is to be handled manually by the response handler (which may then
   3606    * send arbitrary data in response, even non-HTTP responses).
   3607    */
   3608   this._powerSeized = false;
   3609 }
   3610 Response.prototype =
   3611 {
   3612   // PUBLIC CONSTRUCTION API
   3613 
   3614   //
   3615   // see nsIHttpResponse.bodyOutputStream
   3616   //
   3617   get bodyOutputStream()
   3618   {
   3619     if (this._finished)
   3620       throw Cr.NS_ERROR_NOT_AVAILABLE;
   3621 
   3622     if (!this._bodyOutputStream)
   3623     {
   3624       var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX,
   3625                           null);
   3626       this._bodyOutputStream = pipe.outputStream;
   3627       this._bodyInputStream = pipe.inputStream;
   3628       if (this._processAsync || this._powerSeized)
   3629         this._startAsyncProcessor();
   3630     }
   3631 
   3632     return this._bodyOutputStream;
   3633   },
   3634 
   3635   //
   3636   // see nsIHttpResponse.write
   3637   //
   3638   write: function(data)
   3639   {
   3640     if (this._finished)
   3641       throw Cr.NS_ERROR_NOT_AVAILABLE;
   3642 
   3643     var dataAsString = String(data);
   3644     this.bodyOutputStream.write(dataAsString, dataAsString.length);
   3645   },
   3646 
   3647   //
   3648   // see nsIHttpResponse.setStatusLine
   3649   //
   3650   setStatusLine: function(httpVersion, code, description)
   3651   {
   3652     if (!this._headers || this._finished || this._powerSeized)
   3653       throw Cr.NS_ERROR_NOT_AVAILABLE;
   3654     this._ensureAlive();
   3655 
   3656     if (!(code >= 0 && code < 1000))
   3657       throw Cr.NS_ERROR_INVALID_ARG;
   3658 
   3659     try
   3660     {
   3661       var httpVer;
   3662       // avoid version construction for the most common cases
   3663       if (!httpVersion || httpVersion == "1.1")
   3664         httpVer = nsHttpVersion.HTTP_1_1;
   3665       else if (httpVersion == "1.0")
   3666         httpVer = nsHttpVersion.HTTP_1_0;
   3667       else
   3668         httpVer = new nsHttpVersion(httpVersion);
   3669     }
   3670     catch (e)
   3671     {
   3672       throw Cr.NS_ERROR_INVALID_ARG;
   3673     }
   3674 
   3675     // Reason-Phrase = *<TEXT, excluding CR, LF>
   3676     // TEXT          = <any OCTET except CTLs, but including LWS>
   3677     //
   3678     // XXX this ends up disallowing octets which aren't Unicode, I think -- not
   3679     //     much to do if description is IDL'd as string
   3680     if (!description)
   3681       description = "";
   3682     for (var i = 0; i < description.length; i++)
   3683       if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t")
   3684         throw Cr.NS_ERROR_INVALID_ARG;
   3685 
   3686     // set the values only after validation to preserve atomicity
   3687     this._httpDescription = description;
   3688     this._httpCode = code;
   3689     this._httpVersion = httpVer;
   3690   },
   3691 
   3692   //
   3693   // see nsIHttpResponse.setHeader
   3694   //
   3695   setHeader: function(name, value, merge)
   3696   {
   3697     if (!this._headers || this._finished || this._powerSeized)
   3698       throw Cr.NS_ERROR_NOT_AVAILABLE;
   3699     this._ensureAlive();
   3700 
   3701     this._headers.setHeader(name, value, merge);
   3702   },
   3703 
   3704   //
   3705   // see nsIHttpResponse.processAsync
   3706   //
   3707   processAsync: function()
   3708   {
   3709     if (this._finished)
   3710       throw Cr.NS_ERROR_UNEXPECTED;
   3711     if (this._powerSeized)
   3712       throw Cr.NS_ERROR_NOT_AVAILABLE;
   3713     if (this._processAsync)
   3714       return;
   3715     this._ensureAlive();
   3716 
   3717     dumpn("*** processing connection " + this._connection.number + " async");
   3718     this._processAsync = true;
   3719 
   3720     /*
   3721      * Either the bodyOutputStream getter or this method is responsible for
   3722      * starting the asynchronous processor and catching writes of data to the
   3723      * response body of async responses as they happen, for the purpose of
   3724      * forwarding those writes to the actual connection's output stream.
   3725      * If bodyOutputStream is accessed first, calling this method will create
   3726      * the processor (when it first is clear that body data is to be written
   3727      * immediately, not buffered).  If this method is called first, accessing
   3728      * bodyOutputStream will create the processor.  If only this method is
   3729      * called, we'll write nothing, neither headers nor the nonexistent body,
   3730      * until finish() is called.  Since that delay is easily avoided by simply
   3731      * getting bodyOutputStream or calling write(""), we don't worry about it.
   3732      */
   3733     if (this._bodyOutputStream && !this._asyncCopier)
   3734       this._startAsyncProcessor();
   3735   },
   3736 
   3737   //
   3738   // see nsIHttpResponse.seizePower
   3739   //
   3740   seizePower: function()
   3741   {
   3742     if (this._processAsync)
   3743       throw Cr.NS_ERROR_NOT_AVAILABLE;
   3744     if (this._finished)
   3745       throw Cr.NS_ERROR_UNEXPECTED;
   3746     if (this._powerSeized)
   3747       return;
   3748     this._ensureAlive();
   3749 
   3750     dumpn("*** forcefully seizing power over connection " +
   3751           this._connection.number + "...");
   3752 
   3753     // Purge any already-written data without sending it.  We could as easily
   3754     // swap out the streams entirely, but that makes it possible to acquire and
   3755     // unknowingly use a stale reference, so we require there only be one of
   3756     // each stream ever for any response to avoid this complication.
   3757     if (this._asyncCopier)
   3758       this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
   3759     this._asyncCopier = null;
   3760     if (this._bodyOutputStream)
   3761     {
   3762       var input = new BinaryInputStream(this._bodyInputStream);
   3763       var avail;
   3764       while ((avail = input.available()) > 0)
   3765         input.readByteArray(avail);
   3766     }
   3767 
   3768     this._powerSeized = true;
   3769     if (this._bodyOutputStream)
   3770       this._startAsyncProcessor();
   3771   },
   3772 
   3773   //
   3774   // see nsIHttpResponse.finish
   3775   //
   3776   finish: function()
   3777   {
   3778     if (!this._processAsync && !this._powerSeized)
   3779       throw Cr.NS_ERROR_UNEXPECTED;
   3780     if (this._finished)
   3781       return;
   3782 
   3783     dumpn("*** finishing connection " + this._connection.number);
   3784     this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
   3785     if (this._bodyOutputStream)
   3786       this._bodyOutputStream.close();
   3787     this._finished = true;
   3788   },
   3789 
   3790 
   3791   // NSISUPPORTS
   3792 
   3793   //
   3794   // see nsISupports.QueryInterface
   3795   //
   3796   QueryInterface: function(iid)
   3797   {
   3798     if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports))
   3799       return this;
   3800 
   3801     throw Cr.NS_ERROR_NO_INTERFACE;
   3802   },
   3803 
   3804 
   3805   // POST-CONSTRUCTION API (not exposed externally)
   3806 
   3807   /**
   3808    * The HTTP version number of this, as a string (e.g. "1.1").
   3809    */
   3810   get httpVersion()
   3811   {
   3812     this._ensureAlive();
   3813     return this._httpVersion.toString();
   3814   },
   3815 
   3816   /**
   3817    * The HTTP status code of this response, as a string of three characters per
   3818    * RFC 2616.
   3819    */
   3820   get httpCode()
   3821   {
   3822     this._ensureAlive();
   3823 
   3824     var codeString = (this._httpCode < 10 ? "0" : "") +
   3825                      (this._httpCode < 100 ? "0" : "") +
   3826                      this._httpCode;
   3827     return codeString;
   3828   },
   3829 
   3830   /**
   3831    * The description of the HTTP status code of this response, or "" if none is
   3832    * set.
   3833    */
   3834   get httpDescription()
   3835   {
   3836     this._ensureAlive();
   3837 
   3838     return this._httpDescription;
   3839   },
   3840 
   3841   /**
   3842    * The headers in this response, as an nsHttpHeaders object.
   3843    */
   3844   get headers()
   3845   {
   3846     this._ensureAlive();
   3847 
   3848     return this._headers;
   3849   },
   3850 
   3851   //
   3852   // see nsHttpHeaders.getHeader
   3853   //
   3854   getHeader: function(name)
   3855   {
   3856     this._ensureAlive();
   3857 
   3858     return this._headers.getHeader(name);
   3859   },
   3860 
   3861   /**
   3862    * Determines whether this response may be abandoned in favor of a newly
   3863    * constructed response.  A response may be abandoned only if it is not being
   3864    * sent asynchronously and if raw control over it has not been taken from the
   3865    * server.
   3866    *
   3867    * @returns boolean
   3868    *   true iff no data has been written to the network
   3869    */
   3870   partiallySent: function()
   3871   {
   3872     dumpn("*** partiallySent()");
   3873     return this._processAsync || this._powerSeized;
   3874   },
   3875 
   3876   /**
   3877    * If necessary, kicks off the remaining request processing needed to be done
   3878    * after a request handler performs its initial work upon this response.
   3879    */
   3880   complete: function()
   3881   {
   3882     dumpn("*** complete()");
   3883     if (this._processAsync || this._powerSeized)
   3884     {
   3885       NS_ASSERT(this._processAsync ^ this._powerSeized,
   3886                 "can't both send async and relinquish power");
   3887       return;
   3888     }
   3889 
   3890     NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
   3891 
   3892     this._startAsyncProcessor();
   3893 
   3894     // Now make sure we finish processing this request!
   3895     if (this._bodyOutputStream)
   3896       this._bodyOutputStream.close();
   3897   },
   3898 
   3899   /**
   3900    * Abruptly ends processing of this response, usually due to an error in an
   3901    * incoming request but potentially due to a bad error handler.  Since we
   3902    * cannot handle the error in the usual way (giving an HTTP error page in
   3903    * response) because data may already have been sent (or because the response
   3904    * might be expected to have been generated asynchronously or completely from
   3905    * scratch by the handler), we stop processing this response and abruptly
   3906    * close the connection.
   3907    *
   3908    * @param e : Error
   3909    *   the exception which precipitated this abort, or null if no such exception
   3910    *   was generated
   3911    */
   3912   abort: function(e)
   3913   {
   3914     dumpn("*** abort(<" + e + ">)");
   3915 
   3916     // This response will be ended by the processor if one was created.
   3917     var copier = this._asyncCopier;
   3918     if (copier)
   3919     {
   3920       // We dispatch asynchronously here so that any pending writes of data to
   3921       // the connection will be deterministically written.  This makes it easier
   3922       // to specify exact behavior, and it makes observable behavior more
   3923       // predictable for clients.  Note that the correctness of this depends on
   3924       // callbacks in response to _waitToReadData in WriteThroughCopier
   3925       // happening asynchronously with respect to the actual writing of data to
   3926       // bodyOutputStream, as they currently do; if they happened synchronously,
   3927       // an event which ran before this one could write more data to the
   3928       // response body before we get around to canceling the copier.  We have
   3929       // tests for this in test_seizepower.js, however, and I can't think of a
   3930       // way to handle both cases without removing bodyOutputStream access and
   3931       // moving its effective write(data, length) method onto Response, which
   3932       // would be slower and require more code than this anyway.
   3933       gThreadManager.currentThread.dispatch({
   3934         run: function()
   3935         {
   3936           dumpn("*** canceling copy asynchronously...");
   3937           copier.cancel(Cr.NS_ERROR_UNEXPECTED);
   3938         }
   3939       }, Ci.nsIThread.DISPATCH_NORMAL);
   3940     }
   3941     else
   3942     {
   3943       this.end();
   3944     }
   3945   },
   3946 
   3947   /**
   3948    * Closes this response's network connection, marks the response as finished,
   3949    * and notifies the server handler that the request is done being processed.
   3950    */
   3951   end: function()
   3952   {
   3953     NS_ASSERT(!this._ended, "ending this response twice?!?!");
   3954 
   3955     this._connection.close();
   3956     if (this._bodyOutputStream)
   3957       this._bodyOutputStream.close();
   3958 
   3959     this._finished = true;
   3960     this._ended = true;
   3961   },
   3962 
   3963   // PRIVATE IMPLEMENTATION
   3964 
   3965   /**
   3966    * Sends the status line and headers of this response if they haven't been
   3967    * sent and initiates the process of copying data written to this response's
   3968    * body to the network.
   3969    */
   3970   _startAsyncProcessor: function()
   3971   {
   3972     dumpn("*** _startAsyncProcessor()");
   3973 
   3974     // Handle cases where we're being called a second time.  The former case
   3975     // happens when this is triggered both by complete() and by processAsync(),
   3976     // while the latter happens when processAsync() in conjunction with sent
   3977     // data causes abort() to be called.
   3978     if (this._asyncCopier || this._ended)
   3979     {
   3980       dumpn("*** ignoring second call to _startAsyncProcessor");
   3981       return;
   3982     }
   3983 
   3984     // Send headers if they haven't been sent already and should be sent, then
   3985     // asynchronously continue to send the body.
   3986     if (this._headers && !this._powerSeized)
   3987     {
   3988       this._sendHeaders();
   3989       return;
   3990     }
   3991 
   3992     this._headers = null;
   3993     this._sendBody();
   3994   },
   3995 
   3996   /**
   3997    * Signals that all modifications to the response status line and headers are
   3998    * complete and then sends that data over the network to the client.  Once
   3999    * this method completes, a different response to the request that resulted
   4000    * in this response cannot be sent -- the only possible action in case of
   4001    * error is to abort the response and close the connection.
   4002    */
   4003   _sendHeaders: function()
   4004   {
   4005     dumpn("*** _sendHeaders()");
   4006 
   4007     NS_ASSERT(this._headers);
   4008     NS_ASSERT(!this._powerSeized);
   4009 
   4010     // request-line
   4011     var statusLine = "HTTP/" + this.httpVersion + " " +
   4012                      this.httpCode + " " +
   4013                      this.httpDescription + "\r\n";
   4014 
   4015     // header post-processing
   4016 
   4017     var headers = this._headers;
   4018     headers.setHeader("Connection", "close", false);
   4019     headers.setHeader("Server", "httpd.js", false);
   4020     if (!headers.hasHeader("Date"))
   4021       headers.setHeader("Date", toDateString(Date.now()), false);
   4022 
   4023     // Any response not being processed asynchronously must have an associated
   4024     // Content-Length header for reasons of backwards compatibility with the
   4025     // initial server, which fully buffered every response before sending it.
   4026     // Beyond that, however, it's good to do this anyway because otherwise it's
   4027     // impossible to test behaviors that depend on the presence or absence of a
   4028     // Content-Length header.
   4029     if (!this._processAsync)
   4030     {
   4031       dumpn("*** non-async response, set Content-Length");
   4032 
   4033       var bodyStream = this._bodyInputStream;
   4034       var avail = bodyStream ? bodyStream.available() : 0;
   4035 
   4036       // XXX assumes stream will always report the full amount of data available
   4037       headers.setHeader("Content-Length", "" + avail, false);
   4038     }
   4039 
   4040 
   4041     // construct and send response
   4042     dumpn("*** header post-processing completed, sending response head...");
   4043 
   4044     // request-line
   4045     var preambleData = [statusLine];
   4046 
   4047     // headers
   4048     var headEnum = headers.enumerator;
   4049     while (headEnum.hasMoreElements())
   4050     {
   4051       var fieldName = headEnum.getNext()
   4052                               .QueryInterface(Ci.nsISupportsString)
   4053                               .data;
   4054       var values = headers.getHeaderValues(fieldName);
   4055       for (var i = 0, sz = values.length; i < sz; i++)
   4056         preambleData.push(fieldName + ": " + values[i] + "\r\n");
   4057     }
   4058 
   4059     // end request-line/headers
   4060     preambleData.push("\r\n");
   4061 
   4062     var preamble = preambleData.join("");
   4063 
   4064     var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null);
   4065     responseHeadPipe.outputStream.write(preamble, preamble.length);
   4066 
   4067     var response = this;
   4068     var copyObserver =
   4069       {
   4070         onStartRequest: function(request, cx)
   4071         {
   4072           dumpn("*** preamble copying started");
   4073         },
   4074 
   4075         onStopRequest: function(request, cx, statusCode)
   4076         {
   4077           dumpn("*** preamble copying complete " +
   4078                 "[status=0x" + statusCode.toString(16) + "]");
   4079 
   4080           if (!Components.isSuccessCode(statusCode))
   4081           {
   4082             dumpn("!!! header copying problems: non-success statusCode, " +
   4083                   "ending response");
   4084 
   4085             response.end();
   4086           }
   4087           else
   4088           {
   4089             response._sendBody();
   4090           }
   4091         },
   4092 
   4093         QueryInterface: function(aIID)
   4094         {
   4095           if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
   4096             return this;
   4097 
   4098           throw Cr.NS_ERROR_NO_INTERFACE;
   4099         }
   4100       };
   4101 
   4102     var headerCopier = this._asyncCopier =
   4103       new WriteThroughCopier(responseHeadPipe.inputStream,
   4104                              this._connection.output,
   4105                              copyObserver, null);
   4106 
   4107     responseHeadPipe.outputStream.close();
   4108 
   4109     // Forbid setting any more headers or modifying the request line.
   4110     this._headers = null;
   4111   },
   4112 
   4113   /**
   4114    * Asynchronously writes the body of the response (or the entire response, if
   4115    * seizePower() has been called) to the network.
   4116    */
   4117   _sendBody: function()
   4118   {
   4119     dumpn("*** _sendBody");
   4120 
   4121     NS_ASSERT(!this._headers, "still have headers around but sending body?");
   4122 
   4123     // If no body data was written, we're done
   4124     if (!this._bodyInputStream)
   4125     {
   4126       dumpn("*** empty body, response finished");
   4127       this.end();
   4128       return;
   4129     }
   4130 
   4131     var response = this;
   4132     var copyObserver =
   4133       {
   4134         onStartRequest: function(request, context)
   4135         {
   4136           dumpn("*** onStartRequest");
   4137         },
   4138 
   4139         onStopRequest: function(request, cx, statusCode)
   4140         {
   4141           dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");
   4142 
   4143           if (statusCode === Cr.NS_BINDING_ABORTED)
   4144           {
   4145             dumpn("*** terminating copy observer without ending the response");
   4146           }
   4147           else
   4148           {
   4149             if (!Components.isSuccessCode(statusCode))
   4150               dumpn("*** WARNING: non-success statusCode in onStopRequest");
   4151 
   4152             response.end();
   4153           }
   4154         },
   4155 
   4156         QueryInterface: function(aIID)
   4157         {
   4158           if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
   4159             return this;
   4160 
   4161           throw Cr.NS_ERROR_NO_INTERFACE;
   4162         }
   4163       };
   4164 
   4165     dumpn("*** starting async copier of body data...");
   4166     this._asyncCopier =
   4167       new WriteThroughCopier(this._bodyInputStream, this._connection.output,
   4168                             copyObserver, null);
   4169   },
   4170 
   4171   /** Ensures that this hasn't been ended. */
   4172   _ensureAlive: function()
   4173   {
   4174     NS_ASSERT(!this._ended, "not handling response lifetime correctly");
   4175   }
   4176 };
   4177 
   4178 /**
   4179  * Size of the segments in the buffer used in storing response data and writing
   4180  * it to the socket.
   4181  */
   4182 Response.SEGMENT_SIZE = 8192;
   4183 
   4184 /** Serves double duty in WriteThroughCopier implementation. */
   4185 function notImplemented()
   4186 {
   4187   throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   4188 }
   4189 
   4190 /** Returns true iff the given exception represents stream closure. */
   4191 function streamClosed(e)
   4192 {
   4193   return e === Cr.NS_BASE_STREAM_CLOSED ||
   4194          (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED);
   4195 }
   4196 
   4197 /** Returns true iff the given exception represents a blocked stream. */
   4198 function wouldBlock(e)
   4199 {
   4200   return e === Cr.NS_BASE_STREAM_WOULD_BLOCK ||
   4201          (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK);
   4202 }
   4203 
   4204 /**
   4205  * Copies data from source to sink as it becomes available, when that data can
   4206  * be written to sink without blocking.
   4207  *
   4208  * @param source : nsIAsyncInputStream
   4209  *   the stream from which data is to be read
   4210  * @param sink : nsIAsyncOutputStream
   4211  *   the stream to which data is to be copied
   4212  * @param observer : nsIRequestObserver
   4213  *   an observer which will be notified when the copy starts and finishes
   4214  * @param context : nsISupports
   4215  *   context passed to observer when notified of start/stop
   4216  * @throws NS_ERROR_NULL_POINTER
   4217  *   if source, sink, or observer are null
   4218  */
   4219 function WriteThroughCopier(source, sink, observer, context)
   4220 {
   4221   if (!source || !sink || !observer)
   4222     throw Cr.NS_ERROR_NULL_POINTER;
   4223 
   4224   /** Stream from which data is being read. */
   4225   this._source = source;
   4226 
   4227   /** Stream to which data is being written. */
   4228   this._sink = sink;
   4229 
   4230   /** Observer watching this copy. */
   4231   this._observer = observer;
   4232 
   4233   /** Context for the observer watching this. */
   4234   this._context = context;
   4235 
   4236   /**
   4237    * True iff this is currently being canceled (cancel has been called, the
   4238    * callback may not yet have been made).
   4239    */
   4240   this._canceled = false;
   4241 
   4242   /**
   4243    * False until all data has been read from input and written to output, at
   4244    * which point this copy is completed and cancel() is asynchronously called.
   4245    */
   4246   this._completed = false;
   4247 
   4248   /** Required by nsIRequest, meaningless. */
   4249   this.loadFlags = 0;
   4250   /** Required by nsIRequest, meaningless. */
   4251   this.loadGroup = null;
   4252   /** Required by nsIRequest, meaningless. */
   4253   this.name = "response-body-copy";
   4254 
   4255   /** Status of this request. */
   4256   this.status = Cr.NS_OK;
   4257 
   4258   /** Arrays of byte strings waiting to be written to output. */
   4259   this._pendingData = [];
   4260 
   4261   // start copying
   4262   try
   4263   {
   4264     observer.onStartRequest(this, context);
   4265     this._waitToReadData();
   4266     this._waitForSinkClosure();
   4267   }
   4268   catch (e)
   4269   {
   4270     dumpn("!!! error starting copy: " + e +
   4271           ("lineNumber" in e ? ", line " + e.lineNumber : ""));
   4272     dumpn(e.stack);
   4273     this.cancel(Cr.NS_ERROR_UNEXPECTED);
   4274   }
   4275 }
   4276 WriteThroughCopier.prototype =
   4277 {
   4278   /* nsISupports implementation */
   4279 
   4280   QueryInterface: function(iid)
   4281   {
   4282     if (iid.equals(Ci.nsIInputStreamCallback) ||
   4283         iid.equals(Ci.nsIOutputStreamCallback) ||
   4284         iid.equals(Ci.nsIRequest) ||
   4285         iid.equals(Ci.nsISupports))
   4286     {
   4287       return this;
   4288     }
   4289 
   4290     throw Cr.NS_ERROR_NO_INTERFACE;
   4291   },
   4292 
   4293 
   4294   // NSIINPUTSTREAMCALLBACK
   4295 
   4296   /**
   4297    * Receives a more-data-in-input notification and writes the corresponding
   4298    * data to the output.
   4299    *
   4300    * @param input : nsIAsyncInputStream
   4301    *   the input stream on whose data we have been waiting
   4302    */
   4303   onInputStreamReady: function(input)
   4304   {
   4305     if (this._source === null)
   4306       return;
   4307 
   4308     dumpn("*** onInputStreamReady");
   4309 
   4310     //
   4311     // Ordinarily we'll read a non-zero amount of data from input, queue it up
   4312     // to be written and then wait for further callbacks.  The complications in
   4313     // this method are the cases where we deviate from that behavior when errors
   4314     // occur or when copying is drawing to a finish.
   4315     //
   4316     // The edge cases when reading data are:
   4317     //
   4318     //   Zero data is read
   4319     //     If zero data was read, we're at the end of available data, so we can
   4320     //     should stop reading and move on to writing out what we have (or, if
   4321     //     we've already done that, onto notifying of completion).
   4322     //   A stream-closed exception is thrown
   4323     //     This is effectively a less kind version of zero data being read; the
   4324     //     only difference is that we notify of completion with that result
   4325     //     rather than with NS_OK.
   4326     //   Some other exception is thrown
   4327     //     This is the least kind result.  We don't know what happened, so we
   4328     //     act as though the stream closed except that we notify of completion
   4329     //     with the result NS_ERROR_UNEXPECTED.
   4330     //
   4331 
   4332     var bytesWanted = 0, bytesConsumed = -1;
   4333     try
   4334     {
   4335       input = new BinaryInputStream(input);
   4336 
   4337       bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE);
   4338       dumpn("*** input wanted: " + bytesWanted);
   4339 
   4340       if (bytesWanted > 0)
   4341       {
   4342         var data = input.readByteArray(bytesWanted);
   4343         bytesConsumed = data.length;
   4344         this._pendingData.push(String.fromCharCode.apply(String, data));
   4345       }
   4346 
   4347       dumpn("*** " + bytesConsumed + " bytes read");
   4348 
   4349       // Handle the zero-data edge case in the same place as all other edge
   4350       // cases are handled.
   4351       if (bytesWanted === 0)
   4352         throw Cr.NS_BASE_STREAM_CLOSED;
   4353     }
   4354     catch (e)
   4355     {
   4356       if (streamClosed(e))
   4357       {
   4358         dumpn("*** input stream closed");
   4359         e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
   4360       }
   4361       else
   4362       {
   4363         dumpn("!!! unexpected error reading from input, canceling: " + e);
   4364         e = Cr.NS_ERROR_UNEXPECTED;
   4365       }
   4366 
   4367       this._doneReadingSource(e);
   4368       return;
   4369     }
   4370 
   4371     var pendingData = this._pendingData;
   4372 
   4373     NS_ASSERT(bytesConsumed > 0);
   4374     NS_ASSERT(pendingData.length > 0, "no pending data somehow?");
   4375     NS_ASSERT(pendingData[pendingData.length - 1].length > 0,
   4376               "buffered zero bytes of data?");
   4377 
   4378     NS_ASSERT(this._source !== null);
   4379 
   4380     // Reading has gone great, and we've gotten data to write now.  What if we
   4381     // don't have a place to write that data, because output went away just
   4382     // before this read?  Drop everything on the floor, including new data, and
   4383     // cancel at this point.
   4384     if (this._sink === null)
   4385     {
   4386       pendingData.length = 0;
   4387       this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
   4388       return;
   4389     }
   4390 
   4391     // Okay, we've read the data, and we know we have a place to write it.  We
   4392     // need to queue up the data to be written, but *only* if none is queued
   4393     // already -- if data's already queued, the code that actually writes the
   4394     // data will make sure to wait on unconsumed pending data.
   4395     try
   4396     {
   4397       if (pendingData.length === 1)
   4398         this._waitToWriteData();
   4399     }
   4400     catch (e)
   4401     {
   4402       dumpn("!!! error waiting to write data just read, swallowing and " +
   4403             "writing only what we already have: " + e);
   4404       this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
   4405       return;
   4406     }
   4407 
   4408     // Whee!  We successfully read some data, and it's successfully queued up to
   4409     // be written.  All that remains now is to wait for more data to read.
   4410     try
   4411     {
   4412       this._waitToReadData();
   4413     }
   4414     catch (e)
   4415     {
   4416       dumpn("!!! error waiting to read more data: " + e);
   4417       this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
   4418     }
   4419   },
   4420 
   4421 
   4422   // NSIOUTPUTSTREAMCALLBACK
   4423 
   4424   /**
   4425    * Callback when data may be written to the output stream without blocking, or
   4426    * when the output stream has been closed.
   4427    *
   4428    * @param output : nsIAsyncOutputStream
   4429    *   the output stream on whose writability we've been waiting, also known as
   4430    *   this._sink
   4431    */
   4432   onOutputStreamReady: function(output)
   4433   {
   4434     if (this._sink === null)
   4435       return;
   4436 
   4437     dumpn("*** onOutputStreamReady");
   4438 
   4439     var pendingData = this._pendingData;
   4440     if (pendingData.length === 0)
   4441     {
   4442       // There's no pending data to write.  The only way this can happen is if
   4443       // we're waiting on the output stream's closure, so we can respond to a
   4444       // copying failure as quickly as possible (rather than waiting for data to
   4445       // be available to read and then fail to be copied).  Therefore, we must
   4446       // be done now -- don't bother to attempt to write anything and wrap
   4447       // things up.
   4448       dumpn("!!! output stream closed prematurely, ending copy");
   4449 
   4450       this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
   4451       return;
   4452     }
   4453 
   4454 
   4455     NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?");
   4456 
   4457     //
   4458     // Write out the first pending quantum of data.  The possible errors here
   4459     // are:
   4460     //
   4461     //   The write might fail because we can't write that much data
   4462     //     Okay, we've written what we can now, so re-queue what's left and
   4463     //     finish writing it out later.
   4464     //   The write failed because the stream was closed
   4465     //     Discard pending data that we can no longer write, stop reading, and
   4466     //     signal that copying finished.
   4467     //   Some other error occurred.
   4468     //     Same as if the stream were closed, but notify with the status
   4469     //     NS_ERROR_UNEXPECTED so the observer knows something was wonky.
   4470     //
   4471 
   4472     try
   4473     {
   4474       var quantum = pendingData[0];
   4475 
   4476       // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on
   4477       //     undefined behavior!  We're only using this because writeByteArray
   4478       //     is unusably broken for asynchronous output streams; see bug 532834
   4479       //     for details.
   4480       var bytesWritten = output.write(quantum, quantum.length);
   4481       if (bytesWritten === quantum.length)
   4482         pendingData.shift();
   4483       else
   4484         pendingData[0] = quantum.substring(bytesWritten);
   4485 
   4486       dumpn("*** wrote " + bytesWritten + " bytes of data");
   4487     }
   4488     catch (e)
   4489     {
   4490       if (wouldBlock(e))
   4491       {
   4492         NS_ASSERT(pendingData.length > 0,
   4493                   "stream-blocking exception with no data to write?");
   4494         NS_ASSERT(pendingData[0].length > 0,
   4495                   "stream-blocking exception with empty quantum?");
   4496         this._waitToWriteData();
   4497         return;
   4498       }
   4499 
   4500       if (streamClosed(e))
   4501         dumpn("!!! output stream prematurely closed, signaling error...");
   4502       else
   4503         dumpn("!!! unknown error: " + e + ", quantum=" + quantum);
   4504 
   4505       this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
   4506       return;
   4507     }
   4508 
   4509     // The day is ours!  Quantum written, now let's see if we have more data
   4510     // still to write.
   4511     try
   4512     {
   4513       if (pendingData.length > 0)
   4514       {
   4515         this._waitToWriteData();
   4516         return;
   4517       }
   4518     }
   4519     catch (e)
   4520     {
   4521       dumpn("!!! unexpected error waiting to write pending data: " + e);
   4522       this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
   4523       return;
   4524     }
   4525 
   4526     // Okay, we have no more pending data to write -- but might we get more in
   4527     // the future?
   4528     if (this._source !== null)
   4529     {
   4530       /*
   4531        * If we might, then wait for the output stream to be closed.  (We wait
   4532        * only for closure because we have no data to write -- and if we waited
   4533        * for a specific amount of data, we would get repeatedly notified for no
   4534        * reason if over time the output stream permitted more and more data to
   4535        * be written to it without blocking.)
   4536        */
   4537        this._waitForSinkClosure();
   4538     }
   4539     else
   4540     {
   4541       /*
   4542        * On the other hand, if we can't have more data because the input
   4543        * stream's gone away, then it's time to notify of copy completion.
   4544        * Victory!
   4545        */
   4546       this._sink = null;
   4547       this._cancelOrDispatchCancelCallback(Cr.NS_OK);
   4548     }
   4549   },
   4550 
   4551 
   4552   // NSIREQUEST
   4553 
   4554   /** Returns true if the cancel observer hasn't been notified yet. */
   4555   isPending: function()
   4556   {
   4557     return !this._completed;
   4558   },
   4559 
   4560   /** Not implemented, don't use! */
   4561   suspend: notImplemented,
   4562   /** Not implemented, don't use! */
   4563   resume: notImplemented,
   4564 
   4565   /**
   4566    * Cancels data reading from input, asynchronously writes out any pending
   4567    * data, and causes the observer to be notified with the given error code when
   4568    * all writing has finished.
   4569    *
   4570    * @param status : nsresult
   4571    *   the status to pass to the observer when data copying has been canceled
   4572    */
   4573   cancel: function(status)
   4574   {
   4575     dumpn("*** cancel(" + status.toString(16) + ")");
   4576 
   4577     if (this._canceled)
   4578     {
   4579       dumpn("*** suppressing a late cancel");
   4580       return;
   4581     }
   4582 
   4583     this._canceled = true;
   4584     this.status = status;
   4585 
   4586     // We could be in the middle of absolutely anything at this point.  Both
   4587     // input and output might still be around, we might have pending data to
   4588     // write, and in general we know nothing about the state of the world.  We
   4589     // therefore must assume everything's in progress and take everything to its
   4590     // final steady state (or so far as it can go before we need to finish
   4591     // writing out remaining data).
   4592 
   4593     this._doneReadingSource(status);
   4594   },
   4595 
   4596 
   4597   // PRIVATE IMPLEMENTATION
   4598 
   4599   /**
   4600    * Stop reading input if we haven't already done so, passing e as the status
   4601    * when closing the stream, and kick off a copy-completion notice if no more
   4602    * data remains to be written.
   4603    *
   4604    * @param e : nsresult
   4605    *   the status to be used when closing the input stream
   4606    */
   4607   _doneReadingSource: function(e)
   4608   {
   4609     dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")");
   4610 
   4611     this._finishSource(e);
   4612     if (this._pendingData.length === 0)
   4613       this._sink = null;
   4614     else
   4615       NS_ASSERT(this._sink !== null, "null output?");
   4616 
   4617     // If we've written out all data read up to this point, then it's time to
   4618     // signal completion.
   4619     if (this._sink === null)
   4620     {
   4621       NS_ASSERT(this._pendingData.length === 0, "pending data still?");
   4622       this._cancelOrDispatchCancelCallback(e);
   4623     }
   4624   },
   4625 
   4626   /**
   4627    * Stop writing output if we haven't already done so, discard any data that
   4628    * remained to be sent, close off input if it wasn't already closed, and kick
   4629    * off a copy-completion notice.
   4630    *
   4631    * @param e : nsresult
   4632    *   the status to be used when closing input if it wasn't already closed
   4633    */
   4634   _doneWritingToSink: function(e)
   4635   {
   4636     dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")");
   4637 
   4638     this._pendingData.length = 0;
   4639     this._sink = null;
   4640     this._doneReadingSource(e);
   4641   },
   4642 
   4643   /**
   4644    * Completes processing of this copy: either by canceling the copy if it
   4645    * hasn't already been canceled using the provided status, or by dispatching
   4646    * the cancel callback event (with the originally provided status, of course)
   4647    * if it already has been canceled.
   4648    *
   4649    * @param status : nsresult
   4650    *   the status code to use to cancel this, if this hasn't already been
   4651    *   canceled
   4652    */
   4653   _cancelOrDispatchCancelCallback: function(status)
   4654   {
   4655     dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")");
   4656 
   4657     NS_ASSERT(this._source === null, "should have finished input");
   4658     NS_ASSERT(this._sink === null, "should have finished output");
   4659     NS_ASSERT(this._pendingData.length === 0, "should have no pending data");
   4660 
   4661     if (!this._canceled)
   4662     {
   4663       this.cancel(status);
   4664       return;
   4665     }
   4666 
   4667     var self = this;
   4668     var event =
   4669       {
   4670         run: function()
   4671         {
   4672           dumpn("*** onStopRequest async callback");
   4673 
   4674           self._completed = true;
   4675           try
   4676           {
   4677             self._observer.onStopRequest(self, self._context, self.status);
   4678           }
   4679           catch (e)
   4680           {
   4681             NS_ASSERT(false,
   4682                       "how are we throwing an exception here?  we control " +
   4683                       "all the callers!  " + e);
   4684           }
   4685         }
   4686       };
   4687 
   4688     gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
   4689   },
   4690 
   4691   /**
   4692    * Kicks off another wait for more data to be available from the input stream.
   4693    */
   4694   _waitToReadData: function()
   4695   {
   4696     dumpn("*** _waitToReadData");
   4697     this._source.asyncWait(this, 0, Response.SEGMENT_SIZE,
   4698                            gThreadManager.mainThread);
   4699   },
   4700 
   4701   /**
   4702    * Kicks off another wait until data can be written to the output stream.
   4703    */
   4704   _waitToWriteData: function()
   4705   {
   4706     dumpn("*** _waitToWriteData");
   4707 
   4708     var pendingData = this._pendingData;
   4709     NS_ASSERT(pendingData.length > 0, "no pending data to write?");
   4710     NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?");
   4711 
   4712     this._sink.asyncWait(this, 0, pendingData[0].length,
   4713                          gThreadManager.mainThread);
   4714   },
   4715 
   4716   /**
   4717    * Kicks off a wait for the sink to which data is being copied to be closed.
   4718    * We wait for stream closure when we don't have any data to be copied, rather
   4719    * than waiting to write a specific amount of data.  We can't wait to write
   4720    * data because the sink might be infinitely writable, and if no data appears
   4721    * in the source for a long time we might have to spin quite a bit waiting to
   4722    * write, waiting to write again, &c.  Waiting on stream closure instead means
   4723    * we'll get just one notification if the sink dies.  Note that when data
   4724    * starts arriving from the sink we'll resume waiting for data to be written,
   4725    * dropping this closure-only callback entirely.
   4726    */
   4727   _waitForSinkClosure: function()
   4728   {
   4729     dumpn("*** _waitForSinkClosure");
   4730 
   4731     this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0,
   4732                          gThreadManager.mainThread);
   4733   },
   4734 
   4735   /**
   4736    * Closes input with the given status, if it hasn't already been closed;
   4737    * otherwise a no-op.
   4738    *
   4739    * @param status : nsresult
   4740    *   status code use to close the source stream if necessary
   4741    */
   4742   _finishSource: function(status)
   4743   {
   4744     dumpn("*** _finishSource(" + status.toString(16) + ")");
   4745 
   4746     if (this._source !== null)
   4747     {
   4748       this._source.closeWithStatus(status);
   4749       this._source = null;
   4750     }
   4751   }
   4752 };
   4753 
   4754 
   4755 /**
   4756  * A container for utility functions used with HTTP headers.
   4757  */
   4758 const headerUtils =
   4759 {
   4760   /**
   4761    * Normalizes fieldName (by converting it to lowercase) and ensures it is a
   4762    * valid header field name (although not necessarily one specified in RFC
   4763    * 2616).
   4764    *
   4765    * @throws NS_ERROR_INVALID_ARG
   4766    *   if fieldName does not match the field-name production in RFC 2616
   4767    * @returns string
   4768    *   fieldName converted to lowercase if it is a valid header, for characters
   4769    *   where case conversion is possible
   4770    */
   4771   normalizeFieldName: function(fieldName)
   4772   {
   4773     if (fieldName == "")
   4774     {
   4775       dumpn("*** Empty fieldName");
   4776       throw Cr.NS_ERROR_INVALID_ARG;
   4777     }
   4778 
   4779     for (var i = 0, sz = fieldName.length; i < sz; i++)
   4780     {
   4781       if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)])
   4782       {
   4783         dumpn(fieldName + " is not a valid header field name!");
   4784         throw Cr.NS_ERROR_INVALID_ARG;
   4785       }
   4786     }
   4787 
   4788     return fieldName.toLowerCase();
   4789   },
   4790 
   4791   /**
   4792    * Ensures that fieldValue is a valid header field value (although not
   4793    * necessarily as specified in RFC 2616 if the corresponding field name is
   4794    * part of the HTTP protocol), normalizes the value if it is, and
   4795    * returns the normalized value.
   4796    *
   4797    * @param fieldValue : string
   4798    *   a value to be normalized as an HTTP header field value
   4799    * @throws NS_ERROR_INVALID_ARG
   4800    *   if fieldValue does not match the field-value production in RFC 2616
   4801    * @returns string
   4802    *   fieldValue as a normalized HTTP header field value
   4803    */
   4804   normalizeFieldValue: function(fieldValue)
   4805   {
   4806     // field-value    = *( field-content | LWS )
   4807     // field-content  = <the OCTETs making up the field-value
   4808     //                  and consisting of either *TEXT or combinations
   4809     //                  of token, separators, and quoted-string>
   4810     // TEXT           = <any OCTET except CTLs,
   4811     //                  but including LWS>
   4812     // LWS            = [CRLF] 1*( SP | HT )
   4813     //
   4814     // quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> )
   4815     // qdtext         = <any TEXT except <">>
   4816     // quoted-pair    = "\" CHAR
   4817     // CHAR           = <any US-ASCII character (octets 0 - 127)>
   4818 
   4819     // Any LWS that occurs between field-content MAY be replaced with a single
   4820     // SP before interpreting the field value or forwarding the message
   4821     // downstream (section 4.2); we replace 1*LWS with a single SP
   4822     var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " ");
   4823 
   4824     // remove leading/trailing LWS (which has been converted to SP)
   4825     val = val.replace(/^ +/, "").replace(/ +$/, "");
   4826 
   4827     // that should have taken care of all CTLs, so val should contain no CTLs
   4828     dumpn("*** Normalized value: '" + val + "'");
   4829     for (var i = 0, len = val.length; i < len; i++)
   4830       if (isCTL(val.charCodeAt(i)))
   4831       {
   4832         dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
   4833         throw Cr.NS_ERROR_INVALID_ARG;
   4834       }
   4835 
   4836     // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
   4837     //     normalize, however, so this can be construed as a tightening of the
   4838     //     spec and not entirely as a bug
   4839     return val;
   4840   }
   4841 };
   4842 
   4843 
   4844 
   4845 /**
   4846  * Converts the given string into a string which is safe for use in an HTML
   4847  * context.
   4848  *
   4849  * @param str : string
   4850  *   the string to make HTML-safe
   4851  * @returns string
   4852  *   an HTML-safe version of str
   4853  */
   4854 function htmlEscape(str)
   4855 {
   4856   // this is naive, but it'll work
   4857   var s = "";
   4858   for (var i = 0; i < str.length; i++)
   4859     s += "&#" + str.charCodeAt(i) + ";";
   4860   return s;
   4861 }
   4862 
   4863 
   4864 /**
   4865  * Constructs an object representing an HTTP version (see section 3.1).
   4866  *
   4867  * @param versionString
   4868  *   a string of the form "#.#", where # is an non-negative decimal integer with
   4869  *   or without leading zeros
   4870  * @throws
   4871  *   if versionString does not specify a valid HTTP version number
   4872  */
   4873 function nsHttpVersion(versionString)
   4874 {
   4875   var matches = /^(\d+)\.(\d+)$/.exec(versionString);
   4876   if (!matches)
   4877     throw "Not a valid HTTP version!";
   4878 
   4879   /** The major version number of this, as a number. */
   4880   this.major = parseInt(matches[1], 10);
   4881 
   4882   /** The minor version number of this, as a number. */
   4883   this.minor = parseInt(matches[2], 10);
   4884 
   4885   if (isNaN(this.major) || isNaN(this.minor) ||
   4886       this.major < 0    || this.minor < 0)
   4887     throw "Not a valid HTTP version!";
   4888 }
   4889 nsHttpVersion.prototype =
   4890 {
   4891   /**
   4892    * Returns the standard string representation of the HTTP version represented
   4893    * by this (e.g., "1.1").
   4894    */
   4895   toString: function ()
   4896   {
   4897     return this.major + "." + this.minor;
   4898   },
   4899 
   4900   /**
   4901    * Returns true if this represents the same HTTP version as otherVersion,
   4902    * false otherwise.
   4903    *
   4904    * @param otherVersion : nsHttpVersion
   4905    *   the version to compare against this
   4906    */
   4907   equals: function (otherVersion)
   4908   {
   4909     return this.major == otherVersion.major &&
   4910            this.minor == otherVersion.minor;
   4911   },
   4912 
   4913   /** True if this >= otherVersion, false otherwise. */
   4914   atLeast: function(otherVersion)
   4915   {
   4916     return this.major > otherVersion.major ||
   4917            (this.major == otherVersion.major &&
   4918             this.minor >= otherVersion.minor);
   4919   }
   4920 };
   4921 
   4922 nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0");
   4923 nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1");
   4924 
   4925 
   4926 /**
   4927  * An object which stores HTTP headers for a request or response.
   4928  *
   4929  * Note that since headers are case-insensitive, this object converts headers to
   4930  * lowercase before storing them.  This allows the getHeader and hasHeader
   4931  * methods to work correctly for any case of a header, but it means that the
   4932  * values returned by .enumerator may not be equal case-sensitively to the
   4933  * values passed to setHeader when adding headers to this.
   4934  */
   4935 function nsHttpHeaders()
   4936 {
   4937   /**
   4938    * A hash of headers, with header field names as the keys and header field
   4939    * values as the values.  Header field names are case-insensitive, but upon
   4940    * insertion here they are converted to lowercase.  Header field values are
   4941    * normalized upon insertion to contain no leading or trailing whitespace.
   4942    *
   4943    * Note also that per RFC 2616, section 4.2, two headers with the same name in
   4944    * a message may be treated as one header with the same field name and a field
   4945    * value consisting of the separate field values joined together with a "," in
   4946    * their original order.  This hash stores multiple headers with the same name
   4947    * in this manner.
   4948    */
   4949   this._headers = {};
   4950 }
   4951 nsHttpHeaders.prototype =
   4952 {
   4953   /**
   4954    * Sets the header represented by name and value in this.
   4955    *
   4956    * @param name : string
   4957    *   the header name
   4958    * @param value : string
   4959    *   the header value
   4960    * @throws NS_ERROR_INVALID_ARG
   4961    *   if name or value is not a valid header component
   4962    */
   4963   setHeader: function(fieldName, fieldValue, merge)
   4964   {
   4965     var name = headerUtils.normalizeFieldName(fieldName);
   4966     var value = headerUtils.normalizeFieldValue(fieldValue);
   4967 
   4968     // The following three headers are stored as arrays because their real-world
   4969     // syntax prevents joining individual headers into a single header using 
   4970     // ",".  See also <http://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
   4971     if (merge && name in this._headers)
   4972     {
   4973       if (name === "www-authenticate" ||
   4974           name === "proxy-authenticate" ||
   4975           name === "set-cookie") 
   4976       {
   4977         this._headers[name].push(value);
   4978       }
   4979       else 
   4980       {
   4981         this._headers[name][0] += "," + value;
   4982         NS_ASSERT(this._headers[name].length === 1,
   4983             "how'd a non-special header have multiple values?")
   4984       }
   4985     }
   4986     else
   4987     {
   4988       this._headers[name] = [value];
   4989     }
   4990   },
   4991 
   4992   /**
   4993    * Returns the value for the header specified by this.
   4994    *
   4995    * @throws NS_ERROR_INVALID_ARG
   4996    *   if fieldName does not constitute a valid header field name
   4997    * @throws NS_ERROR_NOT_AVAILABLE
   4998    *   if the given header does not exist in this
   4999    * @returns string
   5000    *   the field value for the given header, possibly with non-semantic changes
   5001    *   (i.e., leading/trailing whitespace stripped, whitespace runs replaced
   5002    *   with spaces, etc.) at the option of the implementation; multiple 
   5003    *   instances of the header will be combined with a comma, except for 
   5004    *   the three headers noted in the description of getHeaderValues
   5005    */
   5006   getHeader: function(fieldName)
   5007   {
   5008     return this.getHeaderValues(fieldName).join("\n");
   5009   },
   5010 
   5011   /**
   5012    * Returns the value for the header specified by fieldName as an array.
   5013    *
   5014    * @throws NS_ERROR_INVALID_ARG
   5015    *   if fieldName does not constitute a valid header field name
   5016    * @throws NS_ERROR_NOT_AVAILABLE
   5017    *   if the given header does not exist in this
   5018    * @returns [string]
   5019    *   an array of all the header values in this for the given
   5020    *   header name.  Header values will generally be collapsed
   5021    *   into a single header by joining all header values together
   5022    *   with commas, but certain headers (Proxy-Authenticate,
   5023    *   WWW-Authenticate, and Set-Cookie) violate the HTTP spec
   5024    *   and cannot be collapsed in this manner.  For these headers
   5025    *   only, the returned array may contain multiple elements if
   5026    *   that header has been added more than once.
   5027    */
   5028   getHeaderValues: function(fieldName)
   5029   {
   5030     var name = headerUtils.normalizeFieldName(fieldName);
   5031 
   5032     if (name in this._headers)
   5033       return this._headers[name];
   5034     else
   5035       throw Cr.NS_ERROR_NOT_AVAILABLE;
   5036   },
   5037 
   5038   /**
   5039    * Returns true if a header with the given field name exists in this, false
   5040    * otherwise.
   5041    *
   5042    * @param fieldName : string
   5043    *   the field name whose existence is to be determined in this
   5044    * @throws NS_ERROR_INVALID_ARG
   5045    *   if fieldName does not constitute a valid header field name
   5046    * @returns boolean
   5047    *   true if the header's present, false otherwise
   5048    */
   5049   hasHeader: function(fieldName)
   5050   {
   5051     var name = headerUtils.normalizeFieldName(fieldName);
   5052     return (name in this._headers);
   5053   },
   5054 
   5055   /**
   5056    * Returns a new enumerator over the field names of the headers in this, as
   5057    * nsISupportsStrings.  The names returned will be in lowercase, regardless of
   5058    * how they were input using setHeader (header names are case-insensitive per
   5059    * RFC 2616).
   5060    */
   5061   get enumerator()
   5062   {
   5063     var headers = [];
   5064     for (var i in this._headers)
   5065     {
   5066       var supports = new SupportsString();
   5067       supports.data = i;
   5068       headers.push(supports);
   5069     }
   5070 
   5071     return new nsSimpleEnumerator(headers);
   5072   }
   5073 };
   5074 
   5075 
   5076 /**
   5077  * Constructs an nsISimpleEnumerator for the given array of items.
   5078  *
   5079  * @param items : Array
   5080  *   the items, which must all implement nsISupports
   5081  */
   5082 function nsSimpleEnumerator(items)
   5083 {
   5084   this._items = items;
   5085   this._nextIndex = 0;
   5086 }
   5087 nsSimpleEnumerator.prototype =
   5088 {
   5089   hasMoreElements: function()
   5090   {
   5091     return this._nextIndex < this._items.length;
   5092   },
   5093   getNext: function()
   5094   {
   5095     if (!this.hasMoreElements())
   5096       throw Cr.NS_ERROR_NOT_AVAILABLE;
   5097 
   5098     return this._items[this._nextIndex++];
   5099   },
   5100   QueryInterface: function(aIID)
   5101   {
   5102     if (Ci.nsISimpleEnumerator.equals(aIID) ||
   5103         Ci.nsISupports.equals(aIID))
   5104       return this;
   5105 
   5106     throw Cr.NS_ERROR_NO_INTERFACE;
   5107   }
   5108 };
   5109 
   5110 
   5111 /**
   5112  * A representation of the data in an HTTP request.
   5113  *
   5114  * @param port : uint
   5115  *   the port on which the server receiving this request runs
   5116  */
   5117 function Request(port)
   5118 {
   5119   /** Method of this request, e.g. GET or POST. */
   5120   this._method = "";
   5121 
   5122   /** Path of the requested resource; empty paths are converted to '/'. */
   5123   this._path = "";
   5124 
   5125   /** Query string, if any, associated with this request (not including '?'). */
   5126   this._queryString = "";
   5127 
   5128   /** Scheme of requested resource, usually http, always lowercase. */
   5129   this._scheme = "http";
   5130 
   5131   /** Hostname on which the requested resource resides. */
   5132   this._host = undefined;
   5133 
   5134   /** Port number over which the request was received. */
   5135   this._port = port;
   5136 
   5137   var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);
   5138 
   5139   /** Stream from which data in this request's body may be read. */
   5140   this._bodyInputStream = bodyPipe.inputStream;
   5141 
   5142   /** Stream to which data in this request's body is written. */
   5143   this._bodyOutputStream = bodyPipe.outputStream;
   5144 
   5145   /**
   5146    * The headers in this request.
   5147    */
   5148   this._headers = new nsHttpHeaders();
   5149 
   5150   /**
   5151    * For the addition of ad-hoc properties and new functionality without having
   5152    * to change nsIHttpRequest every time; currently lazily created, as its only
   5153    * use is in directory listings.
   5154    */
   5155   this._bag = null;
   5156 }
   5157 Request.prototype =
   5158 {
   5159   // SERVER METADATA
   5160 
   5161   //
   5162   // see nsIHttpRequest.scheme
   5163   //
   5164   get scheme()
   5165   {
   5166     return this._scheme;
   5167   },
   5168 
   5169   //
   5170   // see nsIHttpRequest.host
   5171   //
   5172   get host()
   5173   {
   5174     return this._host;
   5175   },
   5176 
   5177   //
   5178   // see nsIHttpRequest.port
   5179   //
   5180   get port()
   5181   {
   5182     return this._port;
   5183   },
   5184 
   5185   // REQUEST LINE
   5186 
   5187   //
   5188   // see nsIHttpRequest.method
   5189   //
   5190   get method()
   5191   {
   5192     return this._method;
   5193   },
   5194 
   5195   //
   5196   // see nsIHttpRequest.httpVersion
   5197   //
   5198   get httpVersion()
   5199   {
   5200     return this._httpVersion.toString();
   5201   },
   5202 
   5203   //
   5204   // see nsIHttpRequest.path
   5205   //
   5206   get path()
   5207   {
   5208     return this._path;
   5209   },
   5210 
   5211   //
   5212   // see nsIHttpRequest.queryString
   5213   //
   5214   get queryString()
   5215   {
   5216     return this._queryString;
   5217   },
   5218 
   5219   // HEADERS
   5220 
   5221   //
   5222   // see nsIHttpRequest.getHeader
   5223   //
   5224   getHeader: function(name)
   5225   {
   5226     return this._headers.getHeader(name);
   5227   },
   5228 
   5229   //
   5230   // see nsIHttpRequest.hasHeader
   5231   //
   5232   hasHeader: function(name)
   5233   {
   5234     return this._headers.hasHeader(name);
   5235   },
   5236 
   5237   //
   5238   // see nsIHttpRequest.headers
   5239   //
   5240   get headers()
   5241   {
   5242     return this._headers.enumerator;
   5243   },
   5244 
   5245   //
   5246   // see nsIPropertyBag.enumerator
   5247   //
   5248   get enumerator()
   5249   {
   5250     this._ensurePropertyBag();
   5251     return this._bag.enumerator;
   5252   },
   5253 
   5254   //
   5255   // see nsIHttpRequest.headers
   5256   //
   5257   get bodyInputStream()
   5258   {
   5259     return this._bodyInputStream;
   5260   },
   5261 
   5262   //
   5263   // see nsIPropertyBag.getProperty
   5264   //
   5265   getProperty: function(name) 
   5266   {
   5267     this._ensurePropertyBag();
   5268     return this._bag.getProperty(name);
   5269   },
   5270 
   5271 
   5272   // NSISUPPORTS
   5273 
   5274   //
   5275   // see nsISupports.QueryInterface
   5276   //
   5277   QueryInterface: function(iid)
   5278   {
   5279     if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports))
   5280       return this;
   5281 
   5282     throw Cr.NS_ERROR_NO_INTERFACE;
   5283   },
   5284 
   5285 
   5286   // PRIVATE IMPLEMENTATION
   5287   
   5288   /** Ensures a property bag has been created for ad-hoc behaviors. */
   5289   _ensurePropertyBag: function()
   5290   {
   5291     if (!this._bag)
   5292       this._bag = new WritablePropertyBag();
   5293   }
   5294 };
   5295 
   5296 
   5297 // XPCOM trappings
   5298 
   5299 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
   5300 
   5301 /**
   5302  * Creates a new HTTP server listening for loopback traffic on the given port,
   5303  * starts it, and runs the server until the server processes a shutdown request,
   5304  * spinning an event loop so that events posted by the server's socket are
   5305  * processed.
   5306  *
   5307  * This method is primarily intended for use in running this script from within
   5308  * xpcshell and running a functional HTTP server without having to deal with
   5309  * non-essential details.
   5310  *
   5311  * Note that running multiple servers using variants of this method probably
   5312  * doesn't work, simply due to how the internal event loop is spun and stopped.
   5313  *
   5314  * @note
   5315  *   This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code);
   5316  *   you should use this server as a component in Mozilla 1.8.
   5317  * @param port
   5318  *   the port on which the server will run, or -1 if there exists no preference
   5319  *   for a specific port; note that attempting to use some values for this
   5320  *   parameter (particularly those below 1024) may cause this method to throw or
   5321  *   may result in the server being prematurely shut down
   5322  * @param basePath
   5323  *   a local directory from which requests will be served (i.e., if this is
   5324  *   "/home/jwalden/" then a request to /index.html will load
   5325  *   /home/jwalden/index.html); if this is omitted, only the default URLs in
   5326  *   this server implementation will be functional
   5327  */
   5328 function server(port, basePath)
   5329 {
   5330   if (basePath)
   5331   {
   5332     var lp = Cc["@mozilla.org/file/local;1"]
   5333                .createInstance(Ci.nsILocalFile);
   5334     lp.initWithPath(basePath);
   5335   }
   5336 
   5337   // if you're running this, you probably want to see debugging info
   5338   DEBUG = true;
   5339 
   5340   var srv = new nsHttpServer();
   5341   if (lp)
   5342     srv.registerDirectory("/", lp);
   5343   srv.registerContentType("sjs", SJS_TYPE);
   5344   srv.identity.setPrimary("http", "localhost", port);
   5345   srv.start(port);
   5346 
   5347   var thread = gThreadManager.currentThread;
   5348   while (!srv.isStopped())
   5349     thread.processNextEvent(true);
   5350 
   5351   // get rid of any pending requests
   5352   while (thread.hasPendingEvents())
   5353     thread.processNextEvent(true);
   5354 
   5355   DEBUG = false;
   5356 }