Bug 428009 - Update the HTTP server to require a *correct* host be specified with it, not just *a* host via an absolute URI as Request-URI or a specified Host header. This also gives request handlers proper details about the location to which the request was targeted. r=biesi on the raw socket usage in the test code, r=ted on the build changes, r=sayrer on the server changes
This commit is contained in:
@@ -53,6 +53,7 @@ _PGO_FILES = \
|
||||
profileserver.py \
|
||||
index.html \
|
||||
quit.js \
|
||||
server-locations.txt \
|
||||
$(NULL)
|
||||
|
||||
|
||||
|
||||
@@ -37,11 +37,13 @@
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
import codecs
|
||||
from datetime import datetime
|
||||
import itertools
|
||||
import logging
|
||||
import shutil
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
@@ -62,56 +64,6 @@ __all__ = [
|
||||
"DEFAULT_APP",
|
||||
]
|
||||
|
||||
# Since some tests require cross-domain support in Mochitest, across ports,
|
||||
# domains, subdomains, etc. we use a proxy autoconfig hack to map a bunch of
|
||||
# servers onto localhost:8888. We have to grant them the same privileges as
|
||||
# localhost:8888 here, since the browser only knows them as the URLs they're
|
||||
# pretending to be. We also have two servers which are set up but don't have
|
||||
# privileges, for testing privilege functionality.
|
||||
#
|
||||
# These lists must be kept in sync with the following list:
|
||||
#
|
||||
# http://developer.mozilla.org/en/docs/Mochitest#How_do_I_test_issues_which_only_show_up_when_tests_are_run_across_domains.3F
|
||||
#
|
||||
servers = [
|
||||
"localhost:8888", # MUST be first -- see PAC pref-setting code
|
||||
"example.org:80",
|
||||
"test1.example.org:80",
|
||||
"test2.example.org:80",
|
||||
"sub1.test1.example.org:80",
|
||||
"sub1.test2.example.org:80",
|
||||
"sub2.test1.example.org:80",
|
||||
"sub2.test2.example.org:80",
|
||||
"example.org:8000",
|
||||
"test1.example.org:8000",
|
||||
"test2.example.org:8000",
|
||||
"sub1.test1.example.org:8000",
|
||||
"sub1.test2.example.org:8000",
|
||||
"sub2.test1.example.org:8000",
|
||||
"sub2.test2.example.org:8000",
|
||||
"example.com:80",
|
||||
"test1.example.com:80",
|
||||
"test2.example.com:80",
|
||||
"sub1.test1.example.com:80",
|
||||
"sub1.test2.example.com:80",
|
||||
"sub2.test1.example.com:80",
|
||||
"sub2.test2.example.com:80",
|
||||
"sectest1.example.org:80",
|
||||
"sub.sectest2.example.org:80",
|
||||
"sub1.xn--lt-uia.example.org:8000", # U+00E4 U+006C U+0074
|
||||
"sub2.xn--lt-uia.example.org:80", # U+00E4 U+006C U+0074
|
||||
"xn--exmple-cua.test:80",
|
||||
"sub1.xn--exmple-cua.test:80",
|
||||
"xn--hxajbheg2az3al.xn--jxalpdlp:80", # Greek IDN for example.test
|
||||
"sub1.xn--hxajbheg2az3al.xn--jxalpdlp:80",
|
||||
]
|
||||
|
||||
unprivilegedServers = [
|
||||
"sectest2.example.org:80",
|
||||
"sub.sectest1.example.org:80",
|
||||
]
|
||||
|
||||
|
||||
# These are generated in mozilla/build/Makefile.in
|
||||
#expand DIST_BIN = "./" + __XPC_BIN_PATH__
|
||||
#expand IS_WIN32 = len("__WIN32__") != 0
|
||||
@@ -223,6 +175,88 @@ class Process:
|
||||
# PROFILE SETUP #
|
||||
#################
|
||||
|
||||
class SyntaxError(Exception):
|
||||
"Signifies a syntax error on a particular line in server-locations.txt."
|
||||
|
||||
def __init__(self, lineno, msg = None):
|
||||
self.lineno = lineno
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
s = "Syntax error on line " + str(self.lineno)
|
||||
if self.msg:
|
||||
s += ": %s." % self.msg
|
||||
else:
|
||||
s += "."
|
||||
return s
|
||||
|
||||
|
||||
class Location:
|
||||
"Represents a location line in server-locations.txt."
|
||||
|
||||
def __init__(self, scheme, host, port, options):
|
||||
self.scheme = scheme
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.options = options
|
||||
|
||||
|
||||
def readLocations():
|
||||
"""
|
||||
Reads the locations at which the Mochitest HTTP server is available from
|
||||
server-locations.txt.
|
||||
"""
|
||||
|
||||
locationFile = codecs.open("server-locations.txt", "r", "UTF-8")
|
||||
|
||||
# Perhaps more detail than necessary, but it's the easiest way to make sure
|
||||
# we get exactly the format we want. See server-locations.txt for the exact
|
||||
# format guaranteed here.
|
||||
lineRe = re.compile(r"^(?P<scheme>[a-z][-a-z0-9+.]*)"
|
||||
r"://"
|
||||
r"(?P<host>"
|
||||
r"\d+\.\d+\.\d+\.\d+"
|
||||
r"|"
|
||||
r"(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*"
|
||||
r"[a-z](?:[-a-z0-9]*[a-z0-9])?"
|
||||
r")"
|
||||
r":"
|
||||
r"(?P<port>\d+)"
|
||||
r"(?:"
|
||||
r"\s+"
|
||||
r"(?P<options>\w+(?:,\w+)*)"
|
||||
r")?$")
|
||||
locations = []
|
||||
lineno = 0
|
||||
seenPrimary = False
|
||||
for line in locationFile:
|
||||
lineno += 1
|
||||
if line.startswith("#") or line == "\n":
|
||||
continue
|
||||
|
||||
match = lineRe.match(line)
|
||||
if not match:
|
||||
raise SyntaxError(lineno)
|
||||
|
||||
options = match.group("options")
|
||||
if options:
|
||||
options = options.split(",")
|
||||
if "primary" in options:
|
||||
if seenPrimary:
|
||||
raise SyntaxError(lineno, "multiple primary locations")
|
||||
seenPrimary = True
|
||||
else:
|
||||
options = []
|
||||
|
||||
locations.append(Location(match.group("scheme"), match.group("host"),
|
||||
match.group("port"), options))
|
||||
|
||||
if not seenPrimary:
|
||||
raise SyntaxError(lineno + 1, "missing primary location")
|
||||
|
||||
return locations
|
||||
|
||||
|
||||
def initializeProfile(profileDir):
|
||||
"Sets up the standard testing profile."
|
||||
|
||||
@@ -250,42 +284,49 @@ user_pref("camino.warn_when_closing", false); // Camino-only, harmless to others
|
||||
"""
|
||||
prefs.append(part)
|
||||
|
||||
# Grant God-power to all the servers on which tests can run.
|
||||
for (i, server) in itertools.izip(itertools.count(1), servers):
|
||||
locations = readLocations()
|
||||
|
||||
# Grant God-power to all the privileged servers on which tests run.
|
||||
privileged = filter(lambda loc: "privileged" in loc.options, locations)
|
||||
for (i, l) in itertools.izip(itertools.count(1), privileged):
|
||||
part = """
|
||||
user_pref("capability.principal.codebase.p%(i)d.granted",
|
||||
"UniversalXPConnect UniversalBrowserRead UniversalBrowserWrite \
|
||||
UniversalPreferencesRead UniversalPreferencesWrite \
|
||||
UniversalFileRead");
|
||||
user_pref("capability.principal.codebase.p%(i)d.id", "http://%(server)s");
|
||||
user_pref("capability.principal.codebase.p%(i)d.id", "%(origin)s");
|
||||
user_pref("capability.principal.codebase.p%(i)d.subjectName", "");
|
||||
""" % {"i": i, "server": server}
|
||||
""" % { "i": i,
|
||||
"origin": (l.scheme + "://" + l.host + ":" + l.port) }
|
||||
prefs.append(part)
|
||||
|
||||
# Now add the two servers that do NOT have God-power so we can properly test
|
||||
# the granting and receiving of God-power. Strip off the first server because
|
||||
# we proxy all the others to it.
|
||||
allServers = servers[1:] + unprivilegedServers
|
||||
|
||||
|
||||
# Now actually create the preference to make the proxying happen.
|
||||
quotedServers = ", ".join(map(lambda x: "'" + x + "'", allServers))
|
||||
# We need to proxy every server but the primary one.
|
||||
origins = ["'%s://%s:%s'" % (l.scheme, l.host, l.port)
|
||||
for l in filter(lambda l: "primary" not in l.options, locations)]
|
||||
origins = ", ".join(origins)
|
||||
|
||||
pacURL = """data:text/plain,
|
||||
function FindProxyForURL(url, host)
|
||||
{
|
||||
var servers = [%(quotedServers)s];
|
||||
var regex = new RegExp('http://(?:[^/@]*@)?(.*?(:\\\\\\\\d+)?)/');
|
||||
var origins = [%(origins)s];
|
||||
var regex = new RegExp('^([a-z][-a-z0-9+.]*)' +
|
||||
'://' +
|
||||
'(?:[^/@]*@)?' +
|
||||
'(.*?)' +
|
||||
'(?::(\\\\\\\\d+))?/');
|
||||
var matches = regex.exec(url);
|
||||
if (!matches)
|
||||
return 'DIRECT';
|
||||
var hostport = matches[1], port = matches[2];
|
||||
if (!port)
|
||||
hostport += ':80';
|
||||
if (servers.indexOf(hostport) >= 0)
|
||||
var isHttp = matches[1] == 'http';
|
||||
if (!matches[3])
|
||||
matches[3] = isHttp ? '80' : '443';
|
||||
var origin = matches[1] + '://' + matches[2] + ':' + matches[3];
|
||||
if (origins.indexOf(origin) < 0)
|
||||
return 'DIRECT';
|
||||
if (isHttp)
|
||||
return 'PROXY localhost:8888';
|
||||
return 'DIRECT';
|
||||
}""" % {"quotedServers": quotedServers}
|
||||
}""" % { "origins": origins }
|
||||
pacURL = "".join(pacURL.splitlines())
|
||||
|
||||
part = """
|
||||
|
||||
116
build/pgo/server-locations.txt
Normal file
116
build/pgo/server-locations.txt
Normal file
@@ -0,0 +1,116 @@
|
||||
#
|
||||
# ***** 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 mozilla.org code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# Jeff Walden <jwalden+code@mit.edu>.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2008
|
||||
# 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 *****
|
||||
|
||||
#
|
||||
# This file defines the locations at which this HTTP server may be accessed.
|
||||
# It is referred to by the following page, so if this file moves, that page must
|
||||
# be modified accordingly:
|
||||
#
|
||||
# http://developer.mozilla.org/en/docs/Mochitest#How_do_I_test_issues_which_only_show_up_when_tests_are_run_across_domains.3F
|
||||
#
|
||||
# Empty lines and lines which begin with "#" are ignored and may be used for
|
||||
# storing comments. All other lines consist of an origin followed by whitespace
|
||||
# and a comma-separated list of options (if indeed any options are needed).
|
||||
#
|
||||
# The format of an origin is, referring to RFC 2396, a scheme (either "http" or
|
||||
# "https"), followed by "://", followed by a host, followed by ":", followed by
|
||||
# a port number. The colon and port number must be present even if the port
|
||||
# number is the default for the protocol.
|
||||
#
|
||||
# Unrecognized options are ignored. Recognized options are "primary" and
|
||||
# "privileged". "primary" denotes a location which is the canonical location of
|
||||
# the server; this location is the one assumed for requests which don't
|
||||
# otherwise identify a particular origin (e.g. HTTP/1.0 requests). "privileged"
|
||||
# denotes a location which should have the ability to request elevated
|
||||
# privileges; the default is no privileges.
|
||||
#
|
||||
|
||||
#
|
||||
# This is the primary location from which tests run.
|
||||
#
|
||||
http://localhost:8888 primary,privileged
|
||||
|
||||
#
|
||||
# These are a common set of prefixes scattered across one TLD with two ports and
|
||||
# another TLD on a single port.
|
||||
#
|
||||
http://example.org:80 privileged
|
||||
http://test1.example.org:80 privileged
|
||||
http://test2.example.org:80 privileged
|
||||
http://sub1.test1.example.org:80 privileged
|
||||
http://sub1.test2.example.org:80 privileged
|
||||
http://sub2.test1.example.org:80 privileged
|
||||
http://sub2.test2.example.org:80 privileged
|
||||
http://example.org:8000 privileged
|
||||
http://test1.example.org:8000 privileged
|
||||
http://test2.example.org:8000 privileged
|
||||
http://sub1.test1.example.org:8000 privileged
|
||||
http://sub1.test2.example.org:8000 privileged
|
||||
http://sub2.test1.example.org:8000 privileged
|
||||
http://sub2.test2.example.org:8000 privileged
|
||||
http://example.com:80 privileged
|
||||
http://test1.example.com:80 privileged
|
||||
http://test2.example.com:80 privileged
|
||||
http://sub1.test1.example.com:80 privileged
|
||||
http://sub1.test2.example.com:80 privileged
|
||||
http://sub2.test1.example.com:80 privileged
|
||||
http://sub2.test2.example.com:80 privileged
|
||||
|
||||
#
|
||||
# These are subdomains of <ält.example.org>.
|
||||
#
|
||||
http://sub1.xn--lt-uia.example.org:8000 privileged
|
||||
http://sub2.xn--lt-uia.example.org:80 privileged
|
||||
http://xn--exmple-cua.test:80 privileged
|
||||
http://sub1.xn--exmple-cua.test:80 privileged
|
||||
|
||||
#
|
||||
# These are subdomains of <παράδειγμα.δοκιμή>, the Greek IDN for example.test.
|
||||
#
|
||||
http://xn--hxajbheg2az3al.xn--jxalpdlp:80 privileged
|
||||
http://sub1.xn--hxajbheg2az3al.xn--jxalpdlp:80 privileged
|
||||
|
||||
#
|
||||
# These hosts are used in tests which exercise privilege-granting functionality;
|
||||
# we could reuse some of the names above, but specific names make it easier to
|
||||
# distinguish one from the other in tests (as well as what functionality is
|
||||
# being tested).
|
||||
#
|
||||
http://sectest1.example.org:80 privileged
|
||||
http://sub.sectest2.example.org:80 privileged
|
||||
http://sectest2.example.org:80
|
||||
http://sub.sectest1.example.org:80
|
||||
@@ -46,6 +46,11 @@ code which does this:
|
||||
|
||||
server.stop();
|
||||
|
||||
This server will only respond to requests on 127.0.0.1:8080 or localhost:8080.
|
||||
If you want it to respond to requests at different hosts (say via a proxy
|
||||
mechanism), you must use server.identity.add() or server.identity.setPrimary()
|
||||
to add it.
|
||||
|
||||
|
||||
Using httpd.js as an Inline Script or from xpcshell
|
||||
---------------------------------------------------
|
||||
@@ -69,24 +74,19 @@ whenever possible.
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
While httpd.js 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.
|
||||
|
||||
httpd.js makes no effort to time out requests, beyond any the socket itself
|
||||
might or might not provide. I don't believe it provides any by default, but
|
||||
I haven't verified this.
|
||||
|
||||
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.
|
||||
Every incoming request is processed by the corresponding request handler
|
||||
synchronously. In other words, once the first CRLFCRLF of a request is
|
||||
received, the entire response is created before any new incoming requests can be
|
||||
served. I anticipate adding asynchronous handler functionality in bug 396226,
|
||||
but it may be some time before that happens.
|
||||
|
||||
There is no way to access the body of an incoming request. This problem is
|
||||
merely a symptom of the previous one, and they will probably both be addressed
|
||||
at the same time.
|
||||
|
||||
|
||||
Other Goodies
|
||||
|
||||
@@ -207,9 +207,6 @@ const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
|
||||
const StreamCopier = CC("@mozilla.org/network/async-stream-copier;1",
|
||||
"nsIAsyncStreamCopier",
|
||||
"init");
|
||||
const Pump = CC("@mozilla.org/network/input-stream-pump;1",
|
||||
"nsIInputStreamPump",
|
||||
"init");
|
||||
const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1",
|
||||
"nsIConverterInputStream",
|
||||
"init");
|
||||
@@ -338,6 +335,9 @@ function nsHttpServer()
|
||||
/** The handler used to process requests to this server. */
|
||||
this._handler = new ServerHandler(this);
|
||||
|
||||
/** Naming information for this server. */
|
||||
this._identity = new ServerIdentity();
|
||||
|
||||
/**
|
||||
* Indicates when the server is to be shut down at the end of the request.
|
||||
*/
|
||||
@@ -423,6 +423,7 @@ nsHttpServer.prototype =
|
||||
|
||||
dumpn(">>> listening on port " + socket.port);
|
||||
socket.asyncListen(this);
|
||||
this._identity._initialize(port, true);
|
||||
this._socket = socket;
|
||||
},
|
||||
|
||||
@@ -437,6 +438,11 @@ nsHttpServer.prototype =
|
||||
dumpn(">>> stopping listening on port " + this._socket.port);
|
||||
this._socket.close();
|
||||
this._socket = null;
|
||||
|
||||
// We can't have this identity any more, and the port on which we're running
|
||||
// this server now could be meaningless the next time around.
|
||||
this._identity._teardown();
|
||||
|
||||
this._doQuit = false;
|
||||
|
||||
// spin an event loop and wait for the socket-close notification
|
||||
@@ -506,6 +512,14 @@ nsHttpServer.prototype =
|
||||
this._handler.registerContentType(ext, type);
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServer.serverIdentity
|
||||
//
|
||||
get identity()
|
||||
{
|
||||
return this._identity;
|
||||
},
|
||||
|
||||
// NSISUPPORTS
|
||||
|
||||
//
|
||||
@@ -575,6 +589,263 @@ nsHttpServer.prototype =
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// RFC 2396 section 3.2.2:
|
||||
//
|
||||
// host = hostname | IPv4address
|
||||
// hostname = *( domainlabel "." ) toplabel [ "." ]
|
||||
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
|
||||
// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
|
||||
// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
|
||||
//
|
||||
|
||||
const HOST_REGEX =
|
||||
new RegExp("^(?:" +
|
||||
// *( domainlabel "." )
|
||||
"(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" +
|
||||
// toplabel
|
||||
"[a-z](?:[a-z0-9-]*[a-z0-9])?" +
|
||||
"|" +
|
||||
// IPv4 address
|
||||
"\\d+\\.\\d+\\.\\d+\\.\\d+" +
|
||||
")$",
|
||||
"i");
|
||||
|
||||
|
||||
/**
|
||||
* Represents the identity of a server. An identity consists of a set of
|
||||
* (scheme, host, port) tuples denoted as locations (allowing a single server to
|
||||
* serve multiple sites or to be used behind both HTTP and HTTPS proxies for any
|
||||
* host/port). Any incoming request must be to one of these locations, or it
|
||||
* will be rejected with an HTTP 400 error. One location, denoted as the
|
||||
* primary location, is the location assigned in contexts where a location
|
||||
* cannot otherwise be endogenously derived, such as for HTTP/1.0 requests.
|
||||
*
|
||||
* A single identity may contain at most one location per unique host/port pair;
|
||||
* other than that, no restrictions are placed upon what locations may
|
||||
* constitute an identity.
|
||||
*/
|
||||
function ServerIdentity()
|
||||
{
|
||||
/** The scheme of the primary location. */
|
||||
this._primaryScheme = "http";
|
||||
|
||||
/** The hostname of the primary location. */
|
||||
this._primaryHost = "127.0.0.1"
|
||||
|
||||
/** The port number of the primary location. */
|
||||
this._primaryPort = -1;
|
||||
|
||||
/**
|
||||
* The current port number for the corresponding server, stored so that a new
|
||||
* primary location can always be set if the current one is removed.
|
||||
*/
|
||||
this._defaultPort = -1;
|
||||
|
||||
/**
|
||||
* Maps hosts to maps of ports to schemes, e.g. the following would represent
|
||||
* https://example.com:789/ and http://example.org/:
|
||||
*
|
||||
* {
|
||||
* "xexample.com": { 789: "https" },
|
||||
* "xexample.org": { 80: "http" }
|
||||
* }
|
||||
*
|
||||
* Note the "x" prefix on hostnames, which prevents collisions with special
|
||||
* JS names like "prototype".
|
||||
*/
|
||||
this._locations = { "xlocalhost": {} };
|
||||
}
|
||||
ServerIdentity.prototype =
|
||||
{
|
||||
/**
|
||||
* Initializes the primary name for the corresponding server, based on the
|
||||
* provided port number.
|
||||
*/
|
||||
_initialize: function(port, addSecondaryDefault)
|
||||
{
|
||||
if (this._primaryPort !== -1)
|
||||
this.add("http", "localhost", port);
|
||||
else
|
||||
this.setPrimary("http", "localhost", port);
|
||||
this._defaultPort = port;
|
||||
|
||||
// Only add this if we're being called at server startup
|
||||
if (addSecondaryDefault)
|
||||
this.add("http", "127.0.0.1", port);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called at server shutdown time, unsets the primary location only if it was
|
||||
* the default-assigned location and removes the default location from the
|
||||
* set of locations used.
|
||||
*/
|
||||
_teardown: function()
|
||||
{
|
||||
// Not the default primary location, nothing special to do here
|
||||
this.remove("http", "127.0.0.1", this._defaultPort);
|
||||
|
||||
// This is a *very* tricky bit of reasoning here; make absolutely sure the
|
||||
// tests for this code pass before you commit changes to it.
|
||||
if (this._primaryScheme == "http" &&
|
||||
this._primaryHost == "localhost" &&
|
||||
this._primaryPort == this._defaultPort)
|
||||
{
|
||||
// Make sure we don't trigger the readding logic in .remove(), then remove
|
||||
// the default location.
|
||||
var port = this._defaultPort;
|
||||
this._defaultPort = -1;
|
||||
this.remove("http", "localhost", port);
|
||||
|
||||
// Ensure a server start triggers the setPrimary() path in ._initialize()
|
||||
this._primaryPort = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No reason not to remove directly as it's not our primary location
|
||||
this.remove("http", "localhost", this._defaultPort);
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServerIdentity.primaryScheme
|
||||
//
|
||||
get primaryScheme()
|
||||
{
|
||||
if (this._primaryPort === -1)
|
||||
throw Cr.NS_ERROR_NOT_INITIALIZED;
|
||||
return this._primaryScheme;
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServerIdentity.primaryHost
|
||||
//
|
||||
get primaryHost()
|
||||
{
|
||||
if (this._primaryPort === -1)
|
||||
throw Cr.NS_ERROR_NOT_INITIALIZED;
|
||||
return this._primaryHost;
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServerIdentity.primaryPort
|
||||
//
|
||||
get primaryPort()
|
||||
{
|
||||
if (this._primaryPort === -1)
|
||||
throw Cr.NS_ERROR_NOT_INITIALIZED;
|
||||
return this._primaryPort;
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServerIdentity.add
|
||||
//
|
||||
add: function(scheme, host, port)
|
||||
{
|
||||
this._validate(scheme, host, port);
|
||||
|
||||
var entry = this._locations["x" + host];
|
||||
if (!entry)
|
||||
this._locations["x" + host] = entry = {};
|
||||
|
||||
entry[port] = scheme;
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServerIdentity.remove
|
||||
//
|
||||
remove: function(scheme, host, port)
|
||||
{
|
||||
this._validate(scheme, host, port);
|
||||
|
||||
var entry = this._locations["x" + host];
|
||||
if (!entry)
|
||||
return false;
|
||||
|
||||
var present = port in entry;
|
||||
delete entry[port];
|
||||
|
||||
if (this._primaryScheme == scheme &&
|
||||
this._primaryHost == host &&
|
||||
this._primaryPort == port &&
|
||||
this._defaultPort !== -1)
|
||||
{
|
||||
// Always keep at least one identity in existence at any time, unless
|
||||
// we're in the process of shutting down (the last condition above).
|
||||
this._primaryPort = -1;
|
||||
this._initialize(this._defaultPort, false);
|
||||
}
|
||||
|
||||
return present;
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServerIdentity.has
|
||||
//
|
||||
has: function(scheme, host, port)
|
||||
{
|
||||
this._validate(scheme, host, port);
|
||||
|
||||
return "x" + host in this._locations &&
|
||||
scheme === this._locations["x" + host][port];
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServerIdentity.has
|
||||
//
|
||||
getScheme: function(host, port)
|
||||
{
|
||||
this._validate("http", host, port);
|
||||
|
||||
var entry = this._locations["x" + host];
|
||||
if (!entry)
|
||||
return "";
|
||||
|
||||
return entry[port] || "";
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServerIdentity.setPrimary
|
||||
//
|
||||
setPrimary: function(scheme, host, port)
|
||||
{
|
||||
this._validate(scheme, host, port);
|
||||
|
||||
this.add(scheme, host, port);
|
||||
|
||||
this._primaryScheme = scheme;
|
||||
this._primaryHost = host;
|
||||
this._primaryPort = port;
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures scheme, host, and port are all valid with respect to RFC 2396.
|
||||
*
|
||||
* @throws NS_ERROR_ILLEGAL_VALUE
|
||||
* if any argument doesn't match the corresponding production
|
||||
*/
|
||||
_validate: function(scheme, host, port)
|
||||
{
|
||||
if (scheme !== "http" && scheme !== "https")
|
||||
{
|
||||
dumpn("*** server only supports http/https schemes: '" + scheme + "'");
|
||||
dumpStack();
|
||||
throw Cr.NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
if (!HOST_REGEX.test(host))
|
||||
{
|
||||
dumpn("*** unexpected host: '" + host + "'");
|
||||
throw Cr.NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
if (port < 0 || port > 65535)
|
||||
{
|
||||
dumpn("*** unexpected port: '" + port + "'");
|
||||
throw Cr.NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Represents a connection to the server (and possibly in the future the thread
|
||||
* on which the connection is processed).
|
||||
@@ -865,8 +1136,6 @@ RequestReader.prototype =
|
||||
// XXX things to fix here:
|
||||
//
|
||||
// - need to support RFC 2047-encoded non-US-ASCII characters
|
||||
// - really support absolute URLs (requires telling the server all its
|
||||
// hostnames, beyond just localhost:port or 127.0.0.1:port)
|
||||
|
||||
this._data.appendBytes(readBytes(input, count));
|
||||
|
||||
@@ -902,11 +1171,78 @@ RequestReader.prototype =
|
||||
var metadata = this._metadata;
|
||||
var headers = metadata._headers;
|
||||
|
||||
var isHttp11 = metadata._httpVersion.equals(nsHttpVersion.HTTP_1_1);
|
||||
|
||||
// 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header
|
||||
if (isHttp11 && !headers.hasHeader("Host"))
|
||||
var identity = this._connection.server.identity;
|
||||
if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
|
||||
{
|
||||
if (!headers.hasHeader("Host"))
|
||||
{
|
||||
dumpn("*** malformed HTTP/1.1 or greater request with no Host header!");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
// If the Request-URI wasn't absolute, then we need to determine our host.
|
||||
// We have to determine what scheme was used to access us based on the
|
||||
// server identity data at this point, because the request just doesn't
|
||||
// contain enough data on its own to do this, sadly.
|
||||
if (!metadata._host)
|
||||
{
|
||||
var host, port;
|
||||
var hostPort = headers.getHeader("Host");
|
||||
var colon = hostPort.indexOf(":");
|
||||
if (colon < 0)
|
||||
{
|
||||
host = hostPort;
|
||||
port = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
host = hostPort.substring(0, colon);
|
||||
port = hostPort.substring(colon + 1);
|
||||
}
|
||||
|
||||
// NB: We allow an empty port here because, oddly, a colon may be
|
||||
// present even without a port number, e.g. "example.com:"; in this
|
||||
// case the default port applies.
|
||||
if (!HOST_REGEX.test(host) || !/^\d*$/.test(port))
|
||||
{
|
||||
dumpn("*** malformed hostname (" + hostPort + ") in Host " +
|
||||
"header, 400 time");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
// If we're not given a port, we're stuck, because we don't know what
|
||||
// scheme to use to look up the correct port here, in general. Since
|
||||
// the HTTPS case requires a tunnel/proxy and thus requires that the
|
||||
// requested URI be absolute (and thus contain the necessary
|
||||
// information), let's assume HTTP will prevail and use that.
|
||||
port = +port || 80;
|
||||
|
||||
var scheme = identity.getScheme(host, port);
|
||||
if (!scheme)
|
||||
{
|
||||
dumpn("*** unrecognized hostname (" + hostPort + ") in Host " +
|
||||
"header, 400 time");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
metadata._scheme = scheme;
|
||||
metadata._host = host;
|
||||
metadata._port = port;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NS_ASSERT(metadata._host === undefined,
|
||||
"HTTP/1.0 doesn't allow absolute paths in the request line!");
|
||||
|
||||
metadata._scheme = identity.primaryScheme;
|
||||
metadata._host = identity.primaryHost;
|
||||
metadata._port = identity.primaryPort;
|
||||
}
|
||||
|
||||
NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port),
|
||||
"must have a location we recognize by now!");
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -998,8 +1334,7 @@ RequestReader.prototype =
|
||||
try
|
||||
{
|
||||
metadata._httpVersion = new nsHttpVersion(match[1]);
|
||||
if (!metadata._httpVersion.equals(nsHttpVersion.HTTP_1_0) &&
|
||||
!metadata._httpVersion.equals(nsHttpVersion.HTTP_1_1))
|
||||
if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0))
|
||||
throw "unsupported HTTP version";
|
||||
}
|
||||
catch (e)
|
||||
@@ -1010,25 +1345,46 @@ RequestReader.prototype =
|
||||
|
||||
|
||||
var fullPath = request[1];
|
||||
var serverIdentity = this._connection.server.identity;
|
||||
|
||||
var scheme, host, port;
|
||||
|
||||
if (fullPath.charAt(0) != "/")
|
||||
{
|
||||
// XXX we don't really support absolute URIs yet -- a MUST for HTTP/1.1;
|
||||
// for now just get the path and use that, ignoring hostport
|
||||
// No absolute paths in the request line in HTTP prior to 1.1
|
||||
if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
|
||||
throw HTTP_400;
|
||||
|
||||
try
|
||||
{
|
||||
var uri = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService)
|
||||
.newURI(fullPath, null, null);
|
||||
fullPath = uri.path;
|
||||
}
|
||||
catch (e) { /* invalid URI */ }
|
||||
if (fullPath.charAt(0) != "/")
|
||||
scheme = uri.scheme;
|
||||
host = metadata._host = uri.asciiHost;
|
||||
port = uri.port;
|
||||
if (port === -1)
|
||||
{
|
||||
this.errorCode = 400;
|
||||
return;
|
||||
if (scheme === "http")
|
||||
port = 80;
|
||||
else if (scheme === "https")
|
||||
port = 443;
|
||||
else
|
||||
throw HTTP_400;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// If the host is not a valid host on the server, the response MUST be a
|
||||
// 400 (Bad Request) error message (section 5.2). Alternately, the URI
|
||||
// is malformed.
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
var splitter = fullPath.indexOf("?");
|
||||
if (splitter < 0)
|
||||
@@ -1042,6 +1398,10 @@ RequestReader.prototype =
|
||||
metadata._queryString = fullPath.substring(splitter + 1);
|
||||
}
|
||||
|
||||
metadata._scheme = scheme;
|
||||
metadata._host = host;
|
||||
metadata._port = port;
|
||||
|
||||
// our work here is finished
|
||||
this._state = READER_IN_HEADERS;
|
||||
},
|
||||
@@ -2399,7 +2759,10 @@ ServerHandler.prototype =
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/plain", false);
|
||||
|
||||
var body = "Request (semantically equivalent, slightly reformatted):\n\n";
|
||||
var body = "Request-URI: " +
|
||||
metadata.scheme + "://" + metadata.host + ":" + metadata.port +
|
||||
metadata.path + "\n\n";
|
||||
body += "Request (semantically equivalent, slightly reformatted):\n\n";
|
||||
body += metadata.method + " " + metadata.path;
|
||||
|
||||
if (metadata.queryString)
|
||||
@@ -2615,7 +2978,6 @@ Response.prototype =
|
||||
get httpVersion()
|
||||
{
|
||||
this._ensureAlive();
|
||||
|
||||
return this._httpVersion.toString();
|
||||
},
|
||||
|
||||
@@ -2938,6 +3300,14 @@ nsHttpVersion.prototype =
|
||||
{
|
||||
return this.major == otherVersion.major &&
|
||||
this.minor == otherVersion.minor;
|
||||
},
|
||||
|
||||
/** True if this >= otherVersion, false otherwise. */
|
||||
atLeast: function(otherVersion)
|
||||
{
|
||||
return this.major > otherVersion.major ||
|
||||
(this.major == otherVersion.major &&
|
||||
this.minor >= otherVersion.minor);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3096,12 +3466,23 @@ nsSimpleEnumerator.prototype =
|
||||
*/
|
||||
function Request(port)
|
||||
{
|
||||
/** Method of this request, e.g. GET or POST. */
|
||||
this._method = "";
|
||||
|
||||
/** Path of the requested resource; empty paths are converted to '/'. */
|
||||
this._path = "";
|
||||
|
||||
/** Query string, if any, associated with this request (not including '?'). */
|
||||
this._queryString = "";
|
||||
this._host = "";
|
||||
|
||||
/** Scheme of requested resource, usually http, always lowercase. */
|
||||
this._scheme = "http";
|
||||
|
||||
/** Hostname on which the requested resource resides. */
|
||||
this._host = undefined;
|
||||
|
||||
/** Port number over which the request was received. */
|
||||
this._port = port;
|
||||
this._host = "localhost"; // XXX or from environment or server itself?
|
||||
|
||||
/**
|
||||
* The headers in this request.
|
||||
@@ -3119,6 +3500,14 @@ Request.prototype =
|
||||
{
|
||||
// SERVER METADATA
|
||||
|
||||
//
|
||||
// see nsIHttpRequestMetadata.scheme
|
||||
//
|
||||
get scheme()
|
||||
{
|
||||
return this._scheme;
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpRequestMetadata.host
|
||||
//
|
||||
@@ -3366,6 +3755,7 @@ function server(port, basePath)
|
||||
if (lp)
|
||||
srv.registerDirectory("/", lp);
|
||||
srv.registerContentType("sjs", SJS_TYPE);
|
||||
srv.identity.setPrimary("http", "localhost", port);
|
||||
srv.start(port);
|
||||
|
||||
var thread = gThreadManager.currentThread;
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsIServerSocket.idl"
|
||||
#include "nsIPropertyBag.idl"
|
||||
|
||||
interface nsILocalFile;
|
||||
@@ -47,12 +46,13 @@ interface nsIHttpServer;
|
||||
interface nsIHttpRequestHandler;
|
||||
interface nsIHttpRequestMetadata;
|
||||
interface nsIHttpResponse;
|
||||
interface nsIHttpServerIdentity;
|
||||
|
||||
/**
|
||||
* An interface which represents an HTTP server.
|
||||
*/
|
||||
[scriptable, uuid(5520f79e-ecd5-4c40-843b-97ee13a23747)]
|
||||
interface nsIHttpServer : nsIServerSocketListener
|
||||
[scriptable, uuid(9049C469-8402-4FA6-883C-826B0FE9CAF9)]
|
||||
interface nsIHttpServer : nsISupports
|
||||
{
|
||||
/**
|
||||
* Starts up this server, listening upon the given port. This method may
|
||||
@@ -183,6 +183,108 @@ interface nsIHttpServer : nsIServerSocketListener
|
||||
* handler, under the key "directory".
|
||||
*/
|
||||
void setIndexHandler(in nsIHttpRequestHandler handler);
|
||||
|
||||
/** Represents the locations at which this server is reachable. */
|
||||
readonly attribute nsIHttpServerIdentity identity;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a set of names for a server, one of which is the primary name for
|
||||
* the server and the rest of which are secondary. By default every server will
|
||||
* contain ("http", "localhost", port) and ("http", "127.0.0.1", port) as names,
|
||||
* where port is what was provided to the corresponding server when started;
|
||||
* however, except for their being removed when the corresponding server stops
|
||||
* they have no special importance.
|
||||
*/
|
||||
[scriptable, uuid(a89de175-ae8e-4c46-91a5-0dba99bbd284)]
|
||||
interface nsIHttpServerIdentity : nsISupports
|
||||
{
|
||||
/**
|
||||
* The primary scheme at which the corresponding server is located, defaulting
|
||||
* to 'http'. This name will be the value of nsIHttpRequestMetadata.scheme
|
||||
* for HTTP/1.0 requests.
|
||||
*
|
||||
* This value is always set when the corresponding server is running. If the
|
||||
* server is not running, this value is set only if it has been set to a
|
||||
* non-default name using setPrimary. In this case reading this value will
|
||||
* throw NS_ERROR_NOT_INITIALIZED.
|
||||
*/
|
||||
readonly attribute string primaryScheme;
|
||||
|
||||
/**
|
||||
* The primary name by which the corresponding server is known, defaulting to
|
||||
* 'localhost'. This name will be the value of nsIHttpRequestMetadata.host
|
||||
* for HTTP/1.0 requests.
|
||||
*
|
||||
* This value is always set when the corresponding server is running. If the
|
||||
* server is not running, this value is set only if it has been set to a
|
||||
* non-default name using setPrimary. In this case reading this value will
|
||||
* throw NS_ERROR_NOT_INITIALIZED.
|
||||
*/
|
||||
readonly attribute string primaryHost;
|
||||
|
||||
/**
|
||||
* The primary port on which the corresponding server runs, defaulting to the
|
||||
* associated server's port. This name will be the value of
|
||||
* nsIHttpRequestMetadata.port for HTTP/1.0 requests.
|
||||
*
|
||||
* This value is always set when the corresponding server is running. If the
|
||||
* server is not running, this value is set only if it has been set to a
|
||||
* non-default name using setPrimary. In this case reading this value will
|
||||
* throw NS_ERROR_NOT_INITIALIZED.
|
||||
*/
|
||||
readonly attribute long primaryPort;
|
||||
|
||||
/**
|
||||
* Adds a location at which this server may be accessed.
|
||||
*
|
||||
* @throws NS_ERROR_ILLEGAL_VALUE
|
||||
* if scheme or host do not match the scheme or host productions imported
|
||||
* into RFC 2616 from RFC 2396, or if port is not a valid port number
|
||||
*/
|
||||
void add(in string scheme, in string host, in long port);
|
||||
|
||||
/**
|
||||
* Removes this name from the list of names by which the corresponding server
|
||||
* is known. If name is also the primary name for the server, the primary
|
||||
* name reverts to 'http://127.0.0.1' with the associated server's port.
|
||||
*
|
||||
* @throws NS_ERROR_ILLEGAL_VALUE
|
||||
* if scheme or host do not match the scheme or host productions imported
|
||||
* into RFC 2616 from RFC 2396, or if port is not a valid port number
|
||||
* @returns
|
||||
* true if the given name was a name for this server, false otherwise
|
||||
*/
|
||||
PRBool remove(in string scheme, in string host, in long port);
|
||||
|
||||
/**
|
||||
* Returns true if the given name is in this, false otherwise.
|
||||
*
|
||||
* @throws NS_ERROR_ILLEGAL_VALUE
|
||||
* if scheme or host do not match the scheme or host productions imported
|
||||
* into RFC 2616 from RFC 2396, or if port is not a valid port number
|
||||
*/
|
||||
PRBool has(in string scheme, in string host, in long port);
|
||||
|
||||
/**
|
||||
* Returns the scheme for the name with the given host and port, if one is
|
||||
* present; otherwise returns the empty string.
|
||||
*
|
||||
* @throws NS_ERROR_ILLEGAL_VALUE
|
||||
* if host does not match the host production imported into RFC 2616 from
|
||||
* RFC 2396, or if port is not a valid port number
|
||||
*/
|
||||
string getScheme(in string host, in long port);
|
||||
|
||||
/**
|
||||
* Designates the given name as the primary name in this and adds it to this
|
||||
* if it is not already present.
|
||||
*
|
||||
* @throws NS_ERROR_ILLEGAL_VALUE
|
||||
* if scheme or host do not match the scheme or host productions imported
|
||||
* into RFC 2616 from RFC 2396, or if port is not a valid port number
|
||||
*/
|
||||
void setPrimary(in string scheme, in string host, in long port);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -220,7 +322,7 @@ interface nsIHttpRequestHandler : nsISupports
|
||||
/**
|
||||
* A representation of the data included in an HTTP request.
|
||||
*/
|
||||
[scriptable, uuid(3a899b17-b6eb-4333-8ef4-912df454a551)]
|
||||
[scriptable, uuid(45b92a9e-5e0a-42da-81a6-983e4b1bc1b0)]
|
||||
interface nsIHttpRequestMetadata : nsIPropertyBag
|
||||
{
|
||||
/**
|
||||
@@ -228,10 +330,20 @@ interface nsIHttpRequestMetadata : nsIPropertyBag
|
||||
*/
|
||||
readonly attribute string method;
|
||||
|
||||
/**
|
||||
* The scheme of the requested path, usually 'http' but might possibly be
|
||||
* 'https' if some form of SSL tunneling is in use. Note that this value
|
||||
* cannot be accurately determined unless the incoming request used the
|
||||
* absolute-path form of the request line; it defaults to 'http', so only
|
||||
* if it is something else can you be entirely certain it's correct.
|
||||
*/
|
||||
readonly attribute string scheme;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* host is specified in this.port. This value is in the ASCII character
|
||||
* encoding.
|
||||
*/
|
||||
readonly attribute string host;
|
||||
|
||||
|
||||
@@ -44,10 +44,9 @@ DEBUG = true;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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).
|
||||
*/
|
||||
function createServer()
|
||||
{
|
||||
@@ -101,6 +100,65 @@ function fileContents(file)
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the lines, delimited by CRLF, in data, returning each line
|
||||
* without the trailing line separator.
|
||||
*
|
||||
* @param data : string
|
||||
* a string consisting of lines of data separated by CRLFs
|
||||
* @returns Iterator
|
||||
* an Iterator which returns each line from data in turn; note that this
|
||||
* includes a final empty line if data ended with a CRLF
|
||||
*/
|
||||
function LineIterator(data)
|
||||
{
|
||||
var start = 0, index = 0;
|
||||
do
|
||||
{
|
||||
index = data.indexOf("\r\n");
|
||||
if (index >= 0)
|
||||
yield data.substring(0, index);
|
||||
else
|
||||
yield data;
|
||||
|
||||
data = data.substring(index + 2);
|
||||
}
|
||||
while (index >= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws if iter does not contain exactly the CRLF-separated lines in the
|
||||
* array expectedLines.
|
||||
*
|
||||
* @param iter : Iterator
|
||||
* an Iterator which returns lines of text
|
||||
* @param expectedLines : [string]
|
||||
* an array of the expected lines of text
|
||||
* @throws string
|
||||
* an error message if iter doesn't agree with expectedLines
|
||||
*/
|
||||
function expectLines(iter, expectedLines)
|
||||
{
|
||||
var index = 0;
|
||||
for (var line in iter)
|
||||
{
|
||||
if (expectedLines.length == index)
|
||||
throw "Error: got more than " + expectedLines.length + " expected lines!";
|
||||
|
||||
var expected = expectedLines[index++];
|
||||
if (expected !== line)
|
||||
throw "Error on line " + index + "!\n" +
|
||||
" actual: '" + line + "',\n" +
|
||||
" expect: '" + expected + "'";
|
||||
}
|
||||
|
||||
if (expectedLines.length !== index)
|
||||
{
|
||||
throw "Expected more lines! Got " + index +
|
||||
", expected " + expectedLines.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************
|
||||
* SIMPLE SUPPORT FOR LOADING/TESTING A SERIES OF URLS *
|
||||
@@ -210,3 +268,168 @@ function runHttpTests(testArray, done)
|
||||
performNextTest();
|
||||
}
|
||||
|
||||
|
||||
/****************************************
|
||||
* RAW REQUEST FORMAT TESTING FUNCTIONS *
|
||||
****************************************/
|
||||
|
||||
/**
|
||||
* Sends a raw string of bytes to the given host and port and checks that the
|
||||
* response is acceptable.
|
||||
*
|
||||
* @param host : string
|
||||
* the host to which a connection should be made
|
||||
* @param port : PRUint16
|
||||
* the port to use for the connection
|
||||
* @param data : string
|
||||
* the raw data to send, as a string of characters with codes in the range
|
||||
* 0-255
|
||||
* @param responseCheck : function(string) : void
|
||||
* a function which is provided with the data sent by the remote host which
|
||||
* conducts whatever tests it wants on that data; useful for tweaking the test
|
||||
* environment between tests
|
||||
*/
|
||||
function RawTest(host, port, data, responseCheck)
|
||||
{
|
||||
if (0 > port || 65535 < port || port % 1 !== 0)
|
||||
throw "bad port";
|
||||
if (!/^[\x00-\xff]*$/.test(data))
|
||||
throw "bad data contains non-byte-valued character";
|
||||
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.data = data;
|
||||
this.responseCheck = responseCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all the tests in testArray, an array of RawTests.
|
||||
*
|
||||
* @param testArray : [RawTest]
|
||||
* an array of RawTests to run, in order
|
||||
* @param done
|
||||
* function to call when all tests have run (e.g. to shut down the server)
|
||||
*/
|
||||
function runRawTests(testArray, done)
|
||||
{
|
||||
do_test_pending();
|
||||
|
||||
var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
|
||||
.getService(Ci.nsISocketTransportService);
|
||||
|
||||
var currentThread = Cc["@mozilla.org/thread-manager;1"]
|
||||
.getService()
|
||||
.currentThread;
|
||||
|
||||
/** Kicks off running the next test in the array. */
|
||||
function performNextTest()
|
||||
{
|
||||
if (++testIndex == testArray.length)
|
||||
{
|
||||
do_test_finished();
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var rawTest = testArray[testIndex];
|
||||
|
||||
var transport =
|
||||
sts.createTransport(null, 0, rawTest.host, rawTest.port, null);
|
||||
|
||||
var inStream = transport.openInputStream(0, 0, 0)
|
||||
.QueryInterface(Ci.nsIAsyncInputStream);
|
||||
var outStream = transport.openOutputStream(0, 0, 0)
|
||||
.QueryInterface(Ci.nsIAsyncOutputStream);
|
||||
|
||||
// reset
|
||||
dataIndex = 0;
|
||||
received = "";
|
||||
|
||||
waitForMoreInput(inStream);
|
||||
waitToWriteOutput(outStream);
|
||||
}
|
||||
|
||||
function waitForMoreInput(stream)
|
||||
{
|
||||
stream.asyncWait(reader, 0, 0, currentThread);
|
||||
}
|
||||
|
||||
function waitToWriteOutput(stream)
|
||||
{
|
||||
stream.asyncWait(writer, 0, testArray[testIndex].data.length - dataIndex,
|
||||
currentThread);
|
||||
}
|
||||
|
||||
/** Index of the test being run. */
|
||||
var testIndex = -1;
|
||||
|
||||
/** Index of remaining data to be written to the socket in current test. */
|
||||
var dataIndex = 0;
|
||||
|
||||
/** Data received so far from the server. */
|
||||
var received = "";
|
||||
|
||||
/** Reads data from the socket. */
|
||||
var reader =
|
||||
{
|
||||
onInputStreamReady: function(stream)
|
||||
{
|
||||
var bis = new BinaryInputStream(stream);
|
||||
|
||||
var av = 0;
|
||||
try
|
||||
{
|
||||
av = bis.available();
|
||||
}
|
||||
catch (e) { /* default to 0 */ }
|
||||
|
||||
if (av > 0)
|
||||
{
|
||||
received += String.fromCharCode.apply(null, bis.readByteArray(av));
|
||||
waitForMoreInput(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
var rawTest = testArray[testIndex];
|
||||
try
|
||||
{
|
||||
rawTest.responseCheck(received);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
do_throw("error thrown by responseCheck: " + e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.close();
|
||||
performNextTest();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Writes data to the socket. */
|
||||
var writer =
|
||||
{
|
||||
onOutputStreamReady: function(stream)
|
||||
{
|
||||
var data = testArray[testIndex].data.substring(dataIndex);
|
||||
|
||||
var written = 0;
|
||||
try
|
||||
{
|
||||
written = stream.write(data, data.length);
|
||||
dataIndex += written;
|
||||
}
|
||||
catch (e) { /* stream could have been closed, just ignore */ }
|
||||
|
||||
// Keep reading data until there's no more data to read
|
||||
if (written != 0)
|
||||
waitToWriteOutput(stream);
|
||||
else
|
||||
stream.close();
|
||||
}
|
||||
};
|
||||
|
||||
performNextTest();
|
||||
}
|
||||
|
||||
693
netwerk/test/httpserver/test/test_host.js
Normal file
693
netwerk/test/httpserver/test/test_host.js
Normal file
@@ -0,0 +1,693 @@
|
||||
/* -*- 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) 2008
|
||||
* 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 that the scheme, host, and port of the server are correctly recorded
|
||||
* and used in HTTP requests and responses.
|
||||
*/
|
||||
|
||||
const PORT = 4444;
|
||||
|
||||
var srv;
|
||||
|
||||
function run_test()
|
||||
{
|
||||
srv = createServer();
|
||||
|
||||
srv.registerPathHandler("/http/1.0-request", http10Request);
|
||||
srv.registerPathHandler("/http/1.1-good-host", http11goodHost);
|
||||
srv.registerPathHandler("/http/1.1-good-host-wacky-port",
|
||||
http11goodHostWackyPort);
|
||||
srv.registerPathHandler("/http/1.1-ip-host", http11ipHost);
|
||||
|
||||
const FAKE_PORT_ONE = 8888;
|
||||
const FAKE_PORT_TWO = 8889;
|
||||
|
||||
srv.start(FAKE_PORT_ONE);
|
||||
|
||||
var id = srv.identity;
|
||||
|
||||
// The default location is http://localhost:PORT, where PORT is whatever you
|
||||
// provided when you started the server. http://127.0.0.1:PORT is also part
|
||||
// of the default set of locations.
|
||||
do_check_eq(id.primaryScheme, "http");
|
||||
do_check_eq(id.primaryHost, "localhost");
|
||||
do_check_eq(id.primaryPort, FAKE_PORT_ONE);
|
||||
do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
|
||||
// This should be a nop.
|
||||
id.add("http", "localhost", FAKE_PORT_ONE);
|
||||
do_check_eq(id.primaryScheme, "http");
|
||||
do_check_eq(id.primaryHost, "localhost");
|
||||
do_check_eq(id.primaryPort, FAKE_PORT_ONE);
|
||||
do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
|
||||
// Change the primary location and make sure all the getters work correctly.
|
||||
id.setPrimary("http", "127.0.0.1", FAKE_PORT_ONE);
|
||||
do_check_eq(id.primaryScheme, "http");
|
||||
do_check_eq(id.primaryHost, "127.0.0.1");
|
||||
do_check_eq(id.primaryPort, FAKE_PORT_ONE);
|
||||
do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
|
||||
// Okay, now remove the primary location -- we fall back to the original
|
||||
// location.
|
||||
id.remove("http", "127.0.0.1", FAKE_PORT_ONE);
|
||||
do_check_eq(id.primaryScheme, "http");
|
||||
do_check_eq(id.primaryHost, "localhost");
|
||||
do_check_eq(id.primaryPort, FAKE_PORT_ONE);
|
||||
do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
|
||||
// You can't remove every location -- try this and the original default
|
||||
// location will be silently readded.
|
||||
id.remove("http", "localhost", FAKE_PORT_ONE);
|
||||
do_check_eq(id.primaryScheme, "http");
|
||||
do_check_eq(id.primaryHost, "localhost");
|
||||
do_check_eq(id.primaryPort, FAKE_PORT_ONE);
|
||||
do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
|
||||
// Okay, now that we've exercised that behavior, shut down the server and
|
||||
// restart it on the correct port, to exercise port-changing behaviors at
|
||||
// server start and stop.
|
||||
srv.stop();
|
||||
|
||||
// Our primary location is gone because it was dependent on the port on which
|
||||
// the server was running.
|
||||
checkPrimariesThrow(id);
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
|
||||
srv.start(FAKE_PORT_TWO);
|
||||
|
||||
// We should have picked up http://localhost:8889 as our primary location now
|
||||
// that we've restarted.
|
||||
do_check_eq(id.primaryScheme, "http");
|
||||
do_check_eq(id.primaryHost, "localhost", FAKE_PORT_TWO);
|
||||
do_check_eq(id.primaryPort, FAKE_PORT_TWO);
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
do_check_true(id.has("http", "localhost", FAKE_PORT_TWO));
|
||||
do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
|
||||
|
||||
// Now we're going to see what happens when we shut down with a primary
|
||||
// location that wasn't a default. That location should persist, and the
|
||||
// default we remove should still not be present.
|
||||
id.setPrimary("http", "example.com", FAKE_PORT_TWO);
|
||||
do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
|
||||
do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
|
||||
do_check_true(id.has("http", "localhost", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
|
||||
id.remove("http", "localhost", FAKE_PORT_TWO);
|
||||
do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
|
||||
do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
|
||||
id.remove("http", "127.0.0.1", FAKE_PORT_TWO);
|
||||
do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
|
||||
srv.stop();
|
||||
|
||||
// Only the default added location disappears; any others stay around,
|
||||
// possibly as the primary location. We may have removed the default primary
|
||||
// location, but the one we set manually should persist here.
|
||||
do_check_eq(id.primaryScheme, "http");
|
||||
do_check_eq(id.primaryHost, "example.com");
|
||||
do_check_eq(id.primaryPort, FAKE_PORT_TWO);
|
||||
do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
|
||||
srv.start(PORT);
|
||||
|
||||
// Starting always adds HTTP entries for 127.0.0.1:port and localhost:port.
|
||||
do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
do_check_true(id.has("http", "localhost", PORT));
|
||||
do_check_true(id.has("http", "127.0.0.1", PORT));
|
||||
|
||||
// Remove the primary location we'd left set from last time.
|
||||
id.remove("http", "example.com", FAKE_PORT_TWO);
|
||||
|
||||
// Default-port behavior testing requires the server responds to requests
|
||||
// claiming to be on one such port.
|
||||
id.add("http", "localhost", 80);
|
||||
|
||||
// Make sure we don't have anything lying around from running on either the
|
||||
// first or the second port -- all we should have is our generated default,
|
||||
// plus the additional port to test "portless" hostport variants.
|
||||
do_check_true(id.has("http", "localhost", 80));
|
||||
do_check_eq(id.primaryScheme, "http");
|
||||
do_check_eq(id.primaryHost, "localhost");
|
||||
do_check_eq(id.primaryPort, PORT);
|
||||
do_check_true(id.has("http", "localhost", PORT));
|
||||
do_check_true(id.has("http", "127.0.0.1", PORT));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
|
||||
do_check_false(id.has("http", "example.com", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
|
||||
do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
|
||||
|
||||
// Okay, finally done with identity testing. Our primary location is the one
|
||||
// we want it to be, so we're off!
|
||||
runRawTests(tests, function() { srv.stop(); });
|
||||
}
|
||||
|
||||
|
||||
/*********************
|
||||
* UTILITY FUNCTIONS *
|
||||
*********************/
|
||||
|
||||
/**
|
||||
* Verifies that all .primary* getters on a server identity correctly throw
|
||||
* NS_ERROR_NOT_INITIALIZED.
|
||||
*
|
||||
* @param id : nsIHttpServerIdentity
|
||||
* the server identity to test
|
||||
*/
|
||||
function checkPrimariesThrow(id)
|
||||
{
|
||||
var threw = false;
|
||||
try
|
||||
{
|
||||
id.primaryScheme;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
do_check_true(threw);
|
||||
|
||||
threw = false;
|
||||
try
|
||||
{
|
||||
id.primaryHost;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
do_check_true(threw);
|
||||
|
||||
threw = false;
|
||||
try
|
||||
{
|
||||
id.primaryPort;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
do_check_true(threw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spew a bunch of HTTP metadata from request into the body of response.
|
||||
*
|
||||
* @param request : nsIHttpRequestMetadata
|
||||
* the request whose metadata should be output
|
||||
* @param response : nsIHttpResponse
|
||||
* the response to which the metadata is written
|
||||
*/
|
||||
function writeDetails(request, response)
|
||||
{
|
||||
response.write("Method: " + request.method + "\r\n");
|
||||
response.write("Path: " + request.path + "\r\n");
|
||||
response.write("Query: " + request.queryString + "\r\n");
|
||||
response.write("Version: " + request.httpVersion + "\r\n");
|
||||
response.write("Scheme: " + request.scheme + "\r\n");
|
||||
response.write("Host: " + request.host + "\r\n");
|
||||
response.write("Port: " + request.port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances iter past all non-blank lines and a single blank line, after which
|
||||
* point the body of the response will be returned next from the iterator.
|
||||
*
|
||||
* @param iter : Iterator
|
||||
* an iterator over the CRLF-delimited lines in an HTTP response, currently
|
||||
* just after the Request-Line
|
||||
*/
|
||||
function skipHeaders(iter)
|
||||
{
|
||||
var line = iter.next();
|
||||
while (line !== "")
|
||||
line = iter.next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to check for a 400 response.
|
||||
*/
|
||||
function check400(data)
|
||||
{
|
||||
var iter = LineIterator(data);
|
||||
|
||||
// Status-Line
|
||||
var firstLine = iter.next();
|
||||
do_check_eq(firstLine.substring(0, HTTP_400_LEADER_LENGTH), HTTP_400_LEADER);
|
||||
}
|
||||
|
||||
|
||||
/***************
|
||||
* BEGIN TESTS *
|
||||
***************/
|
||||
|
||||
const HTTP_400_LEADER = "HTTP/1.1 400 ";
|
||||
const HTTP_400_LEADER_LENGTH = HTTP_400_LEADER.length;
|
||||
|
||||
var test, data;
|
||||
var tests = [];
|
||||
|
||||
// HTTP/1.0 request, to ensure we see our default scheme/host/port
|
||||
|
||||
function http10Request(request, response)
|
||||
{
|
||||
writeDetails(request, response);
|
||||
response.setStatusLine("1.0", 200, "TEST PASSED");
|
||||
}
|
||||
data = "GET /http/1.0-request HTTP/1.0\r\n" +
|
||||
"\r\n";
|
||||
function check10(data)
|
||||
{
|
||||
var iter = LineIterator(data);
|
||||
|
||||
// Status-Line
|
||||
do_check_eq(iter.next(), "HTTP/1.0 200 TEST PASSED");
|
||||
|
||||
skipHeaders(iter);
|
||||
|
||||
// Okay, next line must be the data we expected to be written
|
||||
var body =
|
||||
[
|
||||
"Method: GET",
|
||||
"Path: /http/1.0-request",
|
||||
"Query: ",
|
||||
"Version: 1.0",
|
||||
"Scheme: http",
|
||||
"Host: localhost",
|
||||
"Port: 4444",
|
||||
];
|
||||
|
||||
expectLines(iter, body);
|
||||
}
|
||||
test = new RawTest("localhost", PORT, data, check10),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, no Host header, expect a 400 response
|
||||
|
||||
data = "GET /http/1.1-request HTTP/1.1\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, wrong host, expect a 400 response
|
||||
|
||||
data = "GET /http/1.1-request HTTP/1.1\r\n" +
|
||||
"Host: not-localhost\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, wrong host/right port, expect a 400 response
|
||||
|
||||
data = "GET /http/1.1-request HTTP/1.1\r\n" +
|
||||
"Host: not-localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, Host header has host but no port, expect a 400 response
|
||||
|
||||
data = "GET /http/1.1-request HTTP/1.1\r\n" +
|
||||
"Host: 127.0.0.1\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, Request-URI has wrong port, expect a 400 response
|
||||
|
||||
data = "GET http://127.0.0.1/http/1.1-request HTTP/1.1\r\n" +
|
||||
"Host: 127.0.0.1\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, Request-URI has wrong port, expect a 400 response
|
||||
|
||||
data = "GET http://localhost:31337/http/1.1-request HTTP/1.1\r\n" +
|
||||
"Host: localhost:31337\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, Request-URI has wrong scheme, expect a 400 response
|
||||
|
||||
data = "GET https://localhost:4444/http/1.1-request HTTP/1.1\r\n" +
|
||||
"Host: localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, correct Host header, expect handler's response
|
||||
|
||||
function http11goodHost(request, response)
|
||||
{
|
||||
writeDetails(request, response);
|
||||
response.setStatusLine("1.1", 200, "TEST PASSED");
|
||||
}
|
||||
data = "GET /http/1.1-good-host HTTP/1.1\r\n" +
|
||||
"Host: localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
function check11goodHost(data)
|
||||
{
|
||||
var iter = LineIterator(data);
|
||||
|
||||
// Status-Line
|
||||
do_check_eq(iter.next(), "HTTP/1.1 200 TEST PASSED");
|
||||
|
||||
skipHeaders(iter);
|
||||
|
||||
// Okay, next line must be the data we expected to be written
|
||||
var body =
|
||||
[
|
||||
"Method: GET",
|
||||
"Path: /http/1.1-good-host",
|
||||
"Query: ",
|
||||
"Version: 1.1",
|
||||
"Scheme: http",
|
||||
"Host: localhost",
|
||||
"Port: 4444",
|
||||
];
|
||||
|
||||
expectLines(iter, body);
|
||||
}
|
||||
test = new RawTest("localhost", PORT, data, check11goodHost),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, Host header is secondary identity
|
||||
|
||||
function http11ipHost(request, response)
|
||||
{
|
||||
writeDetails(request, response);
|
||||
response.setStatusLine("1.1", 200, "TEST PASSED");
|
||||
}
|
||||
data = "GET /http/1.1-ip-host HTTP/1.1\r\n" +
|
||||
"Host: 127.0.0.1:4444\r\n" +
|
||||
"\r\n";
|
||||
function check11ipHost(data)
|
||||
{
|
||||
var iter = LineIterator(data);
|
||||
|
||||
// Status-Line
|
||||
do_check_eq(iter.next(), "HTTP/1.1 200 TEST PASSED");
|
||||
|
||||
skipHeaders(iter);
|
||||
|
||||
// Okay, next line must be the data we expected to be written
|
||||
var body =
|
||||
[
|
||||
"Method: GET",
|
||||
"Path: /http/1.1-ip-host",
|
||||
"Query: ",
|
||||
"Version: 1.1",
|
||||
"Scheme: http",
|
||||
"Host: 127.0.0.1",
|
||||
"Port: 4444",
|
||||
];
|
||||
|
||||
expectLines(iter, body);
|
||||
}
|
||||
test = new RawTest("localhost", PORT, data, check11ipHost),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, absolute path, accurate Host header
|
||||
|
||||
// reusing previous request handler so not defining a new one
|
||||
|
||||
data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
|
||||
"Host: localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check11goodHost),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, absolute path, inaccurate Host header
|
||||
|
||||
// reusing previous request handler so not defining a new one
|
||||
|
||||
data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
|
||||
"Host: localhost:1234\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check11goodHost),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, absolute path, different inaccurate Host header
|
||||
|
||||
// reusing previous request handler so not defining a new one
|
||||
|
||||
data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
|
||||
"Host: not-localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check11goodHost),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, absolute path, yet another inaccurate Host header
|
||||
|
||||
// reusing previous request handler so not defining a new one
|
||||
|
||||
data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
|
||||
"Host: yippity-skippity\r\n" +
|
||||
"\r\n";
|
||||
function checkInaccurate(data)
|
||||
{
|
||||
check11goodHost(data);
|
||||
|
||||
// dynamism setup
|
||||
srv.identity.setPrimary("http", "127.0.0.1", 4444);
|
||||
}
|
||||
test = new RawTest("localhost", PORT, data, checkInaccurate),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.0 request, absolute path, different inaccurate Host header
|
||||
|
||||
// reusing previous request handler so not defining a new one
|
||||
|
||||
data = "GET /http/1.0-request HTTP/1.0\r\n" +
|
||||
"Host: not-localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
function check10ip(data)
|
||||
{
|
||||
var iter = LineIterator(data);
|
||||
|
||||
// Status-Line
|
||||
do_check_eq(iter.next(), "HTTP/1.0 200 TEST PASSED");
|
||||
|
||||
skipHeaders(iter);
|
||||
|
||||
// Okay, next line must be the data we expected to be written
|
||||
var body =
|
||||
[
|
||||
"Method: GET",
|
||||
"Path: /http/1.0-request",
|
||||
"Query: ",
|
||||
"Version: 1.0",
|
||||
"Scheme: http",
|
||||
"Host: 127.0.0.1",
|
||||
"Port: 4444",
|
||||
];
|
||||
|
||||
expectLines(iter, body);
|
||||
}
|
||||
test = new RawTest("localhost", PORT, data, check10ip),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, Host header with implied port
|
||||
|
||||
function http11goodHostWackyPort(request, response)
|
||||
{
|
||||
writeDetails(request, response);
|
||||
response.setStatusLine("1.1", 200, "TEST PASSED");
|
||||
}
|
||||
data = "GET /http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n";
|
||||
function check11goodHostWackyPort(data)
|
||||
{
|
||||
var iter = LineIterator(data);
|
||||
|
||||
// Status-Line
|
||||
do_check_eq(iter.next(), "HTTP/1.1 200 TEST PASSED");
|
||||
|
||||
skipHeaders(iter);
|
||||
|
||||
// Okay, next line must be the data we expected to be written
|
||||
var body =
|
||||
[
|
||||
"Method: GET",
|
||||
"Path: /http/1.1-good-host-wacky-port",
|
||||
"Query: ",
|
||||
"Version: 1.1",
|
||||
"Scheme: http",
|
||||
"Host: localhost",
|
||||
"Port: 80",
|
||||
];
|
||||
|
||||
expectLines(iter, body);
|
||||
}
|
||||
test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, Host header with wacky implied port
|
||||
|
||||
data = "GET /http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
|
||||
"Host: localhost:\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, absolute URI with implied port
|
||||
|
||||
data = "GET http://localhost/http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, absolute URI with wacky implied port
|
||||
|
||||
data = "GET http://localhost:/http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, absolute URI with explicit implied port, ignored Host
|
||||
|
||||
data = "GET http://localhost:80/http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
|
||||
"Host: who-cares\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, a malformed Request-URI
|
||||
|
||||
data = "GET is-this-the-real-life-is-this-just-fantasy HTTP/1.1\r\n" +
|
||||
"Host: localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, a malformed Host header
|
||||
|
||||
data = "GET /http/1.1-request HTTP/1.1\r\n" +
|
||||
"Host: la la la\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, a malformed Host header but absolute URI, 5.2 sez fine
|
||||
|
||||
data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
|
||||
"Host: la la la\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check11goodHost),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.0 request, absolute URI, but those aren't valid in HTTP/1.0
|
||||
|
||||
data = "GET http://localhost:4444/http/1.1-request HTTP/1.0\r\n" +
|
||||
"Host: localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, absolute URI with unrecognized host
|
||||
|
||||
data = "GET http://not-localhost:4444/http/1.1-request HTTP/1.1\r\n" +
|
||||
"Host: not-localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
|
||||
|
||||
// HTTP/1.1 request, absolute URI with unrecognized host (but not in Host)
|
||||
|
||||
data = "GET http://not-localhost:4444/http/1.1-request HTTP/1.1\r\n" +
|
||||
"Host: localhost:4444\r\n" +
|
||||
"\r\n";
|
||||
test = new RawTest("localhost", PORT, data, check400),
|
||||
tests.push(test);
|
||||
@@ -68,6 +68,7 @@ _SERV_FILES = \
|
||||
redirect-a11y.html \
|
||||
redirect.html \
|
||||
redirect.js \
|
||||
$(topsrcdir)/build/pgo/server-locations.txt \
|
||||
$(topsrcdir)/netwerk/test/httpserver/httpd.js \
|
||||
$(NULL)
|
||||
|
||||
|
||||
@@ -134,14 +134,8 @@ function runServer()
|
||||
serverBasePath.append("_tests");
|
||||
serverBasePath.append("testing");
|
||||
serverBasePath.append("mochitest");
|
||||
server = new nsHttpServer();
|
||||
server.registerDirectory("/", serverBasePath);
|
||||
|
||||
server.registerPathHandler("/server/shutdown", serverShutdown);
|
||||
|
||||
server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality
|
||||
|
||||
server.setIndexHandler(defaultDirHandler);
|
||||
server = createMochitestServer(serverBasePath);
|
||||
server.start(SERVER_PORT);
|
||||
|
||||
// touch a file in the profile directory to indicate we're alive
|
||||
@@ -183,6 +177,93 @@ function runServer()
|
||||
thread.processNextEvent(true);
|
||||
}
|
||||
|
||||
/** Creates and returns an HTTP server configured to serve Mochitests. */
|
||||
function createMochitestServer(serverBasePath)
|
||||
{
|
||||
var server = new nsHttpServer();
|
||||
|
||||
server.registerDirectory("/", serverBasePath);
|
||||
server.registerPathHandler("/server/shutdown", serverShutdown);
|
||||
server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality
|
||||
server.setIndexHandler(defaultDirHandler);
|
||||
|
||||
processLocations(server);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the HTTP server about all the locations at which it might receive
|
||||
* requests, so that it can properly respond to requests on any of the hosts it
|
||||
* serves.
|
||||
*/
|
||||
function processLocations(server)
|
||||
{
|
||||
var serverLocations = serverBasePath.clone();
|
||||
serverLocations.append("server-locations.txt");
|
||||
|
||||
const PR_RDONLY = 0x01;
|
||||
var fis = new FileInputStream(serverLocations, PR_RDONLY, 0444,
|
||||
Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
||||
|
||||
var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
|
||||
lis.QueryInterface(Ci.nsIUnicharLineInputStream);
|
||||
|
||||
const LINE_REGEXP =
|
||||
new RegExp("^([a-z][-a-z0-9+.]*)" +
|
||||
"://" +
|
||||
"(" +
|
||||
"\\d+\\.\\d+\\.\\d+\\.\\d+" +
|
||||
"|" +
|
||||
"(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\\.)*" +
|
||||
"[a-z](?:[-a-z0-9]*[a-z0-9])?" +
|
||||
")" +
|
||||
":" +
|
||||
"(\\d+)" +
|
||||
"(?:" +
|
||||
"\\s+" +
|
||||
"(\\w+(?:,\\w+)*)" +
|
||||
")?$");
|
||||
|
||||
var line = {};
|
||||
var lineno = 0;
|
||||
var seenPrimary = false;
|
||||
do
|
||||
{
|
||||
var more = lis.readLine(line);
|
||||
lineno++;
|
||||
|
||||
var lineValue = line.value;
|
||||
if (lineValue.charAt(0) == "#" || lineValue == "")
|
||||
continue;
|
||||
|
||||
var match = LINE_REGEXP.exec(lineValue);
|
||||
if (!match)
|
||||
throw "Syntax error in server-locations.txt, line " + lineno;
|
||||
|
||||
var [, scheme, host, port, options] = match;
|
||||
if (options)
|
||||
{
|
||||
if (options.split(",").indexOf("primary") >= 0)
|
||||
{
|
||||
if (seenPrimary)
|
||||
{
|
||||
throw "Multiple primary locations in server-locations.txt, " +
|
||||
"line " + lineno;
|
||||
}
|
||||
|
||||
server.identity.setPrimary(scheme, host, port);
|
||||
seenPrimary = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
server.identity.add(scheme, host, port);
|
||||
}
|
||||
while (more);
|
||||
}
|
||||
|
||||
|
||||
// PATH HANDLERS
|
||||
|
||||
// /server/shutdown
|
||||
|
||||
Reference in New Issue
Block a user