netwerk/test/httpserver/httpd.js

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

     1 /* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
     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  */
    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 ];
    41 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    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;
    49 const PR_UINT32_MAX = Math.pow(2, 32) - 1;
    51 /** True if debugging output is enabled, false otherwise. */
    52 var DEBUG = false; // non-const *only* so tweakable in server tests
    54 /** True if debugging output should be timestamped. */
    55 var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests
    57 var gGlobalObject = this;
    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:");
    73     var stack = new Error().stack.split(/\n/);
    74     dumpn(stack.map(function(val) { return "###!!!   " + val; }).join("\n"));
    76     throw Cr.NS_ERROR_ABORT;
    77   }
    78 }
    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 };
    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");
   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");
   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 }
   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 }
   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)));
   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 = "^";
   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;
   161 /** Type used to denote SJS scripts for CGI-like functionality. */
   162 const SJS_TYPE = "sjs";
   164 /** Base for relative timestamps produced by dumpn(). */
   165 var firstStamp = 0;
   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();
   178       var elapsed = Date.now() - firstStamp; // milliseconds
   179       var min = Math.floor(elapsed / 60000);
   180       var sec = (elapsed % 60000) / 1000;
   182       if (sec < 10)
   183         prefix += min + ":0" + sec.toFixed(3) + " | ";
   184       else
   185         prefix += min + ":" + sec.toFixed(3) + " | ";
   186     }
   188     dump(prefix + str + "\n");
   189   }
   190 }
   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 }
   201 /** The XPCOM thread manager. */
   202 var gThreadManager = null;
   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 }
   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");
   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");
   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   //
   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"];
   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;
   290     var mins = date.getUTCMinutes();
   291     rv += ":";
   292     rv += (mins < 10) ? "0" + mins : mins;
   294     var secs = date.getUTCSeconds();
   295     rv += ":";
   296     rv += (secs < 10) ? "0" + secs : secs;
   298     return rv;
   299   }
   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();
   316     var rv = (day < 10) ? "0" + day : day;
   317     rv += " " + monthStrings[month];
   318     rv += " " + year;
   320     return rv;
   321   }
   323   date = new Date(date);
   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 }
   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 }
   351 /**
   352  * Instantiates a new HTTP server.
   353  */
   354 function nsHttpServer()
   355 {
   356   if (!gThreadManager)
   357     gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService();
   359   /** The port on which this server listens. */
   360   this._port = undefined;
   362   /** The socket associated with this. */
   363   this._socket = null;
   365   /** The handler used to process requests to this server. */
   366   this._handler = new ServerHandler(this);
   368   /** Naming information for this server. */
   369   this._identity = new ServerIdentity();
   371   /**
   372    * Indicates when the server is to be shut down at the end of the request.
   373    */
   374   this._doQuit = false;
   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;
   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;
   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}"),
   399   // NSISERVERSOCKETLISTENER
   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 + ")");
   415     dumpn(">>> new connection on " + trans.host + ":" + trans.port);
   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     }
   432     var connectionNumber = ++this._connectionGen;
   434     try
   435     {
   436       var conn = new Connection(input, output, this, socket.port, trans.port,
   437                                 connectionNumber);
   438       var reader = new RequestReader(conn);
   440       // XXX add request timeout functionality here!
   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     }
   457     this._connections[connectionNumber] = conn;
   458     dumpn("*** starting connection " + connectionNumber);
   459   },
   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");
   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   },
   503   // NSIHTTPSERVER
   505   //
   506   // see nsIHttpServer.start
   507   //
   508   start: function(port)
   509   {
   510     this._start(port, "localhost")
   511   },
   513   _start: function(port, host)
   514   {
   515     if (this._socket)
   516       throw Cr.NS_ERROR_ALREADY_INITIALIZED;
   518     this._port = port;
   519     this._doQuit = this._socketClosed = false;
   521     this._host = host;
   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"));
   533     try
   534     {
   535       var loopback = true;
   536       if (this._host != "127.0.0.1" && this._host != "localhost") {
   537         var loopback = false;
   538       }
   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);
   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         }
   561         if (!allowed && this._port == -1)
   562         {
   563           dumpn(">>>Throwing away ServerSocket with bad port.");
   564           temp.close();
   565           continue;
   566         }
   568         socket = temp;
   569         break;
   570       }
   572       if (!socket) {
   573         throw new Error("No socket server available. Are there no available ports?");
   574       }
   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   },
   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;
   600     this._stopCallback = typeof callback === "function"
   601                        ? callback
   602                        : function() { callback.onStopped(); };
   604     dumpn(">>> stopping listening on port " + this._socket.port);
   605     this._socket.close();
   606     this._socket = null;
   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();
   612     this._doQuit = false;
   614     // socket-close notification and pending request completion happen async
   615   },
   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;
   625     this._handler.registerFile(path, file);
   626   },
   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;
   640     // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping
   641     //     exists!
   643     this._handler.registerDirectory(path, directory);
   644   },
   646   //
   647   // see nsIHttpServer.registerPathHandler
   648   //
   649   registerPathHandler: function(path, handler)
   650   {
   651     this._handler.registerPathHandler(path, handler);
   652   },
   654   //
   655   // see nsIHttpServer.registerPrefixHandler
   656   //
   657   registerPrefixHandler: function(prefix, handler)
   658   {
   659     this._handler.registerPrefixHandler(prefix, handler);
   660   },
   662   //
   663   // see nsIHttpServer.registerErrorHandler
   664   //
   665   registerErrorHandler: function(code, handler)
   666   {
   667     this._handler.registerErrorHandler(code, handler);
   668   },
   670   //
   671   // see nsIHttpServer.setIndexHandler
   672   //
   673   setIndexHandler: function(handler)
   674   {
   675     this._handler.setIndexHandler(handler);
   676   },
   678   //
   679   // see nsIHttpServer.registerContentType
   680   //
   681   registerContentType: function(ext, type)
   682   {
   683     this._handler.registerContentType(ext, type);
   684   },
   686   //
   687   // see nsIHttpServer.serverIdentity
   688   //
   689   get identity()
   690   {
   691     return this._identity;
   692   },
   694   //
   695   // see nsIHttpServer.getState
   696   //
   697   getState: function(path, k)
   698   {
   699     return this._handler._getState(path, k);
   700   },
   702   //
   703   // see nsIHttpServer.setState
   704   //
   705   setState: function(path, k, v)
   706   {
   707     return this._handler._setState(path, k, v);
   708   },
   710   //
   711   // see nsIHttpServer.getSharedState
   712   //
   713   getSharedState: function(k)
   714   {
   715     return this._handler._getSharedState(k);
   716   },
   718   //
   719   // see nsIHttpServer.setSharedState
   720   //
   721   setSharedState: function(k, v)
   722   {
   723     return this._handler._setSharedState(k, v);
   724   },
   726   //
   727   // see nsIHttpServer.getObjectState
   728   //
   729   getObjectState: function(k)
   730   {
   731     return this._handler._getObjectState(k);
   732   },
   734   //
   735   // see nsIHttpServer.setObjectState
   736   //
   737   setObjectState: function(k, v)
   738   {
   739     return this._handler._setObjectState(k, v);
   740   },
   743   // NSISUPPORTS
   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;
   755     throw Cr.NS_ERROR_NO_INTERFACE;
   756   },
   759   // NON-XPCOM PUBLIC API
   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   },
   771   // PRIVATE IMPLEMENTATION
   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   },
   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");
   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   },
   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];
   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   },
   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 };
   851 this.HttpServer = nsHttpServer;
   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 //
   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");
   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";
   894   /** The hostname of the primary location. */
   895   this._primaryHost = "127.0.0.1"
   897   /** The port number of the primary location. */
   898   this._primaryPort = -1;
   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;
   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
   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   },
   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   },
   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   },
   954   //
   955   // see nsIHttpServerIdentity.add
   956   //
   957   add: function(scheme, host, port)
   958   {
   959     this._validate(scheme, host, port);
   961     var entry = this._locations["x" + host];
   962     if (!entry)
   963       this._locations["x" + host] = entry = {};
   965     entry[port] = scheme;
   966   },
   968   //
   969   // see nsIHttpServerIdentity.remove
   970   //
   971   remove: function(scheme, host, port)
   972   {
   973     this._validate(scheme, host, port);
   975     var entry = this._locations["x" + host];
   976     if (!entry)
   977       return false;
   979     var present = port in entry;
   980     delete entry[port];
   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     }
   993     return present;
   994   },
   996   //
   997   // see nsIHttpServerIdentity.has
   998   //
   999   has: function(scheme, host, port)
  1001     this._validate(scheme, host, port);
  1003     return "x" + host in this._locations &&
  1004            scheme === this._locations["x" + host][port];
  1005   },
  1007   //
  1008   // see nsIHttpServerIdentity.has
  1009   //
  1010   getScheme: function(host, port)
  1012     this._validate("http", host, port);
  1014     var entry = this._locations["x" + host];
  1015     if (!entry)
  1016       return "";
  1018     return entry[port] || "";
  1019   },
  1021   //
  1022   // see nsIHttpServerIdentity.setPrimary
  1023   //
  1024   setPrimary: function(scheme, host, port)
  1026     this._validate(scheme, host, port);
  1028     this.add(scheme, host, port);
  1030     this._primaryScheme = scheme;
  1031     this._primaryHost = host;
  1032     this._primaryPort = port;
  1033   },
  1036   // NSISUPPORTS
  1038   //
  1039   // see nsISupports.QueryInterface
  1040   //
  1041   QueryInterface: function(iid)
  1043     if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports))
  1044       return this;
  1046     throw Cr.NS_ERROR_NO_INTERFACE;
  1047   },
  1050   // PRIVATE IMPLEMENTATION
  1052   /**
  1053    * Initializes the primary name for the corresponding server, based on the
  1054    * provided port number.
  1055    */
  1056   _initialize: function(port, host, addSecondaryDefault)
  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;
  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   },
  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()
  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);
  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)
  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);
  1094       // Ensure a server start triggers the setPrimary() path in ._initialize()
  1095       this._primaryPort = -1;
  1097     else
  1099       // No reason not to remove directly as it's not our primary location
  1100       this.remove("http", this._host, this._defaultPort);
  1102   },
  1104   /**
  1105    * Ensures scheme, host, and port are all valid with respect to RFC 2396.
  1107    * @throws NS_ERROR_ILLEGAL_VALUE
  1108    *   if any argument doesn't match the corresponding production
  1109    */
  1110   _validate: function(scheme, host, port)
  1112     if (scheme !== "http" && scheme !== "https")
  1114       dumpn("*** server only supports http/https schemes: '" + scheme + "'");
  1115       dumpStack();
  1116       throw Cr.NS_ERROR_ILLEGAL_VALUE;
  1118     if (!HOST_REGEX.test(host))
  1120       dumpn("*** unexpected host: '" + host + "'");
  1121       throw Cr.NS_ERROR_ILLEGAL_VALUE;
  1123     if (port < 0 || port > 65535)
  1125       dumpn("*** unexpected port: '" + port + "'");
  1126       throw Cr.NS_ERROR_ILLEGAL_VALUE;
  1129 };
  1132 /**
  1133  * Represents a connection to the server (and possibly in the future the thread
  1134  * on which the connection is processed).
  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)
  1151   dumpn("*** opening new connection " + number + " on port " + outgoingPort);
  1153   /** Stream of incoming data. */
  1154   this.input = input;
  1156   /** Stream for outgoing data. */
  1157   this.output = output;
  1159   /** The server associated with this request. */
  1160   this.server = server;
  1162   /** The port on which the server is running. */
  1163   this.port = port;
  1165   /** The outgoing poort used by this connection. */
  1166   this._outgoingPort = outgoingPort;
  1168   /** The serial number of this connection. */
  1169   this.number = number;
  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;
  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;
  1182   /** State variable for debugging. */
  1183   this._processed = false;
  1185   /** whether or not 1st line of request has been received */
  1186   this._requestStarted = false; 
  1188 Connection.prototype =
  1190   /** Closes this connection's input/output streams. */
  1191   close: function()
  1193     if (this._closed)
  1194         return;
  1196     dumpn("*** closing connection " + this.number +
  1197           " on port " + this._outgoingPort);
  1199     this.input.close();
  1200     this.output.close();
  1201     this._closed = true;
  1203     var server = this.server;
  1204     server._connectionClosed(this);
  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   },
  1211   /**
  1212    * Initiates processing of this connection, using the data in the given
  1213    * request.
  1215    * @param request : Request
  1216    *   the request which should be processed
  1217    */
  1218   process: function(request)
  1220     NS_ASSERT(!this._closed && !this._processed);
  1222     this._processed = true;
  1224     this.request = request;
  1225     this.server._handler.handleResponse(this);
  1226   },
  1228   /**
  1229    * Initiates processing of this connection, generating a response with the
  1230    * given HTTP error code.
  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)
  1240     NS_ASSERT(!this._closed && !this._processed);
  1242     this._processed = true;
  1243     this.request = request;
  1244     this.server._handler.handleError(code, this);
  1245   },
  1247   /** Converts this to a string for debugging purposes. */
  1248   toString: function()
  1250     return "<Connection(" + this.number +
  1251            (this.request ? ", " + this.request.path : "") +"): " +
  1252            (this._closed ? "closed" : "open") + ">";
  1253   },
  1255   requestStarted: function()
  1257     this._requestStarted = true;
  1259 };
  1263 /** Returns an array of count bytes from the given input stream. */
  1264 function readBytes(inputStream, count)
  1266   return new BinaryInputStream(inputStream).readByteArray(count);
  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;
  1278 /**
  1279  * Reads incoming request data asynchronously, does any necessary preprocessing,
  1280  * and forwards it to the request handler.  Processing occurs in three states:
  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
  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.
  1293  * @param connection : Connection
  1294  *   the connection for the request being read
  1295  */
  1296 function RequestReader(connection)
  1298   /** Connection metadata for this request. */
  1299   this._connection = connection;
  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();
  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;
  1316   /** The current state of parsing the incoming request. */
  1317   this._state = READER_IN_REQUEST_LINE;
  1319   /** Metadata constructed from the incoming request for the request handler. */
  1320   this._metadata = new Request(connection.port);
  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.
  1327    * These fields are always either both undefined or both strings.
  1328    */
  1329   this._lastHeaderName = this._lastHeaderValue = undefined;
  1331 RequestReader.prototype =
  1333   // NSIINPUTSTREAMCALLBACK
  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.
  1340    * @param input : nsIAsyncInputStream
  1341    *   the stream of incoming data from the connection
  1342    */
  1343   onInputStreamReady: function(input)
  1345     dumpn("*** onInputStreamReady(input=" + input + ") on thread " +
  1346           gThreadManager.currentThread + " (main is " +
  1347           gThreadManager.mainThread + ")");
  1348     dumpn("*** this._state == " + this._state);
  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;
  1356     try
  1358       data.appendBytes(readBytes(input, input.available()));
  1360     catch (e)
  1362       if (streamClosed(e))
  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);
  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;
  1378     switch (this._state)
  1380       default:
  1381         NS_ASSERT(false, "invalid state: " + this._state);
  1382         break;
  1384       case READER_IN_REQUEST_LINE:
  1385         if (!this._processRequestLine())
  1386           break;
  1387         /* fall through */
  1389       case READER_IN_HEADERS:
  1390         if (!this._processHeaders())
  1391           break;
  1392         /* fall through */
  1394       case READER_IN_BODY:
  1395         this._processBody();
  1398     if (this._state != READER_FINISHED)
  1399       input.asyncWait(this, 0, 0, gThreadManager.currentThread);
  1400   },
  1402   //
  1403   // see nsISupports.QueryInterface
  1404   //
  1405   QueryInterface: function(aIID)
  1407     if (aIID.equals(Ci.nsIInputStreamCallback) ||
  1408         aIID.equals(Ci.nsISupports))
  1409       return this;
  1411     throw Cr.NS_ERROR_NO_INTERFACE;
  1412   },
  1415   // PRIVATE API
  1417   /**
  1418    * Processes unprocessed, downloaded data as a request line.
  1420    * @returns boolean
  1421    *   true iff the request line has been fully processed
  1422    */
  1423   _processRequestLine: function()
  1425     NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
  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...");
  1435     // if we don't have a full line, wait until we do
  1436     if (!readSuccess)
  1437       return false;
  1439     // we have the first non-blank line
  1440     try
  1442       this._parseRequestLine(line.value);
  1443       this._state = READER_IN_HEADERS;
  1444       this._connection.requestStarted();
  1445       return true;
  1447     catch (e)
  1449       this._handleError(e);
  1450       return false;
  1452   },
  1454   /**
  1455    * Processes stored data, assuming it is either at the beginning or in
  1456    * the middle of processing request headers.
  1458    * @returns boolean
  1459    *   true iff header data in the request has been fully processed
  1460    */
  1461   _processHeaders: function()
  1463     NS_ASSERT(this._state == READER_IN_HEADERS);
  1465     // XXX things to fix here:
  1466     //
  1467     // - need to support RFC 2047-encoded non-US-ASCII characters
  1469     try
  1471       var done = this._parseHeaders();
  1472       if (done)
  1474         var request = this._metadata;
  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);
  1484         this._state = READER_IN_BODY;
  1486       return done;
  1488     catch (e)
  1490       this._handleError(e);
  1491       return false;
  1493   },
  1495   /**
  1496    * Processes stored data, assuming it is either at the beginning or in
  1497    * the middle of processing the request body.
  1499    * @returns boolean
  1500    *   true iff the request body has been fully processed
  1501    */
  1502   _processBody: function()
  1504     NS_ASSERT(this._state == READER_IN_BODY);
  1506     // XXX handle chunked transfer-coding request bodies!
  1508     try
  1510       if (this._contentLength > 0)
  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));
  1517         var bos = new BinaryOutputStream(this._metadata._bodyOutputStream);
  1518         bos.writeByteArray(data, count);
  1519         this._contentLength -= count;
  1522       dumpn("*** remaining body data len=" + this._contentLength);
  1523       if (this._contentLength == 0)
  1525         this._validateRequest();
  1526         this._state = READER_FINISHED;
  1527         this._handleResponse();
  1528         return true;
  1531       return false;
  1533     catch (e)
  1535       this._handleError(e);
  1536       return false;
  1538   },
  1540   /**
  1541    * Does various post-header checks on the data in this request.
  1543    * @throws : HttpError
  1544    *   if the request was malformed in some way
  1545    */
  1546   _validateRequest: function()
  1548     NS_ASSERT(this._state == READER_IN_BODY);
  1550     dumpn("*** _validateRequest");
  1552     var metadata = this._metadata;
  1553     var headers = metadata._headers;
  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))
  1559       if (!headers.hasHeader("Host"))
  1561         dumpn("*** malformed HTTP/1.1 or greater request with no Host header!");
  1562         throw HTTP_400;
  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)
  1571         var host, port;
  1572         var hostPort = headers.getHeader("Host");
  1573         var colon = hostPort.indexOf(":");
  1574         if (colon < 0)
  1576           host = hostPort;
  1577           port = "";
  1579         else
  1581           host = hostPort.substring(0, colon);
  1582           port = hostPort.substring(colon + 1);
  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))
  1590           dumpn("*** malformed hostname (" + hostPort + ") in Host " +
  1591                 "header, 400 time");
  1592           throw HTTP_400;
  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;
  1602         var scheme = identity.getScheme(host, port);
  1603         if (!scheme)
  1605           dumpn("*** unrecognized hostname (" + hostPort + ") in Host " +
  1606                 "header, 400 time");
  1607           throw HTTP_400;
  1610         metadata._scheme = scheme;
  1611         metadata._host = host;
  1612         metadata._port = port;
  1615     else
  1617       NS_ASSERT(metadata._host === undefined,
  1618                 "HTTP/1.0 doesn't allow absolute paths in the request line!");
  1620       metadata._scheme = identity.primaryScheme;
  1621       metadata._host = identity.primaryHost;
  1622       metadata._port = identity.primaryPort;
  1625     NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port),
  1626               "must have a location we recognize by now!");
  1627   },
  1629   /**
  1630    * Handles responses in case of error, either in the server or in the request.
  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)
  1640     // Don't fall back into normal processing!
  1641     this._state = READER_FINISHED;
  1643     var server = this._connection.server;
  1644     if (e instanceof HttpError)
  1646       var code = e.code;
  1648     else
  1650       dumpn("!!! UNEXPECTED ERROR: " + e +
  1651             (e.lineNumber ? ", line " + e.lineNumber : ""));
  1653       // no idea what happened -- be paranoid and shut down
  1654       code = 500;
  1655       server._requestQuit();
  1658     // make attempted reuse of data an error
  1659     this._data = null;
  1661     this._connection.processError(code, this._metadata);
  1662   },
  1664   /**
  1665    * Now that we've read the request line and headers, we can actually hand off
  1666    * the request to be handled.
  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()
  1673     NS_ASSERT(this._state == READER_FINISHED);
  1675     // We don't need the line-based data any more, so make attempted reuse an
  1676     // error.
  1677     this._data = null;
  1679     this._connection.process(this._metadata);
  1680   },
  1683   // PARSING
  1685   /**
  1686    * Parses the request line for the HTTP request associated with this.
  1688    * @param line : string
  1689    *   the request line
  1690    */
  1691   _parseRequestLine: function(line)
  1693     NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
  1695     dumpn("*** _parseRequestLine('" + line + "')");
  1697     var metadata = this._metadata;
  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)
  1704       dumpn("*** No request in line");
  1705       throw HTTP_400;
  1708     metadata._method = request[0];
  1710     // get the HTTP version
  1711     var ver = request[2];
  1712     var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
  1713     if (!match)
  1715       dumpn("*** No HTTP version in line");
  1716       throw HTTP_400;
  1719     // determine HTTP version
  1720     try
  1722       metadata._httpVersion = new nsHttpVersion(match[1]);
  1723       if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0))
  1724         throw "unsupported HTTP version";
  1726     catch (e)
  1728       // we support HTTP/1.0 and HTTP/1.1 only
  1729       throw HTTP_501;
  1733     var fullPath = request[1];
  1734     var serverIdentity = this._connection.server.identity;
  1736     var scheme, host, port;
  1738     if (fullPath.charAt(0) != "/")
  1740       // No absolute paths in the request line in HTTP prior to 1.1
  1741       if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
  1743         dumpn("*** Metadata version too low");
  1744         throw HTTP_400;
  1747       try
  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)
  1758           if (scheme === "http")
  1760             port = 80;
  1762           else if (scheme === "https")
  1764             port = 443;
  1766           else
  1768             dumpn("*** Unknown scheme: " + scheme);
  1769             throw HTTP_400;
  1773       catch (e)
  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;
  1782       if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
  1784         dumpn("*** serverIdentity unknown or path does not start with '/'");
  1785         throw HTTP_400;
  1789     var splitter = fullPath.indexOf("?");
  1790     if (splitter < 0)
  1792       // _queryString already set in ctor
  1793       metadata._path = fullPath;
  1795     else
  1797       metadata._path = fullPath.substring(0, splitter);
  1798       metadata._queryString = fullPath.substring(splitter + 1);
  1801     metadata._scheme = scheme;
  1802     metadata._host = host;
  1803     metadata._port = port;
  1804   },
  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.
  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()
  1817     NS_ASSERT(this._state == READER_IN_HEADERS);
  1819     dumpn("*** _parseHeaders");
  1821     var data = this._data;
  1823     var headers = this._metadata._headers;
  1824     var lastName = this._lastHeaderName;
  1825     var lastVal = this._lastHeaderValue;
  1827     var line = {};
  1828     while (true)
  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 + "'");
  1837       if (!data.readLine(line))
  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;
  1845       var lineText = line.value;
  1846       dumpn("*** Line text: '" + lineText + "'");
  1847       var firstChar = lineText.charAt(0);
  1849       // blank line means end of headers
  1850       if (lineText == "")
  1852         // we're finished with the previous header
  1853         if (lastName)
  1855           try
  1857             headers.setHeader(lastName, lastVal, true);
  1859           catch (e)
  1861             dumpn("*** setHeader threw on last header, e == " + e);
  1862             throw HTTP_400;
  1865         else
  1867           // no headers in request -- valid for HTTP/1.0 requests
  1870         // either way, we're done processing headers
  1871         this._state = READER_IN_BODY;
  1872         return true;
  1874       else if (firstChar == " " || firstChar == "\t")
  1876         // multi-line header if we've already seen a header line
  1877         if (!lastName)
  1879           dumpn("We don't have a header to continue!");
  1880           throw HTTP_400;
  1883         // append this line's text to the value; starts with SP/HT, so no need
  1884         // for separating whitespace
  1885         lastVal += lineText;
  1887       else
  1889         // we have a new header, so set the old one (if one existed)
  1890         if (lastName)
  1892           try
  1894             headers.setHeader(lastName, lastVal, true);
  1896           catch (e)
  1898             dumpn("*** setHeader threw on a header, e == " + e);
  1899             throw HTTP_400;
  1903         var colon = lineText.indexOf(":"); // first colon must be splitter
  1904         if (colon < 1)
  1906           dumpn("*** No colon or missing header field-name");
  1907           throw HTTP_400;
  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)
  1916 };
  1919 /** The character codes for CR and LF. */
  1920 const CR = 0x0D, LF = 0x0A;
  1922 /**
  1923  * Calculates the number of characters before the first CRLF pair in array, or
  1924  * -1 if the array contains no CRLF pair.
  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)
  1938   for (var i = array.indexOf(CR, start); i >= 0; i = array.indexOf(CR, i + 1))
  1940     if (array[i + 1] == LF)
  1941       return i;
  1943   return -1;
  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()
  1953   /** An array of queued bytes from which to get line-based characters. */
  1954   this._data = [];
  1956   /** Start index from which to search for CRLF. */
  1957   this._start = 0;
  1959 LineData.prototype =
  1961   /**
  1962    * Appends the bytes in the given array to the internal data cache maintained
  1963    * by this.
  1964    */
  1965   appendBytes: function(bytes)
  1967     var count = bytes.length;
  1968     var quantum = 262144; // just above half SpiderMonkey's argument-count limit
  1969     if (count < quantum)
  1971       Array.prototype.push.apply(this._data, bytes);
  1972       return;
  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)
  1980       var slice = bytes.slice(start, Math.min(start + quantum, count));
  1981       Array.prototype.push.apply(this._data, slice);
  1983   },
  1985   /**
  1986    * Removes and returns a line of data, delimited by CRLF, from this.
  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)
  1999     var data = this._data;
  2000     var length = findCRLF(data, this._start);
  2001     if (length < 0)
  2003       this._start = data.length;
  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;
  2011       return false;
  2014     // Reset for future lines.
  2015     this._start = 0;
  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)
  2027       var slice = leading.slice(start, Math.min(start + quantum, length));
  2028       line += String.fromCharCode.apply(null, slice);
  2031     out.value = line;
  2032     return true;
  2033   },
  2035   /**
  2036    * Removes the bytes currently within this and returns them in an array.
  2038    * @returns Array
  2039    *   the bytes within this when this method is called
  2040    */
  2041   purge: function()
  2043     var data = this._data;
  2044     this._data = [];
  2045     return data;
  2047 };
  2051 /**
  2052  * Creates a request-handling function for an nsIHttpRequestHandler object.
  2053  */
  2054 function createHandlerFunc(handler)
  2056   return function(metadata, response) { handler.handle(metadata, response); };
  2060 /**
  2061  * The default handler for directories; writes an HTML response containing a
  2062  * slightly-formatted directory listing.
  2063  */
  2064 function defaultIndexHandler(metadata, response)
  2066   response.setHeader("Content-Type", "text/html;charset=utf-8", false);
  2068   var path = htmlEscape(decodeURI(metadata.path));
  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   //
  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">';
  2084   var directory = metadata.getProperty("directory");
  2085   NS_ASSERT(directory && directory.isDirectory());
  2087   var fileList = [];
  2088   var files = directory.directoryEntries;
  2089   while (files.hasMoreElements())
  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);
  2099   fileList.sort(fileSort);
  2101   for (var i = 0; i < fileList.length; i++)
  2103     var file = fileList[i];
  2104     try
  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() ? "/" : "";
  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>';
  2117       body += item;
  2119     catch (e) { /* some file system error, ignore the file */ }
  2122   body    += '    </ol>\
  2123                 </body>\
  2124               </html>';
  2126   response.bodyOutputStream.write(body, body.length);
  2129 /**
  2130  * Sorts a and b (nsIFile objects) into an aesthetically pleasing order.
  2131  */
  2132 function fileSort(a, b)
  2134   var dira = a.isDirectory(), dirb = b.isDirectory();
  2136   if (dira && !dirb)
  2137     return -1;
  2138   if (dirb && !dira)
  2139     return 1;
  2141   var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase();
  2142   return nameb > namea ? -1 : 1;
  2146 /**
  2147  * Converts an externally-provided path into an internal path for use in
  2148  * determining file mappings.
  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)
  2160   if (encoded)
  2161     path = decodeURI(path);
  2163   var comps = path.split("/");
  2164   for (var i = 0, sz = comps.length; i < sz; i++)
  2166     var comp = comps[i];
  2167     if (comp.charAt(comp.length - 1) == HIDDEN_CHAR)
  2168       comps[i] = comp + HIDDEN_CHAR;
  2170   return comps.join("/");
  2173 const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;
  2175 /**
  2176  * Adds custom-specified headers for the given file to the given response, if
  2177  * any such headers are specified.
  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)
  2190   var name = file.leafName;
  2191   if (name.charAt(name.length - 1) == HIDDEN_CHAR)
  2192     name = name.substring(0, name.length - 1);
  2194   var headerFile = file.parent;
  2195   headerFile.append(name + HEADERS_SUFFIX);
  2197   if (!headerFile.exists())
  2198     return;
  2200   const PR_RDONLY = 0x01;
  2201   var fis = new FileInputStream(headerFile, PR_RDONLY, PERMS_READONLY,
  2202                                 Ci.nsIFileInputStream.CLOSE_ON_EOF);
  2204   try
  2206     var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
  2207     lis.QueryInterface(Ci.nsIUnicharLineInputStream);
  2209     var line = {value: ""};
  2210     var more = lis.readLine(line);
  2212     if (!more && line.value == "")
  2213       return;
  2216     // request line
  2218     var status = line.value;
  2219     if (status.indexOf("HTTP ") == 0)
  2221       status = status.substring(5);
  2222       var space = status.indexOf(" ");
  2223       var code, description;
  2224       if (space < 0)
  2226         code = status;
  2227         description = "";
  2229       else
  2231         code = status.substring(0, space);
  2232         description = status.substring(space + 1, status.length);
  2235       response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);
  2237       line.value = "";
  2238       more = lis.readLine(line);
  2241     // headers
  2242     while (more || line.value != "")
  2244       var header = line.value;
  2245       var colon = header.indexOf(":");
  2247       response.setHeader(header.substring(0, colon),
  2248                          header.substring(colon + 1, header.length),
  2249                          false); // allow overriding server-set headers
  2251       line.value = "";
  2252       more = lis.readLine(line);
  2255   catch (e)
  2257     dumpn("WARNING: error in headers for " + metadata.path + ": " + e);
  2258     throw HTTP_500;
  2260   finally
  2262     fis.close();
  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.
  2274  * @param server : nsHttpServer
  2275  *   the server in which this handler is being used
  2276  */
  2277 function ServerHandler(server)
  2279   // FIELDS
  2281   /**
  2282    * The nsHttpServer instance associated with this handler.
  2283    */
  2284   this._server = server;
  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).
  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();
  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.
  2301    * @see ServerHandler.prototype._defaultPaths
  2302    */
  2303   this._overridePaths = {};
  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.
  2310    * @see ServerHandler.prototype._defaultPaths
  2311    */
  2312   this._overridePrefixes = {};
  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.
  2319    * @see ServerHandler.prototype._defaultErrors
  2320    */
  2321   this._overrideErrors = {};
  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 = {};
  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;
  2335   /** Per-path state storage for the server. */
  2336   this._state = {};
  2338   /** Entire-server state storage. */
  2339   this._sharedState = {};
  2341   /** Entire-server state storage for nsISupports values. */
  2342   this._objectState = {};
  2344 ServerHandler.prototype =
  2346   // PUBLIC API
  2348   /**
  2349    * Handles a request to this server, responding to the request appropriately
  2350    * and initiating server shutdown if necessary.
  2352    * This method never throws an exception.
  2354    * @param connection : Connection
  2355    *   the connection for this request
  2356    */
  2357   handleResponse: function(connection)
  2359     var request = connection.request;
  2360     var response = new Response(connection);
  2362     var path = request.path;
  2363     dumpn("*** path == " + path);
  2365     try
  2367       try
  2369         if (path in this._overridePaths)
  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);
  2376         else
  2378           var longestPrefix = "";
  2379           for (let prefix in this._overridePrefixes) {
  2380             if (prefix.length > longestPrefix.length &&
  2381                 path.substr(0, prefix.length) == prefix)
  2383               longestPrefix = prefix;
  2386           if (longestPrefix.length > 0)
  2388             dumpn("calling prefix override for " + longestPrefix);
  2389             this._overridePrefixes[longestPrefix](request, response);
  2391           else
  2393             this._handleDefault(request, response);
  2397       catch (e)
  2399         if (response.partiallySent())
  2401           response.abort(e);
  2402           return;
  2405         if (!(e instanceof HttpError))
  2407           dumpn("*** unexpected error: e == " + e);
  2408           throw HTTP_500;
  2410         if (e.code !== 404)
  2411           throw e;
  2413         dumpn("*** default: " + (path in this._defaultPaths));
  2415         response = new Response(connection);
  2416         if (path in this._defaultPaths)
  2417           this._defaultPaths[path](request, response);
  2418         else
  2419           throw HTTP_404;
  2422     catch (e)
  2424       if (response.partiallySent())
  2426         response.abort(e);
  2427         return;
  2430       var errorCode = "internal";
  2432       try
  2434         if (!(e instanceof HttpError))
  2435           throw e;
  2437         errorCode = e.code;
  2438         dumpn("*** errorCode == " + errorCode);
  2440         response = new Response(connection);
  2441         if (e.customErrorHandling)
  2442           e.customErrorHandling(response);
  2443         this._handleError(errorCode, request, response);
  2444         return;
  2446       catch (e2)
  2448         dumpn("*** error handling " + errorCode + " error: " +
  2449               "e2 == " + e2 + ", shutting down server");
  2451         connection.server._requestQuit();
  2452         response.abort(e2);
  2453         return;
  2457     response.complete();
  2458   },
  2460   //
  2461   // see nsIHttpServer.registerFile
  2462   //
  2463   registerFile: function(path, file)
  2465     if (!file)
  2467       dumpn("*** unregistering '" + path + "' mapping");
  2468       delete this._overridePaths[path];
  2469       return;
  2472     dumpn("*** registering '" + path + "' as mapping to " + file.path);
  2473     file = file.clone();
  2475     var self = this;
  2476     this._overridePaths[path] =
  2477       function(request, response)
  2479         if (!file.exists())
  2480           throw HTTP_404;
  2482         response.setStatusLine(request.httpVersion, 200, "OK");
  2483         self._writeFileResponse(request, file, response, 0, file.fileSize);
  2484       };
  2485   },
  2487   //
  2488   // see nsIHttpServer.registerPathHandler
  2489   //
  2490   registerPathHandler: function(path, handler)
  2492     // XXX true path validation!
  2493     if (path.charAt(0) != "/")
  2494       throw Cr.NS_ERROR_INVALID_ARG;
  2496     this._handlerToField(handler, this._overridePaths, path);
  2497   },
  2499   //
  2500   // see nsIHttpServer.registerPrefixHandler
  2501   //
  2502   registerPrefixHandler: function(path, handler)
  2504     // XXX true path validation!
  2505     if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/")
  2506       throw Cr.NS_ERROR_INVALID_ARG;
  2508     this._handlerToField(handler, this._overridePrefixes, path);
  2509   },
  2511   //
  2512   // see nsIHttpServer.registerDirectory
  2513   //
  2514   registerDirectory: function(path, directory)
  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);
  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;
  2527     key = toInternalPath(key, false);
  2529     if (directory)
  2531       dumpn("*** mapping '" + path + "' to the location " + directory.path);
  2532       this._pathDirectoryMap.put(key, directory);
  2534     else
  2536       dumpn("*** removing mapping for '" + path + "'");
  2537       this._pathDirectoryMap.put(key, null);
  2539   },
  2541   //
  2542   // see nsIHttpServer.registerErrorHandler
  2543   //
  2544   registerErrorHandler: function(err, handler)
  2546     if (!(err in HTTP_ERROR_CODES))
  2547       dumpn("*** WARNING: registering non-HTTP/1.1 error code " +
  2548             "(" + err + ") handler -- was this intentional?");
  2550     this._handlerToField(handler, this._overrideErrors, err);
  2551   },
  2553   //
  2554   // see nsIHttpServer.setIndexHandler
  2555   //
  2556   setIndexHandler: function(handler)
  2558     if (!handler)
  2559       handler = defaultIndexHandler;
  2560     else if (typeof(handler) != "function")
  2561       handler = createHandlerFunc(handler);
  2563     this._indexHandler = handler;
  2564   },
  2566   //
  2567   // see nsIHttpServer.registerContentType
  2568   //
  2569   registerContentType: function(ext, type)
  2571     if (!type)
  2572       delete this._mimeMappings[ext];
  2573     else
  2574       this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type);
  2575   },
  2577   // PRIVATE API
  2579   /**
  2580    * Sets or remove (if handler is null) a handler in an object with a key.
  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)
  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   },
  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).
  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)
  2615     dumpn("*** _handleDefault()");
  2617     response.setStatusLine(metadata.httpVersion, 200, "OK");
  2619     var path = metadata.path;
  2620     NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">");
  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);
  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())
  2630       file.append("index.html"); // make configurable?
  2631       if (!file.exists() || file.isDirectory())
  2633         metadata._ensurePropertyBag();
  2634         metadata._bag.setPropertyAsInterface("directory", file.parent);
  2635         this._indexHandler(metadata, response);
  2636         return;
  2640     // alternately, the file might not exist
  2641     if (!file.exists())
  2642       throw HTTP_404;
  2644     var start, end;
  2645     if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) &&
  2646         metadata.hasHeader("Range") &&
  2647         this._getTypeFromFile(file) !== SJS_TYPE)
  2649       var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
  2650       if (!rangeMatch)
  2652         dumpn("*** Range header bogosity: '" + metadata.getHeader("Range") + "'");
  2653         throw HTTP_400;
  2656       if (rangeMatch[1] !== undefined)
  2657         start = parseInt(rangeMatch[1], 10);
  2659       if (rangeMatch[2] !== undefined)
  2660         end = parseInt(rangeMatch[2], 10);
  2662       if (start === undefined && end === undefined)
  2664         dumpn("*** More Range header bogosity: '" + metadata.getHeader("Range") + "'");
  2665         throw HTTP_400;
  2668       // No start given, so the end is really the count of bytes from the
  2669       // end of the file.
  2670       if (start === undefined)
  2672         start = Math.max(0, file.fileSize - end);
  2673         end   = file.fileSize - 1;
  2676       // start and end are inclusive
  2677       if (end === undefined || end >= file.fileSize)
  2678         end = file.fileSize - 1;
  2680       if (start !== undefined && start >= file.fileSize) {
  2681         var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable");
  2682         HTTP_416.customErrorHandling = function(errorResponse)
  2684           maybeAddHeaders(file, metadata, errorResponse);
  2685         };
  2686         throw HTTP_416;
  2689       if (end < start)
  2691         response.setStatusLine(metadata.httpVersion, 200, "OK");
  2692         start = 0;
  2693         end = file.fileSize - 1;
  2695       else
  2697         response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
  2698         var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize;
  2699         response.setHeader("Content-Range", contentRange);
  2702     else
  2704       start = 0;
  2705       end = file.fileSize - 1;
  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   },
  2714   /**
  2715    * Writes an HTTP response for the given file, including setting headers for
  2716    * file metadata.
  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)
  2731     const PR_RDONLY = 0x01;
  2733     var type = this._getTypeFromFile(file);
  2734     if (type === SJS_TYPE)
  2736       var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
  2737                                     Ci.nsIFileInputStream.CLOSE_ON_EOF);
  2739       try
  2741         var sis = new ScriptableInputStream(fis);
  2742         var s = Cu.Sandbox(gGlobalObject);
  2743         s.importFunction(dump, "dump");
  2745         // Define a basic key-value state-preservation API across requests, with
  2746         // keys initially corresponding to the empty string.
  2747         var self = this;
  2748         var path = metadata.path;
  2749         s.importFunction(function getState(k)
  2751           return self._getState(path, k);
  2752         });
  2753         s.importFunction(function setState(k, v)
  2755           self._setState(path, k, v);
  2756         });
  2757         s.importFunction(function getSharedState(k)
  2759           return self._getSharedState(k);
  2760         });
  2761         s.importFunction(function setSharedState(k, v)
  2763           self._setSharedState(k, v);
  2764         });
  2765         s.importFunction(function getObjectState(k, callback)
  2767           callback(self._getObjectState(k));
  2768         });
  2769         s.importFunction(function setObjectState(k, v)
  2771           self._setObjectState(k, v);
  2772         });
  2773         s.importFunction(function registerPathHandler(p, h)
  2775           self.registerPathHandler(p, h);
  2776         });
  2778         // Make it possible for sjs files to access their location
  2779         this._setState(path, "__LOCATION__", file.path);
  2781         try
  2783           // Alas, the line number in errors dumped to console when calling the
  2784           // request handler is simply an offset from where we load the SJS file.
  2785           // Work around this in a reasonably non-fragile way by dynamically
  2786           // getting the line number where we evaluate the SJS file.  Don't
  2787           // separate these two lines!
  2788           var line = new Error().lineNumber;
  2789           Cu.evalInSandbox(sis.read(file.fileSize), s, "latest");
  2791         catch (e)
  2793           dumpn("*** syntax error in SJS at " + file.path + ": " + e);
  2794           throw HTTP_500;
  2797         try
  2799           s.handleRequest(metadata, response);
  2801         catch (e)
  2803           dump("*** error running SJS at " + file.path + ": " +
  2804                e + " on line " +
  2805                (e instanceof Error
  2806                ? e.lineNumber + " in httpd.js"
  2807                : (e.lineNumber - line)) + "\n");
  2808           throw HTTP_500;
  2811       finally
  2813         fis.close();
  2816     else
  2818       try
  2820         response.setHeader("Last-Modified",
  2821                            toDateString(file.lastModifiedTime),
  2822                            false);
  2824       catch (e) { /* lastModifiedTime threw, ignore */ }
  2826       response.setHeader("Content-Type", type, false);
  2827       maybeAddHeaders(file, metadata, response);
  2828       response.setHeader("Content-Length", "" + count, false);
  2830       var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
  2831                                     Ci.nsIFileInputStream.CLOSE_ON_EOF);
  2833       offset = offset || 0;
  2834       count  = count || file.fileSize;
  2835       NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset");
  2836       NS_ASSERT(count >= 0, "bad count");
  2837       NS_ASSERT(offset + count <= file.fileSize, "bad total data size");
  2839       try
  2841         if (offset !== 0)
  2843           // Seek (or read, if seeking isn't supported) to the correct offset so
  2844           // the data sent to the client matches the requested range.
  2845           if (fis instanceof Ci.nsISeekableStream)
  2846             fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset);
  2847           else
  2848             new ScriptableInputStream(fis).read(offset);
  2851       catch (e)
  2853         fis.close();
  2854         throw e;
  2857       function writeMore()
  2859         gThreadManager.currentThread
  2860                       .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL);
  2863       var input = new BinaryInputStream(fis);
  2864       var output = new BinaryOutputStream(response.bodyOutputStream);
  2865       var writeData =
  2867           run: function()
  2869             var chunkSize = Math.min(65536, count);
  2870             count -= chunkSize;
  2871             NS_ASSERT(count >= 0, "underflow");
  2873             try
  2875               var data = input.readByteArray(chunkSize);
  2876               NS_ASSERT(data.length === chunkSize,
  2877                         "incorrect data returned?  got " + data.length +
  2878                         ", expected " + chunkSize);
  2879               output.writeByteArray(data, data.length);
  2880               if (count === 0)
  2882                 fis.close();
  2883                 response.finish();
  2885               else
  2887                 writeMore();
  2890             catch (e)
  2892               try
  2894                 fis.close();
  2896               finally
  2898                 response.finish();
  2900               throw e;
  2903         };
  2905       writeMore();
  2907       // Now that we know copying will start, flag the response as async.
  2908       response.processAsync();
  2910   },
  2912   /**
  2913    * Get the value corresponding to a given key for the given path for SJS state
  2914    * preservation across requests.
  2916    * @param path : string
  2917    *   the path from which the given state is to be retrieved
  2918    * @param k : string
  2919    *   the key whose corresponding value is to be returned
  2920    * @returns string
  2921    *   the corresponding value, which is initially the empty string
  2922    */
  2923   _getState: function(path, k)
  2925     var state = this._state;
  2926     if (path in state && k in state[path])
  2927       return state[path][k];
  2928     return "";
  2929   },
  2931   /**
  2932    * Set the value corresponding to a given key for the given path for SJS state
  2933    * preservation across requests.
  2935    * @param path : string
  2936    *   the path from which the given state is to be retrieved
  2937    * @param k : string
  2938    *   the key whose corresponding value is to be set
  2939    * @param v : string
  2940    *   the value to be set
  2941    */
  2942   _setState: function(path, k, v)
  2944     if (typeof v !== "string")
  2945       throw new Error("non-string value passed");
  2946     var state = this._state;
  2947     if (!(path in state))
  2948       state[path] = {};
  2949     state[path][k] = v;
  2950   },
  2952   /**
  2953    * Get the value corresponding to a given key for SJS state preservation
  2954    * across requests.
  2956    * @param k : string
  2957    *   the key whose corresponding value is to be returned
  2958    * @returns string
  2959    *   the corresponding value, which is initially the empty string
  2960    */
  2961   _getSharedState: function(k)
  2963     var state = this._sharedState;
  2964     if (k in state)
  2965       return state[k];
  2966     return "";
  2967   },
  2969   /**
  2970    * Set the value corresponding to a given key for SJS state preservation
  2971    * across requests.
  2973    * @param k : string
  2974    *   the key whose corresponding value is to be set
  2975    * @param v : string
  2976    *   the value to be set
  2977    */
  2978   _setSharedState: function(k, v)
  2980     if (typeof v !== "string")
  2981       throw new Error("non-string value passed");
  2982     this._sharedState[k] = v;
  2983   },
  2985   /**
  2986    * Returns the object associated with the given key in the server for SJS
  2987    * state preservation across requests.
  2989    * @param k : string
  2990    *  the key whose corresponding object is to be returned
  2991    * @returns nsISupports
  2992    *  the corresponding object, or null if none was present
  2993    */
  2994   _getObjectState: function(k)
  2996     if (typeof k !== "string")
  2997       throw new Error("non-string key passed");
  2998     return this._objectState[k] || null;
  2999   },
  3001   /**
  3002    * Sets the object associated with the given key in the server for SJS
  3003    * state preservation across requests.
  3005    * @param k : string
  3006    *  the key whose corresponding object is to be set
  3007    * @param v : nsISupports
  3008    *  the object to be associated with the given key; may be null
  3009    */
  3010   _setObjectState: function(k, v)
  3012     if (typeof k !== "string")
  3013       throw new Error("non-string key passed");
  3014     if (typeof v !== "object")
  3015       throw new Error("non-object value passed");
  3016     if (v && !("QueryInterface" in v))
  3018       throw new Error("must pass an nsISupports; use wrappedJSObject to ease " +
  3019                       "pain when using the server from JS");
  3022     this._objectState[k] = v;
  3023   },
  3025   /**
  3026    * Gets a content-type for the given file, first by checking for any custom
  3027    * MIME-types registered with this handler for the file's extension, second by
  3028    * asking the global MIME service for a content-type, and finally by failing
  3029    * over to application/octet-stream.
  3031    * @param file : nsIFile
  3032    *   the nsIFile for which to get a file type
  3033    * @returns string
  3034    *   the best content-type which can be determined for the file
  3035    */
  3036   _getTypeFromFile: function(file)
  3038     try
  3040       var name = file.leafName;
  3041       var dot = name.lastIndexOf(".");
  3042       if (dot > 0)
  3044         var ext = name.slice(dot + 1);
  3045         if (ext in this._mimeMappings)
  3046           return this._mimeMappings[ext];
  3048       return Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
  3049                .getService(Ci.nsIMIMEService)
  3050                .getTypeFromFile(file);
  3052     catch (e)
  3054       return "application/octet-stream";
  3056   },
  3058   /**
  3059    * Returns the nsILocalFile which corresponds to the path, as determined using
  3060    * all registered path->directory mappings and any paths which are explicitly
  3061    * overridden.
  3063    * @param path : string
  3064    *   the server path for which a file should be retrieved, e.g. "/foo/bar"
  3065    * @throws HttpError
  3066    *   when the correct action is the corresponding HTTP error (i.e., because no
  3067    *   mapping was found for a directory in path, the referenced file doesn't
  3068    *   exist, etc.)
  3069    * @returns nsILocalFile
  3070    *   the file to be sent as the response to a request for the path
  3071    */
  3072   _getFileForPath: function(path)
  3074     // decode and add underscores as necessary
  3075     try
  3077       path = toInternalPath(path, true);
  3079     catch (e)
  3081       dumpn("*** toInternalPath threw " + e);
  3082       throw HTTP_400; // malformed path
  3085     // next, get the directory which contains this path
  3086     var pathMap = this._pathDirectoryMap;
  3088     // An example progression of tmp for a path "/foo/bar/baz/" might be:
  3089     // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", ""
  3090     var tmp = path.substring(1);
  3091     while (true)
  3093       // do we have a match for current head of the path?
  3094       var file = pathMap.get(tmp);
  3095       if (file)
  3097         // XXX hack; basically disable showing mapping for /foo/bar/ when the
  3098         //     requested path was /foo/bar, because relative links on the page
  3099         //     will all be incorrect -- we really need the ability to easily
  3100         //     redirect here instead
  3101         if (tmp == path.substring(1) &&
  3102             tmp.length != 0 &&
  3103             tmp.charAt(tmp.length - 1) != "/")
  3104           file = null;
  3105         else
  3106           break;
  3109       // if we've finished trying all prefixes, exit
  3110       if (tmp == "")
  3111         break;
  3113       tmp = tmp.substring(0, tmp.lastIndexOf("/"));
  3116     // no mapping applies, so 404
  3117     if (!file)
  3118       throw HTTP_404;
  3121     // last, get the file for the path within the determined directory
  3122     var parentFolder = file.parent;
  3123     var dirIsRoot = (parentFolder == null);
  3125     // Strategy here is to append components individually, making sure we
  3126     // never move above the given directory; this allows paths such as
  3127     // "<file>/foo/../bar" but prevents paths such as "<file>/../base-sibling";
  3128     // this component-wise approach also means the code works even on platforms
  3129     // which don't use "/" as the directory separator, such as Windows
  3130     var leafPath = path.substring(tmp.length + 1);
  3131     var comps = leafPath.split("/");
  3132     for (var i = 0, sz = comps.length; i < sz; i++)
  3134       var comp = comps[i];
  3136       if (comp == "..")
  3137         file = file.parent;
  3138       else if (comp == "." || comp == "")
  3139         continue;
  3140       else
  3141         file.append(comp);
  3143       if (!dirIsRoot && file.equals(parentFolder))
  3144         throw HTTP_403;
  3147     return file;
  3148   },
  3150   /**
  3151    * Writes the error page for the given HTTP error code over the given
  3152    * connection.
  3154    * @param errorCode : uint
  3155    *   the HTTP error code to be used
  3156    * @param connection : Connection
  3157    *   the connection on which the error occurred
  3158    */
  3159   handleError: function(errorCode, connection)
  3161     var response = new Response(connection);
  3163     dumpn("*** error in request: " + errorCode);
  3165     this._handleError(errorCode, new Request(connection.port), response);
  3166   }, 
  3168   /**
  3169    * Handles a request which generates the given error code, using the
  3170    * user-defined error handler if one has been set, gracefully falling back to
  3171    * the x00 status code if the code has no handler, and failing to status code
  3172    * 500 if all else fails.
  3174    * @param errorCode : uint
  3175    *   the HTTP error which is to be returned
  3176    * @param metadata : Request
  3177    *   metadata for the request, which will often be incomplete since this is an
  3178    *   error
  3179    * @param response : Response
  3180    *   an uninitialized Response should be initialized when this method
  3181    *   completes with information which represents the desired error code in the
  3182    *   ideal case or a fallback code in abnormal circumstances (i.e., 500 is a
  3183    *   fallback for 505, per HTTP specs)
  3184    */
  3185   _handleError: function(errorCode, metadata, response)
  3187     if (!metadata)
  3188       throw Cr.NS_ERROR_NULL_POINTER;
  3190     var errorX00 = errorCode - (errorCode % 100);
  3192     try
  3194       if (!(errorCode in HTTP_ERROR_CODES))
  3195         dumpn("*** WARNING: requested invalid error: " + errorCode);
  3197       // RFC 2616 says that we should try to handle an error by its class if we
  3198       // can't otherwise handle it -- if that fails, we revert to handling it as
  3199       // a 500 internal server error, and if that fails we throw and shut down
  3200       // the server
  3202       // actually handle the error
  3203       try
  3205         if (errorCode in this._overrideErrors)
  3206           this._overrideErrors[errorCode](metadata, response);
  3207         else
  3208           this._defaultErrors[errorCode](metadata, response);
  3210       catch (e)
  3212         if (response.partiallySent())
  3214           response.abort(e);
  3215           return;
  3218         // don't retry the handler that threw
  3219         if (errorX00 == errorCode)
  3220           throw HTTP_500;
  3222         dumpn("*** error in handling for error code " + errorCode + ", " +
  3223               "falling back to " + errorX00 + "...");
  3224         response = new Response(response._connection);
  3225         if (errorX00 in this._overrideErrors)
  3226           this._overrideErrors[errorX00](metadata, response);
  3227         else if (errorX00 in this._defaultErrors)
  3228           this._defaultErrors[errorX00](metadata, response);
  3229         else
  3230           throw HTTP_500;
  3233     catch (e)
  3235       if (response.partiallySent())
  3237         response.abort();
  3238         return;
  3241       // we've tried everything possible for a meaningful error -- now try 500
  3242       dumpn("*** error in handling for error code " + errorX00 + ", falling " +
  3243             "back to 500...");
  3245       try
  3247         response = new Response(response._connection);
  3248         if (500 in this._overrideErrors)
  3249           this._overrideErrors[500](metadata, response);
  3250         else
  3251           this._defaultErrors[500](metadata, response);
  3253       catch (e2)
  3255         dumpn("*** multiple errors in default error handlers!");
  3256         dumpn("*** e == " + e + ", e2 == " + e2);
  3257         response.abort(e2);
  3258         return;
  3262     response.complete();
  3263   },
  3265   // FIELDS
  3267   /**
  3268    * This object contains the default handlers for the various HTTP error codes.
  3269    */
  3270   _defaultErrors:
  3272     400: function(metadata, response)
  3274       // none of the data in metadata is reliable, so hard-code everything here
  3275       response.setStatusLine("1.1", 400, "Bad Request");
  3276       response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
  3278       var body = "Bad request\n";
  3279       response.bodyOutputStream.write(body, body.length);
  3280     },
  3281     403: function(metadata, response)
  3283       response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
  3284       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
  3286       var body = "<html>\
  3287                     <head><title>403 Forbidden</title></head>\
  3288                     <body>\
  3289                       <h1>403 Forbidden</h1>\
  3290                     </body>\
  3291                   </html>";
  3292       response.bodyOutputStream.write(body, body.length);
  3293     },
  3294     404: function(metadata, response)
  3296       response.setStatusLine(metadata.httpVersion, 404, "Not Found");
  3297       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
  3299       var body = "<html>\
  3300                     <head><title>404 Not Found</title></head>\
  3301                     <body>\
  3302                       <h1>404 Not Found</h1>\
  3303                       <p>\
  3304                         <span style='font-family: monospace;'>" +
  3305                           htmlEscape(metadata.path) +
  3306                        "</span> was not found.\
  3307                       </p>\
  3308                     </body>\
  3309                   </html>";
  3310       response.bodyOutputStream.write(body, body.length);
  3311     },
  3312     416: function(metadata, response)
  3314       response.setStatusLine(metadata.httpVersion,
  3315                             416,
  3316                             "Requested Range Not Satisfiable");
  3317       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
  3319       var body = "<html>\
  3320                    <head>\
  3321                     <title>416 Requested Range Not Satisfiable</title></head>\
  3322                     <body>\
  3323                      <h1>416 Requested Range Not Satisfiable</h1>\
  3324                      <p>The byte range was not valid for the\
  3325                         requested resource.\
  3326                      </p>\
  3327                     </body>\
  3328                   </html>";
  3329       response.bodyOutputStream.write(body, body.length);
  3330     },
  3331     500: function(metadata, response)
  3333       response.setStatusLine(metadata.httpVersion,
  3334                              500,
  3335                              "Internal Server Error");
  3336       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
  3338       var body = "<html>\
  3339                     <head><title>500 Internal Server Error</title></head>\
  3340                     <body>\
  3341                       <h1>500 Internal Server Error</h1>\
  3342                       <p>Something's broken in this server and\
  3343                         needs to be fixed.</p>\
  3344                     </body>\
  3345                   </html>";
  3346       response.bodyOutputStream.write(body, body.length);
  3347     },
  3348     501: function(metadata, response)
  3350       response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
  3351       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
  3353       var body = "<html>\
  3354                     <head><title>501 Not Implemented</title></head>\
  3355                     <body>\
  3356                       <h1>501 Not Implemented</h1>\
  3357                       <p>This server is not (yet) Apache.</p>\
  3358                     </body>\
  3359                   </html>";
  3360       response.bodyOutputStream.write(body, body.length);
  3361     },
  3362     505: function(metadata, response)
  3364       response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
  3365       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
  3367       var body = "<html>\
  3368                     <head><title>505 HTTP Version Not Supported</title></head>\
  3369                     <body>\
  3370                       <h1>505 HTTP Version Not Supported</h1>\
  3371                       <p>This server only supports HTTP/1.0 and HTTP/1.1\
  3372                         connections.</p>\
  3373                     </body>\
  3374                   </html>";
  3375       response.bodyOutputStream.write(body, body.length);
  3377   },
  3379   /**
  3380    * Contains handlers for the default set of URIs contained in this server.
  3381    */
  3382   _defaultPaths:
  3384     "/": function(metadata, response)
  3386       response.setStatusLine(metadata.httpVersion, 200, "OK");
  3387       response.setHeader("Content-Type", "text/html;charset=utf-8", false);
  3389       var body = "<html>\
  3390                     <head><title>httpd.js</title></head>\
  3391                     <body>\
  3392                       <h1>httpd.js</h1>\
  3393                       <p>If you're seeing this page, httpd.js is up and\
  3394                         serving requests!  Now set a base path and serve some\
  3395                         files!</p>\
  3396                     </body>\
  3397                   </html>";
  3399       response.bodyOutputStream.write(body, body.length);
  3400     },
  3402     "/trace": function(metadata, response)
  3404       response.setStatusLine(metadata.httpVersion, 200, "OK");
  3405       response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
  3407       var body = "Request-URI: " +
  3408                  metadata.scheme + "://" + metadata.host + ":" + metadata.port +
  3409                  metadata.path + "\n\n";
  3410       body += "Request (semantically equivalent, slightly reformatted):\n\n";
  3411       body += metadata.method + " " + metadata.path;
  3413       if (metadata.queryString)
  3414         body +=  "?" + metadata.queryString;
  3416       body += " HTTP/" + metadata.httpVersion + "\r\n";
  3418       var headEnum = metadata.headers;
  3419       while (headEnum.hasMoreElements())
  3421         var fieldName = headEnum.getNext()
  3422                                 .QueryInterface(Ci.nsISupportsString)
  3423                                 .data;
  3424         body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n";
  3427       response.bodyOutputStream.write(body, body.length);
  3430 };
  3433 /**
  3434  * Maps absolute paths to files on the local file system (as nsILocalFiles).
  3435  */
  3436 function FileMap()
  3438   /** Hash which will map paths to nsILocalFiles. */
  3439   this._map = {};
  3441 FileMap.prototype =
  3443   // PUBLIC API
  3445   /**
  3446    * Maps key to a clone of the nsILocalFile value if value is non-null;
  3447    * otherwise, removes any extant mapping for key.
  3449    * @param key : string
  3450    *   string to which a clone of value is mapped
  3451    * @param value : nsILocalFile
  3452    *   the file to map to key, or null to remove a mapping
  3453    */
  3454   put: function(key, value)
  3456     if (value)
  3457       this._map[key] = value.clone();
  3458     else
  3459       delete this._map[key];
  3460   },
  3462   /**
  3463    * Returns a clone of the nsILocalFile mapped to key, or null if no such
  3464    * mapping exists.
  3466    * @param key : string
  3467    *   key to which the returned file maps
  3468    * @returns nsILocalFile
  3469    *   a clone of the mapped file, or null if no mapping exists
  3470    */
  3471   get: function(key)
  3473     var val = this._map[key];
  3474     return val ? val.clone() : null;
  3476 };
  3479 // Response CONSTANTS
  3481 // token       = *<any CHAR except CTLs or separators>
  3482 // CHAR        = <any US-ASCII character (0-127)>
  3483 // CTL         = <any US-ASCII control character (0-31) and DEL (127)>
  3484 // separators  = "(" | ")" | "<" | ">" | "@"
  3485 //             | "," | ";" | ":" | "\" | <">
  3486 //             | "/" | "[" | "]" | "?" | "="
  3487 //             | "{" | "}" | SP  | HT
  3488 const IS_TOKEN_ARRAY =
  3489   [0, 0, 0, 0, 0, 0, 0, 0, //   0
  3490    0, 0, 0, 0, 0, 0, 0, 0, //   8
  3491    0, 0, 0, 0, 0, 0, 0, 0, //  16
  3492    0, 0, 0, 0, 0, 0, 0, 0, //  24
  3494    0, 1, 0, 1, 1, 1, 1, 1, //  32
  3495    0, 0, 1, 1, 0, 1, 1, 0, //  40
  3496    1, 1, 1, 1, 1, 1, 1, 1, //  48
  3497    1, 1, 0, 0, 0, 0, 0, 0, //  56
  3499    0, 1, 1, 1, 1, 1, 1, 1, //  64
  3500    1, 1, 1, 1, 1, 1, 1, 1, //  72
  3501    1, 1, 1, 1, 1, 1, 1, 1, //  80
  3502    1, 1, 1, 0, 0, 0, 1, 1, //  88
  3504    1, 1, 1, 1, 1, 1, 1, 1, //  96
  3505    1, 1, 1, 1, 1, 1, 1, 1, // 104
  3506    1, 1, 1, 1, 1, 1, 1, 1, // 112
  3507    1, 1, 1, 0, 1, 0, 1];   // 120
  3510 /**
  3511  * Determines whether the given character code is a CTL.
  3513  * @param code : uint
  3514  *   the character code
  3515  * @returns boolean
  3516  *   true if code is a CTL, false otherwise
  3517  */
  3518 function isCTL(code)
  3520   return (code >= 0 && code <= 31) || (code == 127);
  3523 /**
  3524  * Represents a response to an HTTP request, encapsulating all details of that
  3525  * response.  This includes all headers, the HTTP version, status code and
  3526  * explanation, and the entity itself.
  3528  * @param connection : Connection
  3529  *   the connection over which this response is to be written
  3530  */
  3531 function Response(connection)
  3533   /** The connection over which this response will be written. */
  3534   this._connection = connection;
  3536   /**
  3537    * The HTTP version of this response; defaults to 1.1 if not set by the
  3538    * handler.
  3539    */
  3540   this._httpVersion = nsHttpVersion.HTTP_1_1;
  3542   /**
  3543    * The HTTP code of this response; defaults to 200.
  3544    */
  3545   this._httpCode = 200;
  3547   /**
  3548    * The description of the HTTP code in this response; defaults to "OK".
  3549    */
  3550   this._httpDescription = "OK";
  3552   /**
  3553    * An nsIHttpHeaders object in which the headers in this response should be
  3554    * stored.  This property is null after the status line and headers have been
  3555    * written to the network, and it may be modified up until it is cleared,
  3556    * except if this._finished is set first (in which case headers are written
  3557    * asynchronously in response to a finish() call not preceded by
  3558    * flushHeaders()).
  3559    */
  3560   this._headers = new nsHttpHeaders();
  3562   /**
  3563    * Set to true when this response is ended (completely constructed if possible
  3564    * and the connection closed); further actions on this will then fail.
  3565    */
  3566   this._ended = false;
  3568   /**
  3569    * A stream used to hold data written to the body of this response.
  3570    */
  3571   this._bodyOutputStream = null;
  3573   /**
  3574    * A stream containing all data that has been written to the body of this
  3575    * response so far.  (Async handlers make the data contained in this
  3576    * unreliable as a way of determining content length in general, but auxiliary
  3577    * saved information can sometimes be used to guarantee reliability.)
  3578    */
  3579   this._bodyInputStream = null;
  3581   /**
  3582    * A stream copier which copies data to the network.  It is initially null
  3583    * until replaced with a copier for response headers; when headers have been
  3584    * fully sent it is replaced with a copier for the response body, remaining
  3585    * so for the duration of response processing.
  3586    */
  3587   this._asyncCopier = null;
  3589   /**
  3590    * True if this response has been designated as being processed
  3591    * asynchronously rather than for the duration of a single call to
  3592    * nsIHttpRequestHandler.handle.
  3593    */
  3594   this._processAsync = false;
  3596   /**
  3597    * True iff finish() has been called on this, signaling that no more changes
  3598    * to this may be made.
  3599    */
  3600   this._finished = false;
  3602   /**
  3603    * True iff powerSeized() has been called on this, signaling that this
  3604    * response is to be handled manually by the response handler (which may then
  3605    * send arbitrary data in response, even non-HTTP responses).
  3606    */
  3607   this._powerSeized = false;
  3609 Response.prototype =
  3611   // PUBLIC CONSTRUCTION API
  3613   //
  3614   // see nsIHttpResponse.bodyOutputStream
  3615   //
  3616   get bodyOutputStream()
  3618     if (this._finished)
  3619       throw Cr.NS_ERROR_NOT_AVAILABLE;
  3621     if (!this._bodyOutputStream)
  3623       var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX,
  3624                           null);
  3625       this._bodyOutputStream = pipe.outputStream;
  3626       this._bodyInputStream = pipe.inputStream;
  3627       if (this._processAsync || this._powerSeized)
  3628         this._startAsyncProcessor();
  3631     return this._bodyOutputStream;
  3632   },
  3634   //
  3635   // see nsIHttpResponse.write
  3636   //
  3637   write: function(data)
  3639     if (this._finished)
  3640       throw Cr.NS_ERROR_NOT_AVAILABLE;
  3642     var dataAsString = String(data);
  3643     this.bodyOutputStream.write(dataAsString, dataAsString.length);
  3644   },
  3646   //
  3647   // see nsIHttpResponse.setStatusLine
  3648   //
  3649   setStatusLine: function(httpVersion, code, description)
  3651     if (!this._headers || this._finished || this._powerSeized)
  3652       throw Cr.NS_ERROR_NOT_AVAILABLE;
  3653     this._ensureAlive();
  3655     if (!(code >= 0 && code < 1000))
  3656       throw Cr.NS_ERROR_INVALID_ARG;
  3658     try
  3660       var httpVer;
  3661       // avoid version construction for the most common cases
  3662       if (!httpVersion || httpVersion == "1.1")
  3663         httpVer = nsHttpVersion.HTTP_1_1;
  3664       else if (httpVersion == "1.0")
  3665         httpVer = nsHttpVersion.HTTP_1_0;
  3666       else
  3667         httpVer = new nsHttpVersion(httpVersion);
  3669     catch (e)
  3671       throw Cr.NS_ERROR_INVALID_ARG;
  3674     // Reason-Phrase = *<TEXT, excluding CR, LF>
  3675     // TEXT          = <any OCTET except CTLs, but including LWS>
  3676     //
  3677     // XXX this ends up disallowing octets which aren't Unicode, I think -- not
  3678     //     much to do if description is IDL'd as string
  3679     if (!description)
  3680       description = "";
  3681     for (var i = 0; i < description.length; i++)
  3682       if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t")
  3683         throw Cr.NS_ERROR_INVALID_ARG;
  3685     // set the values only after validation to preserve atomicity
  3686     this._httpDescription = description;
  3687     this._httpCode = code;
  3688     this._httpVersion = httpVer;
  3689   },
  3691   //
  3692   // see nsIHttpResponse.setHeader
  3693   //
  3694   setHeader: function(name, value, merge)
  3696     if (!this._headers || this._finished || this._powerSeized)
  3697       throw Cr.NS_ERROR_NOT_AVAILABLE;
  3698     this._ensureAlive();
  3700     this._headers.setHeader(name, value, merge);
  3701   },
  3703   //
  3704   // see nsIHttpResponse.processAsync
  3705   //
  3706   processAsync: function()
  3708     if (this._finished)
  3709       throw Cr.NS_ERROR_UNEXPECTED;
  3710     if (this._powerSeized)
  3711       throw Cr.NS_ERROR_NOT_AVAILABLE;
  3712     if (this._processAsync)
  3713       return;
  3714     this._ensureAlive();
  3716     dumpn("*** processing connection " + this._connection.number + " async");
  3717     this._processAsync = true;
  3719     /*
  3720      * Either the bodyOutputStream getter or this method is responsible for
  3721      * starting the asynchronous processor and catching writes of data to the
  3722      * response body of async responses as they happen, for the purpose of
  3723      * forwarding those writes to the actual connection's output stream.
  3724      * If bodyOutputStream is accessed first, calling this method will create
  3725      * the processor (when it first is clear that body data is to be written
  3726      * immediately, not buffered).  If this method is called first, accessing
  3727      * bodyOutputStream will create the processor.  If only this method is
  3728      * called, we'll write nothing, neither headers nor the nonexistent body,
  3729      * until finish() is called.  Since that delay is easily avoided by simply
  3730      * getting bodyOutputStream or calling write(""), we don't worry about it.
  3731      */
  3732     if (this._bodyOutputStream && !this._asyncCopier)
  3733       this._startAsyncProcessor();
  3734   },
  3736   //
  3737   // see nsIHttpResponse.seizePower
  3738   //
  3739   seizePower: function()
  3741     if (this._processAsync)
  3742       throw Cr.NS_ERROR_NOT_AVAILABLE;
  3743     if (this._finished)
  3744       throw Cr.NS_ERROR_UNEXPECTED;
  3745     if (this._powerSeized)
  3746       return;
  3747     this._ensureAlive();
  3749     dumpn("*** forcefully seizing power over connection " +
  3750           this._connection.number + "...");
  3752     // Purge any already-written data without sending it.  We could as easily
  3753     // swap out the streams entirely, but that makes it possible to acquire and
  3754     // unknowingly use a stale reference, so we require there only be one of
  3755     // each stream ever for any response to avoid this complication.
  3756     if (this._asyncCopier)
  3757       this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
  3758     this._asyncCopier = null;
  3759     if (this._bodyOutputStream)
  3761       var input = new BinaryInputStream(this._bodyInputStream);
  3762       var avail;
  3763       while ((avail = input.available()) > 0)
  3764         input.readByteArray(avail);
  3767     this._powerSeized = true;
  3768     if (this._bodyOutputStream)
  3769       this._startAsyncProcessor();
  3770   },
  3772   //
  3773   // see nsIHttpResponse.finish
  3774   //
  3775   finish: function()
  3777     if (!this._processAsync && !this._powerSeized)
  3778       throw Cr.NS_ERROR_UNEXPECTED;
  3779     if (this._finished)
  3780       return;
  3782     dumpn("*** finishing connection " + this._connection.number);
  3783     this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
  3784     if (this._bodyOutputStream)
  3785       this._bodyOutputStream.close();
  3786     this._finished = true;
  3787   },
  3790   // NSISUPPORTS
  3792   //
  3793   // see nsISupports.QueryInterface
  3794   //
  3795   QueryInterface: function(iid)
  3797     if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports))
  3798       return this;
  3800     throw Cr.NS_ERROR_NO_INTERFACE;
  3801   },
  3804   // POST-CONSTRUCTION API (not exposed externally)
  3806   /**
  3807    * The HTTP version number of this, as a string (e.g. "1.1").
  3808    */
  3809   get httpVersion()
  3811     this._ensureAlive();
  3812     return this._httpVersion.toString();
  3813   },
  3815   /**
  3816    * The HTTP status code of this response, as a string of three characters per
  3817    * RFC 2616.
  3818    */
  3819   get httpCode()
  3821     this._ensureAlive();
  3823     var codeString = (this._httpCode < 10 ? "0" : "") +
  3824                      (this._httpCode < 100 ? "0" : "") +
  3825                      this._httpCode;
  3826     return codeString;
  3827   },
  3829   /**
  3830    * The description of the HTTP status code of this response, or "" if none is
  3831    * set.
  3832    */
  3833   get httpDescription()
  3835     this._ensureAlive();
  3837     return this._httpDescription;
  3838   },
  3840   /**
  3841    * The headers in this response, as an nsHttpHeaders object.
  3842    */
  3843   get headers()
  3845     this._ensureAlive();
  3847     return this._headers;
  3848   },
  3850   //
  3851   // see nsHttpHeaders.getHeader
  3852   //
  3853   getHeader: function(name)
  3855     this._ensureAlive();
  3857     return this._headers.getHeader(name);
  3858   },
  3860   /**
  3861    * Determines whether this response may be abandoned in favor of a newly
  3862    * constructed response.  A response may be abandoned only if it is not being
  3863    * sent asynchronously and if raw control over it has not been taken from the
  3864    * server.
  3866    * @returns boolean
  3867    *   true iff no data has been written to the network
  3868    */
  3869   partiallySent: function()
  3871     dumpn("*** partiallySent()");
  3872     return this._processAsync || this._powerSeized;
  3873   },
  3875   /**
  3876    * If necessary, kicks off the remaining request processing needed to be done
  3877    * after a request handler performs its initial work upon this response.
  3878    */
  3879   complete: function()
  3881     dumpn("*** complete()");
  3882     if (this._processAsync || this._powerSeized)
  3884       NS_ASSERT(this._processAsync ^ this._powerSeized,
  3885                 "can't both send async and relinquish power");
  3886       return;
  3889     NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
  3891     this._startAsyncProcessor();
  3893     // Now make sure we finish processing this request!
  3894     if (this._bodyOutputStream)
  3895       this._bodyOutputStream.close();
  3896   },
  3898   /**
  3899    * Abruptly ends processing of this response, usually due to an error in an
  3900    * incoming request but potentially due to a bad error handler.  Since we
  3901    * cannot handle the error in the usual way (giving an HTTP error page in
  3902    * response) because data may already have been sent (or because the response
  3903    * might be expected to have been generated asynchronously or completely from
  3904    * scratch by the handler), we stop processing this response and abruptly
  3905    * close the connection.
  3907    * @param e : Error
  3908    *   the exception which precipitated this abort, or null if no such exception
  3909    *   was generated
  3910    */
  3911   abort: function(e)
  3913     dumpn("*** abort(<" + e + ">)");
  3915     // This response will be ended by the processor if one was created.
  3916     var copier = this._asyncCopier;
  3917     if (copier)
  3919       // We dispatch asynchronously here so that any pending writes of data to
  3920       // the connection will be deterministically written.  This makes it easier
  3921       // to specify exact behavior, and it makes observable behavior more
  3922       // predictable for clients.  Note that the correctness of this depends on
  3923       // callbacks in response to _waitToReadData in WriteThroughCopier
  3924       // happening asynchronously with respect to the actual writing of data to
  3925       // bodyOutputStream, as they currently do; if they happened synchronously,
  3926       // an event which ran before this one could write more data to the
  3927       // response body before we get around to canceling the copier.  We have
  3928       // tests for this in test_seizepower.js, however, and I can't think of a
  3929       // way to handle both cases without removing bodyOutputStream access and
  3930       // moving its effective write(data, length) method onto Response, which
  3931       // would be slower and require more code than this anyway.
  3932       gThreadManager.currentThread.dispatch({
  3933         run: function()
  3935           dumpn("*** canceling copy asynchronously...");
  3936           copier.cancel(Cr.NS_ERROR_UNEXPECTED);
  3938       }, Ci.nsIThread.DISPATCH_NORMAL);
  3940     else
  3942       this.end();
  3944   },
  3946   /**
  3947    * Closes this response's network connection, marks the response as finished,
  3948    * and notifies the server handler that the request is done being processed.
  3949    */
  3950   end: function()
  3952     NS_ASSERT(!this._ended, "ending this response twice?!?!");
  3954     this._connection.close();
  3955     if (this._bodyOutputStream)
  3956       this._bodyOutputStream.close();
  3958     this._finished = true;
  3959     this._ended = true;
  3960   },
  3962   // PRIVATE IMPLEMENTATION
  3964   /**
  3965    * Sends the status line and headers of this response if they haven't been
  3966    * sent and initiates the process of copying data written to this response's
  3967    * body to the network.
  3968    */
  3969   _startAsyncProcessor: function()
  3971     dumpn("*** _startAsyncProcessor()");
  3973     // Handle cases where we're being called a second time.  The former case
  3974     // happens when this is triggered both by complete() and by processAsync(),
  3975     // while the latter happens when processAsync() in conjunction with sent
  3976     // data causes abort() to be called.
  3977     if (this._asyncCopier || this._ended)
  3979       dumpn("*** ignoring second call to _startAsyncProcessor");
  3980       return;
  3983     // Send headers if they haven't been sent already and should be sent, then
  3984     // asynchronously continue to send the body.
  3985     if (this._headers && !this._powerSeized)
  3987       this._sendHeaders();
  3988       return;
  3991     this._headers = null;
  3992     this._sendBody();
  3993   },
  3995   /**
  3996    * Signals that all modifications to the response status line and headers are
  3997    * complete and then sends that data over the network to the client.  Once
  3998    * this method completes, a different response to the request that resulted
  3999    * in this response cannot be sent -- the only possible action in case of
  4000    * error is to abort the response and close the connection.
  4001    */
  4002   _sendHeaders: function()
  4004     dumpn("*** _sendHeaders()");
  4006     NS_ASSERT(this._headers);
  4007     NS_ASSERT(!this._powerSeized);
  4009     // request-line
  4010     var statusLine = "HTTP/" + this.httpVersion + " " +
  4011                      this.httpCode + " " +
  4012                      this.httpDescription + "\r\n";
  4014     // header post-processing
  4016     var headers = this._headers;
  4017     headers.setHeader("Connection", "close", false);
  4018     headers.setHeader("Server", "httpd.js", false);
  4019     if (!headers.hasHeader("Date"))
  4020       headers.setHeader("Date", toDateString(Date.now()), false);
  4022     // Any response not being processed asynchronously must have an associated
  4023     // Content-Length header for reasons of backwards compatibility with the
  4024     // initial server, which fully buffered every response before sending it.
  4025     // Beyond that, however, it's good to do this anyway because otherwise it's
  4026     // impossible to test behaviors that depend on the presence or absence of a
  4027     // Content-Length header.
  4028     if (!this._processAsync)
  4030       dumpn("*** non-async response, set Content-Length");
  4032       var bodyStream = this._bodyInputStream;
  4033       var avail = bodyStream ? bodyStream.available() : 0;
  4035       // XXX assumes stream will always report the full amount of data available
  4036       headers.setHeader("Content-Length", "" + avail, false);
  4040     // construct and send response
  4041     dumpn("*** header post-processing completed, sending response head...");
  4043     // request-line
  4044     var preambleData = [statusLine];
  4046     // headers
  4047     var headEnum = headers.enumerator;
  4048     while (headEnum.hasMoreElements())
  4050       var fieldName = headEnum.getNext()
  4051                               .QueryInterface(Ci.nsISupportsString)
  4052                               .data;
  4053       var values = headers.getHeaderValues(fieldName);
  4054       for (var i = 0, sz = values.length; i < sz; i++)
  4055         preambleData.push(fieldName + ": " + values[i] + "\r\n");
  4058     // end request-line/headers
  4059     preambleData.push("\r\n");
  4061     var preamble = preambleData.join("");
  4063     var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null);
  4064     responseHeadPipe.outputStream.write(preamble, preamble.length);
  4066     var response = this;
  4067     var copyObserver =
  4069         onStartRequest: function(request, cx)
  4071           dumpn("*** preamble copying started");
  4072         },
  4074         onStopRequest: function(request, cx, statusCode)
  4076           dumpn("*** preamble copying complete " +
  4077                 "[status=0x" + statusCode.toString(16) + "]");
  4079           if (!Components.isSuccessCode(statusCode))
  4081             dumpn("!!! header copying problems: non-success statusCode, " +
  4082                   "ending response");
  4084             response.end();
  4086           else
  4088             response._sendBody();
  4090         },
  4092         QueryInterface: function(aIID)
  4094           if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
  4095             return this;
  4097           throw Cr.NS_ERROR_NO_INTERFACE;
  4099       };
  4101     var headerCopier = this._asyncCopier =
  4102       new WriteThroughCopier(responseHeadPipe.inputStream,
  4103                              this._connection.output,
  4104                              copyObserver, null);
  4106     responseHeadPipe.outputStream.close();
  4108     // Forbid setting any more headers or modifying the request line.
  4109     this._headers = null;
  4110   },
  4112   /**
  4113    * Asynchronously writes the body of the response (or the entire response, if
  4114    * seizePower() has been called) to the network.
  4115    */
  4116   _sendBody: function()
  4118     dumpn("*** _sendBody");
  4120     NS_ASSERT(!this._headers, "still have headers around but sending body?");
  4122     // If no body data was written, we're done
  4123     if (!this._bodyInputStream)
  4125       dumpn("*** empty body, response finished");
  4126       this.end();
  4127       return;
  4130     var response = this;
  4131     var copyObserver =
  4133         onStartRequest: function(request, context)
  4135           dumpn("*** onStartRequest");
  4136         },
  4138         onStopRequest: function(request, cx, statusCode)
  4140           dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");
  4142           if (statusCode === Cr.NS_BINDING_ABORTED)
  4144             dumpn("*** terminating copy observer without ending the response");
  4146           else
  4148             if (!Components.isSuccessCode(statusCode))
  4149               dumpn("*** WARNING: non-success statusCode in onStopRequest");
  4151             response.end();
  4153         },
  4155         QueryInterface: function(aIID)
  4157           if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
  4158             return this;
  4160           throw Cr.NS_ERROR_NO_INTERFACE;
  4162       };
  4164     dumpn("*** starting async copier of body data...");
  4165     this._asyncCopier =
  4166       new WriteThroughCopier(this._bodyInputStream, this._connection.output,
  4167                             copyObserver, null);
  4168   },
  4170   /** Ensures that this hasn't been ended. */
  4171   _ensureAlive: function()
  4173     NS_ASSERT(!this._ended, "not handling response lifetime correctly");
  4175 };
  4177 /**
  4178  * Size of the segments in the buffer used in storing response data and writing
  4179  * it to the socket.
  4180  */
  4181 Response.SEGMENT_SIZE = 8192;
  4183 /** Serves double duty in WriteThroughCopier implementation. */
  4184 function notImplemented()
  4186   throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  4189 /** Returns true iff the given exception represents stream closure. */
  4190 function streamClosed(e)
  4192   return e === Cr.NS_BASE_STREAM_CLOSED ||
  4193          (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED);
  4196 /** Returns true iff the given exception represents a blocked stream. */
  4197 function wouldBlock(e)
  4199   return e === Cr.NS_BASE_STREAM_WOULD_BLOCK ||
  4200          (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK);
  4203 /**
  4204  * Copies data from source to sink as it becomes available, when that data can
  4205  * be written to sink without blocking.
  4207  * @param source : nsIAsyncInputStream
  4208  *   the stream from which data is to be read
  4209  * @param sink : nsIAsyncOutputStream
  4210  *   the stream to which data is to be copied
  4211  * @param observer : nsIRequestObserver
  4212  *   an observer which will be notified when the copy starts and finishes
  4213  * @param context : nsISupports
  4214  *   context passed to observer when notified of start/stop
  4215  * @throws NS_ERROR_NULL_POINTER
  4216  *   if source, sink, or observer are null
  4217  */
  4218 function WriteThroughCopier(source, sink, observer, context)
  4220   if (!source || !sink || !observer)
  4221     throw Cr.NS_ERROR_NULL_POINTER;
  4223   /** Stream from which data is being read. */
  4224   this._source = source;
  4226   /** Stream to which data is being written. */
  4227   this._sink = sink;
  4229   /** Observer watching this copy. */
  4230   this._observer = observer;
  4232   /** Context for the observer watching this. */
  4233   this._context = context;
  4235   /**
  4236    * True iff this is currently being canceled (cancel has been called, the
  4237    * callback may not yet have been made).
  4238    */
  4239   this._canceled = false;
  4241   /**
  4242    * False until all data has been read from input and written to output, at
  4243    * which point this copy is completed and cancel() is asynchronously called.
  4244    */
  4245   this._completed = false;
  4247   /** Required by nsIRequest, meaningless. */
  4248   this.loadFlags = 0;
  4249   /** Required by nsIRequest, meaningless. */
  4250   this.loadGroup = null;
  4251   /** Required by nsIRequest, meaningless. */
  4252   this.name = "response-body-copy";
  4254   /** Status of this request. */
  4255   this.status = Cr.NS_OK;
  4257   /** Arrays of byte strings waiting to be written to output. */
  4258   this._pendingData = [];
  4260   // start copying
  4261   try
  4263     observer.onStartRequest(this, context);
  4264     this._waitToReadData();
  4265     this._waitForSinkClosure();
  4267   catch (e)
  4269     dumpn("!!! error starting copy: " + e +
  4270           ("lineNumber" in e ? ", line " + e.lineNumber : ""));
  4271     dumpn(e.stack);
  4272     this.cancel(Cr.NS_ERROR_UNEXPECTED);
  4275 WriteThroughCopier.prototype =
  4277   /* nsISupports implementation */
  4279   QueryInterface: function(iid)
  4281     if (iid.equals(Ci.nsIInputStreamCallback) ||
  4282         iid.equals(Ci.nsIOutputStreamCallback) ||
  4283         iid.equals(Ci.nsIRequest) ||
  4284         iid.equals(Ci.nsISupports))
  4286       return this;
  4289     throw Cr.NS_ERROR_NO_INTERFACE;
  4290   },
  4293   // NSIINPUTSTREAMCALLBACK
  4295   /**
  4296    * Receives a more-data-in-input notification and writes the corresponding
  4297    * data to the output.
  4299    * @param input : nsIAsyncInputStream
  4300    *   the input stream on whose data we have been waiting
  4301    */
  4302   onInputStreamReady: function(input)
  4304     if (this._source === null)
  4305       return;
  4307     dumpn("*** onInputStreamReady");
  4309     //
  4310     // Ordinarily we'll read a non-zero amount of data from input, queue it up
  4311     // to be written and then wait for further callbacks.  The complications in
  4312     // this method are the cases where we deviate from that behavior when errors
  4313     // occur or when copying is drawing to a finish.
  4314     //
  4315     // The edge cases when reading data are:
  4316     //
  4317     //   Zero data is read
  4318     //     If zero data was read, we're at the end of available data, so we can
  4319     //     should stop reading and move on to writing out what we have (or, if
  4320     //     we've already done that, onto notifying of completion).
  4321     //   A stream-closed exception is thrown
  4322     //     This is effectively a less kind version of zero data being read; the
  4323     //     only difference is that we notify of completion with that result
  4324     //     rather than with NS_OK.
  4325     //   Some other exception is thrown
  4326     //     This is the least kind result.  We don't know what happened, so we
  4327     //     act as though the stream closed except that we notify of completion
  4328     //     with the result NS_ERROR_UNEXPECTED.
  4329     //
  4331     var bytesWanted = 0, bytesConsumed = -1;
  4332     try
  4334       input = new BinaryInputStream(input);
  4336       bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE);
  4337       dumpn("*** input wanted: " + bytesWanted);
  4339       if (bytesWanted > 0)
  4341         var data = input.readByteArray(bytesWanted);
  4342         bytesConsumed = data.length;
  4343         this._pendingData.push(String.fromCharCode.apply(String, data));
  4346       dumpn("*** " + bytesConsumed + " bytes read");
  4348       // Handle the zero-data edge case in the same place as all other edge
  4349       // cases are handled.
  4350       if (bytesWanted === 0)
  4351         throw Cr.NS_BASE_STREAM_CLOSED;
  4353     catch (e)
  4355       if (streamClosed(e))
  4357         dumpn("*** input stream closed");
  4358         e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
  4360       else
  4362         dumpn("!!! unexpected error reading from input, canceling: " + e);
  4363         e = Cr.NS_ERROR_UNEXPECTED;
  4366       this._doneReadingSource(e);
  4367       return;
  4370     var pendingData = this._pendingData;
  4372     NS_ASSERT(bytesConsumed > 0);
  4373     NS_ASSERT(pendingData.length > 0, "no pending data somehow?");
  4374     NS_ASSERT(pendingData[pendingData.length - 1].length > 0,
  4375               "buffered zero bytes of data?");
  4377     NS_ASSERT(this._source !== null);
  4379     // Reading has gone great, and we've gotten data to write now.  What if we
  4380     // don't have a place to write that data, because output went away just
  4381     // before this read?  Drop everything on the floor, including new data, and
  4382     // cancel at this point.
  4383     if (this._sink === null)
  4385       pendingData.length = 0;
  4386       this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
  4387       return;
  4390     // Okay, we've read the data, and we know we have a place to write it.  We
  4391     // need to queue up the data to be written, but *only* if none is queued
  4392     // already -- if data's already queued, the code that actually writes the
  4393     // data will make sure to wait on unconsumed pending data.
  4394     try
  4396       if (pendingData.length === 1)
  4397         this._waitToWriteData();
  4399     catch (e)
  4401       dumpn("!!! error waiting to write data just read, swallowing and " +
  4402             "writing only what we already have: " + e);
  4403       this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
  4404       return;
  4407     // Whee!  We successfully read some data, and it's successfully queued up to
  4408     // be written.  All that remains now is to wait for more data to read.
  4409     try
  4411       this._waitToReadData();
  4413     catch (e)
  4415       dumpn("!!! error waiting to read more data: " + e);
  4416       this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
  4418   },
  4421   // NSIOUTPUTSTREAMCALLBACK
  4423   /**
  4424    * Callback when data may be written to the output stream without blocking, or
  4425    * when the output stream has been closed.
  4427    * @param output : nsIAsyncOutputStream
  4428    *   the output stream on whose writability we've been waiting, also known as
  4429    *   this._sink
  4430    */
  4431   onOutputStreamReady: function(output)
  4433     if (this._sink === null)
  4434       return;
  4436     dumpn("*** onOutputStreamReady");
  4438     var pendingData = this._pendingData;
  4439     if (pendingData.length === 0)
  4441       // There's no pending data to write.  The only way this can happen is if
  4442       // we're waiting on the output stream's closure, so we can respond to a
  4443       // copying failure as quickly as possible (rather than waiting for data to
  4444       // be available to read and then fail to be copied).  Therefore, we must
  4445       // be done now -- don't bother to attempt to write anything and wrap
  4446       // things up.
  4447       dumpn("!!! output stream closed prematurely, ending copy");
  4449       this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
  4450       return;
  4454     NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?");
  4456     //
  4457     // Write out the first pending quantum of data.  The possible errors here
  4458     // are:
  4459     //
  4460     //   The write might fail because we can't write that much data
  4461     //     Okay, we've written what we can now, so re-queue what's left and
  4462     //     finish writing it out later.
  4463     //   The write failed because the stream was closed
  4464     //     Discard pending data that we can no longer write, stop reading, and
  4465     //     signal that copying finished.
  4466     //   Some other error occurred.
  4467     //     Same as if the stream were closed, but notify with the status
  4468     //     NS_ERROR_UNEXPECTED so the observer knows something was wonky.
  4469     //
  4471     try
  4473       var quantum = pendingData[0];
  4475       // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on
  4476       //     undefined behavior!  We're only using this because writeByteArray
  4477       //     is unusably broken for asynchronous output streams; see bug 532834
  4478       //     for details.
  4479       var bytesWritten = output.write(quantum, quantum.length);
  4480       if (bytesWritten === quantum.length)
  4481         pendingData.shift();
  4482       else
  4483         pendingData[0] = quantum.substring(bytesWritten);
  4485       dumpn("*** wrote " + bytesWritten + " bytes of data");
  4487     catch (e)
  4489       if (wouldBlock(e))
  4491         NS_ASSERT(pendingData.length > 0,
  4492                   "stream-blocking exception with no data to write?");
  4493         NS_ASSERT(pendingData[0].length > 0,
  4494                   "stream-blocking exception with empty quantum?");
  4495         this._waitToWriteData();
  4496         return;
  4499       if (streamClosed(e))
  4500         dumpn("!!! output stream prematurely closed, signaling error...");
  4501       else
  4502         dumpn("!!! unknown error: " + e + ", quantum=" + quantum);
  4504       this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
  4505       return;
  4508     // The day is ours!  Quantum written, now let's see if we have more data
  4509     // still to write.
  4510     try
  4512       if (pendingData.length > 0)
  4514         this._waitToWriteData();
  4515         return;
  4518     catch (e)
  4520       dumpn("!!! unexpected error waiting to write pending data: " + e);
  4521       this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
  4522       return;
  4525     // Okay, we have no more pending data to write -- but might we get more in
  4526     // the future?
  4527     if (this._source !== null)
  4529       /*
  4530        * If we might, then wait for the output stream to be closed.  (We wait
  4531        * only for closure because we have no data to write -- and if we waited
  4532        * for a specific amount of data, we would get repeatedly notified for no
  4533        * reason if over time the output stream permitted more and more data to
  4534        * be written to it without blocking.)
  4535        */
  4536        this._waitForSinkClosure();
  4538     else
  4540       /*
  4541        * On the other hand, if we can't have more data because the input
  4542        * stream's gone away, then it's time to notify of copy completion.
  4543        * Victory!
  4544        */
  4545       this._sink = null;
  4546       this._cancelOrDispatchCancelCallback(Cr.NS_OK);
  4548   },
  4551   // NSIREQUEST
  4553   /** Returns true if the cancel observer hasn't been notified yet. */
  4554   isPending: function()
  4556     return !this._completed;
  4557   },
  4559   /** Not implemented, don't use! */
  4560   suspend: notImplemented,
  4561   /** Not implemented, don't use! */
  4562   resume: notImplemented,
  4564   /**
  4565    * Cancels data reading from input, asynchronously writes out any pending
  4566    * data, and causes the observer to be notified with the given error code when
  4567    * all writing has finished.
  4569    * @param status : nsresult
  4570    *   the status to pass to the observer when data copying has been canceled
  4571    */
  4572   cancel: function(status)
  4574     dumpn("*** cancel(" + status.toString(16) + ")");
  4576     if (this._canceled)
  4578       dumpn("*** suppressing a late cancel");
  4579       return;
  4582     this._canceled = true;
  4583     this.status = status;
  4585     // We could be in the middle of absolutely anything at this point.  Both
  4586     // input and output might still be around, we might have pending data to
  4587     // write, and in general we know nothing about the state of the world.  We
  4588     // therefore must assume everything's in progress and take everything to its
  4589     // final steady state (or so far as it can go before we need to finish
  4590     // writing out remaining data).
  4592     this._doneReadingSource(status);
  4593   },
  4596   // PRIVATE IMPLEMENTATION
  4598   /**
  4599    * Stop reading input if we haven't already done so, passing e as the status
  4600    * when closing the stream, and kick off a copy-completion notice if no more
  4601    * data remains to be written.
  4603    * @param e : nsresult
  4604    *   the status to be used when closing the input stream
  4605    */
  4606   _doneReadingSource: function(e)
  4608     dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")");
  4610     this._finishSource(e);
  4611     if (this._pendingData.length === 0)
  4612       this._sink = null;
  4613     else
  4614       NS_ASSERT(this._sink !== null, "null output?");
  4616     // If we've written out all data read up to this point, then it's time to
  4617     // signal completion.
  4618     if (this._sink === null)
  4620       NS_ASSERT(this._pendingData.length === 0, "pending data still?");
  4621       this._cancelOrDispatchCancelCallback(e);
  4623   },
  4625   /**
  4626    * Stop writing output if we haven't already done so, discard any data that
  4627    * remained to be sent, close off input if it wasn't already closed, and kick
  4628    * off a copy-completion notice.
  4630    * @param e : nsresult
  4631    *   the status to be used when closing input if it wasn't already closed
  4632    */
  4633   _doneWritingToSink: function(e)
  4635     dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")");
  4637     this._pendingData.length = 0;
  4638     this._sink = null;
  4639     this._doneReadingSource(e);
  4640   },
  4642   /**
  4643    * Completes processing of this copy: either by canceling the copy if it
  4644    * hasn't already been canceled using the provided status, or by dispatching
  4645    * the cancel callback event (with the originally provided status, of course)
  4646    * if it already has been canceled.
  4648    * @param status : nsresult
  4649    *   the status code to use to cancel this, if this hasn't already been
  4650    *   canceled
  4651    */
  4652   _cancelOrDispatchCancelCallback: function(status)
  4654     dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")");
  4656     NS_ASSERT(this._source === null, "should have finished input");
  4657     NS_ASSERT(this._sink === null, "should have finished output");
  4658     NS_ASSERT(this._pendingData.length === 0, "should have no pending data");
  4660     if (!this._canceled)
  4662       this.cancel(status);
  4663       return;
  4666     var self = this;
  4667     var event =
  4669         run: function()
  4671           dumpn("*** onStopRequest async callback");
  4673           self._completed = true;
  4674           try
  4676             self._observer.onStopRequest(self, self._context, self.status);
  4678           catch (e)
  4680             NS_ASSERT(false,
  4681                       "how are we throwing an exception here?  we control " +
  4682                       "all the callers!  " + e);
  4685       };
  4687     gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
  4688   },
  4690   /**
  4691    * Kicks off another wait for more data to be available from the input stream.
  4692    */
  4693   _waitToReadData: function()
  4695     dumpn("*** _waitToReadData");
  4696     this._source.asyncWait(this, 0, Response.SEGMENT_SIZE,
  4697                            gThreadManager.mainThread);
  4698   },
  4700   /**
  4701    * Kicks off another wait until data can be written to the output stream.
  4702    */
  4703   _waitToWriteData: function()
  4705     dumpn("*** _waitToWriteData");
  4707     var pendingData = this._pendingData;
  4708     NS_ASSERT(pendingData.length > 0, "no pending data to write?");
  4709     NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?");
  4711     this._sink.asyncWait(this, 0, pendingData[0].length,
  4712                          gThreadManager.mainThread);
  4713   },
  4715   /**
  4716    * Kicks off a wait for the sink to which data is being copied to be closed.
  4717    * We wait for stream closure when we don't have any data to be copied, rather
  4718    * than waiting to write a specific amount of data.  We can't wait to write
  4719    * data because the sink might be infinitely writable, and if no data appears
  4720    * in the source for a long time we might have to spin quite a bit waiting to
  4721    * write, waiting to write again, &c.  Waiting on stream closure instead means
  4722    * we'll get just one notification if the sink dies.  Note that when data
  4723    * starts arriving from the sink we'll resume waiting for data to be written,
  4724    * dropping this closure-only callback entirely.
  4725    */
  4726   _waitForSinkClosure: function()
  4728     dumpn("*** _waitForSinkClosure");
  4730     this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0,
  4731                          gThreadManager.mainThread);
  4732   },
  4734   /**
  4735    * Closes input with the given status, if it hasn't already been closed;
  4736    * otherwise a no-op.
  4738    * @param status : nsresult
  4739    *   status code use to close the source stream if necessary
  4740    */
  4741   _finishSource: function(status)
  4743     dumpn("*** _finishSource(" + status.toString(16) + ")");
  4745     if (this._source !== null)
  4747       this._source.closeWithStatus(status);
  4748       this._source = null;
  4751 };
  4754 /**
  4755  * A container for utility functions used with HTTP headers.
  4756  */
  4757 const headerUtils =
  4759   /**
  4760    * Normalizes fieldName (by converting it to lowercase) and ensures it is a
  4761    * valid header field name (although not necessarily one specified in RFC
  4762    * 2616).
  4764    * @throws NS_ERROR_INVALID_ARG
  4765    *   if fieldName does not match the field-name production in RFC 2616
  4766    * @returns string
  4767    *   fieldName converted to lowercase if it is a valid header, for characters
  4768    *   where case conversion is possible
  4769    */
  4770   normalizeFieldName: function(fieldName)
  4772     if (fieldName == "")
  4774       dumpn("*** Empty fieldName");
  4775       throw Cr.NS_ERROR_INVALID_ARG;
  4778     for (var i = 0, sz = fieldName.length; i < sz; i++)
  4780       if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)])
  4782         dumpn(fieldName + " is not a valid header field name!");
  4783         throw Cr.NS_ERROR_INVALID_ARG;
  4787     return fieldName.toLowerCase();
  4788   },
  4790   /**
  4791    * Ensures that fieldValue is a valid header field value (although not
  4792    * necessarily as specified in RFC 2616 if the corresponding field name is
  4793    * part of the HTTP protocol), normalizes the value if it is, and
  4794    * returns the normalized value.
  4796    * @param fieldValue : string
  4797    *   a value to be normalized as an HTTP header field value
  4798    * @throws NS_ERROR_INVALID_ARG
  4799    *   if fieldValue does not match the field-value production in RFC 2616
  4800    * @returns string
  4801    *   fieldValue as a normalized HTTP header field value
  4802    */
  4803   normalizeFieldValue: function(fieldValue)
  4805     // field-value    = *( field-content | LWS )
  4806     // field-content  = <the OCTETs making up the field-value
  4807     //                  and consisting of either *TEXT or combinations
  4808     //                  of token, separators, and quoted-string>
  4809     // TEXT           = <any OCTET except CTLs,
  4810     //                  but including LWS>
  4811     // LWS            = [CRLF] 1*( SP | HT )
  4812     //
  4813     // quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> )
  4814     // qdtext         = <any TEXT except <">>
  4815     // quoted-pair    = "\" CHAR
  4816     // CHAR           = <any US-ASCII character (octets 0 - 127)>
  4818     // Any LWS that occurs between field-content MAY be replaced with a single
  4819     // SP before interpreting the field value or forwarding the message
  4820     // downstream (section 4.2); we replace 1*LWS with a single SP
  4821     var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " ");
  4823     // remove leading/trailing LWS (which has been converted to SP)
  4824     val = val.replace(/^ +/, "").replace(/ +$/, "");
  4826     // that should have taken care of all CTLs, so val should contain no CTLs
  4827     dumpn("*** Normalized value: '" + val + "'");
  4828     for (var i = 0, len = val.length; i < len; i++)
  4829       if (isCTL(val.charCodeAt(i)))
  4831         dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
  4832         throw Cr.NS_ERROR_INVALID_ARG;
  4835     // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
  4836     //     normalize, however, so this can be construed as a tightening of the
  4837     //     spec and not entirely as a bug
  4838     return val;
  4840 };
  4844 /**
  4845  * Converts the given string into a string which is safe for use in an HTML
  4846  * context.
  4848  * @param str : string
  4849  *   the string to make HTML-safe
  4850  * @returns string
  4851  *   an HTML-safe version of str
  4852  */
  4853 function htmlEscape(str)
  4855   // this is naive, but it'll work
  4856   var s = "";
  4857   for (var i = 0; i < str.length; i++)
  4858     s += "&#" + str.charCodeAt(i) + ";";
  4859   return s;
  4863 /**
  4864  * Constructs an object representing an HTTP version (see section 3.1).
  4866  * @param versionString
  4867  *   a string of the form "#.#", where # is an non-negative decimal integer with
  4868  *   or without leading zeros
  4869  * @throws
  4870  *   if versionString does not specify a valid HTTP version number
  4871  */
  4872 function nsHttpVersion(versionString)
  4874   var matches = /^(\d+)\.(\d+)$/.exec(versionString);
  4875   if (!matches)
  4876     throw "Not a valid HTTP version!";
  4878   /** The major version number of this, as a number. */
  4879   this.major = parseInt(matches[1], 10);
  4881   /** The minor version number of this, as a number. */
  4882   this.minor = parseInt(matches[2], 10);
  4884   if (isNaN(this.major) || isNaN(this.minor) ||
  4885       this.major < 0    || this.minor < 0)
  4886     throw "Not a valid HTTP version!";
  4888 nsHttpVersion.prototype =
  4890   /**
  4891    * Returns the standard string representation of the HTTP version represented
  4892    * by this (e.g., "1.1").
  4893    */
  4894   toString: function ()
  4896     return this.major + "." + this.minor;
  4897   },
  4899   /**
  4900    * Returns true if this represents the same HTTP version as otherVersion,
  4901    * false otherwise.
  4903    * @param otherVersion : nsHttpVersion
  4904    *   the version to compare against this
  4905    */
  4906   equals: function (otherVersion)
  4908     return this.major == otherVersion.major &&
  4909            this.minor == otherVersion.minor;
  4910   },
  4912   /** True if this >= otherVersion, false otherwise. */
  4913   atLeast: function(otherVersion)
  4915     return this.major > otherVersion.major ||
  4916            (this.major == otherVersion.major &&
  4917             this.minor >= otherVersion.minor);
  4919 };
  4921 nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0");
  4922 nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1");
  4925 /**
  4926  * An object which stores HTTP headers for a request or response.
  4928  * Note that since headers are case-insensitive, this object converts headers to
  4929  * lowercase before storing them.  This allows the getHeader and hasHeader
  4930  * methods to work correctly for any case of a header, but it means that the
  4931  * values returned by .enumerator may not be equal case-sensitively to the
  4932  * values passed to setHeader when adding headers to this.
  4933  */
  4934 function nsHttpHeaders()
  4936   /**
  4937    * A hash of headers, with header field names as the keys and header field
  4938    * values as the values.  Header field names are case-insensitive, but upon
  4939    * insertion here they are converted to lowercase.  Header field values are
  4940    * normalized upon insertion to contain no leading or trailing whitespace.
  4942    * Note also that per RFC 2616, section 4.2, two headers with the same name in
  4943    * a message may be treated as one header with the same field name and a field
  4944    * value consisting of the separate field values joined together with a "," in
  4945    * their original order.  This hash stores multiple headers with the same name
  4946    * in this manner.
  4947    */
  4948   this._headers = {};
  4950 nsHttpHeaders.prototype =
  4952   /**
  4953    * Sets the header represented by name and value in this.
  4955    * @param name : string
  4956    *   the header name
  4957    * @param value : string
  4958    *   the header value
  4959    * @throws NS_ERROR_INVALID_ARG
  4960    *   if name or value is not a valid header component
  4961    */
  4962   setHeader: function(fieldName, fieldValue, merge)
  4964     var name = headerUtils.normalizeFieldName(fieldName);
  4965     var value = headerUtils.normalizeFieldValue(fieldValue);
  4967     // The following three headers are stored as arrays because their real-world
  4968     // syntax prevents joining individual headers into a single header using 
  4969     // ",".  See also <http://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
  4970     if (merge && name in this._headers)
  4972       if (name === "www-authenticate" ||
  4973           name === "proxy-authenticate" ||
  4974           name === "set-cookie") 
  4976         this._headers[name].push(value);
  4978       else 
  4980         this._headers[name][0] += "," + value;
  4981         NS_ASSERT(this._headers[name].length === 1,
  4982             "how'd a non-special header have multiple values?")
  4985     else
  4987       this._headers[name] = [value];
  4989   },
  4991   /**
  4992    * Returns the value for the header specified by this.
  4994    * @throws NS_ERROR_INVALID_ARG
  4995    *   if fieldName does not constitute a valid header field name
  4996    * @throws NS_ERROR_NOT_AVAILABLE
  4997    *   if the given header does not exist in this
  4998    * @returns string
  4999    *   the field value for the given header, possibly with non-semantic changes
  5000    *   (i.e., leading/trailing whitespace stripped, whitespace runs replaced
  5001    *   with spaces, etc.) at the option of the implementation; multiple 
  5002    *   instances of the header will be combined with a comma, except for 
  5003    *   the three headers noted in the description of getHeaderValues
  5004    */
  5005   getHeader: function(fieldName)
  5007     return this.getHeaderValues(fieldName).join("\n");
  5008   },
  5010   /**
  5011    * Returns the value for the header specified by fieldName as an array.
  5013    * @throws NS_ERROR_INVALID_ARG
  5014    *   if fieldName does not constitute a valid header field name
  5015    * @throws NS_ERROR_NOT_AVAILABLE
  5016    *   if the given header does not exist in this
  5017    * @returns [string]
  5018    *   an array of all the header values in this for the given
  5019    *   header name.  Header values will generally be collapsed
  5020    *   into a single header by joining all header values together
  5021    *   with commas, but certain headers (Proxy-Authenticate,
  5022    *   WWW-Authenticate, and Set-Cookie) violate the HTTP spec
  5023    *   and cannot be collapsed in this manner.  For these headers
  5024    *   only, the returned array may contain multiple elements if
  5025    *   that header has been added more than once.
  5026    */
  5027   getHeaderValues: function(fieldName)
  5029     var name = headerUtils.normalizeFieldName(fieldName);
  5031     if (name in this._headers)
  5032       return this._headers[name];
  5033     else
  5034       throw Cr.NS_ERROR_NOT_AVAILABLE;
  5035   },
  5037   /**
  5038    * Returns true if a header with the given field name exists in this, false
  5039    * otherwise.
  5041    * @param fieldName : string
  5042    *   the field name whose existence is to be determined in this
  5043    * @throws NS_ERROR_INVALID_ARG
  5044    *   if fieldName does not constitute a valid header field name
  5045    * @returns boolean
  5046    *   true if the header's present, false otherwise
  5047    */
  5048   hasHeader: function(fieldName)
  5050     var name = headerUtils.normalizeFieldName(fieldName);
  5051     return (name in this._headers);
  5052   },
  5054   /**
  5055    * Returns a new enumerator over the field names of the headers in this, as
  5056    * nsISupportsStrings.  The names returned will be in lowercase, regardless of
  5057    * how they were input using setHeader (header names are case-insensitive per
  5058    * RFC 2616).
  5059    */
  5060   get enumerator()
  5062     var headers = [];
  5063     for (var i in this._headers)
  5065       var supports = new SupportsString();
  5066       supports.data = i;
  5067       headers.push(supports);
  5070     return new nsSimpleEnumerator(headers);
  5072 };
  5075 /**
  5076  * Constructs an nsISimpleEnumerator for the given array of items.
  5078  * @param items : Array
  5079  *   the items, which must all implement nsISupports
  5080  */
  5081 function nsSimpleEnumerator(items)
  5083   this._items = items;
  5084   this._nextIndex = 0;
  5086 nsSimpleEnumerator.prototype =
  5088   hasMoreElements: function()
  5090     return this._nextIndex < this._items.length;
  5091   },
  5092   getNext: function()
  5094     if (!this.hasMoreElements())
  5095       throw Cr.NS_ERROR_NOT_AVAILABLE;
  5097     return this._items[this._nextIndex++];
  5098   },
  5099   QueryInterface: function(aIID)
  5101     if (Ci.nsISimpleEnumerator.equals(aIID) ||
  5102         Ci.nsISupports.equals(aIID))
  5103       return this;
  5105     throw Cr.NS_ERROR_NO_INTERFACE;
  5107 };
  5110 /**
  5111  * A representation of the data in an HTTP request.
  5113  * @param port : uint
  5114  *   the port on which the server receiving this request runs
  5115  */
  5116 function Request(port)
  5118   /** Method of this request, e.g. GET or POST. */
  5119   this._method = "";
  5121   /** Path of the requested resource; empty paths are converted to '/'. */
  5122   this._path = "";
  5124   /** Query string, if any, associated with this request (not including '?'). */
  5125   this._queryString = "";
  5127   /** Scheme of requested resource, usually http, always lowercase. */
  5128   this._scheme = "http";
  5130   /** Hostname on which the requested resource resides. */
  5131   this._host = undefined;
  5133   /** Port number over which the request was received. */
  5134   this._port = port;
  5136   var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);
  5138   /** Stream from which data in this request's body may be read. */
  5139   this._bodyInputStream = bodyPipe.inputStream;
  5141   /** Stream to which data in this request's body is written. */
  5142   this._bodyOutputStream = bodyPipe.outputStream;
  5144   /**
  5145    * The headers in this request.
  5146    */
  5147   this._headers = new nsHttpHeaders();
  5149   /**
  5150    * For the addition of ad-hoc properties and new functionality without having
  5151    * to change nsIHttpRequest every time; currently lazily created, as its only
  5152    * use is in directory listings.
  5153    */
  5154   this._bag = null;
  5156 Request.prototype =
  5158   // SERVER METADATA
  5160   //
  5161   // see nsIHttpRequest.scheme
  5162   //
  5163   get scheme()
  5165     return this._scheme;
  5166   },
  5168   //
  5169   // see nsIHttpRequest.host
  5170   //
  5171   get host()
  5173     return this._host;
  5174   },
  5176   //
  5177   // see nsIHttpRequest.port
  5178   //
  5179   get port()
  5181     return this._port;
  5182   },
  5184   // REQUEST LINE
  5186   //
  5187   // see nsIHttpRequest.method
  5188   //
  5189   get method()
  5191     return this._method;
  5192   },
  5194   //
  5195   // see nsIHttpRequest.httpVersion
  5196   //
  5197   get httpVersion()
  5199     return this._httpVersion.toString();
  5200   },
  5202   //
  5203   // see nsIHttpRequest.path
  5204   //
  5205   get path()
  5207     return this._path;
  5208   },
  5210   //
  5211   // see nsIHttpRequest.queryString
  5212   //
  5213   get queryString()
  5215     return this._queryString;
  5216   },
  5218   // HEADERS
  5220   //
  5221   // see nsIHttpRequest.getHeader
  5222   //
  5223   getHeader: function(name)
  5225     return this._headers.getHeader(name);
  5226   },
  5228   //
  5229   // see nsIHttpRequest.hasHeader
  5230   //
  5231   hasHeader: function(name)
  5233     return this._headers.hasHeader(name);
  5234   },
  5236   //
  5237   // see nsIHttpRequest.headers
  5238   //
  5239   get headers()
  5241     return this._headers.enumerator;
  5242   },
  5244   //
  5245   // see nsIPropertyBag.enumerator
  5246   //
  5247   get enumerator()
  5249     this._ensurePropertyBag();
  5250     return this._bag.enumerator;
  5251   },
  5253   //
  5254   // see nsIHttpRequest.headers
  5255   //
  5256   get bodyInputStream()
  5258     return this._bodyInputStream;
  5259   },
  5261   //
  5262   // see nsIPropertyBag.getProperty
  5263   //
  5264   getProperty: function(name) 
  5266     this._ensurePropertyBag();
  5267     return this._bag.getProperty(name);
  5268   },
  5271   // NSISUPPORTS
  5273   //
  5274   // see nsISupports.QueryInterface
  5275   //
  5276   QueryInterface: function(iid)
  5278     if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports))
  5279       return this;
  5281     throw Cr.NS_ERROR_NO_INTERFACE;
  5282   },
  5285   // PRIVATE IMPLEMENTATION
  5287   /** Ensures a property bag has been created for ad-hoc behaviors. */
  5288   _ensurePropertyBag: function()
  5290     if (!this._bag)
  5291       this._bag = new WritablePropertyBag();
  5293 };
  5296 // XPCOM trappings
  5298 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
  5300 /**
  5301  * Creates a new HTTP server listening for loopback traffic on the given port,
  5302  * starts it, and runs the server until the server processes a shutdown request,
  5303  * spinning an event loop so that events posted by the server's socket are
  5304  * processed.
  5306  * This method is primarily intended for use in running this script from within
  5307  * xpcshell and running a functional HTTP server without having to deal with
  5308  * non-essential details.
  5310  * Note that running multiple servers using variants of this method probably
  5311  * doesn't work, simply due to how the internal event loop is spun and stopped.
  5313  * @note
  5314  *   This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code);
  5315  *   you should use this server as a component in Mozilla 1.8.
  5316  * @param port
  5317  *   the port on which the server will run, or -1 if there exists no preference
  5318  *   for a specific port; note that attempting to use some values for this
  5319  *   parameter (particularly those below 1024) may cause this method to throw or
  5320  *   may result in the server being prematurely shut down
  5321  * @param basePath
  5322  *   a local directory from which requests will be served (i.e., if this is
  5323  *   "/home/jwalden/" then a request to /index.html will load
  5324  *   /home/jwalden/index.html); if this is omitted, only the default URLs in
  5325  *   this server implementation will be functional
  5326  */
  5327 function server(port, basePath)
  5329   if (basePath)
  5331     var lp = Cc["@mozilla.org/file/local;1"]
  5332                .createInstance(Ci.nsILocalFile);
  5333     lp.initWithPath(basePath);
  5336   // if you're running this, you probably want to see debugging info
  5337   DEBUG = true;
  5339   var srv = new nsHttpServer();
  5340   if (lp)
  5341     srv.registerDirectory("/", lp);
  5342   srv.registerContentType("sjs", SJS_TYPE);
  5343   srv.identity.setPrimary("http", "localhost", port);
  5344   srv.start(port);
  5346   var thread = gThreadManager.currentThread;
  5347   while (!srv.isStopped())
  5348     thread.processNextEvent(true);
  5350   // get rid of any pending requests
  5351   while (thread.hasPendingEvents())
  5352     thread.processNextEvent(true);
  5354   DEBUG = false;

mercurial