Bug 342877 - Substantially revamp/rewrite the testing-only HTTP server used in necko unit tests, making it suitable for use in Mozilla tests which require files be stored on an HTTP server (e.g. because the test depends on certain HTTP headers being set or not set, the functionality uses HTTP, etc.) but which don't require that the server have a specified hostname. Docs, implementation, and tests (!) are in netwerk/test/httpserver/; if you have questions about using the server in unit tests or have some use case which the server could support better or doesn't support at all, CC me on the bug and I'll see how I can help. Huge (huge) thanks to biesi for the review of the huge patch. r=davel on test-harness changes, r=biesi on the server and test changes

git-svn-id: svn://10.0.0.236/trunk@217001 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
jwalden%mit.edu 2006-12-15 02:13:54 +00:00
parent 23b002054c
commit 6262239dc7
22 changed files with 4472 additions and 247 deletions

View File

@ -45,6 +45,10 @@ include $(DEPTH)/config/autoconf.mk
MODULE = test_necko
DIRS = \
httpserver \
$(NULL)
REQUIRES = xpcom \
string \
necko \

View File

@ -0,0 +1,81 @@
# vim: noexpandtab ts=8 sw=8
#
# ***** 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 MozJSHTTP code.
#
# The Initial Developer of the Original Code is
# Jeff Walden <jwalden+code@mit.edu>.
# Portions created by the Initial Developer are Copyright (C) 2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# 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 *****
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = test_necko
XPIDLSRCS = \
nsIHttpServer.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk
_UNIT_FILES := $(wildcard $(srcdir)/test/*.js)
_SERVER_SCRIPT_FILES = \
httpd.js \
$(NULL)
# copy the server to the xpcshell test harness directory
libs:: $(_SERVER_SCRIPT_FILES)
$(INSTALL) $^ $(DIST)/bin/test-harness/xpcshell-simple
#
# Server tests don't go in with necko's tests because:
#
# - we then have to think about file name conflicts in two different locations
# - head_*.js files in separate locations are both run, even when this is not
# desired (e.g., we must either always import httpd.js [slowing down test
# processing even for tests which don't need it] or we must manually import
# it in every test that uses it [which would be extremely annoying for the
# server tests which all want it])
# - httpd.js doesn't clutter the global scope too much, but it does become a
# concern (consider Cc/Ci/Cr in particular, which as constants cannot be
# safely redefined)
#
libs:: $(_UNIT_FILES)
$(INSTALL) $^ $(DIST)/bin/httpserver_tests
check::
$(RUN_TEST_PROGRAM) $(DIST)/bin/test_all.sh $(DIST)/bin/httpserver_tests

View File

@ -0,0 +1,97 @@
MozJSHTTP README
================
MozJSHTTP is a small cross-platform implementation of an HTTP/1.1 server in
JavaScript for the Mozilla platform.
MozJSHTTP may be used as an XPCOM component, as an inline script in a document
with XPCOM privileges, or from the XPCOM shell (xpcshell). Currently, its most-
supported method of use is from the XPCOM shell, where you can get all the
dynamicity of JS in adding request handlers and the like, but component-based
equivalent functionality is planned.
Using MozJSHTTP as an XPCOM Component
-------------------------------------
First, create an XPT file for nsIHttpServer.idl, using the xpidl tool included
in the Mozilla SDK for the environment in which you wish to run MozJSHTTP. See
<http://developer.mozilla.org/en/docs/XPIDL:xpidl> for further details on how to
do this.
Next, register httpd.js and nsIHttpServer.xpt in your Mozilla application. In
Firefox, these simply need to be added to the /components directory of your XPI.
Other applications may require use of regxpcom or other techniques; consult the
applicable documentation for further details.
Finally, create an instance of the server using the following command:
var server = Components.classes["@mozilla.org/server/jshttp;1"]
.createInstance(Components.interfaces.nsIHttpServer);
At this point you'll want to initialize the server, since by default it doesn't
serve many useful paths. For more information on this, see the IDL docs for the
nsIHttpServer interface in nsIHttpServer.idl, particularly for
registerDirectory (useful for mapping the contents of directories onto request
paths), registerPathHandler (for setting a custom handler for a specific path on
the server, such as CGI functionality), and registerFile (for mapping a file to
a specific path).
Finally, you'll want to start (and later stop) the server. Here's some example
code which does this:
server.start(8080); // port on which server will operate
// ...server now runs and serves requests...
server.stop();
Using MozJSHTTP as an Inline Script or from xpcshell
----------------------------------------------------
Using MozJSHTTP as a script or from xpcshell isn't very different from using it
as a component; the only real difference lies in how you create an instance of
the server. To create an instance, do the following:
var server = new nsHttpServer();
You now can use |server| exactly as you would when |server| was created as an
XPCOM component. Note, however, that doing so will trample over the global
namespace, and global values defined in MozJSHTTP will leak into your script.
This may typically be benign, but since some of the global values defined are
constants (specifically, Cc/Ci/Cr as abbreviations for the classes, interfaces,
and results properties of Components), it's possible this trampling could
break your script. In general you should use MozJSHTTP as an XPCOM component
whenever possible.
Known Issues
------------
While MozJSHTTP runs on Mozilla 1.8 and 1.9 platforms, it doesn't run quite as
well on 1.8 due to the absence of some APIs, specifically the threading APIs.
The biggest problem here is that server shutdown (see nsIHttpServer.stop) is not
guaranteed to complete after all pending requests have been served; if you are
using the server in 1.8 code, you should probably wait a few seconds after
calling server.stop() before the host application closes to ensure that all
requests have completed. Things probably aren't going to break too horribly if
you don't do this, but better safe than sorry.
To be clear: the guarantee that nsIHttpServer.stop says implementations should
make when possible (that .stop returns only when all pending requests have been
serviced) cannot be made in a 1.8 environment; it can be made in a 1.9
environment. Use 1.9 if this matters to you, or hack around it as described
here.
Other Goodies
-------------
A special testing function, |server|, is provided for use in xpcshell for quick
testing of the server; see the source code for details on its use. You don't
want to use this in a script, however, because doing so will block until the
server is shut down. It's also a good example of how to use the basic
functionality of MozJSHTTP, if you need one.
Have fun!

View File

@ -0,0 +1,22 @@
Bugs to fix:
- make content-length generation not rely on .available() returning the entire
size of the body stream's contents -- some sort of wrapper (but how does that
work for the unscriptable method WriteSegments, which is good to support from
a performance standpoint?)
Ideas for future improvements:
- mod_cern_meta or Apache asis functionality (probably the former, since
asis+binary files looks annoying but is a definite want, and line endings are
likely a pain):
<http://httpd.apache.org/docs/2.0/mod/mod_cern_meta.html> and
<http://httpd.apache.org/docs/2.0/mod/mod_asis.html>
- add API to disable response buffering which, when called, causes errors when
you try to do anything other than write to the body stream (i.e., modify
headers or status line) once you've written anything to it -- useful when
storing the entire response in memory is unfeasible (e.g., you're testing
>4GB download characteristics)
- add an API which performs asynchronous response processing (instead of
nsIHttpRequestHandler.handle, which must construct the response before control
returns; |void asyncHandle(request, response)|) -- useful, and can it be done
in JS?
- other awesomeness?

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,320 @@
/* ***** 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)
*
* 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"
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; 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 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 400 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);
};
/**
* 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(acbb5ea6-58b3-4750-bcc7-618910ebca9d)]
interface nsIHttpRequestMetadata : nsISupports
{
/**
* 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 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 should expose body of request here!
};
/**
* Represents an HTTP response, as described in RFC 2616, section 6.
*/
[scriptable, uuid(017f962b-137e-4911-887c-61808e89fa4f)]
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;
};

View File

@ -0,0 +1,81 @@
/* -*- 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 MozJSHTTP code.
*
* The Initial Developer of the Original Code is
* Jeff Walden <jwalden+code@mit.edu>.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
do_import_script("test-harness/xpcshell-simple/httpd.js");
/**
* Constructs a new nsHttpServer instance. This function is intended to
* encapsulate construction of a server so that at some point in the future
* it is possible to run these tests (with at most slight modifications) against
* the server when used as an XPCOM component (not as an inline script) with
* only slight modifications.
*/
function createServer()
{
return new nsHttpServer();
}
/**
* Creates a new HTTP channel.
*
* @param url
* the URL of the channel to create
*/
function makeChannel(url)
{
var ios = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
var chan = ios.newChannel(url, null, null)
.QueryInterface(Ci.nsIHttpChannel);
return chan;
}
/**
* Make a binary input stream wrapper for the given stream.
*
* @param stream
* the nsIInputStream to wrap
*/
function makeBIS(stream)
{
var bis = Cc["@mozilla.org/binaryinputstream;1"]
.createInstance(Ci.nsIBinaryInputStream);
bis.setInputStream(stream);
return bis;
}

View File

@ -0,0 +1,201 @@
/* -*- 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 MozJSHTTP code.
*
* The Initial Developer of the Original Code is
* Jeff Walden <jwalden+code@mit.edu>.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
// basic functionality test, from the client programmer's POV
var paths =
[
"http://localhost:4444/objHandler",
"http://localhost:4444/functionHandler",
"http://localhost:4444/non-existent-path" // intended to produce 404
];
var currPathIndex = 0;
var listener =
{
// NSISTREAMLISTENER
onDataAvailable: function(request, cx, inputStream, offset, count)
{
makeBIS(inputStream).readByteArray(count); // required by API
},
// NSIREQUESTOBSERVER
onStartRequest: function(request, cx)
{
var ch = request.QueryInterface(Ci.nsIHttpChannel)
.QueryInterface(Ci.nsIHttpChannelInternal);
// common properties *always* appended by server or invariants for every
// URL in paths
do_check_true(ch.contentLength > -1);
do_check_eq(ch.getResponseHeader("connection"), "close");
do_check_false(ch.isNoStoreResponse());
var reqMin = {}, reqMaj = {}, respMin = {}, respMaj = {};
switch (currPathIndex)
{
case 0:
do_check_eq(ch.responseStatus, 200);
do_check_true(ch.requestSucceeded);
do_check_eq(ch.getResponseHeader("content-type"), "text/plain");
do_check_eq(ch.responseStatusText, "OK");
ch.getRequestVersion(reqMaj, reqMin);
ch.getResponseVersion(respMaj, respMin);
do_check_true(reqMaj.value == respMaj.value &&
reqMin.value == respMin.value);
break;
case 1:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
do_check_eq(ch.getResponseHeader("foopy"), "quux-baz");
do_check_eq(ch.responseStatusText, "Page Not Found");
ch.getResponseVersion(respMaj, respMin);
do_check_true(respMaj.value == 1 && respMin.value == 1);
break;
case 2:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
break;
}
},
onStopRequest: function(request, cx, status)
{
if (++currPathIndex == paths.length)
srv.stop();
else
performNextTest();
do_test_finished();
},
// NSISUPPORTS
QueryInterface: function(aIID)
{
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsIRequestObserver) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
function performNextTest()
{
do_test_pending();
var ch = makeChannel(paths[currPathIndex]);
ch.asyncOpen(listener, null);
}
var srv;
function run_test()
{
srv = createServer();
// base path
// XXX should actually test this works with a file by comparing streams!
var dirServ = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
var path = dirServ.get("CurProcD", Ci.nsILocalFile);
srv.registerDirectory("/", path);
// register a few test paths
srv.registerPathHandler("/objHandler", objHandler);
srv.registerPathHandler("/functionHandler", functionHandler);
srv.start(4444);
performNextTest();
}
// PATH HANDLERS
// /objHandler
var objHandler =
{
handle: function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/plain", false);
var body = "Request (slightly reformatted):\n\n";
body += metadata.method + " " + metadata.path;
do_check_eq(metadata.port, 4444);
if (metadata.queryString)
body += "?" + metadata.queryString;
body += " HTTP/" + metadata.httpVersion + "\n";
var headEnum = metadata.headers;
while (headEnum.hasMoreElements())
{
var fieldName = headEnum.getNext()
.QueryInterface(Ci.nsISupportsString)
.data;
body += fieldName + ": " + metadata.getHeader(fieldName) + "\n";
}
response.bodyOutputStream.write(body, body.length);
},
QueryInterface: function(id)
{
if (id.equals(Ci.nsISupports) || id.equals(Ci.nsIHttpRequestHandler))
return this;
throw Cr.NS_ERROR_NOINTERFACE;
}
};
// /functionHandler
function functionHandler(metadata, response)
{
response.setStatusLine("1.1", 404, "Page Not Found");
response.setHeader("foopy", "quux-baz");
do_check_eq(metadata.port, 4444);
do_check_eq(metadata.host, "localhost");
do_check_eq(metadata.path.charAt(0), "/");
var body = "this is text\n";
response.bodyOutputStream.write(body, body.length);
}

View File

@ -0,0 +1,120 @@
/* -*- 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 MozJSHTTP code.
*
* The Initial Developer of the Original Code is
* Jeff Walden <jwalden+code@mit.edu>.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
// in its original incarnation, the server didn't like empty response-bodies;
// see the comment in _end for details
var paths =
[
"http://localhost:4444/empty-body-unwritten",
"http://localhost:4444/empty-body-written"
];
var currPathIndex = 0;
var listener =
{
// NSISTREAMLISTENER
onDataAvailable: function(request, cx, inputStream, offset, count)
{
makeBIS(inputStream).readByteArray(count); // required by API
},
// NSIREQUESTOBSERVER
onStartRequest: function(request, cx)
{
var ch = request.QueryInterface(Ci.nsIHttpChannel)
.QueryInterface(Ci.nsIHttpChannelInternal);
do_check_true(ch.contentLength == 0);
},
onStopRequest: function(request, cx, status)
{
if (++currPathIndex == paths.length)
srv.stop();
else
performNextTest();
do_test_finished();
},
// NSISUPPORTS
QueryInterface: function(aIID)
{
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsIRequestObserver) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
function performNextTest()
{
do_test_pending();
var ch = makeChannel(paths[currPathIndex]);
ch.asyncOpen(listener, null);
}
var srv;
function run_test()
{
srv = createServer();
// register a few test paths
srv.registerPathHandler("/empty-body-unwritten", emptyBodyUnwritten);
srv.registerPathHandler("/empty-body-written", emptyBodyWritten);
srv.start(4444);
performNextTest();
}
// PATH HANDLERS
// /empty-body-unwritten
function emptyBodyUnwritten(metadata, response)
{
response.setStatusLine("1.1", 200, "OK");
}
// /empty-body-written
function emptyBodyWritten(metadata, response)
{
response.setStatusLine("1.1", 200, "OK");
var body = "";
response.bodyOutputStream.write(body, body.length);
}

View File

@ -0,0 +1,152 @@
/* -*- 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 MozJSHTTP code.
*
* The Initial Developer of the Original Code is
* Jeff Walden <jwalden+code@mit.edu>.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
// Request handlers may throw exceptions, and those exception should be caught
// by the server and converted into the proper error codes.
var paths =
[
"http://localhost:4444/throws/exception",
"http://localhost:4444/this/file/does/not/exist/and/404s",
"http://localhost:4444/attempts/404/fails/so/400/fails/so/500s"
];
var currPathIndex = 0;
var listener =
{
// NSISTREAMLISTENER
onDataAvailable: function(request, cx, inputStream, offset, count)
{
makeBIS(inputStream).readByteArray(count); // required by API
},
// NSIREQUESTOBSERVER
onStartRequest: function(request, cx)
{
var ch = request.QueryInterface(Ci.nsIHttpChannel)
.QueryInterface(Ci.nsIHttpChannelInternal);
switch (currPathIndex)
{
case 0:
checkStatusLine(ch, 1, 1, 500, "Internal Server Error");
break;
case 1:
checkStatusLine(ch, 1, 1, 400, "Bad Request");
break;
case 2:
checkStatusLine(ch, 1, 1, 500, "Internal Server Error");
break;
}
},
onStopRequest: function(request, cx, status)
{
do_check_true(Components.isSuccessCode(status));
switch (currPathIndex)
{
case 0:
break;
case 1:
srv.registerErrorHandler(400, throwsException);
break;
case 2:
break;
}
if (++currPathIndex == paths.length)
srv.stop();
else
performNextTest();
do_test_finished();
},
// NSISUPPORTS
QueryInterface: function(aIID)
{
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsIRequestObserver) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
function checkStatusLine(channel, httpMaxVer, httpMinVer, httpCode, statusText)
{
do_check_eq(channel.responseStatus, httpCode);
do_check_eq(channel.responseStatusText, statusText);
var respMaj = {}, respMin = {};
channel.getResponseVersion(respMaj, respMin);
do_check_eq(respMaj.value, httpMaxVer);
do_check_eq(respMin.value, httpMinVer);
}
function performNextTest()
{
do_test_pending();
var ch = makeChannel(paths[currPathIndex]);
ch.asyncOpen(listener, null);
}
var srv;
function run_test()
{
srv = createServer();
srv.registerErrorHandler(404, throwsException);
srv.registerPathHandler("/throws/exception", throwsException);
srv.start(4444);
performNextTest();
}
// PATH HANDLERS
// /throws/exception (and also a 404 and 400 error handler)
function throwsException(metadata, response)
{
throw "this shouldn't cause an exit...";
do_throw("Not reached!");
}

View File

@ -0,0 +1,221 @@
/* -*- 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 MozJSHTTP code.
*
* The Initial Developer of the Original Code is
* Jeff Walden <jwalden+code@mit.edu>.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
// tests for header storage in MozJSHTTP; nsHttpHeaders is an *internal* data
// structure and is not to be used directly outside of httpd.js itself except
// for testing purposes
/**
* Ensures that a fieldname-fieldvalue combination is a valid header.
*
* @param fieldName
* the name of the header
* @param fieldValue
* the value of the header
* @param headers
* an nsHttpHeaders object to use to check validity
*/
function assertValidHeader(fieldName, fieldValue, headers)
{
try
{
headers.setHeader(fieldName, fieldValue, false);
}
catch (e)
{
do_throw("Unexpected exception thrown: " + e);
}
}
/**
* Ensures that a fieldname-fieldvalue combination is not a valid header.
*
* @param fieldName
* the name of the header
* @param fieldValue
* the value of the header
* @param headers
* an nsHttpHeaders object to use to check validity
*/
function assertInvalidHeader(fieldName, fieldValue, headers)
{
try
{
headers.setHeader(fieldName, fieldValue, false);
throw "Setting (" + fieldName + ", " +
fieldValue + ") as header succeeded!";
}
catch (e)
{
if (e !== Cr.NS_ERROR_INVALID_ARG)
do_throw("Unexpected exception thrown: " + e);
}
}
function run_test()
{
testHeaderValidity();
testGetHeader();
testHeaderEnumerator();
testHasHeader();
}
function testHeaderValidity()
{
var headers = new nsHttpHeaders();
assertInvalidHeader("f o", "bar", headers);
assertInvalidHeader("f\0n", "bar", headers);
assertInvalidHeader("foo:", "bar", headers);
assertInvalidHeader("f\\o", "bar", headers);
assertInvalidHeader("@xml", "bar", headers);
assertInvalidHeader("fiz(", "bar", headers);
assertInvalidHeader("HTTP/1.1", "bar", headers);
assertInvalidHeader("b\"b", "bar", headers);
assertInvalidHeader("ascsd\t", "bar", headers);
assertInvalidHeader("{fds", "bar", headers);
assertInvalidHeader("baz?", "bar", headers);
assertInvalidHeader("a\\b\\c", "bar", headers);
assertInvalidHeader("\0x7F", "bar", headers);
assertInvalidHeader("\0x1F", "bar", headers);
assertInvalidHeader("f\n", "bar", headers);
assertInvalidHeader("foo", "b\nar", headers);
assertInvalidHeader("foo", "b\rar", headers);
assertInvalidHeader("foo", "b\0", headers);
// request splitting, fwiw -- we're actually immune to this type of attack so
// long as we don't implement persistent connections
assertInvalidHeader("f\r\nGET /badness HTTP/1.1\r\nFoo", "bar", headers);
assertValidHeader("f'", "baz", headers);
assertValidHeader("f`", "baz", headers);
assertValidHeader("f.", "baz", headers);
assertValidHeader("f---", "baz", headers);
assertValidHeader("---", "baz", headers);
assertValidHeader("~~~", "baz", headers);
assertValidHeader("~~~", "b\r\n bar", headers);
assertValidHeader("~~~", "b\r\n\tbar", headers);
}
function testGetHeader()
{
var headers = new nsHttpHeaders();
headers.setHeader("Content-Type", "text/html", false);
var c = headers.getHeader("content-type");
do_check_eq(c, "text/html");
headers.setHeader("test", "FOO", false);
var c = headers.getHeader("test");
do_check_eq(c, "FOO");
try
{
headers.getHeader(":");
throw "Failed to throw for invalid header";
}
catch (e)
{
if (e !== Cr.NS_ERROR_INVALID_ARG)
do_throw("headers.getHeader(':') must throw invalid arg");
}
try
{
headers.getHeader("valid");
throw 'header doesn\'t exist';
}
catch (e)
{
if (e !== Cr.NS_ERROR_NOT_AVAILABLE)
do_throw("shouldn't be a header named 'valid' in headers!");
}
}
function testHeaderEnumerator()
{
var headers = new nsHttpHeaders();
var heads =
{
"foo": "17",
"baz": "two six niner",
"decaf": "class Program { int .7; int main(){ .7 = 5; return 7 - .7; } }"
};
for (var i in heads)
headers.setHeader(i, heads[i], false);
var en = headers.enumerator;
while (en.hasMoreElements())
{
var it = en.getNext().QueryInterface(Ci.nsISupportsString).data;
do_check_true(it.toLowerCase() in heads);
delete heads[it.toLowerCase()];
}
for (var i in heads)
do_throw("still have properties in heads!?!?");
}
function testHasHeader()
{
var headers = new nsHttpHeaders();
headers.setHeader("foo", "bar");
do_check_true(headers.hasHeader("foo"));
do_check_true(headers.hasHeader("fOo"));
do_check_false(headers.hasHeader("not-there"));
headers.setHeader("f`'~", "bar");
do_check_true(headers.hasHeader("F`'~"));
try
{
headers.hasHeader(":");
throw "failed to throw";
}
catch (e)
{
if (e !== Cr.NS_ERROR_INVALID_ARG)
do_throw(".hasHeader for an invalid name should throw");
}
}

View File

@ -0,0 +1,314 @@
/* -*- 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 MozJSHTTP code.
*
* The Initial Developer of the Original Code is
* Jeff Walden <jwalden+code@mit.edu>.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
// tests the registerDirectory API
const BASE = "http://localhost:4444";
var paths =
[
/* 0*/ BASE + "/test_registerdirectory.js", // without a base path
/* 1*/ BASE + "/test_registerdirectory.js", // with a base path
/* 2*/ BASE + "/test_registerdirectory.js", // without a base path
/* 3*/ BASE + "/test_registerdirectory.js", // no registered path handler
/* 4*/ BASE + "/test_registerdirectory.js", // registered path handler
/* 5*/ BASE + "/test_registerdirectory.js", // removed path handler
/* 6*/ BASE + "/test_registerdirectory.js", // with a base path
/* 7*/ BASE + "/test_registerdirectory.js", // ...and a path handler
/* 8*/ BASE + "/test_registerdirectory.js", // removed base handler
/* 9*/ BASE + "/test_registerdirectory.js", // removed path handler
/*10*/ BASE + "/foo/test_registerdirectory.js", // mapping set up, works
/*11*/ BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js", // no mapping, fails
/*12*/ BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js", // mapping, works
/*13*/ BASE + "/foo/test_registerdirectory.js", // two mappings set up, still works
/*14*/ BASE + "/foo/test_registerdirectory.js", // mapping was removed
/*15*/ BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js", // mapping still present, works
/*16*/ BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js", // mapping removed
];
var currPathIndex = 0;
var listener =
{
// NSISTREAMLISTENER
onDataAvailable: function(request, cx, inputStream, offset, count)
{
makeBIS(inputStream).readByteArray(count); // required by API
},
// NSIREQUESTOBSERVER
onStartRequest: function(request, cx)
{
var ch = request.QueryInterface(Ci.nsIHttpChannel);
switch (currPathIndex)
{
case 0:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
break;
case 1:
do_check_eq(ch.responseStatus, 200);
do_check_true(ch.requestSucceeded);
var actualFile = serverBasePath.clone();
actualFile.append("test_registerdirectory.js");
do_check_eq(ch.getResponseHeader("Content-Length"),
actualFile.fileSize.toString());
break;
case 2:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
break;
case 3:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
break;
case 4:
do_check_eq(ch.responseStatus, 200);
do_check_eq(ch.responseStatusText, "OK");
do_check_true(ch.requestSucceeded);
do_check_eq(ch.getResponseHeader("Override-Succeeded"), "yes");
break;
case 5:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
break;
case 6:
do_check_eq(ch.responseStatus, 200);
do_check_true(ch.requestSucceeded);
var actualFile = serverBasePath.clone();
actualFile.append("test_registerdirectory.js");
do_check_eq(ch.getResponseHeader("Content-Length"),
actualFile.fileSize.toString());
break;
case 7:
case 8:
do_check_eq(ch.responseStatus, 200);
do_check_eq(ch.responseStatusText, "OK");
do_check_true(ch.requestSucceeded);
do_check_eq(ch.getResponseHeader("Override-Succeeded"), "yes");
break;
case 9:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
break;
case 10:
do_check_eq(ch.responseStatus, 200);
do_check_eq(ch.responseStatusText, "OK");
break;
case 11:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
break;
case 12:
case 13:
do_check_eq(ch.responseStatus, 200);
do_check_eq(ch.responseStatusText, "OK");
break;
case 14:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
break;
case 15:
do_check_eq(ch.responseStatus, 200);
do_check_eq(ch.responseStatusText, "OK");
break;
case 16:
do_check_eq(ch.responseStatus, 404);
do_check_false(ch.requestSucceeded);
break;
}
},
onStopRequest: function(request, cx, status)
{
var dirServ;
switch (currPathIndex)
{
case 0:
// now set a base path
dirServ = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
serverBasePath = dirServ.get("CurProcD", Ci.nsILocalFile);
serverBasePath.append("httpserver_tests");
srv.registerDirectory("/", serverBasePath);
break;
case 1:
// remove base path
serverBasePath = null;
srv.registerDirectory("/", serverBasePath);
break;
case 3:
// register overriding path
srv.registerPathHandler("/test_registerdirectory.js",
override_test_registerdirectory);
break;
case 4:
// unregister overriding path
srv.registerPathHandler("/test_registerdirectory.js", null);
break;
case 5:
// set the base path again
dirServ = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
serverBasePath = dirServ.get("CurProcD", Ci.nsILocalFile);
serverBasePath.append("httpserver_tests");
srv.registerDirectory("/", serverBasePath);
break;
case 6:
// register overriding path
srv.registerPathHandler("/test_registerdirectory.js",
override_test_registerdirectory);
break;
case 7:
// remove base path
serverBasePath = null;
srv.registerDirectory("/", serverBasePath);
break;
case 8:
// unregister overriding path
srv.registerPathHandler("/test_registerdirectory.js", null);
break;
case 9:
// register /foo/ as a base path
dirServ = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
serverBasePath = dirServ.get("CurProcD", Ci.nsILocalFile);
serverBasePath.append("httpserver_tests");
srv.registerDirectory("/foo/", serverBasePath);
break;
case 10:
// do nothing
break;
case 11:
// now register an overriding path to handle the URL that just failed
srv.registerDirectory("/foo/test_registerdirectory.js/", serverBasePath);
break;
case 12:
// do nothing
break;
case 13:
srv.registerDirectory("/foo/", null);
break;
case 14:
// do nothing
break;
case 15:
srv.registerDirectory("/foo/test_registerdirectory.js/", null);
break;
}
if (!paths[++currPathIndex])
srv.stop();
else
performNextTest();
do_test_finished();
},
// NSISUPPORTS
QueryInterface: function(aIID)
{
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsIRequestObserver) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
function performNextTest()
{
do_test_pending();
var ch = makeChannel(paths[currPathIndex]);
ch.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // important!
ch.asyncOpen(listener, null);
}
var srv;
var serverBasePath;
function run_test()
{
srv = createServer();
srv.start(4444);
performNextTest();
}
// PATH HANDLERS
// override of /test_registerdirectory.js
function override_test_registerdirectory(metadata, response)
{
response.setStatusLine("1.1", 200, "OK");
response.setHeader("Override-Succeeded", "yes");
var body = "success!";
response.bodyOutputStream.write(body, body.length);
}

View File

@ -0,0 +1,225 @@
/* -*- 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 MozJSHTTP code.
*
* The Initial Developer of the Original Code is
* Jeff Walden <jwalden+code@mit.edu>.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
// exercise nsIHttpResponse.setStatusLine, ensure its atomicity, and ensure the
// specified behavior occurs if it's not called
var paths =
[
"http://localhost:4444/no/setstatusline",
"http://localhost:4444/http1_0",
"http://localhost:4444/http1_1",
"http://localhost:4444/invalidVersion",
"http://localhost:4444/invalidStatus",
"http://localhost:4444/invalidDescription",
"http://localhost:4444/crazyCode",
"http://localhost:4444/nullVersion"
];
var currPathIndex = 0;
var listener =
{
// NSISTREAMLISTENER
onDataAvailable: function(request, cx, inputStream, offset, count)
{
makeBIS(inputStream).readByteArray(count); // required by API
},
// NSIREQUESTOBSERVER
onStartRequest: function(request, cx)
{
var ch = request.QueryInterface(Ci.nsIHttpChannel)
.QueryInterface(Ci.nsIHttpChannelInternal);
switch (currPathIndex)
{
case 0:
checkStatusLine(ch, 1, 1, 200, "OK");
break;
case 1:
checkStatusLine(ch, 1, 0, 200, "OK");
break;
case 2:
checkStatusLine(ch, 1, 1, 200, "OK");
break;
case 3:
case 4:
case 5:
checkStatusLine(ch, 1, 1, 200, "OK");
do_check_eq(ch.getResponseHeader("Passed"), "true");
break;
case 6:
checkStatusLine(ch, 1, 1, 617, "Crazy");
break;
case 7:
// currently, this server implementation defaults to 1.1
checkStatusLine(ch, 1, 1, 255, "NULL");
break;
}
},
onStopRequest: function(request, cx, status)
{
do_check_true(Components.isSuccessCode(status));
if (++currPathIndex == paths.length)
srv.stop();
else
performNextTest();
do_test_finished();
},
// NSISUPPORTS
QueryInterface: function(aIID)
{
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsIRequestObserver) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
function checkStatusLine(channel, httpMaxVer, httpMinVer, httpCode, statusText)
{
do_check_eq(channel.responseStatus, httpCode);
do_check_eq(channel.responseStatusText, statusText);
var respMaj = {}, respMin = {};
channel.getResponseVersion(respMaj, respMin);
do_check_eq(respMaj.value, httpMaxVer);
do_check_eq(respMin.value, httpMinVer);
}
function performNextTest()
{
do_test_pending();
var ch = makeChannel(paths[currPathIndex]);
ch.asyncOpen(listener, null);
}
var srv;
function run_test()
{
srv = createServer();
srv.registerPathHandler("/no/setstatusline", noSetstatusline);
srv.registerPathHandler("/http1_0", http1_0);
srv.registerPathHandler("/http1_1", http1_1);
srv.registerPathHandler("/invalidVersion", invalidVersion);
srv.registerPathHandler("/invalidStatus", invalidStatus);
srv.registerPathHandler("/invalidDescription", invalidDescription);
srv.registerPathHandler("/crazyCode", crazyCode);
srv.registerPathHandler("/nullVersion", nullVersion);
srv.start(4444);
performNextTest();
}
// PATH HANDLERS
// /no/setstatusline
function noSetstatusline(metadata, response)
{
}
// /http1_0
function http1_0(metadata, response)
{
response.setStatusLine("1.0", 200, "OK");
}
// /http1_1
function http1_1(metadata, response)
{
response.setStatusLine("1.1", 200, "OK");
}
// /invalidVersion
function invalidVersion(metadata, response)
{
try
{
response.setStatusLine(" 1.0", 200, "FAILED");
}
catch (e)
{
response.setHeader("Passed", "true", false);
}
}
// /invalidStatus
function invalidStatus(metadata, response)
{
try
{
response.setStatusLine("1.0", 1000, "FAILED");
}
catch (e)
{
response.setHeader("Passed", "true", false);
}
}
// /invalidDescription
function invalidDescription(metadata, response)
{
try
{
response.setStatusLine("1.0", 200, "FAILED\x01");
}
catch (e)
{
response.setHeader("Passed", "true", false);
}
}
// /crazyCode
function crazyCode(metadata, response)
{
response.setStatusLine("1.1", 617, "Crazy");
}
// /nullVersion
function nullVersion(metadata, response)
{
response.setStatusLine(null, 255, "NULL");
}

View File

@ -1,226 +0,0 @@
// vim:set sw=2 ts=2 sts=2 cin:
function nsTestServ(port) {
dump(">>> creating nsTestServ instance\n");
this.port = port;
}
nsTestServ.prototype =
{
handler: {
// This function generates standard headers for a specific HTTP result
// code. The code can include text like "Not Found". The return value does
// not include a terminating empty line, so new headers can be added after
// it.
headers: function(code) {
return "HTTP/1.0 " + code + "\r\n" +
"Connection: close\r\n" +
"Server: Necko unit test server\r\n" +
"Content-Type: text/plain\r\n";
},
// This object contains the handlers for the various request URIs
// numeric properties are HTTP result codes
400: function(stream)
{
var response = this.headers("400 Bad Request") +
"\r\n" +
"Bad request";
stream.write(response, response.length);
},
404: function(stream)
{
var response = this.headers("404 Not Found") +
"\r\n" +
"No such path";
stream.write(response, response.length);
},
"/": function(stream)
{
var response = this.headers("200 OK") +
"\r\n" +
"200 OK";
stream.write(response, response.length);
},
"/redirect": function(stream)
{
var response = this.headers("301 Moved Permanently") +
"Location: http://localhost:4444/\r\n" +
"\r\n" +
"Moved";
stream.write(response, response.length);
},
"/redirectfile": function(stream)
{
var response = this.headers("301 Moved Permanently") +
"Location: file:///\r\n" +
"\r\n" +
"Moved to a file URI";
stream.write(response, response.length);
},
"/auth": function(stream, req_head) {
// btoa("guest:guest"), but
// that function is not available here
var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
var successResponse = this.headers("200 OK, authorized") +
'WWW-Authenticate: Basic realm="secret"\r\n' +
"\r\n" +
"success";
var failedResponse = this.headers("401 Unauthorized") +
'WWW-Authenticate: Basic realm="secret"\r\n' +
"\r\n" +
"failed";
if ("authorization" in req_head) {
// Make sure the credentials are right
if (req_head.authorization == expectedHeader) {
stream.write(successResponse, successResponse.length);
return;
}
// fall through to failure response
}
stream.write(failedResponse, failedResponse.length);
},
"/auth/ntlm/simple": function(stream) {
var response = this.headers("401 Unauthorized") +
"WWW-Authenticate: NTLM\r\n" + // realm=\"secret\"\r\n" +
"\r\n" +
"NOTE: This just sends an NTLM challenge, it never\n" +
"accepts the authentication. It also closes\n" +
"the connection after sending the challenge\n";
stream.write(response, response.length);
},
// A URL that causes the current unit test to fail
// Use it to ensure that requests are cancelled before
// they reach the server
"/failtest": function (stream) {
do_throw("This should not be reached");
}
},
QueryInterface: function(iid)
{
if (iid.equals(Components.interfaces.nsIServerSocketListener) ||
iid.equals(Components.interfaces.nsISupports))
return this;
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
return null;
},
/* this function is called when we receive a new connection */
onSocketAccepted: function(serverSocket, clientSocket)
{
dump(">>> accepted connection on "+clientSocket.host+":"+clientSocket.port+"\n");
const nsITransport = Components.interfaces.nsITransport;
var input = clientSocket.openInputStream(nsITransport.OPEN_BLOCKING, 0, 0);
var output = clientSocket.openOutputStream(nsITransport.OPEN_BLOCKING, 0, 0);
var request = this.parseInput(input);
// Strip away query parameters, then unescape the request
var path = request[0].replace(/\?.*/, "");
try {
path = decodeURI(path);
} catch (ex) {
path = 400;
}
if (path in this.handler)
this.handler[path](output, request[1]);
else
this.handler[404](output, request[1]);
input.close();
output.close();
},
onStopListening: function(serverSocket, status)
{
dump(">>> shutting down server socket\n");
this.shutdown = true;
},
startListening: function()
{
const nsIServerSocket = Components.interfaces.nsIServerSocket;
const SERVERSOCKET_CONTRACTID = "@mozilla.org/network/server-socket;1";
var socket = Components.classes[SERVERSOCKET_CONTRACTID].createInstance(nsIServerSocket);
socket.init(this.port, true /* loopback only */, 5);
dump(">>> listening on port "+socket.port+"\n");
socket.asyncListen(this);
this.socket = socket;
},
// Shuts down the server. Note that this processes events.
stopListening: function()
{
if (!this.socket)
return;
this.socket.close();
this.socket = null;
var thr = Components.classes["@mozilla.org/thread-manager;1"]
.getService().currentThread;
while (!this.shutdown) {
thr.processNextEvent(true);
}
},
parseInput: function(input)
{
// We read the input line by line. The first line tells us the requested
// path.
var is = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
is.init(input, "ISO-8859-1", 1024, 0xFFFD);
var lis =
is.QueryInterface(Components.interfaces.nsIUnicharLineInputStream);
var line = {};
var cont = lis.readLine(line);
var request = line.value.split(/ +/);
if (request[0] != "GET" && request[0] != "POST")
return [400];
// This server doesn't support HTTP 0.9
if (request[2] != "HTTP/1.0" && request[2] != "HTTP/1.1")
return [400];
var req_head = {};
while (true) {
lis.readLine(line);
if (line.value == "")
break;
var matches = line.value.match(/^([a-zA-Z]+): *(.*)$/);
if (matches !== null && matches.length == 3) {
// This is slightly wrong - should probably merge headers with the
// same name
req_head[matches[1].toLowerCase()] = matches[2];
}
}
if (request[1][0] != "/")
return [400];
return [request[1], req_head];
},
socket: null,
shutdown: false
}
function start_server(port) {
var serv = new nsTestServ(port);
serv.startListening();
return serv;
}

View File

@ -1,11 +1,14 @@
// This file tests authentication prompt callbacks
do_import_script("test-harness/xpcshell-simple/httpd.js");
const FLAG_RETURN_FALSE = 1 << 0;
const FLAG_WRONG_PASSWORD = 1 << 1;
const nsIAuthPrompt2 = Components.interfaces.nsIAuthPrompt2;
const nsIAuthInformation = Components.interfaces.nsIAuthInformation;
function AuthPrompt1(flags) {
this.flags = flags;
}
@ -202,7 +205,7 @@ var listener = {
current_test++;
tests[current_test]();
} else {
httpserv.stopListening();
httpserv.stop();
}
do_test_finished();
@ -225,7 +228,13 @@ var current_test = 0;
var httpserv = null;
function run_test() {
httpserv = start_server(4444);
httpserv = new nsHttpServer();
httpserv.registerPathHandler("/auth", authHandler);
httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
httpserv.start(4444);
tests[0]();
}
@ -308,3 +317,43 @@ function test_ntlm() {
do_test_pending();
}
// PATH HANDLERS
// /auth
function authHandler(metadata, response) {
// btoa("guest:guest"), but that function is not available here
var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
var body;
if (metadata.hasHeader("Authorization") &&
metadata.getHeader("Authorization") == expectedHeader)
{
response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
body = "success";
}
else
{
// didn't know guest:guest, failure
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
body = "failed";
}
response.bodyOutputStream.write(body, body.length);
}
// /auth/ntlm/simple
function authNtlmSimple(metadata, response) {
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", "NTLM" /* + ' realm="secret"' */);
var body = "NOTE: This just sends an NTLM challenge, it never\n" +
"accepts the authentication. It also closes\n" +
"the connection after sending the challenge\n";
response.bodyOutputStream.write(body, body.length);
}

View File

@ -1,11 +1,8 @@
do_import_script("test-harness/xpcshell-simple/httpd.js");
var server;
var BUGID = "331825";
function handle_response(stream) {
var response = server.handler.headers("304 Not Modified") + "\r\n";
stream.write(response, response.length);
}
function TestListener() {
}
TestListener.prototype.onStartRequest = function(request, context) {
@ -14,15 +11,17 @@ TestListener.prototype.onStopRequest = function(request, context, status) {
var channel = request.QueryInterface(Components.interfaces.nsIHttpChannel);
do_check_eq(channel.responseStatus, 304);
server.stop();
do_test_finished();
server.stopListening();
}
function run_test() {
// start server
server = new nsTestServ(4444);
server.handler["/bug" + BUGID] = handle_response;
server.startListening();
server = new nsHttpServer();
server.registerPathHandler("/bug" + BUGID, bug331825);
server.start(4444);
// make request
var channel =
@ -36,3 +35,8 @@ function run_test() {
do_test_pending();
}
// PATH HANDLER FOR /bug331825
function bug331825(metadata, response) {
response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
}

View File

@ -1,5 +1,7 @@
// This file tests nsIContentSniffer, introduced in bug 324985
do_import_script("test-harness/xpcshell-simple/httpd.js");
const unknownType = "application/x-unknown-content-type";
const sniffedType = "application/x-sniffed";
@ -89,7 +91,8 @@ var urls = [
var httpserv = null;
function run_test() {
httpserv = start_server(4444);
httpserv = new nsHttpServer();
httpserv.start(4444);
Components.manager.nsIComponentRegistrar.registerFactory(snifferCID,
"Unit test content sniffer", snifferContract, sniffer);
@ -103,7 +106,7 @@ function run_test_iteration(index) {
sniffing_enabled = false;
index = listener._iteration = 1;
} else {
httpserv.stopListening();
httpserv.stop();
return; // we're done
}
}

View File

@ -1,5 +1,7 @@
// This file tests bug 250375
do_import_script("test-harness/xpcshell-simple/httpd.js");
function check_request_header(chan, name, value) {
var chanValue;
try {
@ -33,7 +35,7 @@ var listener = {
if (this._iteration == 1)
run_test_continued();
else
httpserv.stopListening();
httpserv.stop();
do_test_finished();
},
@ -52,7 +54,8 @@ function makeChan() {
var httpserv = null;
function run_test() {
httpserv = start_server(4444);
httpserv = new nsHttpServer();
httpserv.start(4444);
var chan = makeChan();
@ -81,4 +84,3 @@ function run_test_continued() {
do_test_pending();
}

View File

@ -1,5 +1,7 @@
// This file tests channel event sinks (bug 315598 et al)
do_import_script("test-harness/xpcshell-simple/httpd.js");
const sinkCID = Components.ID("{14aa4b81-e266-45cb-88f8-89595dece114}");
const sinkContract = "@mozilla.org/network/unittest/channeleventsink;1";
@ -82,7 +84,7 @@ var listener = {
if (this._iteration <= 2)
run_test_continued();
else
httpserv.stopListening();
httpserv.stop();
do_test_finished();
},
@ -101,7 +103,10 @@ function makeChan(url) {
var httpserv = null;
function run_test() {
httpserv = start_server(4444);
httpserv = new nsHttpServer();
httpserv.registerPathHandler("/redirect", redirect);
httpserv.registerPathHandler("/redirectfile", redirectfile);
httpserv.start(4444);
Components.manager.nsIComponentRegistrar.registerFactory(sinkCID,
"Unit test Event sink", sinkContract, eventsink);
@ -141,3 +146,25 @@ function run_test_continued() {
do_test_pending();
}
// PATHS
// /redirect
function redirect(metadata, response) {
response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
response.setHeader("Location",
"http://localhost:" + metadata.port + "/",
false);
var body = "Moved\n";
response.bodyOutputStream.write(body, body.length);
}
// /redirectfile
function redirectfile(metadata, response) {
response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("Location", "file:///etc/", false);
var body = "Attempted to move to a file URI, but failed.\n";
response.bodyOutputStream.write(body, body.length);
}

View File

@ -1,6 +1,8 @@
// This file ensures that canceling a channel early does not
// send the request to the server (bug 350790)
do_import_script("test-harness/xpcshell-simple/httpd.js");
const NS_BINDING_ABORTED = 0x804b0002;
var observer = {
@ -31,7 +33,7 @@ var listener = {
},
onStopRequest: function test_onStopR(request, ctx, status) {
httpserv.stopListening();
httpserv.stop();
do_test_finished();
}
};
@ -48,7 +50,9 @@ function makeChan(url) {
var httpserv = null;
function run_test() {
httpserv = start_server(4444);
httpserv = new nsHttpServer();
httpserv.registerPathHandler("/failtest", failtest);
httpserv.start(4444);
var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
@ -61,3 +65,9 @@ function run_test() {
do_test_pending();
}
// PATHS
// /failtest
function failtest(metadata, response) {
do_throw("This should not be reached");
}

View File

@ -117,6 +117,10 @@ function do_check_true(condition) {
do_check_eq(condition, true);
}
function do_check_false(condition) {
do_check_eq(condition, false);
}
function do_test_pending() {
dump("*** test pending\n");
_tests_pending++;
@ -127,3 +131,12 @@ function do_test_finished() {
if (--_tests_pending == 0)
_do_quit();
}
function do_import_script(distRelativePath) {
var scriptPath = environment["DIST"];
if (scriptPath.charAt(scriptPath.length - 1) != "/")
scriptPath += "/";
scriptPath += distRelativePath;
load(scriptPath);
}

View File

@ -83,7 +83,7 @@ done
for t in $testdir/test_*.js
do
echo -n "$t: "
$bin/xpcshell $headfiles -f $t $tailfiles 2> $t.log 1>&2
DIST="$bin" $bin/xpcshell $headfiles -f $t $tailfiles 2> $t.log 1>&2
if [ `grep -c '\*\*\* PASS' $t.log` = 0 ]
then
echo "FAIL"