Bug 367214 - mod_cern_meta support in the HTTP server, enabling modification of the HTTP status and headers sent in the response for a file. r=biesi

This commit is contained in:
jwalden@mit.edu 2007-05-20 18:35:06 -07:00
parent 1adf199346
commit 2b27fe648e
23 changed files with 694 additions and 50 deletions

View File

@ -49,9 +49,10 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const CC = Components.Constructor;
/** True if debugging output is enabled, false otherwise. */
const DEBUG = false; // tweak manually during server hacking
var DEBUG = false; // non-const *only* so tweakable in server tests
/**
* Asserts that the given condition holds. If it doesn't, the given message is
@ -131,6 +132,25 @@ function range(x, y)
/** 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;
/** dump(str) with a trailing "\n" -- only outputs if DEBUG */
function dumpn(str)
{
@ -139,6 +159,29 @@ function dumpn(str)
}
/**
* 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 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 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.
*
@ -553,7 +596,7 @@ function defaultIndexHandler(metadata, response)
{
response.setHeader("Content-Type", "text/html", false);
var path = htmlEscape(metadata.path);
var path = htmlEscape(decodeURI(metadata.path));
//
// Just do a very basic bit of directory listings -- no need for too much
@ -577,7 +620,10 @@ function defaultIndexHandler(metadata, response)
while (files.hasMoreElements())
{
var f = files.getNext().QueryInterface(Ci.nsIFile);
if (!f.isHidden())
var name = f.leafName;
if (!f.isHidden() &&
(name.charAt(name.length - 1) != HIDDEN_CHAR ||
name.charAt(name.length - 2) == HIDDEN_CHAR))
fileList.push(f);
}
@ -589,6 +635,8 @@ function defaultIndexHandler(metadata, response)
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
@ -626,6 +674,122 @@ function fileSort(a, b)
}
/**
* 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.
@ -792,6 +956,8 @@ ServerHandler.prototype =
// and clean up the response
throw e;
}
maybeAddHeaders(file, metadata, response);
};
},
@ -818,6 +984,13 @@ ServerHandler.prototype =
// 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);
@ -866,7 +1039,8 @@ ServerHandler.prototype =
* @param key
* The field name of the handler.
*/
_handlerToField: function(handler, dict, key) {
_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;
@ -935,6 +1109,8 @@ ServerHandler.prototype =
// finally...
dumpn("*** handling '" + path + "' as mapping to " + file.path);
this._writeFileResponse(file, response);
maybeAddHeaders(file, metadata, response);
}
catch (e)
{
@ -964,10 +1140,9 @@ ServerHandler.prototype =
response.setHeader("Content-Type", getTypeFromFile(file), false);
var fis = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
const PR_RDONLY = 0x01;
fis.init(file, PR_RDONLY, 0444, Ci.nsIFileInputStream.CLOSE_ON_EOF);
var fis = new FileInputStream(file, PR_RDONLY, 0444,
Ci.nsIFileInputStream.CLOSE_ON_EOF);
response.bodyOutputStream.writeFrom(fis, file.fileSize);
fis.close();
},
@ -987,12 +1162,10 @@ ServerHandler.prototype =
*/
_getFileForPath: function(path)
{
var pathMap = this._pathDirectoryMap;
// first, decode path and strip the leading '/'
// decode and add underscores as necessary
try
{
path = decodeURI(path);
path = toInternalPath(path, true);
}
catch (e)
{
@ -1000,6 +1173,7 @@ ServerHandler.prototype =
}
// 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", ""
@ -1295,9 +1469,8 @@ ServerHandler.prototype =
// body -- written async, because pipes deadlock if we do
// |outStream.writeFrom(bodyStream, bodyStream.available());|
var copier = Cc["@mozilla.org/network/async-stream-copier;1"]
.createInstance(Ci.nsIAsyncStreamCopier);
copier.init(bodyStream, outStream, null, true, true, 8192);
var copier = new StreamCopier(bodyStream, outStream, null,
true, true, 8192);
copier.asyncCopy(copyObserver, null);
}
else
@ -1565,10 +1738,8 @@ Response.prototype =
if (!this._bodyOutputStream && !this._outputProcessed)
{
var pipe = Cc["@mozilla.org/pipe;1"]
.createInstance(Ci.nsIPipe);
const PR_UINT32_MAX = Math.pow(2, 32) - 1;
pipe.init(false, false, 0, PR_UINT32_MAX, null);
var pipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);
this._bodyOutputStream = pipe.outputStream;
this._bodyInputStream = pipe.inputStream;
}
@ -1838,16 +2009,19 @@ const headerUtils =
*/
normalizeFieldName: function(fieldName)
{
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;
}
}
if (fieldName == "")
throw Cr.NS_ERROR_INVALID_ARG;
return fieldName.toLowerCase();
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();
},
/**
@ -2072,8 +2246,7 @@ nsHttpHeaders.prototype =
var headers = [];
for (var i in this._headers)
{
var supports = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
var supports = new SupportsString();
supports.data = i;
headers.push(supports);
}
@ -2145,8 +2318,7 @@ function RequestMetadata(port)
* For the addition of ad-hoc properties and new functionality
* without having to tweak nsIHttpRequestMetadata every time.
*/
this._bag = Cc["@mozilla.org/hash-property-bag;1"]
.createInstance(Ci.nsIWritablePropertyBag2);
this._bag = new WritablePropertyBag();
/**
* The numeric HTTP error, if any, associated with this request. This value
@ -2288,11 +2460,8 @@ RequestMetadata.prototype =
// read the input line by line; the first line either tells us the requested
// path or is empty, in which case the second line contains the path
var is = Cc["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Ci.nsIConverterInputStream);
is.init(input, "ISO-8859-1", 1024, 0xFFFD);
var lis = is.QueryInterface(Ci.nsIUnicharLineInputStream);
var lis = new ConverterInputStream(input, "ISO-8859-1", 1024, 0xFFFD);
lis.QueryInterface(Ci.nsIUnicharLineInputStream);
this._parseRequestLine(lis);
@ -2592,11 +2761,7 @@ const module =
};
/**
* NSGetModule, so this code can be used as a JS component. Whether multiple
* servers can run at once is currently questionable, but the code certainly
* isn't very far from supporting it.
*/
/** NSGetModule, so this code can be used as a JS component. */
function NSGetModule(compMgr, fileSpec)
{
return module;
@ -2639,6 +2804,9 @@ function server(port, basePath)
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);
@ -2653,4 +2821,6 @@ function server(port, basePath)
// get rid of any pending requests
while (thread.hasPendingEvents())
thread.processNextEvent(true);
DEBUG = false;
}

View File

@ -123,7 +123,7 @@ interface nsIHttpServer : nsIServerSocketListener
* 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.
* 400, not a server-provided 404 that might not throw.
* @note
* If the error handler handles HTTP 500 and throws, behavior is undefined.
*/

View File

@ -0,0 +1 @@
If this has goofy headers on it, it's a success.

View File

@ -0,0 +1,3 @@
HTTP 500 This Isn't A Server Error
Foo-RFC: 3092
Shaving-Cream-Atom: Illudium Phosdex

View File

@ -0,0 +1,2 @@
This page is a text file served with status 501. (That's really a lie, tho,
because this is definitely Implemented.)

View File

@ -0,0 +1,2 @@
HTTP 501 Unimplemented
Content-Type: text/plain

View File

@ -0,0 +1,9 @@
<html>
<head>
<title>This is really HTML, not text</title>
</head>
<body>
<p>This file is really HTML; the test_ctype_override.txt^headers^ file sets a
new header that overwrites the default text/plain header.</p>
</body>
</html>

View File

@ -0,0 +1 @@
Content-Type: text/html

View File

@ -0,0 +1,9 @@
<html>
<head>
<title>This is a 404 page</title>
</head>
<body>
<p>This page has a 404 HTTP status associated with it, via
<code>test_status_override.html^headers^</code>.</p>
</body>
</html>

View File

@ -0,0 +1 @@
HTTP 404 Can't Find This

View File

@ -0,0 +1 @@
This page has an HTTP status override without a description (it defaults to "").

View File

@ -0,0 +1,10 @@
<html>
<head>
<title>Welcome to bar.html^</title>
</head>
<body>
<p>This file is named with two trailing carets, so the last is stripped
away, producing bar.html^ as the final name.</p>
</body>
</html>

View File

@ -0,0 +1,2 @@
HTTP 200 OK
Content-Type: text/html

View File

@ -0,0 +1 @@
This file shouldn't be shown in directory listings.

View File

@ -0,0 +1 @@
This file should show up in directory listings as SHOULD_SEE_THIS.txt^.

View File

@ -0,0 +1,2 @@
File in a directory named with a trailing caret (in the virtual FS; on disk it
actually ends with two carets).

View File

@ -0,0 +1,9 @@
<html>
<head>
<title>ERROR</title>
</head>
<body>
<p>This file should never be served by the web server because its name ends
with a caret not followed by another caret.</p>
</body>
</html>

View File

@ -0,0 +1 @@
This should be seen.

View File

@ -38,6 +38,15 @@
do_import_script("netwerk/test/httpserver/httpd.js");
// if these tests fail, we'll want the debug output
DEBUG = true;
// XPCOM constructor shorthands
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream",
"setInputStream");
/**
* Constructs a new nsHttpServer instance. This function is intended to
* encapsulate construction of a server so that at some point in the future
@ -74,8 +83,115 @@ function makeChannel(url)
*/
function makeBIS(stream)
{
var bis = Cc["@mozilla.org/binaryinputstream;1"]
.createInstance(Ci.nsIBinaryInputStream);
bis.setInputStream(stream);
return bis;
return new BinaryInputStream(stream);
}
/*******************************************************
* SIMPLE SUPPORT FOR LOADING/TESTING A SERIES OF URLS *
*******************************************************/
/**
* Represents a path to load from the tested HTTP server, along with actions to
* take before, during, and after loading the associated page.
*
* @param path
* the URL to load from the server
* @param initChannel
* a function which takes as a single parameter a channel created for path and
* initializes its state, or null if no additional initialization is needed
* @param onStartRequest
* called during onStartRequest for the load of the URL, with the same
* parameters; the request parameter has been QI'd to nsIHttpChannel and
* nsIHttpChannelInternal for convenience; may be null if nothing needs to be
* done
* @param onStopRequest
* called during onStopRequest for the channel, with the same parameters plus
* a trailing parameter containing an array of the bytes of data downloaded in
* the body of the channel response; the request parameter has been QI'd to
* nsIHttpChannel and nsIHttpChannelInternal for convenience; may be null if
* nothing needs to be done
*/
function Test(path, initChannel, onStartRequest, onStopRequest)
{
function nil() { }
this.path = path;
this.initChannel = initChannel || nil;
this.onStartRequest = onStartRequest || nil;
this.onStopRequest = onStopRequest || nil;
}
/**
* Runs all the tests in testArray.
*
* @param testArray
* a non-empty array of Tests to run, in order
* @param done
* function to call when all tests have run (e.g. to shut down the server)
*/
function runHttpTests(testArray, done)
{
/** Kicks off running the next test in the array. */
function performNextTest()
{
if (++testIndex == testArray.length)
{
done();
return;
}
do_test_pending();
var test = testArray[testIndex];
var ch = makeChannel(test.path);
test.initChannel(ch);
ch.asyncOpen(listener, null);
}
/** Index of the test being run. */
var testIndex = -1;
/** Stream listener for the channels. */
var listener =
{
/** Array of bytes of data in body of response. */
_data: [],
onStartRequest: function(request, cx)
{
var ch = request.QueryInterface(Ci.nsIHttpChannel)
.QueryInterface(Ci.nsIHttpChannelInternal);
this._data.length = 0;
testArray[testIndex].onStartRequest(ch, cx);
},
onDataAvailable: function(request, cx, inputStream, offset, count)
{
Array.prototype.push.apply(this._data,
makeBIS(inputStream).readByteArray(count));
},
onStopRequest: function(request, cx, status)
{
var ch = request.QueryInterface(Ci.nsIHttpChannel)
.QueryInterface(Ci.nsIHttpChannelInternal);
testArray[testIndex].onStopRequest(ch, cx, status, this._data);
performNextTest();
do_test_finished();
},
QueryInterface: function(aIID)
{
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsIRequestObserver) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
performNextTest();
}

View File

@ -0,0 +1,108 @@
/* -*- 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) 2007
* 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 ***** */
// exercises support for mod_cern_meta-style header/status line modification
const PREFIX = "http://localhost:4444";
const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
"nsIScriptableInputStream",
"init");
var tests =
[
new Test(PREFIX + "/test_both.html",
null, start_testBoth, null),
new Test(PREFIX + "/test_ctype_override.txt",
null, start_test_ctype_override_txt, null),
new Test(PREFIX + "/test_status_override.html",
null, start_test_status_override_html, null),
new Test(PREFIX + "/test_status_override_nodesc.txt",
null, start_test_status_override_nodesc_txt, null),
new Test(PREFIX + "/caret_test.txt^",
null, start_caret_test_txt_, null)
];
function run_test()
{
var srv = createServer();
var cernDir = do_get_file("netwerk/test/httpserver/test/data/cern_meta/");
srv.registerDirectory("/", cernDir);
srv.start(4444);
runHttpTests(tests, function() { srv.stop(); });
}
// TEST DATA
function start_testBoth(ch, cx)
{
do_check_eq(ch.responseStatus, 501);
do_check_eq(ch.responseStatusText, "Unimplemented");
do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
}
function start_test_ctype_override_txt(ch, cx)
{
do_check_eq(ch.getResponseHeader("Content-Type"), "text/html");
}
function start_test_status_override_html(ch, cx)
{
do_check_eq(ch.responseStatus, 404);
do_check_eq(ch.responseStatusText, "Can't Find This");
}
function start_test_status_override_nodesc_txt(ch, cx)
{
do_check_eq(ch.responseStatus, 732);
do_check_eq(ch.responseStatusText, "");
}
function start_caret_test_txt_(ch, cx)
{
do_check_eq(ch.responseStatus, 500);
do_check_eq(ch.responseStatusText, "This Isn't A Server Error");
do_check_eq(ch.getResponseHeader("Foo-RFC"), "3092");
do_check_eq(ch.getResponseHeader("Shaving-Cream-Atom"), "Illudium Phosdex");
}

View File

@ -42,11 +42,82 @@
var paths =
[
"http://localhost:4444/", // check top-level directory listing
"http://localhost:4444/foo/" // check non-top-level, too
"http://localhost:4444/", // check top-level directory listing
"http://localhost:4444/foo/", // check non-top-level, too
"http://localhost:4444/bar/folder^/" // trailing-caret leaf with hidden files
];
var currPathIndex = 0;
/** Verifies data in bytes for the trailing-caret path above. */
function hiddenDataCheck(bytes, uri, path)
{
var data = String.fromCharCode.apply(null, bytes);
var parser = Cc["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Ci.nsIDOMParser);
// Note: the index format isn't XML -- it's actually HTML -- but we require
// the index format also be valid XML, albeit XML without namespaces,
// XML declarations, etc. Doing this simplifies output checking.
try
{
var doc = parser.parseFromString(data, "application/xml");
}
catch (e)
{
do_throw("document failed to parse as XML");
}
// See all the .QueryInterface()s and .item()s happening here? That's because
// xpcshell sucks and doesn't have classinfo, so no automatic interface
// flattening or array-style access to items in NodeLists. Suck.
var body = doc.documentElement.getElementsByTagName("body");
do_check_eq(body.length, 1);
body = body.item(0);
// header
var header = body.QueryInterface(Ci.nsIDOMElement)
.getElementsByTagName("h1");
do_check_eq(header.length, 1);
do_check_eq(header.item(0).QueryInterface(Ci.nsIDOM3Node).textContent, path);
// files
var lst = body.getElementsByTagName("ol");
do_check_eq(lst.length, 1);
var items = lst.item(0).QueryInterface(Ci.nsIDOMElement)
.getElementsByTagName("li");
var ios = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
var top = ios.newURI(uri, null, null);
// N.B. No ERROR_IF_SEE_THIS.txt^ file!
var dirEntries = [{name: "CVS", isDirectory: true}, // XXX sigh
{name: "file.txt", isDirectory: false},
{name: "SHOULD_SEE_THIS.txt^", isDirectory: false}];
for (var i = 0; i < items.length; i++)
{
var link = items.item(i)
.childNodes
.item(0)
.QueryInterface(Ci.nsIDOM3Node)
.QueryInterface(Ci.nsIDOMElement);
var f = dirEntries[i];
var sep = f.isDirectory ? "/" : "";
do_check_eq(link.textContent, f.name + sep);
var uri = ios.newURI(link.getAttribute("href"), null, top);
do_check_eq(decodeURIComponent(uri.path), path + f.name + sep);
}
}
/**
* Verifies data in bytes (an array of bytes) represents an index page for the
* given URI and path, which should be a page listing the given directory
@ -123,8 +194,7 @@ function dataCheck(bytes, uri, path, dirEntries)
do_check_eq(link.textContent, f.name + sep);
var uri = ios.newURI(link.getAttribute("href"), null, top);
var fn = encodeURIComponent(f.name) + sep;
do_check_eq(uri.path, path + fn);
do_check_eq(decodeURIComponent(uri.path), path + f.name + sep);
}
}
@ -150,7 +220,10 @@ var listener =
},
onStopRequest: function(request, cx, status)
{
dataCheck(this._data, this._uri, this._path, this._dirEntries);
if (currPathIndex == 2)
hiddenDataCheck(this._data, this._uri, this._path);
else
dataCheck(this._data, this._uri, this._path, this._dirEntries);
if (++currPathIndex == paths.length)
{
@ -182,6 +255,10 @@ function run_test()
srv = createServer();
srv.registerDirectory("/", dir);
var nameDir = do_get_file("netwerk/test/httpserver/test/data/name-scheme/");
srv.registerDirectory("/bar/", nameDir);
srv.start(4444);
performNextTest();

View File

@ -0,0 +1,117 @@
/* -*- 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) 2007
* 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 ***** */
// requests for files beginning with an underscore are handled specially to
// enable htaccess-like functionality without the need to explicitly disable
// display of such files
const PREFIX = "http://localhost:4444";
var tests =
[
new Test(PREFIX + "/bar.html^",
null, start_bar_html_, null),
new Test(PREFIX + "/foo.html^",
null, start_foo_html_, null),
new Test(PREFIX + "/normal-file.txt",
null, start_normal_file_txt, null),
new Test(PREFIX + "/folder^/file.txt",
null, start_folder__file_txt, null),
new Test(PREFIX + "/foo/bar.html^",
null, start_bar_html_, null),
new Test(PREFIX + "/foo/foo.html^",
null, start_foo_html_, null),
new Test(PREFIX + "/foo/normal-file.txt",
null, start_normal_file_txt, null),
new Test(PREFIX + "/foo/folder^/file.txt",
null, start_folder__file_txt, null),
new Test(PREFIX + "/end-caret^/bar.html^",
null, start_bar_html_, null),
new Test(PREFIX + "/end-caret^/foo.html^",
null, start_foo_html_, null),
new Test(PREFIX + "/end-caret^/normal-file.txt",
null, start_normal_file_txt, null),
new Test(PREFIX + "/end-caret^/folder^/file.txt",
null, start_folder__file_txt, null)
];
function run_test()
{
var srv = createServer();
// make sure underscores work in directories "mounted" in directories with
// folders starting with _
var nameDir = do_get_file("netwerk/test/httpserver/test/data/name-scheme/");
srv.registerDirectory("/", nameDir);
srv.registerDirectory("/foo/", nameDir);
srv.registerDirectory("/end-caret^/", nameDir);
srv.start(4444);
runHttpTests(tests, function() { srv.stop(); });
}
// TEST DATA
function start_bar_html_(ch, cx)
{
do_check_eq(ch.responseStatus, 200);
do_check_eq(ch.getResponseHeader("Content-Type"), "text/html");
}
function start_foo_html_(ch, cx)
{
do_check_eq(ch.responseStatus, 404);
}
function start_normal_file_txt(ch, cx)
{
do_check_eq(ch.responseStatus, 200);
do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
}
function start_folder__file_txt(ch, cx)
{
do_check_eq(ch.responseStatus, 200);
do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
}