All pastes #1366389 Raw Edit

httpserv

public diff v1 · immutable
#1366389 ·published 2009-03-20 18:55 UTC
rendered paste body
From 5155173e26fc11081232930c4f95a352ee619ad8 Mon Sep 17 00:00:00 2001From: Mook <mook@songbirdnest.com>Date: Wed, 11 Mar 2009 17:04:42 -0700Subject: [PATCH] initial drop of http server note that httpd.js has been slightly modified from the upstream version (again) :|--- components/remoteapi/src/sbRemotePlayer.cpp        |   30 +- extensions/httpserv/Makefile.in                    |   53 + extensions/httpserv/components/Makefile.in         |   38 + extensions/httpserv/components/public/Makefile.in  |   47 + .../httpserv/components/public/nsIHttpServer.idl   |  383 +++ extensions/httpserv/components/src/Makefile.in     |   52 + extensions/httpserv/components/src/httpd.js        | 3431 ++++++++++++++++++++ extensions/httpserv/components/src/sbhttpserv.js   |  232 ++ extensions/httpserv/defaults/Makefile.in           |   36 + .../httpserv/defaults/preferences/Makefile.in      |   45 + extensions/httpserv/defaults/preferences/prefs.js  |    2 + extensions/httpserv/install.rdf.in                 |   30 + 12 files changed, 4371 insertions(+), 8 deletions(-)diff --git components/remoteapi/src/sbRemotePlayer.cpp components/remoteapi/src/sbRemotePlayer.cppindex 86561e0..8a01905 100644--- components/remoteapi/src/sbRemotePlayer.cpp+++ components/remoteapi/src/sbRemotePlayer.cpp@@ -61,20 +61,22 @@ #include <sbStandardProperties.h> #include <sbIPropertyBuilder.h>  #include <nsAutoPtr.h> #include <nsDOMJSUtils.h> #include <nsIArray.h> #include <nsICategoryManager.h> #include <nsIContent.h>+#include <nsIContentViewer.h> #include <nsIDocShell.h> #include <nsIDocShellTreeItem.h> #include <nsIDocShellTreeOwner.h> #include <nsIDocument.h>+#include <nsIDocumentViewer.h> #include <nsIDOMDocument.h> #include <nsIDOMDocumentEvent.h> #include <nsIDOMElement.h> #include <nsIDOMEvent.h> #include <nsIDOMEventTarget.h> #include <nsIDOMMouseEvent.h> #include <nsIDOMNodeList.h> #include <nsIDOMNSEvent.h>@@ -474,24 +476,36 @@ sbRemotePlayer::InitInternal(nsPIDOMWindow* aWindow)   //   // Get the Chrome Document   //   nsIDocShell *docShell = mPrivWindow->GetDocShell();   NS_ENSURE_STATE(docShell);    // Step up to the chrome layer through the event handler   nsCOMPtr<nsIDOMEventTarget> chromeEventHandler;-  docShell->GetChromeEventHandler( getter_AddRefs(chromeEventHandler) );-  NS_ENSURE_STATE(chromeEventHandler);-  nsCOMPtr<nsIContent> chromeElement(-                                do_QueryInterface( chromeEventHandler, &rv ) );-  NS_ENSURE_SUCCESS( rv, rv );--  // Get the document from the chrome content-  nsIDocument *doc = chromeElement->GetDocument();+  rv = docShell->GetChromeEventHandler( getter_AddRefs(chromeEventHandler) );+  NS_ENSURE_SUCCESS(rv, rv);+  +  nsCOMPtr<nsIDocument> doc;+  if (chromeEventHandler) {+    nsCOMPtr<nsIContent> chromeElement(+                                  do_QueryInterface( chromeEventHandler, &rv ) );+    NS_ENSURE_SUCCESS( rv, rv );+  +    // Get the document from the chrome content+    doc = chromeElement->GetDocument();+  } else {+    nsCOMPtr<nsIContentViewer> cv;+    rv = docShell->GetContentViewer(getter_AddRefs(cv));+    NS_ENSURE_SUCCESS(rv, rv);+    nsCOMPtr<nsIDocumentViewer> dv = do_QueryInterface(cv, &rv);+    NS_ENSURE_SUCCESS(rv, rv);+    rv = dv->GetDocument(getter_AddRefs(doc));+    NS_ENSURE_SUCCESS(rv, rv);+  }   NS_ENSURE_STATE(doc);    mChromeDoc = do_QueryInterface( doc, &rv );   NS_ENSURE_SUCCESS( rv, rv );    LOG(("sbRemotePlayer::Init() -- registering unload listener"));   // Have our HandleEvent called on page unloads, so we can unhook commands   nsCOMPtr<nsIDOMEventTarget> eventTarget( do_QueryInterface(mChromeDoc) );diff --git extensions/httpserv/Makefile.in extensions/httpserv/Makefile.innew file mode 100644index 0000000..22a00da--- /dev/null+++ extensions/httpserv/Makefile.in@@ -0,0 +1,53 @@+#+# Copyright (c) 2008, Pioneers of the Inevitable, Inc.+# All rights reserved.+# +# Redistribution and use in source and binary forms, with or without+# modification, are permitted provided that the following conditions are met:+# +# 	* Redistributions of source code must retain the above copyright notice,+# 	  this list of conditions and the following disclaimer.+# 	* Redistributions in binary form must reproduce the above copyright notice,+# 	  this list of conditions and the following disclaimer in the documentation +# 	  and/or other materials provided with the distribution.+# 	* Neither the name of Pioneers of the Inevitable, Songbird, nor the names+# 	  of its contributors may be used to endorse or promote products derived+# 	  from this software without specific prior written permission.+# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+# ++DEPTH     = ../..+topsrcdir = @top_srcdir@+srcdir    = @srcdir@+VPATH     = @srcdir@++include $(DEPTH)/build/autodefs.mk++SUBDIRS = components defaults++EXTENSION_NAME = httpserv+EXTENSION_UUID = $(EXTENSION_NAME)@songbirdnest.com+EXTENSION_STAGE_DIR = ./.xpistage+EXTENSION_BUILD_ID = $(EXTENSION_NAME)-$(EXTENSION_VER)++EXTENSION_VER := 0.0.0.20090310+EXTENSION_MIN_VER := 1.2.0a+EXTENSION_MAX_VER := 1.2.0b1++XPI_NAME = $(EXTENSION_BUILD_ID)++ifdef DEBUG+INSTALL_EXTENSION = 1+endif++include $(topsrcdir)/build/rules.mkdiff --git extensions/httpserv/components/Makefile.in extensions/httpserv/components/Makefile.innew file mode 100644index 0000000..3e3c16f--- /dev/null+++ extensions/httpserv/components/Makefile.in@@ -0,0 +1,38 @@+#+# BEGIN SONGBIRD GPL+#+# This file is part of the Songbird web player.+#+# Copyright(c) 2008 POTI, Inc.+# http://songbirdnest.com+#+# This file may be licensed under the terms of of the+# GNU General Public License Version 2 (the "GPL").+#+# Software distributed under the License is distributed+# on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either+# express or implied. See the GPL for the specific language+# governing rights and limitations.+#+# You should have received a copy of the GPL along with this+# program. If not, go to http://www.gnu.org/licenses/gpl.html+# or write to the Free Software Foundation, Inc.,+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.+#+# END SONGBIRD GPL+#++DEPTH = ../../..+topsrcdir = @top_srcdir@+srcdir = @srcdir@+VPATH = @srcdir@++include $(DEPTH)/build/autodefs.mk++SUBDIRS = public \+          src \+          $(NULL)++SUBDIRDEPS = src : public++include $(topsrcdir)/build/rules.mkdiff --git extensions/httpserv/components/public/Makefile.in extensions/httpserv/components/public/Makefile.innew file mode 100644index 0000000..43acb34--- /dev/null+++ extensions/httpserv/components/public/Makefile.in@@ -0,0 +1,47 @@+#+# BEGIN SONGBIRD GPL+#+# This file is part of the Songbird web player.+#+# Copyright(c) 2005-2008 POTI, Inc.+# http://songbirdnest.com+#+# This file may be licensed under the terms of of the+# GNU General Public License Version 2 (the "GPL").+#+# Software distributed under the License is distributed+# on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either+# express or implied. See the GPL for the specific language+# governing rights and limitations.+#+# You should have received a copy of the GPL along with this+# program. If not, go to http://www.gnu.org/licenses/gpl.html+# or write to the Free Software Foundation, Inc.,+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.+#+# END SONGBIRD GPL+#++DEPTH = ../../../..+topsrcdir = @top_srcdir@+srcdir = @srcdir@+VPATH = @srcdir@++include $(DEPTH)/build/autodefs.mk++# compile these IDL files+XPIDL_SRCS = nsIHttpServer.idl \+             $(NULL)++# into this xpt (an xpt is like a compiled IDL read at runtime)+XPIDL_MODULE = httpd.xpt+XPIDL_MODULE_TYPELIBS = $(XPIDL_SRCS:.idl=.xpt)++# and put it into the extension+EXTENSION_STAGE_DIR = ../../.xpistage++# distribute the xpt into the components directory+SONGBIRD_COMPONENTS = $(XPIDL_MODULE)++include $(topsrcdir)/build/rules.mk+diff --git extensions/httpserv/components/public/nsIHttpServer.idl extensions/httpserv/components/public/nsIHttpServer.idlnew file mode 100644index 0000000..417ddf6--- /dev/null+++ extensions/httpserv/components/public/nsIHttpServer.idl@@ -0,0 +1,383 @@+/* ***** BEGIN LICENSE BLOCK *****+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1+ *+ * The contents of this file are subject to the Mozilla Public License Version+ * 1.1 (the "License"); you may not use this file except in compliance with+ * the License. You may obtain a copy of the License at+ * http://www.mozilla.org/MPL/+ *+ * Software distributed under the License is distributed on an "AS IS" basis,+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License+ * for the specific language governing rights and limitations under the+ * License.+ *+ * The Original Code is the MozJSHTTP server.+ *+ * The Initial Developer of the Original Code is+ * Mozilla Corporation.+ * Portions created by the Initial Developer are Copyright (C) 2006+ * the Initial Developer. All Rights Reserved.+ *+ * Contributor(s):+ *   Jeff Walden <jwalden+code@mit.edu>          (original author)+ *   Robert Sayre <sayrer@gmail.com>+ *+ * Alternatively, the contents of this file may be used under the terms of+ * either the GNU General Public License Version 2 or later (the "GPL"), or+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),+ * in which case the provisions of the GPL or the LGPL are applicable instead+ * of those above. If you wish to allow use of your version of this file only+ * under the terms of either the GPL or the LGPL, and not to allow others to+ * use your version of this file under the terms of the MPL, indicate your+ * decision by deleting the provisions above and replace them with the notice+ * and other provisions required by the GPL or the LGPL. If you do not delete+ * the provisions above, a recipient may use your version of this file under+ * the terms of any one of the MPL, the GPL or the LGPL.+ *+ * ***** END LICENSE BLOCK ***** */++#include "nsIServerSocket.idl"+#include "nsIPropertyBag.idl"++interface nsILocalFile;+interface nsISimpleEnumerator;+interface nsIOutputStream;++interface nsIHttpServer;+interface nsIHttpRequestHandler;+interface nsIHttpRequestMetadata;+interface nsIHttpResponse;++/**+ * An interface which represents an HTTP server.+ */+[scriptable, uuid(5520f79e-ecd5-4c40-843b-97ee13a23747)]+interface nsIHttpServer : nsIServerSocketListener+{+  /**+   * Starts up this server, listening upon the given port.  This method may+   * throw if the process does not have sufficient privileges to open a socket+   * for the given port, and it also throws when called upon a server which has+   * already been started.+   *+   * @param port+   *   the port upon which listening should happen, or -1 if no specific port is+   *   desired+   */+  void start(in long port);++  /**+   * Shuts down this server if it is running; if it is not, this method is a+   * no-op.+   *+   * This method will do its best to return after the socket in this+   * server has been closed and all pending requests have completed being+   * served, but this may or may not actually happen, since in some+   * implementations this may not actually be possible.  Implementations which+   * can make this promise should make it explicit in implementation+   * documentation.+   */+  void stop();++  /**+   * Associates the local file represented by the string file with all requests+   * which match request.+   *+   * @param path+   *   the path which is to be mapped to the given file; must begin with "/" and+   *   be a valid URI path (i.e., no query string, hash reference, etc.)+   * @param file+   *   the file to serve for the given path, or null to remove any mapping that+   *   might exist; this file must exist for the lifetime of the server+   */+  void registerFile(in string path, in nsILocalFile file);++  /**+   * Registers a custom path handler.+   *+   * @param path+   *   the path on the server (beginning with a "/") which is to be handled by+   *   handler; this path must not include a query string or hash component; it+   *   also should usually be canonicalized, since most browsers will do so+   *   before sending otherwise-matching requests+   * @param handler+   *   an object which will handle any requests for the given path, or null to+   *   remove any existing handler; if while the server is running the handler+   *   throws an exception while responding to a request, an HTTP 500 response+   *   will be returned+   * @throws NS_ERROR_INVALID_ARG+   *   if path does not begin with a "/"+   */+  void registerPathHandler(in string path, in nsIHttpRequestHandler handler);++  /**+   * Registers a custom prefix handler.+   *+   * @param prefix+   *   the path on the server (beginning and ending with "/") which is to be+   *   handled by handler; this path must not include a query string or hash+   *   component. All requests that start with this prefix will be directed to+   *   the given handler.+   * @param handler+   *   an object which will handle any requests for the given path, or null to+   *   remove any existing handler; if while the server is running the handler+   *   throws an exception while responding to a request, an HTTP 500 response+   *   will be returned+   * @throws NS_ERROR_INVALID_ARG+   *   if path does not begin with a "/" or does not end with a "/"+   */+  void registerPrefixHandler(in string prefix, in nsIHttpRequestHandler handler);++  /**+   * Registers a custom error page handler.+   *+   * @param code+   *   the error code which is to be handled by handler+   * @param handler+   *   an object which will handle any requests which generate the given status+   *   code, or null to remove any existing handler.  If the handler throws an+   *   exception during server operation, fallback is to the genericized error+   *   handler (the x00 version), then to 500, using a user-defined error+   *   handler if one exists or the server default handler otherwise.  Fallback+   *   will never occur from a user-provided handler that throws to the same+   *   handler as provided by the server, e.g. a throwing user 404 falls back to+   *   400, not a server-provided 404 that might not throw.+   * @note+   *   If the error handler handles HTTP 500 and throws, behavior is undefined.+   */+  void registerErrorHandler(in unsigned long code, in nsIHttpRequestHandler handler);++  /**+   * Maps all requests to paths beneath path to the corresponding file beneath+   * dir.+   *+   * @param path+   *   the absolute path on the server against which requests will be served+   *   from dir (e.g., "/", "/foo/", etc.); must begin and end with a forward+   *   slash+   * @param dir+   *   the directory to be used to serve all requests for paths underneath path+   *   (except those further overridden by another, deeper path registered with+   *   another directory); if null, any current mapping for the given path is+   *   removed+   * @throws NS_ERROR_INVALID_ARG+   *   if dir is non-null and does not exist or is not a directory, or if path+   *   does not begin with and end with a forward slash+   */+  void registerDirectory(in string path, in nsILocalFile dir);++  /**+   * Associates files with the given extension with the given Content-Type when+   * served by this server, in the absence of any file-specific information+   * about the desired Content-Type.  If type is empty, removes any extant+   * mapping, if one is present.+   *+   * @throws NS_ERROR_INVALID_ARG+   *   if the given type is not a valid header field value, i.e. if it doesn't+   *   match the field-value production in RFC 2616+   * @note+   *   No syntax checking is done of the given type, beyond ensuring that it is+   *   a valid header field value.  Behavior when not given a string matching+   *   the media-type production in RFC 2616 section 3.7 is undefined.+   *   Implementations may choose to define specific behavior for types which do+   *   not match the production, such as for CGI functionality.+   * @note+   *   Implementations MAY treat type as a trusted argument; users who fail to+   *   generate this string from trusted data risk security vulnerabilities.+   */+  void registerContentType(in string extension, in string type);++  /**+   * Sets the handler used to display the contents of a directory if+   * the directory contains no index page.+   *+   * @param handler+   *   an object which will handle any requests for directories which+   *   do not contain index pages, or null to reset to the default+   *   index handler; if while the server is running the handler+   *   throws an exception while responding to a request, an HTTP 500+   *   response will be returned.  An nsIFile corresponding to the+   *   directory is available from the metadata object passed to the+   *   handler, under the key "directory".+   */+  void setIndexHandler(in nsIHttpRequestHandler handler);+};++/**+ * A representation of a handler for HTTP requests.  The handler is used by+ * calling its .handle method with data for an incoming request; it is the+ * handler's job to use that data as it sees fit to make the desired response.+ *+ * @note+ *   This interface uses the [function] attribute, so you can pass a+ *   script-defined function with the functionality of handle() to any+ *   method which has a nsIHttpRequestHandler parameter, instead of wrapping+ *   it in an otherwise empty object.+ */+[scriptable, function, uuid(2bbb4db7-d285-42b3-a3ce-142b8cc7e139)]+interface nsIHttpRequestHandler : nsISupports+{+  /**+   * Processes the HTTP request represented by metadata and initializes the+   * passed-in response to reflect the correct HTTP response.+   *+   * Note that in some uses of nsIHttpRequestHandler, this method is required to+   * not throw an exception; in the general case, however, this method may throw+   * an exception (causing an HTTP 500 response to occur).+   *+   * @param metadata+   *   data representing an HTTP request+   * @param response+   *   an initially-empty response which must be modified to reflect the data+   *   which should be sent as the response to the request described by metadata+   */+  void handle(in nsIHttpRequestMetadata metadata, in nsIHttpResponse response);+};+++/**+ * A representation of the data included in an HTTP request.+ */+[scriptable, uuid(3a899b17-b6eb-4333-8ef4-912df454a551)]+interface nsIHttpRequestMetadata : nsIPropertyBag+{+  /**+   * The request type for this request (see RFC 2616, section 5.1.1).+   */+  readonly attribute string method;+  +  /**+   * The host of the data being requested (e.g. "localhost" for the+   * http://localhost:8080/file resource).  Note that the relevant port on the+   * host is specified in this.port.+   */+  readonly attribute string host;++  /**+   * The port on the server on which the request was received.+   */+  readonly attribute unsigned long port;++  /**+   * The requested path, without any query string (e.g. "/dir/file.txt").  It is+   * guaranteed to begin with a "/".  This string is in the +   */+  readonly attribute string path;++  /**+   * The URL-encoded query string associated with this request, not including+   * the initial "?".+   */+  readonly attribute string queryString;++  /**+   * A string containing the HTTP version of the request (i.e., "1.1").  Leading+   * zeros for either component of the version will be omitted.  (In other+   * words, if the request contains the version "1.01", this attribute will be+   * "1.1"; see RFC 2616, section 3.1.)+   */+  readonly attribute string httpVersion;++  /**+   * Returns the value for the header in this request specified by fieldName.+   *+   * @param fieldName+   *   the name of the field whose value is to be gotten; note that since HTTP+   *   header field names are case-insensitive, this method produces equivalent+   *   results for "HeAdER" and "hEADer" as fieldName+   * @returns+   *   the field value for the given header; note that this value may be+   *   normalized (e.g., leading/trailing whitespace removed from the value [or+   *   from the values which make this up, if the header is a comma-separated+   *   list of values], whitespace runs compressed to single spaces, etc.)+   * @throws NS_ERROR_INVALID_ARG+   *   if fieldName does not constitute a valid header field name+   * @throws NS_ERROR_NOT_AVAILABLE+   *   if the given header does not exist in this+   */+  string getHeader(in string fieldName);++  /**+   * Returns true if a header with the given field name exists in this, false+   * otherwise.+   *+   * @param fieldName+   *   the field name whose existence is to be determined in this; note that+   *   since HTTP header field names are case-insensitive, this method produces+   *   equivalent results for "HeAdER" and "hEADer" as fieldName+   * @throws NS_ERROR_INVALID_ARG+   *   if fieldName does not constitute a valid header field name+   */+  boolean hasHeader(in string fieldName);++  /**+   * An nsISimpleEnumerator of nsISupportsStrings over the names of the headers+   * in this request.  The header field names in the enumerator may not+   * necessarily have the same case as they do in the request itself.+   */+  readonly attribute nsISimpleEnumerator headers;++  // XXX expose request body here!+};+++/**+ * Represents an HTTP response, as described in RFC 2616, section 6.+ */+[scriptable, uuid(a2aaaff7-03bd-43b6-b460-94671e288093)]+interface nsIHttpResponse : nsISupports+{+  /**+   * Sets the status line for this.  If this method is never called on this, the+   * status line defaults to "HTTP/", followed by the server's default HTTP+   * version (e.g. "1.1"), followed by " 200 OK".+   *+   * @param httpVersion+   *   the HTTP version of this, as a string (e.g. "1.1"); if null, the server+   *   default is used+   * @param code+   *   the numeric HTTP status code for this+   * @param description+   *   a human-readable description of code; may be null if no description is+   *   desired+   * @throws NS_ERROR_INVALID_ARG+   *   if httpVersion is not a valid HTTP version string, statusCode is greater+   *   than 999, or description contains invalid characters+   */+  void setStatusLine(in string httpVersion,+                     in unsigned short statusCode,+                     in string description);++  /**+   * Sets the specified header in this.+   *+   * @param name+   *   the name of the header; must match the field-name production per RFC 2616+   * @param value+   *   the value of the header; must match the field-value production per RFC+   *   2616+   * @param merge+   *   when true, if the given header already exists in this, the values passed+   *   to this function will be merged into the existing header, per RFC 2616+   *   header semantics; when false, if the given header already exists in this,+   *   it is overwritten with the passed-in values; if the header doesn't exist+   *   in this, it is set regardless of the value of this parameter+   * @throws NS_ERROR_INVALID_ARG+   *   if name or value is not a valid header component+   */+  void setHeader(in string name, in string value, in boolean merge);++  /**+   * A stream to which data appearing in the body of this response should be+   * written.+   */+  readonly attribute nsIOutputStream bodyOutputStream;++  /**+   * Write a string to the response's output stream.+   *+   * @note+   *   This method is only guaranteed to work with ASCII data.+   */+  void write(in string data);+};diff --git extensions/httpserv/components/src/Makefile.in extensions/httpserv/components/src/Makefile.innew file mode 100644index 0000000..48b6f10--- /dev/null+++ extensions/httpserv/components/src/Makefile.in@@ -0,0 +1,52 @@+#+# BEGIN SONGBIRD GPL+#+# This file is part of the Songbird web player.+#+# Copyright(c) 2008 POTI, Inc.+# http://songbirdnest.com+#+# This file may be licensed under the terms of of the+# GNU General Public License Version 2 (the "GPL").+#+# Software distributed under the License is distributed+# on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either+# express or implied. See the GPL for the specific language+# governing rights and limitations.+#+# You should have received a copy of the GPL along with this+# program. If not, go to http://www.gnu.org/licenses/gpl.html+# or write to the Free Software Foundation, Inc.,+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.+#+# END SONGBIRD GPL+#+# The Original Code is MinimizeToTray.+# Contributor(s):+#   Mark Yen <mook.moz+cvs.mozilla.org@gmail.com>, Original author+#   Brad Peterson <b_peterson@yahoo.com>, Original author+#   Daniel Glazman <daniel.glazman@disruptive-innovations.com>+#   Matthew Gertner <matthew@allpeers.com>++DEPTH = ../../../..+topsrcdir = @top_srcdir@+srcdir = @srcdir@+VPATH = @srcdir@++include $(DEPTH)/build/autodefs.mk++# the name of our component+target_name = httpd++EXTRA_SCRIPT_COMPONENTS = $(srcdir)/httpd.js \+                          $(srcdir)/sbhttpserv.js \+                          $(NULL)++# put this in our staging directory+EXTENSION_STAGE_DIR = ../../.xpistage++# into components/ +SONGBIRD_COMPONENTS = $(EXTRA_SCRIPT_COMPONENTS)++include $(topsrcdir)/build/rules.mk+diff --git extensions/httpserv/components/src/httpd.js extensions/httpserv/components/src/httpd.jsnew file mode 100644index 0000000..7145012--- /dev/null+++ extensions/httpserv/components/src/httpd.js@@ -0,0 +1,3431 @@+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */+/* vim:set ts=2 sw=2 sts=2 et: */+/* ***** BEGIN LICENSE BLOCK *****+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1+ *+ * The contents of this file are subject to the Mozilla Public License Version+ * 1.1 (the "License"); you may not use this file except in compliance with+ * the License. You may obtain a copy of the License at+ * http://www.mozilla.org/MPL/+ *+ * Software distributed under the License is distributed on an "AS IS" basis,+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License+ * for the specific language governing rights and limitations under the+ * License.+ *+ * The Original Code is the MozJSHTTP server.+ *+ * The Initial Developer of the Original Code is+ * Mozilla Corporation.+ * Portions created by the Initial Developer are Copyright (C) 2006+ * the Initial Developer. All Rights Reserved.+ *+ * Contributor(s):+ *   Darin Fisher (v1, netwerk/test/TestServ.js)+ *   Christian Biesinger (v2, netwerk/test/unit/head_http_server.js)+ *   Jeff Walden <jwalden+code@mit.edu> (v3, netwerk/test/httpserver/httpd.js)+ *   Robert Sayre <sayrer@gmail.com>+ *+ * Alternatively, the contents of this file may be used under the terms of+ * either the GNU General Public License Version 2 or later (the "GPL"), or+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),+ * in which case the provisions of the GPL or the LGPL are applicable instead+ * of those above. If you wish to allow use of your version of this file only+ * under the terms of either the GPL or the LGPL, and not to allow others to+ * use your version of this file under the terms of the MPL, indicate your+ * decision by deleting the provisions above and replace them with the notice+ * and other provisions required by the GPL or the LGPL. If you do not delete+ * the provisions above, a recipient may use your version of this file under+ * the terms of any one of the MPL, the GPL or the LGPL.+ *+ * ***** END LICENSE BLOCK ***** */++/*+ * An implementation of an HTTP server both as a loadable script and as an XPCOM+ * component.  See the accompanying README file for user documentation on+ * MozJSHTTP.+ */++const Cc = Components.classes;+const Ci = Components.interfaces;+const Cr = Components.results;+const Cu = Components.utils;+const CC = Components.Constructor;++const EXPORTED_SYMBOLS = ["server", "nsHttpServer", "HttpError"];++/** True if debugging output is enabled, false otherwise. */+var DEBUG = false; // non-const *only* so tweakable in server tests++var gGlobalObject = this;++/**+ * Asserts that the given condition holds.  If it doesn't, the given message is+ * dumped, a stack trace is printed, and an exception is thrown to attempt to+ * stop execution (which unfortunately must rely upon the exception not being+ * accidentally swallowed by the code that uses it).+ */+function NS_ASSERT(cond, msg)+{+  if (DEBUG && !cond)+  {+    dumpn("###!!!");+    dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!"));+    dumpn("###!!! Stack follows:");++    var stack = new Error().stack.split(/\n/);+    dumpn(stack.map(function(val) { return "###!!!   " + val; }).join("\n"));+    +    throw Cr.NS_ERROR_ABORT;+  }+}++/** Constructs an HTTP error object. */+function HttpError(code, description)+{+  this.code = code;+  this.description = description;+}+HttpError.prototype =+{+  toString: function()+  {+    return this.code + " " + this.description;+  }+};++/**+ * Errors thrown to trigger specific HTTP server responses.+ */+const HTTP_400 = new HttpError(400, "Bad Request");+const HTTP_401 = new HttpError(401, "Unauthorized");+const HTTP_402 = new HttpError(402, "Payment Required");+const HTTP_403 = new HttpError(403, "Forbidden");+const HTTP_404 = new HttpError(404, "Not Found");+const HTTP_405 = new HttpError(405, "Method Not Allowed");+const HTTP_406 = new HttpError(406, "Not Acceptable");+const HTTP_407 = new HttpError(407, "Proxy Authentication Required");+const HTTP_408 = new HttpError(408, "Request Timeout");+const HTTP_409 = new HttpError(409, "Conflict");+const HTTP_410 = new HttpError(410, "Gone");+const HTTP_411 = new HttpError(411, "Length Required");+const HTTP_412 = new HttpError(412, "Precondition Failed");+const HTTP_413 = new HttpError(413, "Request Entity Too Large");+const HTTP_414 = new HttpError(414, "Request-URI Too Long");+const HTTP_415 = new HttpError(415, "Unsupported Media Type");+const HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable");+const HTTP_417 = new HttpError(417, "Expectation Failed");++const HTTP_500 = new HttpError(500, "Internal Server Error");+const HTTP_501 = new HttpError(501, "Not Implemented");+const HTTP_502 = new HttpError(502, "Bad Gateway");+const HTTP_503 = new HttpError(503, "Service Unavailable");+const HTTP_504 = new HttpError(504, "Gateway Timeout");+const HTTP_505 = new HttpError(505, "HTTP Version Not Supported");++/** Creates a hash with fields corresponding to the values in arr. */+function array2obj(arr)+{+  var obj = {};+  for (var i = 0; i < arr.length; i++)+    obj[arr[i]] = arr[i];+  return obj;+}++/** Returns an array of the integers x through y, inclusive. */+function range(x, y)+{+  var arr = [];+  for (var i = x; i <= y; i++)+    arr.push(i);+  return arr;+}++/** An object (hash) whose fields are the numbers of all HTTP error codes. */+const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505)));+++/**+ * The character used to distinguish hidden files from non-hidden files, a la+ * the leading dot in Apache.  Since that mechanism also hides files from+ * easy display in LXR, ls output, etc. however, we choose instead to use a+ * suffix character.  If a requested file ends with it, we append another+ * when getting the file on the server.  If it doesn't, we just look up that+ * file.  Therefore, any file whose name ends with exactly one of the character+ * is "hidden" and available for use by the server.+ */+const HIDDEN_CHAR = "^";++/**+ * The file name suffix indicating the file containing overridden headers for+ * a requested file.+ */+const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR;++/** Type used to denote SJS scripts for CGI-like functionality. */+const SJS_TYPE = "sjs";+++/** dump(str) with a trailing "\n" -- only outputs if DEBUG */+function dumpn(str)+{+  if (DEBUG)+    dump(str + "\n");+}++/** Dumps the current JS stack if DEBUG. */+function dumpStack()+{+  // peel off the frames for dumpStack() and Error()+  var stack = new Error().stack.split(/\n/).slice(2);+  stack.forEach(dumpn);+}+++/** The XPCOM thread manager. */+var gThreadManager = null;+++/**+ * JavaScript constructors for commonly-used classes; precreating these is a+ * speedup over doing the same from base principles.  See the docs at+ * http://developer.mozilla.org/en/docs/Components.Constructor for details.+ */+const ServerSocket = CC("@mozilla.org/network/server-socket;1",+                        "nsIServerSocket",+                        "init");+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",+                             "nsIBinaryInputStream",+                             "setInputStream");+const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",+                                 "nsIScriptableInputStream",+                                 "init");+const Pipe = CC("@mozilla.org/pipe;1",+                "nsIPipe",+                "init");+const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",+                           "nsIFileInputStream",+                           "init");+const StreamCopier = CC("@mozilla.org/network/async-stream-copier;1",+                        "nsIAsyncStreamCopier",+                        "init");+const Pump = CC("@mozilla.org/network/input-stream-pump;1",+                "nsIInputStreamPump",+                "init");+const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1",+                                "nsIConverterInputStream",+                                "init");+const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1",+                               "nsIWritablePropertyBag2");+const SupportsString = CC("@mozilla.org/supports-string;1",+                          "nsISupportsString");+++/**+ * Returns the RFC 822/1123 representation of a date.+ *+ * @param date : Number+ *   the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT+ * @returns string+ *   the representation of the given date+ */+function toDateString(date)+{+  //+  // rfc1123-date = wkday "," SP date1 SP time SP "GMT"+  // date1        = 2DIGIT SP month SP 4DIGIT+  //                ; day month year (e.g., 02 Jun 1982)+  // time         = 2DIGIT ":" 2DIGIT ":" 2DIGIT+  //                ; 00:00:00 - 23:59:59+  // wkday        = "Mon" | "Tue" | "Wed"+  //              | "Thu" | "Fri" | "Sat" | "Sun"+  // month        = "Jan" | "Feb" | "Mar" | "Apr"+  //              | "May" | "Jun" | "Jul" | "Aug"+  //              | "Sep" | "Oct" | "Nov" | "Dec"+  //++  const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];+  const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",+                        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];++  /**+   * Processes a date and returns the encoded UTC time as a string according to+   * the format specified in RFC 2616.+   *+   * @param date : Date+   *   the date to process+   * @returns string+   *   a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"+   */+  function toTime(date)+  {+    var hrs = date.getUTCHours();+    var rv  = (hrs < 10) ? "0" + hrs : hrs;+    +    var mins = date.getUTCMinutes();+    rv += ":";+    rv += (mins < 10) ? "0" + mins : mins;++    var secs = date.getUTCSeconds();+    rv += ":";+    rv += (secs < 10) ? "0" + secs : secs;++    return rv;+  }++  /**+   * Processes a date and returns the encoded UTC date as a string according to+   * the date1 format specified in RFC 2616.+   *+   * @param date : Date+   *   the date to process+   * @returns string+   *   a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"+   */+  function toDate1(date)+  {+    var day = date.getUTCDate();+    var month = date.getUTCMonth();+    var year = date.getUTCFullYear();++    var rv = (day < 10) ? "0" + day : day;+    rv += " " + monthStrings[month];+    rv += " " + year;++    return rv;+  }++  date = new Date(date);++  const fmtString = "%wkday%, %date1% %time% GMT";+  var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]);+  rv = rv.replace("%time%", toTime(date));+  return rv.replace("%date1%", toDate1(date));+}++/**+ * Prints out a human-readable representation of the object o and its fields,+ * omitting those whose names begin with "_" if showMembers != true (to ignore+ * "private" properties exposed via getters/setters).+ */+function printObj(o, showMembers)+{+  var s = "******************************\n";+  s +=    "o = {\n";+  for (var i in o)+  {+    if (typeof(i) != "string" ||+        (showMembers || (i.length > 0 && i[0] != "_")))+      s+= "      " + i + ": " + o[i] + ",\n";+  }+  s +=    "    };\n";+  s +=    "******************************";+  dumpn(s);+}++/**+ * Instantiates a new HTTP server.+ */+function nsHttpServer()+{+  if (!gThreadManager)+    gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService();++  /** The port on which this server listens. */+  this._port = undefined;++  /** The socket associated with this. */+  this._socket = null;++  /** The handler used to process requests to this server. */+  this._handler = new ServerHandler(this);++  /**+   * Indicates when the server is to be shut down at the end of the request.+   */+  this._doQuit = false;++  /**+   * True if the socket in this is closed (and closure notifications have been+   * sent and processed if the socket was ever opened), false otherwise.+   */+  this._socketClosed = true;+}+nsHttpServer.prototype =+{+  // NSISERVERSOCKETLISTENER++  /**+   * Processes an incoming request coming in on the given socket and contained+   * in the given transport.+   *+   * @param socket : nsIServerSocket+   *   the socket through which the request was served+   * @param trans : nsISocketTransport+   *   the transport for the request/response+   * @see nsIServerSocketListener.onSocketAccepted+   */+  onSocketAccepted: function(socket, trans)+  {+    dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ") " ++          "on thread " + gThreadManager.currentThread ++          " (main is " + gThreadManager.mainThread + ")");++    dumpn(">>> new connection on " + trans.host + ":" + trans.port);++    const SEGMENT_SIZE = 8192;+    const SEGMENT_COUNT = 1024;+    var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)+                     .QueryInterface(Ci.nsIAsyncInputStream);+    var output = trans.openOutputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);++    var conn = new Connection(input, output, this, socket.port);+    var reader = new RequestReader(conn);++    // XXX add request timeout functionality here!++    // Note: must use main thread here, or we might get a GC that will cause+    //       threadsafety assertions.  We really need to fix XPConnect so that+    //       you can actually do things in multi-threaded JS.  :-(+    input.asyncWait(reader, 0, 0, gThreadManager.mainThread);+  },++  /**+   * Called when the socket associated with this is closed.+   *+   * @param socket : nsIServerSocket+   *   the socket being closed+   * @param status : nsresult+   *   the reason the socket stopped listening (NS_BINDING_ABORTED if the server+   *   was stopped using nsIHttpServer.stop)+   * @see nsIServerSocketListener.onStopListening+   */+  onStopListening: function(socket, status)+  {+    dumpn(">>> shutting down server");+    this._socketClosed = true;+  },++  // NSIHTTPSERVER++  //+  // see nsIHttpServer.start+  //+  start: function(port)+  {+    if (this._socket)+      throw Cr.NS_ERROR_ALREADY_INITIALIZED;++    this._port = port;+    this._doQuit = this._socketClosed = false;++    var socket = new ServerSocket(this._port,+                                  true, // loopback only+                                  -1);  // default number of pending connections++    dumpn(">>> listening on port " + socket.port);+    socket.asyncListen(this);+    this._socket = socket;+  },++  //+  // see nsIHttpServer.stop+  //+  stop: function()+  {+    if (!this._socket)+      return;++    dumpn(">>> stopping listening on port " + this._socket.port);+    this._socket.close();+    this._socket = null;+    this._doQuit = false;++    // spin an event loop and wait for the socket-close notification+    var thr = gThreadManager.currentThread;+    while (!this._socketClosed || this._handler.hasPendingRequests())+      thr.processNextEvent(true);+  },++  //+  // see nsIHttpServer.registerFile+  //+  registerFile: function(path, file)+  {+    if (file && (!file.exists() || file.isDirectory()))+      throw Cr.NS_ERROR_INVALID_ARG;++    this._handler.registerFile(path, file);+  },++  //+  // see nsIHttpServer.registerDirectory+  //+  registerDirectory: function(path, directory)+  {+    // XXX true path validation!+    if (path.charAt(0) != "/" ||+        path.charAt(path.length - 1) != "/" ||+        (directory &&+         (!directory.exists() || !directory.isDirectory())))+      throw Cr.NS_ERROR_INVALID_ARG;++    // XXX determine behavior of non-existent /foo/bar when a /foo/bar/ mapping+    //     exists!++    this._handler.registerDirectory(path, directory);+  },++  //+  // see nsIHttpServer.registerPathHandler+  //+  registerPathHandler: function(path, handler)+  {+    this._handler.registerPathHandler(path, handler);+  },++  //+  // see nsIHttpServer.registerPrefixHandler+  //+  registerPrefixHandler: function(prefix, handler)+  {+    this._handler.registerPrefixHandler(prefix, handler);+  },++  //+  // see nsIHttpServer.registerErrorHandler+  //+  registerErrorHandler: function(code, handler)+  {+    this._handler.registerErrorHandler(code, handler);+  },++  //+  // see nsIHttpServer.setIndexHandler+  //+  setIndexHandler: function(handler)+  {+    this._handler.setIndexHandler(handler);+  },++  //+  // see nsIHttpServer.registerContentType+  //+  registerContentType: function(ext, type)+  {+    this._handler.registerContentType(ext, type);+  },++  // NSISUPPORTS++  //+  // see nsISupports.QueryInterface+  //+  QueryInterface: function(iid)+  {+    if (iid.equals(Ci.nsIHttpServer) ||+        iid.equals(Ci.nsIServerSocketListener) ||+        iid.equals(Ci.nsISupports))+      return this;++    throw Cr.NS_ERROR_NO_INTERFACE;+  },+++  // NON-XPCOM PUBLIC API++  /**+   * Returns true iff this server is not running (and is not in the process of+   * serving any requests still to be processed when the server was last+   * stopped after being run).+   */+  isStopped: function()+  {+    return this._socketClosed && !this._handler.hasPendingRequests();+  },++  +  // PRIVATE IMPLEMENTATION++  /**+   * Closes the passed-in connection.+   *+   * @param connection : Connection+   *   the connection to close+   */+  _endConnection: function(connection)+  {+    //+    // Order is important below: we must decrement handler._pendingRequests+    // BEFORE calling this.stop(), if needed, in connection.destroy().+    // this.stop() returns only when the server socket's closed AND all pending+    // requests are complete, which clearly isn't (and never will be) the case+    // if it were the other way around.+    //++    connection.close();++    NS_ASSERT(this == connection.server);++    this._handler._pendingRequests--;++    connection.destroy();+  },++  /**+   * Requests that the server be shut down when possible.+   */+  _requestQuit: function()+  {+    dumpn(">>> requesting a quit");+    dumpStack();+    this._doQuit = true;+  }++};+++/**+ * Represents a connection to the server (and possibly in the future the thread+ * on which the connection is processed).+ *+ * @param input : nsIInputStream+ *   stream from which incoming data on the connection is read+ * @param output : nsIOutputStream+ *   stream to write data out the connection+ * @param server : nsHttpServer+ *   the server handling the connection+ * @param port : int+ *   the port on which the server is running+ */+function Connection(input, output, server, port)+{+  /** Stream of incoming data. */+  this.input = input;++  /** Stream for outgoing data. */+  this.output = output;++  /** The server associated with this request. */+  this.server = server;++  /** The port on which the server is running. */+  this.port = port;+  +  /** State variables for debugging. */+  this._closed = this._processed = false;+}+Connection.prototype =+{+  /** Closes this connection's input/output streams. */+  close: function()+  {+    this.input.close();+    this.output.close();+    this._closed = true;+  },++  /**+   * Initiates processing of this connection, using the data in the given+   * request.+   *+   * @param request : Request+   *   the request which should be processed+   */+  process: function(request)+  {+    NS_ASSERT(!this._closed && !this._processed);++    this._processed = true;++    this.server._handler.handleResponse(this, request);+  },++  /**+   * Initiates processing of this connection, generating a response with the+   * given HTTP error code.+   *+   * @param code : uint+   *   an HTTP code, so in the range [0, 1000)+   * @param metadata : Request+   *   incomplete data about the incoming request (since there were errors+   *   during its processing+   */+  processError: function(code, metadata)+  {+    NS_ASSERT(!this._closed && !this._processed);++    this._processed = true;++    this.server._handler.handleError(code, this, metadata);+  },++  /** Ends this connection, destroying the resources it uses. */+  end: function()+  {+    this.server._endConnection(this);+  },++  /** Destroys resources used by this. */+  destroy: function()+  {+    if (!this._closed)+      this.close();++    // If an error triggered a server shutdown, act on it now+    var server = this.server;+    if (server._doQuit)+      server.stop();+  }+};++++/** Returns an array of count bytes from the given input stream. */+function readBytes(inputStream, count)+{+  return new BinaryInputStream(inputStream).readByteArray(count);+}++++/** Request reader processing states; see RequestReader for details. */+const READER_INITIAL    = 0;+const READER_IN_HEADERS = 1;+const READER_IN_BODY    = 2;+++/**+ * Reads incoming request data asynchronously, does any necessary preprocessing,+ * and forwards it to the request handler.  Processing occurs in three states:+ *+ *   READER_INITIAL             Haven't read the entire request line yet+ *   READER_IN_HEADERS          Reading headers in the request+ *   READER_IN_BODY             Finished reading all request headers (when body+ *                              support's added, will be reading the body)+ *+ * During the first two stages, initial metadata about the request is gathered+ * into a Request object.  Once the status line and headers have been processed,+ * we create a Response and hand it off to the ServerHandler to be given to the+ * appropriate request handler.+ *+ * XXX we should set up a stream to provide lazy access to the request body+ *+ * @param connection : Connection+ *   the connection for the request being read+ */+function RequestReader(connection)+{+  /** Connection metadata for this request. */+  this._connection = connection;++  /**+   * A container providing line-by-line access to the raw bytes that make up the+   * data which has been read from the connection but has not yet been acted+   * upon (by passing it to the request handler or by extracting request+   * metadata from it).+   */+  this._data = new LineData();++  /** The current state of parsing the incoming request. */+  this._state = READER_INITIAL;++  /** Metadata constructed from the incoming request for the request handler. */+  this._metadata = new Request(connection.port);++  /**+   * Used to preserve state if we run out of line data midway through a+   * multi-line header.  _lastHeaderName stores the name of the header, while+   * _lastHeaderValue stores the value we've seen so far for the header.+   *+   * These fields are always either both undefined or both strings.+   */+  this._lastHeaderName = this._lastHeaderValue = undefined;+}+RequestReader.prototype =+{+  // NSIINPUTSTREAMCALLBACK++  /**+   * Called when more data from the incoming request is available.  This method+   * then reads the available data from input and deals with that data as+   * necessary, depending upon the syntax of already-downloaded data.+   *+   * @param input : nsIAsyncInputStream+   *   the stream of incoming data from the connection+   */+  onInputStreamReady: function(input)+  {+    dumpn("*** onInputStreamReady(input=" + input + ") on thread " ++          gThreadManager.currentThread + " (main is " ++          gThreadManager.mainThread + ")");+    dumpn("*** this._state == " + this._state);++    var count = input.available();++    // Handle cases where we get more data after a request error has been+    // discovered but *before* we can close the connection.+    if (!this._data)+      return;++    var moreAvailable = false;++    switch (this._state)+    {+      case READER_INITIAL:+        moreAvailable = this._processRequestLine(input, count);+        break;++      case READER_IN_HEADERS:+        moreAvailable = this._processHeaders(input, count);+        break;++      case READER_IN_BODY:+        // XXX handle the request body!  until then, just stop reading+        break;++      default:+        NS_ASSERT(false);+    }++    if (moreAvailable)+      input.asyncWait(this, 0, 0, gThreadManager.currentThread);+  },++  //+  // see nsISupports.QueryInterface+  //+  QueryInterface: function(aIID)+  {+    if (aIID.equals(Ci.nsIInputStreamCallback) ||+        aIID.equals(Ci.nsISupports))+      return this;++    throw Cr.NS_ERROR_NO_INTERFACE;+  },+++  // PRIVATE API++  /**+   * Reads count bytes from input and processes unprocessed, downloaded data as+   * a request line.+   *+   * @param input : nsIInputStream+   *   stream from which count bytes of data must be read+   * @param count : PRUint32+   *   the number of bytes of data which must be read from input+   * @returns boolean+   *   true if more data must be read from the request, false otherwise+   */+  _processRequestLine: function(input, count)+  {+    NS_ASSERT(this._state == READER_INITIAL);++    var data = this._data;+    data.appendBytes(readBytes(input, count));+++    // servers SHOULD ignore any empty line(s) received where a Request-Line+    // is expected (section 4.1)+    var line = {};+    var readSuccess;+    while ((readSuccess = data.readLine(line)) && line.value == "")+      dumpn("*** ignoring beginning blank line...");++    // if we don't have a full line, wait until we do+    if (!readSuccess)+      return true;++    // we have the first non-blank line+    try+    {+      this._parseRequestLine(line.value);++      // do we have more header data to read?+      if (!this._parseHeaders())+        return true;++      // headers complete, do a data check and then forward to the handler+      this._validateRequest();+      return this._handleResponse();+    }+    catch (e)+    {+      this._handleError(e);+      return false;+    }+  },++  /**+   * Reads data from input and processes it, assuming it is either at the+   * beginning or in the middle of processing request headers.+   *+   * @param input : nsIInputStream+   *   stream from which count bytes of data must be read+   * @param count : PRUint32+   *   the number of bytes of data which must be read from input+   * @returns boolean+   *   true if more data must be read from the request, false otherwise+   */+  _processHeaders: function(input, count)+  {+    NS_ASSERT(this._state == READER_IN_HEADERS);++    // XXX things to fix here:+    //+    // - need to support RFC 2047-encoded non-US-ASCII characters+    // - really support absolute URLs (requires telling the server all its+    //   hostnames, beyond just localhost:port or 127.0.0.1:port)++    this._data.appendBytes(readBytes(input, count));++    try+    {+      // do we have all the headers?+      if (!this._parseHeaders())+        return true;++      // we have all the headers, continue with the body+      this._validateRequest();+      return this._handleResponse();+    }+    catch (e)+    {+      this._handleError(e);+      return false;+    }+  },++  /**+   * Does various post-header checks on the data in this request.+   *+   * @throws : HttpError+   *   if the request was malformed in some way+   */+  _validateRequest: function()+  {+    NS_ASSERT(this._state == READER_IN_BODY);++    dumpn("*** _validateRequest");++    var metadata = this._metadata;+    var headers = metadata._headers;++    var isHttp11 = metadata._httpVersion.equals(nsHttpVersion.HTTP_1_1);++    // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header+    if (isHttp11 && !headers.hasHeader("Host"))+      throw HTTP_400;+  },++  /**+   * Handles responses in case of error, either in the server or in the request.+   *+   * @param e+   *   the specific error encountered, which is an HttpError in the case where+   *   the request is in some way invalid or cannot be fulfilled; if this isn't+   *   an HttpError we're going to be paranoid and shut down, because that+   *   shouldn't happen, ever+   */+  _handleError: function(e)+  {+    var server = this._connection.server;+    if (e instanceof HttpError)+    {+      var code = e.code;+    }+    else+    {+      // no idea what happened -- be paranoid and shut down+      code = 500;+      server._requestQuit();+    }++    // make attempted reuse of data an error+    this._data = null;++    this._connection.processError(code, this._metadata);+  },++  /**+   * Now that we've read the request line and headers, we can actually hand off+   * the request to be handled.+   *+   * This method is called once per request, after the request line and all+   * headers have been received.+   *+   * @returns boolean+   *   true if more data must be read, false otherwise+   */+  _handleResponse: function()+  {+    NS_ASSERT(this._state == READER_IN_BODY);++    // XXX set up a stream for data in the request body here++    // We don't need the line-based data any more, so make attempted reuse an+    // error.+    this._data = null;++    this._connection.process(this._metadata);++    return false;+  },+++  // PARSING++  /**+   * Parses the request line for the HTTP request associated with this.+   *+   * @param line : string+   *   the request line+   */+  _parseRequestLine: function(line)+  {+    NS_ASSERT(this._state == READER_INITIAL);++    dumpn("*** _parseRequestLine('" + line + "')");++    var metadata = this._metadata;++    // clients and servers SHOULD accept any amount of SP or HT characters+    // between fields, even though only a single SP is required (section 19.3)+    var request = line.split(/[ \t]+/);+    if (!request || request.length != 3)+      throw HTTP_400;++    metadata._method = request[0];++    // get the HTTP version+    var ver = request[2];+    var match = ver.match(/^HTTP\/(\d+\.\d+)$/);+    if (!match)+      throw HTTP_400;++    // determine HTTP version+    try+    {+      metadata._httpVersion = new nsHttpVersion(match[1]);+      if (!metadata._httpVersion.equals(nsHttpVersion.HTTP_1_0) &&+          !metadata._httpVersion.equals(nsHttpVersion.HTTP_1_1))+        throw "unsupported HTTP version";+    }+    catch (e)+    {+      // we support HTTP/1.0 and HTTP/1.1 only+      throw HTTP_501;+    }+++    var fullPath = request[1];++    if (fullPath.charAt(0) != "/")+    {+      // XXX we don't really support absolute URIs yet -- a MUST for HTTP/1.1;+      //     for now just get the path and use that, ignoring hostport+      try+      {+        var uri = Cc["@mozilla.org/network/io-service;1"]+                    .getService(Ci.nsIIOService)+                    .newURI(fullPath, null, null);+        fullPath = uri.path;+      }+      catch (e) { /* invalid URI */ }+      if (fullPath.charAt(0) != "/")+      {+        this.errorCode = 400;+        return;+      }+    }++    var splitter = fullPath.indexOf("?");+    if (splitter < 0)+    {+      // _queryString already set in ctor+      metadata._path = fullPath;+    }+    else+    {+      metadata._path = fullPath.substring(0, splitter);+      metadata._queryString = fullPath.substring(splitter + 1);+    }++    // our work here is finished+    this._state = READER_IN_HEADERS;+  },++  /**+   * Parses all available HTTP headers in this until the header-ending CRLFCRLF,+   * adding them to the store of headers in the request.+   *+   * @throws+   *   HTTP_400 if the headers are malformed+   * @returns boolean+   *   true if all headers have now been processed, false otherwise+   */+  _parseHeaders: function()+  {+    NS_ASSERT(this._state == READER_IN_HEADERS);++    dumpn("*** _parseHeaders");++    var data = this._data;++    var headers = this._metadata._headers;+    var lastName = this._lastHeaderName;+    var lastVal = this._lastHeaderValue;++    var line = {};+    while (true)+    {+      NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),+                lastName === undefined ?+                  "lastVal without lastName?  lastVal: '" + lastVal + "'" :+                  "lastName without lastVal?  lastName: '" + lastName + "'");++      if (!data.readLine(line))+      {+        // save any data we have from the header we might still be processing+        this._lastHeaderName = lastName;+        this._lastHeaderValue = lastVal;+        return false;+      }++      var lineText = line.value;+      var firstChar = lineText.charAt(0);++      // blank line means end of headers+      if (lineText == "")+      {+        // we're finished with the previous header+        if (lastName)+        {+          try+          {+            headers.setHeader(lastName, lastVal, true);+          }+          catch (e)+          {+            dumpn("*** e == " + e);+            throw HTTP_400;+          }+        }+        else+        {+          // no headers in request -- valid for HTTP/1.0 requests+        }++        // either way, we're done processing headers+        this._state = READER_IN_BODY;+        return true;+      }+      else if (firstChar == " " || firstChar == "\t")+      {+        // multi-line header if we've already seen a header line+        if (!lastName)+        {+          // we don't have a header to continue!+          throw HTTP_400;+        }++        // append this line's text to the value; starts with SP/HT, so no need+        // for separating whitespace+        lastVal += lineText;+      }+      else+      {+        // we have a new header, so set the old one (if one existed)+        if (lastName)+        {+          try+          {+            headers.setHeader(lastName, lastVal, true);+          }+          catch (e)+          {+            dumpn("*** e == " + e);+            throw HTTP_400;+          }+        }++        var colon = lineText.indexOf(":"); // first colon must be splitter+        if (colon < 1)+        {+          // no colon or missing header field-name+          throw HTTP_400;+        }++        // set header name, value (to be set in the next loop, usually)+        lastName = lineText.substring(0, colon);+        lastVal = lineText.substring(colon + 1);+      } // empty, continuation, start of header+    } // while (true)+  }+};+++/** The character codes for CR and LF. */+const CR = 0x0D, LF = 0x0A;++/**+ * Calculates the number of characters before the first CRLF pair in array, or+ * -1 if the array contains no CRLF pair.+ *+ * @param array : Array+ *   an array of numbers in the range [0, 256), each representing a single+ *   character; the first CRLF is the lowest index i where+ *   |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,+ *   if such an |i| exists, and -1 otherwise+ * @returns int+ *   the index of the first CRLF if any were present, -1 otherwise+ */+function findCRLF(array)+{+  for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1))+  {+    if (array[i + 1] == LF)+      return i;+  }+  return -1;+}+++/**+ * A container which provides line-by-line access to the arrays of bytes with+ * which it is seeded.+ */+function LineData()+{+  /** An array of queued bytes from which to get line-based characters. */+  this._data = [];+}+LineData.prototype =+{+  /**+   * Appends the bytes in the given array to the internal data cache maintained+   * by this.+   */+  appendBytes: function(bytes)+  {+    Array.prototype.push.apply(this._data, bytes);+  },++  /**+   * Removes and returns a line of data, delimited by CRLF, from this.+   *+   * @param out+   *   an object whose "value" property will be set to the first line of text+   *   present in this, sans CRLF, if this contains a full CRLF-delimited line+   *   of text; if this doesn't contain enough data, the value of the property+   *   is undefined+   * @returns boolean+   *   true if a full line of data could be read from the data in this, false+   *   otherwise+   */+  readLine: function(out)+  {+    var data = this._data;+    var length = findCRLF(data);+    if (length < 0)+      return false;++    //+    // We have the index of the CR, so remove all the characters, including+    // CRLF, from the array with splice, and convert the removed array into the+    // corresponding string, from which we then strip the trailing CRLF.+    //+    // Getting the line in this matter acknowledges that substring is an O(1)+    // operation in SpiderMonkey because strings are immutable, whereas two+    // splices, both from the beginning of the data, are less likely to be as+    // cheap as a single splice plus two extra character conversions.+    //+    var line = String.fromCharCode.apply(null, data.splice(0, length + 2));+    out.value = line.substring(0, length);++    return true;+  },++  /**+   * Retrieve any bytes we may have overread from the request's postdata.  After+   * this method is called, this must not be used in any way.+   *+   * @returns Array+   *   the bytes read past the CRLFCRLF at the end of request headers+   */+  purge: function()+  {+    var data = this._data;+    this._data = null;+    return data;+  }+};++++/**+ * Creates a request-handling function for an nsIHttpRequestHandler object.+ */+function createHandlerFunc(handler)+{+  return function(metadata, response) { handler.handle(metadata, response); };+}+++/**+ * The default handler for directories; writes an HTML response containing a+ * slightly-formatted directory listing.+ */+function defaultIndexHandler(metadata, response)+{+  response.setHeader("Content-Type", "text/html", false);++  var path = htmlEscape(decodeURI(metadata.path));++  //+  // Just do a very basic bit of directory listings -- no need for too much+  // fanciness, especially since we don't have a style sheet in which we can+  // stick rules (don't want to pollute the default path-space).+  //++  var body = '<html>\+                <head>\+                  <title>' + path + '</title>\+                </head>\+                <body>\+                  <h1>' + path + '</h1>\+                  <ol style="list-style-type: none">';++  var directory = metadata.getProperty("directory");+  NS_ASSERT(directory && directory.isDirectory());++  var fileList = [];+  var files = directory.directoryEntries;+  while (files.hasMoreElements())+  {+    var f = files.getNext().QueryInterface(Ci.nsIFile);+    var name = f.leafName;+    if (!f.isHidden() &&+        (name.charAt(name.length - 1) != HIDDEN_CHAR ||+         name.charAt(name.length - 2) == HIDDEN_CHAR))+      fileList.push(f);+  }++  fileList.sort(fileSort);++  for (var i = 0; i < fileList.length; i++)+  {+    var file = fileList[i];+    try+    {+      var name = file.leafName;+      if (name.charAt(name.length - 1) == HIDDEN_CHAR)+        name = name.substring(0, name.length - 1);+      var sep = file.isDirectory() ? "/" : "";++      // Note: using " to delimit the attribute here because encodeURIComponent+      //       passes through '.+      var item = '<li><a href="' + encodeURIComponent(name) + sep + '">' ++                   htmlEscape(name) + sep ++                 '</a></li>';++      body += item;+    }+    catch (e) { /* some file system error, ignore the file */ }+  }++  body    += '    </ol>\+                </body>\+              </html>';++  response.bodyOutputStream.write(body, body.length);+}++/**+ * Sorts a and b (nsIFile objects) into an aesthetically pleasing order.+ */+function fileSort(a, b)+{+  var dira = a.isDirectory(), dirb = b.isDirectory();++  if (dira && !dirb)+    return -1;+  if (dirb && !dira)+    return 1;++  var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase();+  return nameb > namea ? -1 : 1;+}+++/**+ * Converts an externally-provided path into an internal path for use in+ * determining file mappings.+ *+ * @param path+ *   the path to convert+ * @param encoded+ *   true if the given path should be passed through decodeURI prior to+ *   conversion+ * @throws URIError+ *   if path is incorrectly encoded+ */+function toInternalPath(path, encoded)+{+  if (encoded)+    path = decodeURI(path);++  var comps = path.split("/");+  for (var i = 0, sz = comps.length; i < sz; i++)+  {+    var comp = comps[i];+    if (comp.charAt(comp.length - 1) == HIDDEN_CHAR)+      comps[i] = comp + HIDDEN_CHAR;+  }+  return comps.join("/");+}+++/**+ * Adds custom-specified headers for the given file to the given response, if+ * any such headers are specified.+ *+ * @param file+ *   the file on the disk which is to be written+ * @param metadata+ *   metadata about the incoming request+ * @param response+ *   the Response to which any specified headers/data should be written+ * @throws HTTP_500+ *   if an error occurred while processing custom-specified headers+ */+function maybeAddHeaders(file, metadata, response)+{+  var name = file.leafName;+  if (name.charAt(name.length - 1) == HIDDEN_CHAR)+    name = name.substring(0, name.length - 1);++  var headerFile = file.parent;+  headerFile.append(name + HEADERS_SUFFIX);++  if (!headerFile.exists())+    return;++  const PR_RDONLY = 0x01;+  var fis = new FileInputStream(headerFile, PR_RDONLY, 0444,+                                Ci.nsIFileInputStream.CLOSE_ON_EOF);++  var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);+  lis.QueryInterface(Ci.nsIUnicharLineInputStream);++  try+  {+    var line = {value: ""};+    var more = lis.readLine(line);++    if (!more && line.value == "")+      return;+++    // request line++    var status = line.value;+    if (status.indexOf("HTTP ") == 0)+    {+      status = status.substring(5);+      var space = status.indexOf(" ");+      var code, description;+      if (space < 0)+      {+        code = status;+        description = "";+      }+      else+      {+        code = status.substring(0, space);+        description = status.substring(space + 1, status.length);+      }+    +      response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);++      line.value = "";+      more = lis.readLine(line);+    }++    // headers+    while (more || line.value != "")+    {+      var header = line.value;+      var colon = header.indexOf(":");++      response.setHeader(header.substring(0, colon),+                         header.substring(colon + 1, header.length),+                         false); // allow overriding server-set headers++      line.value = "";+      more = lis.readLine(line);+    }+  }+  catch (e)+  {+    dumpn("WARNING: error in headers for " + metadata.path + ": " + e);+    throw HTTP_500;+  }+}+++/**+ * An object which handles requests for a server, executing default and+ * overridden behaviors as instructed by the code which uses and manipulates it.+ * Default behavior includes the paths / and /trace (diagnostics), with some+ * support for HTTP error pages for various codes and fallback to HTTP 500 if+ * those codes fail for any reason.+ *+ * @param server : nsHttpServer+ *   the server in which this handler is being used+ */+function ServerHandler(server)+{+  // FIELDS++  /**+   * The nsHttpServer instance associated with this handler.+   */+  this._server = server;++  /**+   * A variable used to ensure that all requests are fully complete before the+   * server shuts down, to avoid callbacks from compiled code calling back into+   * empty contexts.  See also the comment before this field is next modified.+   */+  this._pendingRequests = 0;++  /**+   * A FileMap object containing the set of path->nsILocalFile mappings for+   * all directory mappings set in the server (e.g., "/" for /var/www/html/,+   * "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2).+   *+   * Note carefully: the leading and trailing "/" in each path (not file) are+   * removed before insertion to simplify the code which uses this.  You have+   * been warned!+   */+  this._pathDirectoryMap = new FileMap();++  /**+   * Custom request handlers for the server in which this resides.  Path-handler+   * pairs are stored as property-value pairs in this property.+   *+   * @see ServerHandler.prototype._defaultPaths+   */+  this._overridePaths = {};++  /**+   * Custom request handlers for the path prefixes on the server in which this+   * resides.  Path-handler pairs are stored as property-value pairs in this+   * property.+   *+   * @see ServerHandler.prototype._defaultPaths+   */+  this._overridePrefixes = {};++  /**+   * Custom request handlers for the error handlers in the server in which this+   * resides.  Path-handler pairs are stored as property-value pairs in this+   * property.+   *+   * @see ServerHandler.prototype._defaultErrors+   */+  this._overrideErrors = {};++  /**+   * Maps file extensions to their MIME types in the server, overriding any+   * mapping that might or might not exist in the MIME service.+   */+  this._mimeMappings = {};++  /**+   * The default handler for requests for directories, used to serve directories+   * when no index file is present.+   */+  this._indexHandler = defaultIndexHandler;+}+ServerHandler.prototype =+{+  // PUBLIC API++  /**+   * Handles a request to this server, responding to the request appropriately+   * and initiating server shutdown if necessary.+   *+   * This method never throws an exception.+   *+   * @param connection : Connection+   *   the connection for this request+   * @param metadata : Request+   *   request metadata as generated from the initial request+   */+  handleResponse: function(connection, metadata)+  {+    var response = new Response();++    var path = metadata.path;+    dumpn("*** path == " + path);++    try+    {+      try+      {+        // explicit paths first, then files based on existing directory mappings,+        // then (if the file doesn't exist) built-in server default paths+        if (path in this._overridePaths)+        {+          this._overridePaths[path](metadata, response);+        }+        else+        {+          let longestPrefix = "";+          for (let prefix in this._overridePrefixes)+          {+            if (prefix.length > longestPrefix.length &&+                path.substr(0, prefix.length) == prefix)+            {+              longestPrefix = prefix;+            }+          }+          if (longestPrefix.length > 0)+          {+            this._overridePrefixes[longestPrefix](metadata, response);+          }+          else+          {+            this._handleDefault(metadata, response);+          }+        }+      }+      catch (e)+      {+        response.recycle();++        if (!(e instanceof HttpError))+        {+          dumpn("*** unexpected error: e == " + e);+          throw HTTP_500;+        }+        if (e.code != 404)+          throw e;++        dumpn("*** default: " + (path in this._defaultPaths));++        if (path in this._defaultPaths)+          this._defaultPaths[path](metadata, response);+        else+          throw HTTP_404;+      }+    }+    catch (e)+    {+      var errorCode = "internal";++      try+      {+        if (!(e instanceof HttpError))+          throw e;++        errorCode = e.code;+        dumpn("*** errorCode == " + errorCode);++        response.recycle();++        this._handleError(errorCode, metadata, response);+      }+      catch (e2)+      {+        dumpn("*** error handling " + errorCode + " error: " ++              "e2 == " + e2 + ", shutting down server");++        response.destroy();+        connection.close();+        connection.server.stop();+        return;+      }+    }++    this._end(response, connection);+  },++  //+  // see nsIHttpServer.registerFile+  //+  registerFile: function(path, file)+  {+    if (!file)+    {+      dumpn("*** unregistering '" + path + "' mapping");+      delete this._overridePaths[path];+      return;+    }++    dumpn("*** registering '" + path + "' as mapping to " + file.path);+    file = file.clone();++    var self = this;+    this._overridePaths[path] =+      function(metadata, response)+      {+        if (!file.exists())+          throw HTTP_404;++        response.setStatusLine(metadata.httpVersion, 200, "OK");+        self._writeFileResponse(metadata, file, response);+      };+  },++  //+  // see nsIHttpServer.registerPathHandler+  //+  registerPathHandler: function(path, handler)+  {+    // XXX true path validation!+    if (path.charAt(0) != "/")+      throw Cr.NS_ERROR_INVALID_ARG;++    this._handlerToField(handler, this._overridePaths, path);+  },++  //+  // see nsIHttpServer.registerPrefixHandler+  //+  registerPrefixHandler: function(path, handler)+  {+    // XXX true path validation!+    if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/")+      throw Cr.NS_ERROR_INVALID_ARG;++    this._handlerToField(handler, this._overridePrefixes, path);+  },++  //+  // see nsIHttpServer.registerDirectory+  //+  registerDirectory: function(path, directory)+  {+    // strip off leading and trailing '/' so that we can use lastIndexOf when+    // determining exactly how a path maps onto a mapped directory --+    // conditional is required here to deal with "/".substring(1, 0) being+    // converted to "/".substring(0, 1) per the JS specification+    var key = path.length == 1 ? "" : path.substring(1, path.length - 1);++    // the path-to-directory mapping code requires that the first character not+    // be "/", or it will go into an infinite loop+    if (key.charAt(0) == "/")+      throw Cr.NS_ERROR_INVALID_ARG;++    key = toInternalPath(key, false);++    if (directory)+    {+      dumpn("*** mapping '" + path + "' to the location " + directory.path);+      this._pathDirectoryMap.put(key, directory);+    }+    else+    {+      dumpn("*** removing mapping for '" + path + "'");+      this._pathDirectoryMap.put(key, null);+    }+  },++  //+  // see nsIHttpServer.registerErrorHandler+  //+  registerErrorHandler: function(err, handler)+  {+    if (!(err in HTTP_ERROR_CODES))+      dumpn("*** WARNING: registering non-HTTP/1.1 error code " ++            "(" + err + ") handler -- was this intentional?");++    this._handlerToField(handler, this._overrideErrors, err);+  },++  //+  // see nsIHttpServer.setIndexHandler+  //+  setIndexHandler: function(handler)+  {+    if (!handler)+      handler = defaultIndexHandler;+    else if (typeof(handler) != "function")+      handler = createHandlerFunc(handler);++    this._indexHandler = handler;+  },++  //+  // see nsIHttpServer.registerContentType+  //+  registerContentType: function(ext, type)+  {+    if (!type)+      delete this._mimeMappings[ext];+    else+      this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type);+  },++  // NON-XPCOM PUBLIC API++  /**+   * Returns true if this handler is in the middle of handling any current+   * requests; this must be false before the server in which this is used may be+   * safely shut down.+   */+  hasPendingRequests: function()+  {+    return this._pendingRequests > 0;+  },+++  // PRIVATE API++  /**+   * Sets or remove (if handler is null) a handler in an object with a key.+   *+   * @param handler+   *   a handler, either function or an nsIHttpRequestHandler+   * @param dict+   *   The object to attach the handler to.+   * @param key+   *   The field name of the handler.+   */+  _handlerToField: function(handler, dict, key)+  {+    // for convenience, handler can be a function if this is run from xpcshell+    if (typeof(handler) == "function")+      dict[key] = handler;+    else if (handler)+      dict[key] = createHandlerFunc(handler);+    else+      delete dict[key];+  },++  /**+   * Handles a request which maps to a file in the local filesystem (if a base+   * path has already been set; otherwise the 404 error is thrown).+   *+   * @param metadata : Request+   *   metadata for the incoming request+   * @param response : Response+   *   an uninitialized Response to the given request, to be initialized by a+   *   request handler+   * @throws HTTP_###+   *   if an HTTP error occurred (usually HTTP_404); note that in this case the+   *   calling code must handle cleanup of the response by calling .destroy()+   *   or .recycle()+   */+  _handleDefault: function(metadata, response)+  {+    response.setStatusLine(metadata.httpVersion, 200, "OK");++    var path = metadata.path;+    NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">");++    // determine the actual on-disk file; this requires finding the deepest+    // path-to-directory mapping in the requested URL+    var file = this._getFileForPath(path);++    // the "file" might be a directory, in which case we either serve the+    // contained index.html or make the index handler write the response+    if (file.exists() && file.isDirectory())+    {+      file.append("index.html"); // make configurable?+      if (!file.exists() || file.isDirectory())+      {+        metadata._ensurePropertyBag();+        metadata._bag.setPropertyAsInterface("directory", file.parent);+        this._indexHandler(metadata, response);+        return;+      }+    }++    // alternately, the file might not exist+    if (!file.exists())+      throw HTTP_404;++    // finally...+    dumpn("*** handling '" + path + "' as mapping to " + file.path);+    this._writeFileResponse(metadata, file, response);+  },++  /**+   * Writes an HTTP response for the given file, including setting headers for+   * file metadata.+   *+   * @param metadata : Request+   *   the Request for which a response is being generated+   * @param file : nsILocalFile+   *   the file which is to be sent in the response+   * @param response : Response+   *   the response to which the file should be written+   */+  _writeFileResponse: function(metadata, file, response)+  {+    const PR_RDONLY = 0x01;++    var type = this._getTypeFromFile(file);+    if (type == SJS_TYPE)+    {+      try+      {+        var fis = new FileInputStream(file, PR_RDONLY, 0444,+                                      Ci.nsIFileInputStream.CLOSE_ON_EOF);+        var sis = new ScriptableInputStream(fis);+        var s = Cu.Sandbox(gGlobalObject);+        Cu.evalInSandbox(sis.read(file.fileSize), s);+        s.handleRequest(metadata, response);+      }+      catch (e)+      {+        dumpn("*** error running SJS: " + e);+        throw HTTP_500;+      }+    }+    else+    {+      try+      {+        response.setHeader("Last-Modified",+                           toDateString(file.lastModifiedTime),+                           false);+      }+      catch (e) { /* lastModifiedTime threw, ignore */ }++      response.setHeader("Content-Type", type, false);+  +      var fis = new FileInputStream(file, PR_RDONLY, 0444,+                                    Ci.nsIFileInputStream.CLOSE_ON_EOF);+      response.bodyOutputStream.writeFrom(fis, file.fileSize);+      fis.close();+      +      maybeAddHeaders(file, metadata, response);+    }+  },++  /**+   * Gets a content-type for the given file, first by checking for any custom+   * MIME-types registered with this handler for the file's extension, second by+   * asking the global MIME service for a content-type, and finally by failing+   * over to application/octet-stream.+   *+   * @param file : nsIFile+   *   the nsIFile for which to get a file type+   * @returns string+   *   the best content-type which can be determined for the file+   */+  _getTypeFromFile: function(file)+  {+    try+    {+      var name = file.leafName;+      var dot = name.lastIndexOf(".");+      if (dot > 0)+      {+        var ext = name.slice(dot + 1);+        if (ext in this._mimeMappings)+          return this._mimeMappings[ext];+      }+      return Cc["@mozilla.org/uriloader/external-helper-app-service;1"]+               .getService(Ci.nsIMIMEService)+               .getTypeFromFile(file);+    }+    catch (e)+    {+      return "application/octet-stream";+    }+  },++  /**+   * Returns the nsILocalFile which corresponds to the path, as determined using+   * all registered path->directory mappings and any paths which are explicitly+   * overridden.+   *+   * @param path : string+   *   the server path for which a file should be retrieved, e.g. "/foo/bar"+   * @throws HttpError+   *   when the correct action is the corresponding HTTP error (i.e., because no+   *   mapping was found for a directory in path, the referenced file doesn't+   *   exist, etc.)+   * @returns nsILocalFile+   *   the file to be sent as the response to a request for the path+   */+  _getFileForPath: function(path)+  {+    // decode and add underscores as necessary+    try+    {+      path = toInternalPath(path, true);+    }+    catch (e)+    {+      throw HTTP_400; // malformed path+    }++    // next, get the directory which contains this path+    var pathMap = this._pathDirectoryMap;++    // An example progression of tmp for a path "/foo/bar/baz/" might be:+    // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", ""+    var tmp = path.substring(1);+    while (true)+    {+      // do we have a match for current head of the path?+      var file = pathMap.get(tmp);+      if (file)+      {+        // XXX hack; basically disable showing mapping for /foo/bar/ when the+        //     requested path was /foo/bar, because relative links on the page+        //     will all be incorrect -- we really need the ability to easily+        //     redirect here instead+        if (tmp == path.substring(1) &&+            tmp.length != 0 &&+            tmp.charAt(tmp.length - 1) != "/")+          file = null;+        else+          break;+      }++      // if we've finished trying all prefixes, exit+      if (tmp == "")+        break;++      tmp = tmp.substring(0, tmp.lastIndexOf("/"));+    }++    // no mapping applies, so 404+    if (!file)+      throw HTTP_404;+++    // last, get the file for the path within the determined directory+    var parentFolder = file.parent;+    var dirIsRoot = (parentFolder == null);++    // Strategy here is to append components individually, making sure we+    // never move above the given directory; this allows paths such as+    // "<file>/foo/../bar" but prevents paths such as "<file>/../base-sibling";+    // this component-wise approach also means the code works even on platforms+    // which don't use "/" as the directory separator, such as Windows+    var leafPath = path.substring(tmp.length + 1);+    var comps = leafPath.split("/");+    for (var i = 0, sz = comps.length; i < sz; i++)+    {+      var comp = comps[i];++      if (comp == "..")+        file = file.parent;+      else if (comp == "." || comp == "")+        continue;+      else+        file.append(comp);++      if (!dirIsRoot && file.equals(parentFolder))+        throw HTTP_403;+    }++    return file;+  },++  /**+   * Writes the error page for the given HTTP error code over the given+   * connection.+   *+   * @param errorCode : uint+   *   the HTTP error code to be used+   * @param connection : Connection+   *   the connection on which the error occurred+   */+  handleError: function(errorCode, connection)+  {+    var response = new Response();++    dumpn("*** error in request: " + errorCode);++    try+    {+      this._handleError(errorCode, new Request(connection.port), response);+      this._end(response, connection);+    }+    catch (e)+    {+      connection.close();+      connection.server.stop();+    }+  }, ++  /**+   * Handles a request which generates the given error code, using the+   * user-defined error handler if one has been set, gracefully falling back to+   * the x00 status code if the code has no handler, and failing to status code+   * 500 if all else fails.+   *+   * @param errorCode : uint+   *   the HTTP error which is to be returned+   * @param metadata : Request+   *   metadata for the request, which will often be incomplete since this is an+   *   error+   * @param response : Response+   *   an uninitialized Response should be initialized when this method+   *   completes with information which represents the desired error code in the+   *   ideal case or a fallback code in abnormal circumstances (i.e., 500 is a+   *   fallback for 505, per HTTP specs)+   */+  _handleError: function(errorCode, metadata, response)+  {+    if (!metadata)+      throw Cr.NS_ERROR_NULL_POINTER;++    var errorX00 = errorCode - (errorCode % 100);++    try+    {+      if (!(errorCode in HTTP_ERROR_CODES))+        dumpn("*** WARNING: requested invalid error: " + errorCode);++      // RFC 2616 says that we should try to handle an error by its class if we+      // can't otherwise handle it -- if that fails, we revert to handling it as+      // a 500 internal server error, and if that fails we throw and shut down+      // the server++      // actually handle the error+      try+      {+        if (errorCode in this._overrideErrors)+          this._overrideErrors[errorCode](metadata, response);+        else if (errorCode in this._defaultErrors)+          this._defaultErrors[errorCode](metadata, response);+      }+      catch (e)+      {+        // don't retry the handler that threw+        if (errorX00 == errorCode)+          throw HTTP_500;++        dumpn("*** error in handling for error code " + errorCode + ", " ++              "falling back to " + errorX00 + "...");+        if (errorX00 in this._overrideErrors)+          this._overrideErrors[errorX00](metadata, response);+        else if (errorX00 in this._defaultErrors)+          this._defaultErrors[errorX00](metadata, response);+        else+          throw HTTP_500;+      }+    }+    catch (e)+    {+      response.recycle();++      // we've tried everything possible for a meaningful error -- now try 500+      dumpn("*** error in handling for error code " + errorX00 + ", falling " ++            "back to 500...");++      try+      {+        if (500 in this._overrideErrors)+          this._overrideErrors[500](metadata, response);+        else+          this._defaultErrors[500](metadata, response);+      }+      catch (e2)+      {+        dumpn("*** multiple errors in default error handlers!");+        dumpn("*** e == " + e + ", e2 == " + e2);+        throw e2;+      }+    }+  },++  /**+   * Called when all processing necessary for the current request has completed+   * and response headers and data have been determined.  This method takes+   * those headers and data, sends them to the HTTP client, and halts further+   * processing.  It will also send a quit message to the server if necessary.+   *+   * This method never throws an exception.+   *+   * @param response : Response+   *   the desired response+   * @param connection : Connection+   *   the connection associated with the given response+   * @note+   *   after completion, response must be considered "dead", and none of its+   *   methods or properties may be accessed+   */+  _end:  function(response, connection)+  {+    // post-processing+    response.setHeader("Connection", "close", false);+    response.setHeader("Server", "httpd.js", false);+    response.setHeader("Date", toDateString(Date.now()), false);++    var bodyStream = response.bodyInputStream;++    // XXX suckage time!+    //+    // If the body of the response has had no data written to it (or has never+    // been accessed -- same deal internally since we'll create one if we have+    // to access bodyInputStream but have neither an input stream nor an+    // output stream), the in-tree implementation of nsIPipe is such that+    // when we try to close the pipe's output stream with no data in it, this+    // is interpreted as an error and closing the output stream also closes+    // the input stream.  .available then throws, so we catch and deal as best+    // as we can.+    //+    // Unfortunately, the easy alternative (substitute a storage stream for a+    // pipe) also doesn't work.  There's no problem writing zero bytes to the+    // output end of the stream, but then attempting to get an input stream to+    // read fails because the seek position must be strictly less than the+    // buffer size.+    //+    // Much as I'd like the zero-byte body to be a mostly-unimportant problem,+    // there are some HTTP responses such as 304 Not Modified which MUST have+    // zero-byte bodies, so this *is* a necessary hack.+    try+    {+      var available = bodyStream.available();+    }+    catch (e)+    {+      available = 0;+    }++    response.setHeader("Content-Length", available.toString(), false);+++    // construct and send response++    // request-line+    var preamble = "HTTP/" + response.httpVersion + " " ++                   response.httpCode + " " ++                   response.httpDescription + "\r\n";++    // headers+    var head = response.headers;+    var headEnum = head.enumerator;+    while (headEnum.hasMoreElements())+    {+      var fieldName = headEnum.getNext()+                              .QueryInterface(Ci.nsISupportsString)+                              .data;+      preamble += fieldName + ": " + head.getHeader(fieldName) + "\r\n";+    }++    // end request-line/headers+    preamble += "\r\n";++    var outStream = connection.output;+    try+    {+      outStream.write(preamble, preamble.length);+    }+    catch (e)+    {+      // Connection closed already?  Even if not, failure to write the response+      // means we probably will fail later anyway, so in the interests of+      // avoiding exceptions we'll (possibly) close the connection and return.+      response.destroy();+      connection.close();+      return;+    }++    // In certain situations, it's possible for us to have a race between+    // the copy observer's onStopRequest and the listener for a channel+    // opened to this server.  Since we include a Content-Length header with+    // every response, if the channel snarfs up all the data we promise,+    // calls onStopRequest on the listener (and the server is shut down+    // by that listener, causing the script to finish executing), and then+    // tries to call onStopRequest on the copyObserver, we'll call into a+    // scope with no Components and cause assertions *and* fail to close the+    // connection properly.  To combat this, during server shutdown we delay+    // full shutdown until any pending requests are fully copied using this+    // property on the server handler.  We increment before (possibly)+    // starting the copy observer and decrement when the copy completes,+    // ensuring that all copies complete before the server fully shuts down.+    //+    // We do this for every request primarily to simplify maintenance of this+    // property (and also because it's less fragile when we can remove the+    // zero-sized body hack used above).+    this._pendingRequests++;++    var server = this._server;++    // If we have a body, send it -- if we don't, then don't bother with a+    // heavyweight async copy which doesn't need to happen and just do+    // response post-processing (usually handled by the copy observer)+    // directly+    if (available != 0)+    {+      /**+       * Observer of the copying of data from the body stream generated by a+       * request handler to the output stream for the server socket.  It+       * handles all post-request-writing cleanup details, such as closing+       * open streams and shutting down the server in case of errors.+       */+      var copyObserver =+        {+          onStartRequest: function(request, context) { /* don't care */ },++          /**+           * Called when the async stream copy completes.  This is place where+           * final cleanup should occur, including stream closures and+           * response destruction.  Note that errors which are detected here+           * should still shut down the server, for safety.+           */+          onStopRequest: function(request, cx, statusCode)+          {+            // statusCode can indicate user-triggered failures (e.g. if the user+            // closes the connection during the copy, which would cause a status+            // of NS_ERROR_NET_RESET), so don't treat its value being an error+            // code as catastrophic.  I can create this situation when running+            // Mochitests in a debug build by clicking the Stop button during+            // test execution, but it's not exactly a surefire way to reproduce+            // the problem.+            if (!Components.isSuccessCode(statusCode))+            {+              dumpn("*** WARNING: non-success statusCode in onStopRequest: " ++                    statusCode);+            }++            // we're completely finished with this response+            response.destroy();++            connection.end();+          },++          QueryInterface: function(aIID)+          {+            if (aIID.equals(Ci.nsIRequestObserver) ||+                aIID.equals(Ci.nsISupports))+              return this;++            throw Cr.NS_ERROR_NO_INTERFACE;+          }+        };+++      //+      // Now write out the body, async since we might be serving this to+      // ourselves on the same thread, and writing too much data would deadlock.+      //+      var copier = new StreamCopier(bodyStream, outStream,+                                    null,+                                    true, true, 8192);+      copier.asyncCopy(copyObserver, null);+    }+    else+    {+      // finished with the response -- destroy+      response.destroy();+      this._server._endConnection(connection);+    }+  },++  // FIELDS++  /**+   * This object contains the default handlers for the various HTTP error codes.+   */+  _defaultErrors:+  {+    400: function(metadata, response)+    {+      // none of the data in metadata is reliable, so hard-code everything here+      response.setStatusLine("1.1", 400, "Bad Request");+      response.setHeader("Content-Type", "text/plain", false);++      var body = "Bad request\n";+      response.bodyOutputStream.write(body, body.length);+    },+    403: function(metadata, response)+    {+      response.setStatusLine(metadata.httpVersion, 403, "Forbidden");+      response.setHeader("Content-Type", "text/html", false);++      var body = "<html>\+                    <head><title>403 Forbidden</title></head>\+                    <body>\+                      <h1>403 Forbidden</h1>\+                    </body>\+                  </html>";+      response.bodyOutputStream.write(body, body.length);+    },+    404: function(metadata, response)+    {+      response.setStatusLine(metadata.httpVersion, 404, "Not Found");+      response.setHeader("Content-Type", "text/html", false);++      var body = "<html>\+                    <head><title>404 Not Found</title></head>\+                    <body>\+                      <h1>404 Not Found</h1>\+                      <p>\+                        <span style='font-family: monospace;'>" ++                          htmlEscape(metadata.path) ++                       "</span> was not found.\+                      </p>\+                    </body>\+                  </html>";+      response.bodyOutputStream.write(body, body.length);+    },+    500: function(metadata, response)+    {+      response.setStatusLine(metadata.httpVersion,+                             500,+                             "Internal Server Error");+      response.setHeader("Content-Type", "text/html", false);++      var body = "<html>\+                    <head><title>500 Internal Server Error</title></head>\+                    <body>\+                      <h1>500 Internal Server Error</h1>\+                      <p>Something's broken in this server and\+                        needs to be fixed.</p>\+                    </body>\+                  </html>";+      response.bodyOutputStream.write(body, body.length);+    },+    501: function(metadata, response)+    {+      response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");+      response.setHeader("Content-Type", "text/html", false);++      var body = "<html>\+                    <head><title>501 Not Implemented</title></head>\+                    <body>\+                      <h1>501 Not Implemented</h1>\+                      <p>This server is not (yet) Apache.</p>\+                    </body>\+                  </html>";+      response.bodyOutputStream.write(body, body.length);+    },+    505: function(metadata, response)+    {+      response.setStatusLine("1.1", 505, "HTTP Version Not Supported");+      response.setHeader("Content-Type", "text/html", false);++      var body = "<html>\+                    <head><title>505 HTTP Version Not Supported</title></head>\+                    <body>\+                      <h1>505 HTTP Version Not Supported</h1>\+                      <p>This server only supports HTTP/1.0 and HTTP/1.1\+                        connections.</p>\+                    </body>\+                  </html>";+      response.bodyOutputStream.write(body, body.length);+    }+  },++  /**+   * Contains handlers for the default set of URIs contained in this server.+   */+  _defaultPaths:+  {+    "/": function(metadata, response)+    {+      response.setStatusLine(metadata.httpVersion, 200, "OK");+      response.setHeader("Content-Type", "text/html", false);++      var body = "<html>\+                    <head><title>MozJSHTTP</title></head>\+                    <body>\+                      <h1>MozJSHTTP</h1>\+                      <p>If you're seeing this page, MozJSHTTP is up and\+                        serving requests!  Now set a base path and serve some\+                        files!</p>\+                    </body>\+                  </html>";++      response.bodyOutputStream.write(body, body.length);+    },++    "/trace": function(metadata, response)+    {+      response.setStatusLine(metadata.httpVersion, 200, "OK");+      response.setHeader("Content-Type", "text/plain", false);++      var body = "Request (semantically equivalent, slightly reformatted):\n\n";+      body += metadata.method + " " + metadata.path;++      if (metadata.queryString)+        body +=  "?" + metadata.queryString;+        +      body += " HTTP/" + metadata.httpVersion + "\r\n";++      var headEnum = metadata.headers;+      while (headEnum.hasMoreElements())+      {+        var fieldName = headEnum.getNext()+                                .QueryInterface(Ci.nsISupportsString)+                                .data;+        body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n";+      }++      response.bodyOutputStream.write(body, body.length);+    }+  }+};+++/**+ * Maps absolute paths to files on the local file system (as nsILocalFiles).+ */+function FileMap()+{+  /** Hash which will map paths to nsILocalFiles. */+  this._map = {};+}+FileMap.prototype =+{+  // PUBLIC API++  /**+   * Maps key to a clone of the nsILocalFile value if value is non-null;+   * otherwise, removes any extant mapping for key.+   *+   * @param key : string+   *   string to which a clone of value is mapped+   * @param value : nsILocalFile+   *   the file to map to key, or null to remove a mapping+   */+  put: function(key, value)+  {+    if (value)+      this._map[key] = value.clone();+    else+      delete this._map[key];+  },++  /**+   * Returns a clone of the nsILocalFile mapped to key, or null if no such+   * mapping exists.+   *+   * @param key : string+   *   key to which the returned file maps+   * @returns nsILocalFile+   *   a clone of the mapped file, or null if no mapping exists+   */+  get: function(key)+  {+    var val = this._map[key];+    return val ? val.clone() : null;+  }+};+++// Response CONSTANTS++// token       = *<any CHAR except CTLs or separators>+// CHAR        = <any US-ASCII character (0-127)>+// CTL         = <any US-ASCII control character (0-31) and DEL (127)>+// separators  = "(" | ")" | "<" | ">" | "@"+//             | "," | ";" | ":" | "\" | <">+//             | "/" | "[" | "]" | "?" | "="+//             | "{" | "}" | SP  | HT+const IS_TOKEN_ARRAY =+  [0, 0, 0, 0, 0, 0, 0, 0, //   0+   0, 0, 0, 0, 0, 0, 0, 0, //   8+   0, 0, 0, 0, 0, 0, 0, 0, //  16+   0, 0, 0, 0, 0, 0, 0, 0, //  24++   0, 1, 0, 1, 1, 1, 1, 1, //  32+   0, 0, 1, 1, 0, 1, 1, 0, //  40+   1, 1, 1, 1, 1, 1, 1, 1, //  48+   1, 1, 0, 0, 0, 0, 0, 0, //  56++   0, 1, 1, 1, 1, 1, 1, 1, //  64+   1, 1, 1, 1, 1, 1, 1, 1, //  72+   1, 1, 1, 1, 1, 1, 1, 1, //  80+   1, 1, 1, 0, 0, 0, 1, 1, //  88++   1, 1, 1, 1, 1, 1, 1, 1, //  96+   1, 1, 1, 1, 1, 1, 1, 1, // 104+   1, 1, 1, 1, 1, 1, 1, 1, // 112+   1, 1, 1, 0, 1, 0, 1];   // 120+++/**+ * Determines whether the given character code is a CTL.+ *+ * @param code : uint+ *   the character code+ * @returns boolean+ *   true if code is a CTL, false otherwise+ */+function isCTL(code)+{+  return (code >= 0 && code <= 31) || (code == 127);+}++/**+ * Represents a response to an HTTP request, encapsulating all details of that+ * response.  This includes all headers, the HTTP version, status code and+ * explanation, and the entity itself.+ */+function Response()+{+  // delegate initialization behavior to .recycle(), for code-sharing;+  // see there for field descriptions as well+  this.recycle();+}+Response.prototype =+{+  // PUBLIC CONSTRUCTION API++  //+  // see nsIHttpResponse.bodyOutputStream+  //+  get bodyOutputStream()+  {+    this._ensureAlive();++    if (!this._bodyOutputStream && !this._outputProcessed)+    {+      const PR_UINT32_MAX = Math.pow(2, 32) - 1;+      var pipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);+      this._bodyOutputStream = pipe.outputStream;+      this._bodyInputStream = pipe.inputStream;+    }++    return this._bodyOutputStream;+  },++  //+  // see nsIHttpResponse.write+  //+  write: function(data)+  {+    var dataAsString = String(data);+    this.bodyOutputStream.write(dataAsString, dataAsString.length);+  },++  //+  // see nsIHttpResponse.setStatusLine+  //+  setStatusLine: function(httpVersion, code, description)+  {+    this._ensureAlive();++    if (!(code >= 0 && code < 1000))+      throw Cr.NS_ERROR_INVALID_ARG;++    try+    {+      var httpVer;+      // avoid version construction for the most common cases+      if (!httpVersion || httpVersion == "1.1")+        httpVer = nsHttpVersion.HTTP_1_1;+      else if (httpVersion == "1.0")+        httpVer = nsHttpVersion.HTTP_1_0;+      else+        httpVer = new nsHttpVersion(httpVersion);+    }+    catch (e)+    {+      throw Cr.NS_ERROR_INVALID_ARG;+    }++    // Reason-Phrase = *<TEXT, excluding CR, LF>+    // TEXT          = <any OCTET except CTLs, but including LWS>+    //+    // XXX this ends up disallowing octets which aren't Unicode, I think -- not+    //     much to do if description is IDL'd as string+    if (!description)+      description = "";+    for (var i = 0; i < description.length; i++)+      if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t")+        throw Cr.NS_ERROR_INVALID_ARG;++    // set the values only after validation to preserve atomicity+    this._httpDescription = description;+    this._httpCode = code;+    this._httpVersion = httpVer;+  },++  //+  // see nsIHttpResponse.setHeader+  //+  setHeader: function(name, value, merge)+  {+    this._ensureAlive();++    this._headers.setHeader(name, value, merge);+  },++  // POST-CONSTRUCTION API (not exposed externally)++  /**+   * The HTTP version number of this, as a string (e.g. "1.1").+   */+  get httpVersion()+  {+    this._ensureAlive();++    return this._httpVersion.toString();+  },++  /**+   * The HTTP status code of this response, as a string of three characters per+   * RFC 2616.+   */+  get httpCode()+  {+    this._ensureAlive();++    var codeString = (this._httpCode < 10 ? "0" : "") ++                     (this._httpCode < 100 ? "0" : "") ++                     this._httpCode;+    return codeString;+  },++  /**+   * The description of the HTTP status code of this response, or "" if none is+   * set.+   */+  get httpDescription()+  {+    this._ensureAlive();++    return this._httpDescription;+  },++  /**+   * The headers in this response, as an nsHttpHeaders object.+   */+  get headers()+  {+    this._ensureAlive();++    return this._headers;+  },++  //+  // see nsHttpHeaders.getHeader+  //+  getHeader: function(name)+  {+    this._ensureAlive();++    return this._headers.getHeader(name);+  },++  /**+   * A stream containing the data stored in the body of this response, which is+   * the data written to this.bodyOutputStream.  Accessing this property will+   * prevent further writes to bodyOutputStream and will remove that property+   * from this, so the only time this should be accessed should be after this+   * Response has been fully processed by a request handler.+   */+  get bodyInputStream()+  {+    this._ensureAlive();++    if (!this._outputProcessed)+    {+      // if nothing was ever written to bodyOutputStream, we never created a+      // pipe -- do so now by writing the empty string to this.bodyOutputStream+      if (!this._bodyOutputStream)+        this.bodyOutputStream.write("", 0);++      this._outputProcessed = true;+    }+    if (this._bodyOutputStream)+    {+      this._bodyOutputStream.close(); // flushes buffered data+      this._bodyOutputStream = null;  // don't try closing again+    }+    return this._bodyInputStream;+  },++  /**+   * Resets this response to its original state, destroying any currently-held+   * resources in the process.  Use this method to invalidate an existing+   * response and reuse it later, such as when an arbitrary handler has+   * failed and may have altered the visible state of this (such as by+   * setting headers).+   *+   * This method may be called on Responses which have been .destroy()ed.+   */+  recycle: function()+  {+    if (this._bodyOutputStream)+    {+      this._bodyOutputStream.close();+      this._bodyOutputStream = null;+    }+    if (this._bodyInputStream)+    {+      this._bodyInputStream.close();+      this._bodyInputStream = null;+    }++    /**+     * The HTTP version of this response; defaults to 1.1 if not set by the+     * handler.+     */+    this._httpVersion = nsHttpVersion.HTTP_1_1;++    /**+     * The HTTP code of this response; defaults to 200.+     */+    this._httpCode = 200;++    /**+     * The description of the HTTP code in this response; defaults to "OK".+     */+    this._httpDescription = "OK";++    /**+     * An nsIHttpHeaders object in which the headers in this response should be+     * stored.+     */+    this._headers = new nsHttpHeaders();++    /**+     * Set to true when this has its .destroy() method called; further actions on+     * this will then fail.+     */+    this._destroyed = false;++    /**+     * Flipped when this.bodyOutputStream is closed; prevents the body from being+     * reopened after it has data written to it and has been closed.+     */+    this._outputProcessed = false;+  },++  /**+   * Destroys all resources held by this.  After this method is called, no other+   * method or property on this must be accessed (except .recycle, which may be+   * used to reuse this Response).  Although in many situations resources may be+   * automagically cleaned up, it is highly recommended that this method be+   * called whenever a Response is no longer used, both as a precaution and+   * because this implementation may not always do so.+   *+   * This method is idempotent.+   */+  destroy: function()+  {+    if (this._destroyed)+      return;++    if (this._bodyOutputStream)+    {+      this._bodyOutputStream.close();+      this._bodyOutputStream = null;+    }+    if (this._bodyInputStream)+    {+      this._bodyInputStream.close();+      this._bodyInputStream = null;+    }++    this._destroyed = true;+  },++  // PRIVATE IMPLEMENTATION++  /** Ensures that this hasn't had its .destroy() method called. */+  _ensureAlive: function()+  {+    if (this._destroyed)+      throw Cr.NS_ERROR_FAILURE;+  }+};+++/**+ * A container for utility functions used with HTTP headers.+ */+const headerUtils =+{+  /**+   * Normalizes fieldName (by converting it to lowercase) and ensures it is a+   * valid header field name (although not necessarily one specified in RFC+   * 2616).+   *+   * @throws NS_ERROR_INVALID_ARG+   *   if fieldName does not match the field-name production in RFC 2616+   * @returns string+   *   fieldName converted to lowercase if it is a valid header, for characters+   *   where case conversion is possible+   */+  normalizeFieldName: function(fieldName)+  {+    if (fieldName == "")+      throw Cr.NS_ERROR_INVALID_ARG;++    for (var i = 0, sz = fieldName.length; i < sz; i++)+    {+      if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)])+      {+        dumpn(fieldName + " is not a valid header field name!");+        throw Cr.NS_ERROR_INVALID_ARG;+      }+    }++    return fieldName.toLowerCase();+  },++  /**+   * Ensures that fieldValue is a valid header field value (although not+   * necessarily as specified in RFC 2616 if the corresponding field name is+   * part of the HTTP protocol), normalizes the value if it is, and+   * returns the normalized value.+   *+   * @param fieldValue : string+   *   a value to be normalized as an HTTP header field value+   * @throws NS_ERROR_INVALID_ARG+   *   if fieldValue does not match the field-value production in RFC 2616+   * @returns string+   *   fieldValue as a normalized HTTP header field value+   */+  normalizeFieldValue: function(fieldValue)+  {+    // field-value    = *( field-content | LWS )+    // field-content  = <the OCTETs making up the field-value+    //                  and consisting of either *TEXT or combinations+    //                  of token, separators, and quoted-string>+    // TEXT           = <any OCTET except CTLs,+    //                  but including LWS>+    // LWS            = [CRLF] 1*( SP | HT )+    //+    // quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> )+    // qdtext         = <any TEXT except <">>+    // quoted-pair    = "\" CHAR+    // CHAR           = <any US-ASCII character (octets 0 - 127)>++    // Any LWS that occurs between field-content MAY be replaced with a single+    // SP before interpreting the field value or forwarding the message+    // downstream (section 4.2); we replace 1*LWS with a single SP+    var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " ");++    // remove leading/trailing LWS (which has been converted to SP)+    val = val.replace(/^ +/, "").replace(/ +$/, "");++    // that should have taken care of all CTLs, so val should contain no CTLs+    for (var i = 0, len = val.length; i < len; i++)+      if (isCTL(val.charCodeAt(i)))+        throw Cr.NS_ERROR_INVALID_ARG;++    // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly+    //     normalize, however, so this can be construed as a tightening of the+    //     spec and not entirely as a bug+    return val;+  }+};++++/**+ * Converts the given string into a string which is safe for use in an HTML+ * context.+ *+ * @param str : string+ *   the string to make HTML-safe+ * @returns string+ *   an HTML-safe version of str+ */+function htmlEscape(str)+{+  // this is naive, but it'll work+  var s = "";+  for (var i = 0; i < str.length; i++)+    s += "&#" + str.charCodeAt(i) + ";";+  return s;+}+++/**+ * Constructs an object representing an HTTP version (see section 3.1).+ *+ * @param versionString+ *   a string of the form "#.#", where # is an non-negative decimal integer with+ *   or without leading zeros+ * @throws+ *   if versionString does not specify a valid HTTP version number+ */+function nsHttpVersion(versionString)+{+  var matches = /^(\d+)\.(\d+)$/.exec(versionString);+  if (!matches)+    throw "Not a valid HTTP version!";++  /** The major version number of this, as a number. */+  this.major = parseInt(matches[1], 10);++  /** The minor version number of this, as a number. */+  this.minor = parseInt(matches[2], 10);++  if (isNaN(this.major) || isNaN(this.minor) ||+      this.major < 0    || this.minor < 0)+    throw "Not a valid HTTP version!";+}+nsHttpVersion.prototype =+{+  /**+   * Returns the standard string representation of the HTTP version represented+   * by this (e.g., "1.1").+   */+  toString: function ()+  {+    return this.major + "." + this.minor;+  },++  /**+   * Returns true if this represents the same HTTP version as otherVersion,+   * false otherwise.+   *+   * @param otherVersion : nsHttpVersion+   *   the version to compare against this+   */+  equals: function (otherVersion)+  {+    return this.major == otherVersion.major &&+           this.minor == otherVersion.minor;+  }+};++nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0");+nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1");+++/**+ * An object which stores HTTP headers for a request or response.+ *+ * Note that since headers are case-insensitive, this object converts headers to+ * lowercase before storing them.  This allows the getHeader and hasHeader+ * methods to work correctly for any case of a header, but it means that the+ * values returned by .enumerator may not be equal case-sensitively to the+ * values passed to setHeader when adding headers to this.+ */+function nsHttpHeaders()+{+  /**+   * A hash of headers, with header field names as the keys and header field+   * values as the values.  Header field names are case-insensitive, but upon+   * insertion here they are converted to lowercase.  Header field values are+   * normalized upon insertion to contain no leading or trailing whitespace.+   *+   * Note also that per RFC 2616, section 4.2, two headers with the same name in+   * a message may be treated as one header with the same field name and a field+   * value consisting of the separate field values joined together with a "," in+   * their original order.  This hash stores multiple headers with the same name+   * in this manner.+   */+  this._headers = {};+}+nsHttpHeaders.prototype =+{+  /**+   * Sets the header represented by name and value in this.+   *+   * @param name : string+   *   the header name+   * @param value : string+   *   the header value+   * @throws NS_ERROR_INVALID_ARG+   *   if name or value is not a valid header component+   */+  setHeader: function(fieldName, fieldValue, merge)+  {+    var name = headerUtils.normalizeFieldName(fieldName);+    var value = headerUtils.normalizeFieldValue(fieldValue);++    if (merge && name in this._headers)+      this._headers[name] = this._headers[name] + "," + value;+    else+      this._headers[name] = value;+  },++  /**+   * Returns the value for the header specified by this.+   *+   * @throws NS_ERROR_INVALID_ARG+   *   if fieldName does not constitute a valid header field name+   * @throws NS_ERROR_NOT_AVAILABLE+   *   if the given header does not exist in this+   * @returns string+   *   the field value for the given header, possibly with non-semantic changes+   *   (i.e., leading/trailing whitespace stripped, whitespace runs replaced+   *   with spaces, etc.) at the option of the implementation+   */+  getHeader: function(fieldName)+  {+    var name = headerUtils.normalizeFieldName(fieldName);++    if (name in this._headers)+      return this._headers[name];+    else+      throw Cr.NS_ERROR_NOT_AVAILABLE;+  },++  /**+   * Returns true if a header with the given field name exists in this, false+   * otherwise.+   *+   * @param fieldName : string+   *   the field name whose existence is to be determined in this+   * @throws NS_ERROR_INVALID_ARG+   *   if fieldName does not constitute a valid header field name+   * @returns boolean+   *   true if the header's present, false otherwise+   */+  hasHeader: function(fieldName)+  {+    var name = headerUtils.normalizeFieldName(fieldName);+    return (name in this._headers);+  },++  /**+   * Returns a new enumerator over the field names of the headers in this, as+   * nsISupportsStrings.  The names returned will be in lowercase, regardless of+   * how they were input using setHeader (header names are case-insensitive per+   * RFC 2616).+   */+  get enumerator()+  {+    var headers = [];+    for (var i in this._headers)+    {+      var supports = new SupportsString();+      supports.data = i;+      headers.push(supports);+    }++    return new nsSimpleEnumerator(headers);+  }+};+++/**+ * Constructs an nsISimpleEnumerator for the given array of items.+ *+ * @param items : Array+ *   the items, which must all implement nsISupports+ */+function nsSimpleEnumerator(items)+{+  this._items = items;+  this._nextIndex = 0;+}+nsSimpleEnumerator.prototype =+{+  hasMoreElements: function()+  {+    return this._nextIndex < this._items.length;+  },+  getNext: function()+  {+    if (!this.hasMoreElements())+      throw Cr.NS_ERROR_NOT_AVAILABLE;++    return this._items[this._nextIndex++];+  },+  QueryInterface: function(aIID)+  {+    if (Ci.nsISimpleEnumerator.equals(aIID) ||+        Ci.nsISupports.equals(aIID))+      return this;++    throw Cr.NS_ERROR_NO_INTERFACE;+  }+};+++/**+ * A representation of the data in an HTTP request.+ *+ * @param port : uint+ *   the port on which the server receiving this request runs+ */+function Request(port)+{+  this._method = "";+  this._path = "";+  this._queryString = "";+  this._host = "";+  this._port = port;+  this._host = "localhost"; // XXX or from environment or server itself?++  /**+   * The headers in this request.+   */+  this._headers = new nsHttpHeaders();++  /**+   * For the addition of ad-hoc properties and new functionality without having+   * to change nsIHttpRequestMetadata every time; currently lazily created,+   * as its only use is in directory listings.+   */+  this._bag = null;+}+Request.prototype =+{+  // SERVER METADATA++  //+  // see nsIHttpRequestMetadata.host+  //+  get host()+  {+    return this._host;+  },++  //+  // see nsIHttpRequestMetadata.port+  //+  get port()+  {+    return this._port;+  },++  // REQUEST LINE++  //+  // see nsIHttpRequestMetadata.method+  //+  get method()+  {+    return this._method;+  },++  //+  // see nsIHttpRequestMetadata.httpVersion+  //+  get httpVersion()+  {+    return this._httpVersion.toString();+  },++  //+  // see nsIHttpRequestMetadata.path+  //+  get path()+  {+    return this._path;+  },++  //+  // see nsIHttpRequestMetadata.queryString+  //+  get queryString()+  {+    return this._queryString;+  },++  // HEADERS++  //+  // see nsIHttpRequestMetadata.getHeader+  //+  getHeader: function(name)+  {+    return this._headers.getHeader(name);+  },++  //+  // see nsIHttpRequestMetadata.hasHeader+  //+  hasHeader: function(name)+  {+    return this._headers.hasHeader(name);+  },++  //+  // see nsIHttpRequestMetadata.headers+  //+  get headers()+  {+    return this._headers.enumerator;+  },++  //+  // see nsIPropertyBag.enumerator+  //+  get enumerator()+  {+    this._ensurePropertyBag();+    return this._bag.enumerator;+  },++  //+  // see nsIPropertyBag.getProperty+  //+  getProperty: function(name) +  {+    this._ensurePropertyBag();+    return this._bag.getProperty(name);+  },+  +  /** Ensures a property bag has been created for ad-hoc behaviors. */+  _ensurePropertyBag: function()+  {+    if (!this._bag)+      this._bag = new WritablePropertyBag();+  }+};+++// XPCOM trappings++/**+ * Creates a factory for instances of an object created using the passed-in+ * constructor.+ */+function makeFactory(ctor)+{+  function ci(outer, iid)+  {+    if (outer != null)+      throw Components.results.NS_ERROR_NO_AGGREGATION;+    return (new ctor()).QueryInterface(iid);+  } ++  return {+           createInstance: ci,+           lockFactory: function(lock) { },+           QueryInterface: function(aIID)+           {+             if (Ci.nsIFactory.equals(aIID) ||+                 Ci.nsISupports.equals(aIID))+               return this;+             throw Cr.NS_ERROR_NO_INTERFACE;+           }+         };+}++/** The XPCOM module containing the HTTP server. */+const module =+{+  // nsISupports+  QueryInterface: function(aIID)+  {+    if (Ci.nsIModule.equals(aIID) ||+        Ci.nsISupports.equals(aIID))+      return this;+    throw Cr.NS_ERROR_NO_INTERFACE;+  },++  // nsIModule+  registerSelf: function(compMgr, fileSpec, location, type)+  {+    compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);+    +    for (var key in this._objects)+    {+      var obj = this._objects[key];+      compMgr.registerFactoryLocation(obj.CID, obj.className, obj.contractID,+                                               fileSpec, location, type);+    }+  },+  unregisterSelf: function (compMgr, location, type)+  {+    compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);++    for (var key in this._objects)+    {+      var obj = this._objects[key];+      compMgr.unregisterFactoryLocation(obj.CID, location);+    }+  },+  getClassObject: function(compMgr, cid, iid)+  {+    if (!iid.equals(Ci.nsIFactory))+      throw Cr.NS_ERROR_NOT_IMPLEMENTED;++    for (var key in this._objects)+    {+      if (cid.equals(this._objects[key].CID))+        return this._objects[key].factory;+    }+    +    throw Cr.NS_ERROR_NO_INTERFACE;+  },+  canUnload: function(compMgr)+  {+    return true;+  },++  // private implementation+  _objects:+  {+    server:+    {+      CID:         Components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"),+      contractID:  "@mozilla.org/server/jshttp;1",+      className:   "MozJSHTTP server",+      factory:     makeFactory(nsHttpServer)+    }+  }+};+++/** NSGetModule, so this code can be used as a JS component. */+function NSGetModule(compMgr, fileSpec)+{+  return module;+}+++/**+ * Creates a new HTTP server listening for loopback traffic on the given port,+ * starts it, and runs the server until the server processes a shutdown request,+ * spinning an event loop so that events posted by the server's socket are+ * processed.+ *+ * This method is primarily intended for use in running this script from within+ * xpcshell and running a functional HTTP server without having to deal with+ * non-essential details.+ *+ * Note that running multiple servers using variants of this method probably+ * doesn't work, simply due to how the internal event loop is spun and stopped.+ *+ * @note+ *   This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code);+ *   you should use this server as a component in Mozilla 1.8.+ * @param port+ *   the port on which the server will run, or -1 if there exists no preference+ *   for a specific port; note that attempting to use some values for this+ *   parameter (particularly those below 1024) may cause this method to throw or+ *   may result in the server being prematurely shut down+ * @param basePath+ *   a local directory from which requests will be served (i.e., if this is+ *   "/home/jwalden/" then a request to /index.html will load+ *   /home/jwalden/index.html); if this is omitted, only the default URLs in+ *   this server implementation will be functional+ */+function server(port, basePath)+{+  if (basePath)+  {+    var lp = Cc["@mozilla.org/file/local;1"]+               .createInstance(Ci.nsILocalFile);+    lp.initWithPath(basePath);+  }++  // if you're running this, you probably want to see debugging info+  DEBUG = true;++  var srv = new nsHttpServer();+  if (lp)+    srv.registerDirectory("/", lp);+  srv.registerContentType("sjs", SJS_TYPE);+  srv.start(port);++  var thread = gThreadManager.currentThread;+  while (!srv.isStopped())+    thread.processNextEvent(true);++  // get rid of any pending requests+  while (thread.hasPendingEvents())+    thread.processNextEvent(true);++  DEBUG = false;+}diff --git extensions/httpserv/components/src/sbhttpserv.js extensions/httpserv/components/src/sbhttpserv.jsnew file mode 100644index 0000000..7127e52--- /dev/null+++ extensions/httpserv/components/src/sbhttpserv.js@@ -0,0 +1,232 @@+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */+/* vim:set ts=2 sw=2 sts=2 et: */++/**+ * Test component for http serving+ */++const Cc = Components.classes;+const Ci = Components.interfaces;++Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");+Components.utils.import("resource://app/jsmodules/SBProperties.jsm");++function dd(msg) {+  //Components.utils.reportError(msg);+}++/**+ * getter for the remote API object+ */+__defineGetter__("songbird", function _get_songbird() {+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);+  var uri = ios.newURI("chrome://dummy/content/", null, null);+  var hiddenWindow = Cc["@mozilla.org/appshell/appShellService;1"]+                       .getService(Ci.nsIAppShellService)+                       .hiddenDOMWindow;++  delete songbird;+  dd(<>attempting to create privileged songbird object:+       uri = {uri} ({uri.spec})+       hiddenWindow = {hiddenWindow}+       </>);+  songbird = Cc["@songbirdnest.com/remoteapi/remoteplayer-factory;1"]+               .getService(Ci.sbIRemotePlayerFactory)+               .createPrivileged(uri, hiddenWindow);+  return songbird;+});++/**+ * getter for the FUEL object+ */+__defineGetter__("Application", function _get_FUEL() {+  delete Application;+  Application = Cc["@mozilla.org/fuel/application;1"].getService(Ci.extIApplication);+  return Application;+});++function SBHttpServ() {+}++SBHttpServ.prototype._getContentType = function SBHttpServ__getContentType(aURI) {+  let match = /\.([^.])*/(aURI.spec);+  if (match) {+    const map = {+    };+    if (match[1] in map) {+      return map[match[1]];+    }+  }+  try {+    return Cc["@mozilla.org/uriloader/external-helper-app-service;1"]+             .getService(Ci.nsIMIMEService)+             .getTypeFromURI(aURI);+  } catch (ex) { /* ignore, no known mapping */}+  return "application/octet-stream";+};++/**+ * nsIObserver implementation+ */+SBHttpServ.prototype.observe = function SBHttpServ_observe(aSubject, aTopic, aData)+{+  dd("observing " + aTopic);+  switch (aTopic) {+    case "app-startup": {+      let obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);+      obs.addObserver(this, "xul-window-visible", false);+      break;+    }+    case "xul-window-visible": {+      let obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);+      obs.removeObserver(this, "xul-window-visible");+      let httpdSrcFile = __LOCATION__.parent;+      httpdSrcFile.append("httpd.js");+      let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);+      Components.utils.import(ios.newFileURI(httpdSrcFile).spec, this.__parent__);+      this._server = new nsHttpServer();+      this._server.registerPrefixHandler("/contents/", this);+      this._server.start(Application.prefs.getValue("extensions.httpserv.port", 80));+      break;+    }+  }+};++/**+ * nsIHttpRequestHandler implementation+ */+SBHttpServ.prototype.handle = function SBHttpServ_handle(aMetadata, aResponse)+{+  dd(<>handling {aMetadata.path}</>);+  try {+    var pathParts = aMetadata.path.split("/");+    pathParts.shift(); // drop the empty leading component+    if (pathParts[0] == "contents") {+      pathParts.shift();  // drop the leading "contents" directory+    }+    var context = songbird.mainLibrary;+    +    dd(<>looking at path parts {uneval(pathParts)}</>);+    for (let idx = 0; idx < pathParts.length; idx += 2) {+      let dirName = decodeURIComponent(pathParts[idx]);+      let dirValue = decodeURIComponent(pathParts[idx + 1]);+      if (dirName === "") {+        break;+      }+      if (["albums", "artists", "genres", "playlists"].indexOf(dirName) != -1) {+        if (typeof(dirValue) == "undefined" || dirValue === "") {+          this.generateFilterListing(aMetadata, context, dirName, aResponse);+          context = null;+          break;+        } else {+          context = context[dirName][dirValue];+          if (!context) {+            dd(<>can't find {dirName} / {dirValue}</>);+            throw new HttpError(404, "Not Found");+          }+        }+      } else if (dirName == "items") {+        if (typeof(dirValue) == "undefined" || dirValue === "") {+          break;+        } else {+          context = context[dirName][dirValue];+          if (!context) {+            dd(<>can't find {dirName} / {dirValue}</>);+            throw new HttpError(404, "Not Found");+          }+        }+      } else {+        dd(<>ignoring unknown directory [{dirName}]</>);+        throw new HttpError(404, "Not Found");;+      }+    }+    if ((context instanceof Ci.sbIMediaItem) && !(context instanceof Ci.sbIMediaList)) {+      const BUFFER_SIZE = 0x80000;+      aResponse.setHeader("Content-Type", this._getContentType(context.contentSrc));+      let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);+      let channel = ios.newChannelFromURI(context.contentSrc);+      aResponse.bodyOutputStream.writeFrom(channel.open(), -1);+    } else if (context && ("items" in context)) {+      this.generateItemListing(aMetadata, context, aResponse);+    }+  } catch (e) {+    Components.utils.reportError(e);+    throw e;+  }+};++SBHttpServ.prototype.generateFilterListing =+function SBHttpServ_generateFilterListing(aMetadata, aContext, aType, aResponse) {+  aResponse.setHeader("Content-Type", "text/html");+  aResponse.write(String(<><![CDATA[+    <html>+      <head>+        <title>Directory listing for {aMetadata.path}</title>+      </head>+      <body>+        <h1>Directory listing for {aMetadata.path}</h1>+        <ul>+  ]]></>).replace(/\{[^}]*\}/g, function(c) eval(c.substr(1,c.length-2))));++  for (let item in aContext[aType]) {+    aResponse.write('<li><a href="' + item + '/">' + item + '</a></li>');+  }++  aResponse.write(<><![CDATA[+    </ul></body></html>+  ]]></>);+}++SBHttpServ.prototype.generateItemListing =+function SBHttpServ_generateItemListing(aMetadata, aContext, aResponse) {+  aResponse.setHeader("Content-Type", "text/html");+  aResponse.write(String(<><![CDATA[+    <html>+      <head>+        <title>Directory listing for {aMetadata.path}</title>+      </head>+      <body>+        <h1>Directory listing for {aMetadata.path}</h1>+        <ul>+  ]]></>).replace(/\{[^}]*\}/g, function(c) eval(c.substr(1,c.length-2))));++  for each (let type in ["artists", "albums", "genres"]) {+    if ((type in aContext) && (aContext[type].length > 1)) {+      aResponse.write('<li><a href="' + type + '/">' + type + '/</a></li>');+    }+  }++  if ("items" in aContext) {+    for each (let item in aContext.items) {+      if (item && (item instanceof Ci.sbIMediaItem)) {+        if (item instanceof Ci.sbIMediaList) {+          continue;+        }+        aResponse.write(+          '<li><a href="items/' + item.guid + '">' ++            item.getProperty(SBProperties.trackName) ++          '</a></li>'+        );+      } else {+        aResponse.write("<li> item" + item + " is no good</li>");+      }+    }+  }+  aResponse.write('</ul></body></html>');+}++/**+ * nsISupports implementation+ */+SBHttpServ.prototype.QueryInterface = XPCOMUtils.generateQI([+  Ci.nsIHttpRequestHandler,+  Ci.nsIObserver+]);+SBHttpServ.prototype.classDescription = "Songbird HTTP Library Server";+SBHttpServ.prototype.classID = Components.ID("{95f96444-1dd1-11b2-9243-fdd98ecc30a2}");+SBHttpServ.prototype.contractID = "@songbirdnest.com/httpserv/test;1";+SBHttpServ.prototype._xpcom_categories = [+  {category: "app-startup", service: true}+];++var NSGetModule = XPCOMUtils.generateNSGetModule([SBHttpServ]);\ No newline at end of filediff --git extensions/httpserv/defaults/Makefile.in extensions/httpserv/defaults/Makefile.innew file mode 100644index 0000000..f765974--- /dev/null+++ extensions/httpserv/defaults/Makefile.in@@ -0,0 +1,36 @@+#+# BEGIN SONGBIRD GPL+# +# This file is part of the Songbird web player.+#+# Copyright(c) 2005-2008 POTI, Inc.+# http://www.songbirdnest.com+# +# This file may be licensed under the terms of of the+# GNU General Public License Version 2 (the GPL).+# +# Software distributed under the License is distributed +# on an AS IS basis, WITHOUT WARRANTY OF ANY KIND, either +# express or implied. See the GPL for the specific language +# governing rights and limitations.+#+# You should have received a copy of the GPL along with this +# program. If not, go to http://www.gnu.org/licenses/gpl.html+# or write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.+# +# END SONGBIRD GPL+#+++DEPTH     = ../../..+topsrcdir = @top_srcdir@+srcdir    = @srcdir@+VPATH     = @srcdir@++include $(DEPTH)/build/autodefs.mk++SUBDIRS = preferences \+          $(NULL)++include $(topsrcdir)/build/rules.mkdiff --git extensions/httpserv/defaults/preferences/Makefile.in extensions/httpserv/defaults/preferences/Makefile.innew file mode 100644index 0000000..d2266b6--- /dev/null+++ extensions/httpserv/defaults/preferences/Makefile.in@@ -0,0 +1,45 @@+#+# BEGIN SONGBIRD GPL+# +# This file is part of the Songbird web player.+#+# Copyright(c) 2005-2008 POTI, Inc.+# http://www.songbirdnest.com+# +# This file may be licensed under the terms of of the+# GNU General Public License Version 2 (the GPL).+# +# Software distributed under the License is distributed +# on an AS IS basis, WITHOUT WARRANTY OF ANY KIND, either +# express or implied. See the GPL for the specific language +# governing rights and limitations.+#+# You should have received a copy of the GPL along with this +# program. If not, go to http://www.gnu.org/licenses/gpl.html+# or write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.+# +# END SONGBIRD GPL+#+++DEPTH     = ../../../..+topsrcdir = @top_srcdir@+srcdir    = @srcdir@+VPATH     = @srcdir@++include $(DEPTH)/build/autodefs.mk++EXTRA_SCRIPT_COMPONENTS = $(srcdir)/prefs.js \+                          $(NULL)++TARGET_DIR = ../../.xpistage/defaults/preferences++include $(topsrcdir)/build/rules.mk++all:: extinstall++extinstall:+	$(MKDIR) -p $(TARGET_DIR)+	$(CP) -f $(EXTRA_SCRIPT_COMPONENTS) $(TARGET_DIR)+diff --git extensions/httpserv/defaults/preferences/prefs.js extensions/httpserv/defaults/preferences/prefs.jsnew file mode 100644index 0000000..582aaf2--- /dev/null+++ extensions/httpserv/defaults/preferences/prefs.js@@ -0,0 +1,2 @@+// port to serve things on+pref("extensions.httpserv.port", 38080);diff --git extensions/httpserv/install.rdf.in extensions/httpserv/install.rdf.innew file mode 100644index 0000000..1bc78a1--- /dev/null+++ extensions/httpserv/install.rdf.in@@ -0,0 +1,30 @@+<?xml version="1.0"?>++#filter substitution+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+     xmlns:em="http://www.mozilla.org/2004/em-rdf#"+     xmlns:songbird="http://www.songbirdnest.com/2007/addon-metadata-rdf#">++  <Description about="urn:mozilla:install-manifest">++    <!-- Songbird target application. -->+    <em:targetApplication>+      <Description>+        <em:id>songbird@songbirdnest.com</em:id>+        <em:minVersion>@EXTENSION_MIN_VER@</em:minVersion>+        <em:maxVersion>@EXTENSION_MAX_VER@</em:maxVersion>+      </Description>+    </em:targetApplication>++    <em:id>httpserv@songbirdnest.com</em:id>+    <em:type>2</em:type>+    <em:version>@EXTENSION_VER@</em:version>+    <em:name>httpserv</em:name>+	<em:creator>Mook (mook@songbirdnest.com)</em:creator>+	<em:description>Serve your music over http</em:description>++	<!--<em:iconURL>chrome://httpserv/content/icon.png</em:iconURL>-->+	<!--<em:homepageURL>http://addons.songbirdnest.com/addon/0000</em:homepageURL>-->+  </Description>+</RDF>+-- 1.5.5.1015.g9d258