Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Bug 484027 - Add a method providing minimally controlled arbitrary wr…

…ite access to the connection within a response, allowing arbitrary information (even data which is not a syntactically valid HTTP response) to be sent in responses. r=sayrer

--HG--
extra : rebase_source : 2d61cccef9b076b2e5dbe1074af99f572d60b700
  • Loading branch information...
commit fa4f06df47999afa853c745ebc288e53a3429cbd 1 parent e666178
@jswalden jswalden authored
View
157 netwerk/test/httpserver/httpd.js
@@ -3332,6 +3332,13 @@ function Response(connection)
* to this may be made.
*/
this._finished = false;
+
+ /**
+ * True iff powerSeized() has been called on this, signaling that this
+ * response is to be handled manually by the response handler (which may then
+ * send arbitrary data in response, even non-HTTP responses).
+ */
+ this._powerSeized = false;
}
Response.prototype =
{
@@ -3351,7 +3358,7 @@ Response.prototype =
null);
this._bodyOutputStream = pipe.outputStream;
this._bodyInputStream = pipe.inputStream;
- if (this._processAsync)
+ if (this._processAsync || this._powerSeized)
this._startAsyncProcessor();
}
@@ -3375,7 +3382,7 @@ Response.prototype =
//
setStatusLine: function(httpVersion, code, description)
{
- if (!this._headers || this._finished)
+ if (!this._headers || this._finished || this._powerSeized)
throw Cr.NS_ERROR_NOT_AVAILABLE;
this._ensureAlive();
@@ -3420,7 +3427,7 @@ Response.prototype =
//
setHeader: function(name, value, merge)
{
- if (!this._headers || this._finished)
+ if (!this._headers || this._finished || this._powerSeized)
throw Cr.NS_ERROR_NOT_AVAILABLE;
this._ensureAlive();
@@ -3434,8 +3441,11 @@ Response.prototype =
{
if (this._finished)
throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
if (this._processAsync)
return;
+ this._ensureAlive();
dumpn("*** processing connection " + this._connection.number + " async");
this._processAsync = true;
@@ -3458,22 +3468,59 @@ Response.prototype =
},
//
+ // see nsIHttpResponse.seizePower
+ //
+ seizePower: function()
+ {
+ if (this._processAsync)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ if (this._finished)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._powerSeized)
+ return;
+ this._ensureAlive();
+
+ dumpn("*** forcefully seizing power over connection " +
+ this._connection.number + "...");
+
+ // Purge any already-written data without sending it. We could as easily
+ // swap out the streams entirely, but that makes it possible to acquire and
+ // unknowingly use a stale reference, so we require there only be one of
+ // each stream ever for any response to avoid this complication.
+ if (this._asyncCopier)
+ this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
+ this._asyncCopier = null;
+ if (this._bodyOutputStream)
+ {
+ var input = new BinaryInputStream(this._bodyInputStream);
+ var avail;
+ while ((avail = input.available()) > 0)
+ input.readByteArray(avail);
+ }
+
+ this._powerSeized = true;
+ if (this._bodyOutputStream)
+ this._startAsyncProcessor();
+ },
+
+ //
// see nsIHttpResponse.finish
//
finish: function()
{
- if (!this._processAsync)
+ if (!this._processAsync && !this._powerSeized)
throw Cr.NS_ERROR_UNEXPECTED;
if (this._finished)
return;
- dumpn("*** finishing async connection " + this._connection.number);
+ dumpn("*** finishing connection " + this._connection.number);
this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
if (this._bodyOutputStream)
this._bodyOutputStream.close();
this._finished = true;
},
+
// POST-CONSTRUCTION API (not exposed externally)
/**
@@ -3532,8 +3579,9 @@ Response.prototype =
/**
* Determines whether this response may be abandoned in favor of a newly
- * constructed response, as determined by whether any of this response's data
- * has been written to the network.
+ * constructed response. A response may be abandoned only if it is not being
+ * sent asynchronously and if raw control over it has not been taken from the
+ * server.
*
* @returns boolean
* true iff no data has been written to the network
@@ -3541,7 +3589,7 @@ Response.prototype =
partiallySent: function()
{
dumpn("*** partiallySent()");
- return this._headers === null;
+ return this._processAsync || this._powerSeized;
},
/**
@@ -3551,8 +3599,12 @@ Response.prototype =
complete: function()
{
dumpn("*** complete()");
- if (this._processAsync)
+ if (this._processAsync || this._powerSeized)
+ {
+ NS_ASSERT(this._processAsync ^ this._powerSeized,
+ "can't both send async and relinquish power");
return;
+ }
NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
@@ -3566,9 +3618,11 @@ Response.prototype =
/**
* Abruptly ends processing of this response, usually due to an error in an
* incoming request but potentially due to a bad error handler. Since we
- * cannot handle the error in the usual way (giving an HTTP error page in response)
- * because data may already have been sent, we stop processing this response
- * and abruptly close the connection.
+ * cannot handle the error in the usual way (giving an HTTP error page in
+ * response) because data may already have been sent (or because the response
+ * might be expected to have been generated asynchronously or completely from
+ * scratch by the handler), we stop processing this response and abruptly
+ * close the connection.
*
* @param e : Error
* the exception which precipitated this abort, or null if no such exception
@@ -3579,11 +3633,34 @@ Response.prototype =
dumpn("*** abort(<" + e + ">)");
// This response will be ended by the processor if one was created.
- var processor = this._asyncCopier;
- if (processor)
- processor.cancel(Cr.NS_BINDING_ABORTED);
+ var copier = this._asyncCopier;
+ if (copier)
+ {
+ // We dispatch asynchronously here so that any pending writes of data to
+ // the connection will be deterministically written. This makes it easier
+ // to specify exact behavior, and it makes observable behavior more
+ // predictable for clients. Note that the correctness of this depends on
+ // callbacks in response to _waitForData in WriteThroughCopier happening
+ // asynchronously with respect to the actual writing of data to
+ // bodyOutputStream, as they currently do; if they happened synchronously,
+ // an event which ran before this one could write more data to the
+ // response body before we get around to canceling the copier. We have
+ // tests for this in test_seizepower.js, however, and I can't think of a
+ // way to handle both cases without removing bodyOutputStream access and
+ // moving its effective write(data, length) method onto Response, which
+ // would be slower and require more code than this anyway.
+ gThreadManager.currentThread.dispatch({
+ run: function()
+ {
+ dumpn("*** canceling copy asynchronously...");
+ copier.cancel(Cr.NS_ERROR_UNEXPECTED);
+ }
+ }, Ci.nsIThreadManager.DISPATCH_NORMAL);
+ }
else
+ {
this.end();
+ }
},
/**
@@ -3616,6 +3693,7 @@ Response.prototype =
dumpn("*** _sendHeaders()");
NS_ASSERT(this._headers);
+ NS_ASSERT(!this._powerSeized);
// request-line
var statusLine = "HTTP/" + this.httpVersion + " " +
@@ -3709,8 +3787,13 @@ Response.prototype =
// Send headers if they haven't been sent already.
if (this._headers)
- this._sendHeaders();
- NS_ASSERT(this._headers === null, "flushHeaders() failed?");
+ {
+ if (this._powerSeized)
+ this._headers = null;
+ else
+ this._sendHeaders();
+ NS_ASSERT(this._headers === null, "_sendHeaders() failed?");
+ }
var response = this;
var connection = this._connection;
@@ -3732,15 +3815,19 @@ Response.prototype =
onStopRequest: function(request, cx, statusCode)
{
- dumpn("*** onStopRequest [status=" + statusCode.toString(16) + "]");
+ dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");
- if (!Components.isSuccessCode(statusCode))
+ if (statusCode === Cr.NS_BINDING_ABORTED)
{
- dumpn("*** WARNING: non-success statusCode in onStopRequest: " +
- statusCode);
+ dumpn("*** terminating copy observer without ending the response");
}
+ else
+ {
+ if (!Components.isSuccessCode(statusCode))
+ dumpn("*** WARNING: non-success statusCode in onStopRequest");
- response.end();
+ response.end();
+ }
},
QueryInterface: function(aIID)
@@ -3784,8 +3871,9 @@ function notImplemented()
* @param input : nsIAsyncInputStream
* the stream from which data is to be read
* @param output : nsIOutputStream
+ * the stream to which data is to be copied
* @param observer : nsIRequestObserver
- * an observer which will be notified when
+ * an observer which will be notified when the copy starts and finishes
* @param context : nsISupports
* context passed to observer when notified of start/stop
* @throws NS_ERROR_NULL_POINTER
@@ -3847,7 +3935,10 @@ WriteThroughCopier.prototype =
dumpn("*** cancel(" + status.toString(16) + ")");
if (this._completed)
+ {
+ dumpn("*** ignoring cancel on already-canceled copier...");
return;
+ }
this._completed = true;
this.status = status;
@@ -3890,13 +3981,16 @@ WriteThroughCopier.prototype =
* Receives a more-data-in-input notification and writes the corresponding
* data to the output.
*/
- onInputStreamReady: function()
+ onInputStreamReady: function(input)
{
dumpn("*** onInputStreamReady");
if (this._completed)
+ {
+ dumpn("*** ignoring stream-ready callback on a canceled copier...");
return;
+ }
- var input = new BinaryInputStream(this._input);
+ input = new BinaryInputStream(input);
try
{
var avail = input.available();
@@ -3931,6 +4025,19 @@ WriteThroughCopier.prototype =
{
dumpn("*** _waitForData");
this._input.asyncWait(this, 0, 1, gThreadManager.mainThread);
+ },
+
+ /** nsISupports implementation */
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIRequest) ||
+ iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIInputStreamCallback))
+ {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
}
};
View
78 netwerk/test/httpserver/nsIHttpServer.idl
@@ -365,9 +365,17 @@ interface nsIHttpRequestHandler : nsISupports
* Processes the HTTP request represented by metadata and initializes the
* passed-in response to reflect the correct HTTP response.
*
- * Note that in some uses of nsIHttpRequestHandler, this method is required to
- * not throw an exception; in the general case, however, this method may throw
- * an exception (causing an HTTP 500 response to occur).
+ * If this method throws an exception, externally observable behavior depends
+ * upon whether is being processed asynchronously and the connection has had
+ * any data written to it (even an explicit zero bytes of data being written)
+ * or whether seizePower() has been called on it. If such has happened, sent
+ * data will be exactly that data written at the time the exception was
+ * thrown. If no data has been written, the response has not had seizePower()
+ * called on it, and it is not being asynchronously created, an error handler
+ * will be invoked (usually 500 unless otherwise specified). Note that some
+ * uses of nsIHttpRequestHandler may require this method to never throw an
+ * exception; in the general case, however, this method may throw an exception
+ * (causing an HTTP 500 response to occur).
*
* @param metadata
* data representing an HTTP request
@@ -504,7 +512,8 @@ interface nsIHttpResponse : nsISupports
* than 999, or description contains invalid characters
* @throws NS_ERROR_NOT_AVAILABLE
* if this response is being processed asynchronously and data has been
- * written to this response's body
+ * written to this response's body, or if seizePower() has been called on
+ * this
*/
void setStatusLine(in string httpVersion,
in unsigned short statusCode,
@@ -530,23 +539,29 @@ interface nsIHttpResponse : nsISupports
* if name or value is not a valid header component
* @throws NS_ERROR_NOT_AVAILABLE
* if this response is being processed asynchronously and data has been
- * written to this response's body
+ * written to this response's body, or if seizePower() has been called on
+ * this
*/
void setHeader(in string name, in string value, in boolean merge);
/**
- * A stream to which data appearing in the body of this response should be
- * written. After this response has been designated as being processed
- * asynchronously, subsequent writes will be synchronously written to the
- * underlying transport. However, immediate write-through visible to the HTTP
- * client cannot be guaranteed, as intermediate buffers both in the server
- * socket and in the client may delay written data; be prepared for potential
- * delays.
+ * A stream to which data appearing in the body of this response (or in the
+ * totality of the response if seizePower() is called) should be written.
+ * After this response has been designated as being processed asynchronously,
+ * or after seizePower() has been called on this, subsequent writes will no
+ * longer be buffered and will be written to the underlying transport without
+ * delaying until the entire response is constructed. Write-through may or
+ * may not be synchronous in the implementation, and in any case particular
+ * behavior may not be observable to the HTTP client as intermediate buffers
+ * both in the server socket and in the client may delay written data; be
+ * prepared for delays at any time.
*
* @note
- * As writes to the underlying transport are synchronous, care must be taken
- * not to block on these writes; it is even possible for deadlock to occur
- * in the case that the server and the client reside in the same process.
+ * Although in the asynchronous cases writes to the underlying transport
+ * are not buffered, care must still be taken not to block for too long on
+ * any such writes; it is even possible for deadlock to occur in the case
+ * that the server and the client reside in the same process. Write data in
+ * small chunks if necessary to avoid this problem.
* @throws NS_ERROR_NOT_AVAILABLE
* if accessed after this response is fully constructed
*/
@@ -578,16 +593,43 @@ interface nsIHttpResponse : nsISupports
* @throws NS_ERROR_UNEXPECTED
* if not initially called within a nsIHttpRequestHandler.handle call or if
* called after this response has been finished
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if seizePower() has been called on this
*/
void processAsync();
/**
+ * Seizes complete control of this response (and its connection) from the
+ * server, allowing raw and unfettered access to data being sent in the HTTP
+ * response. Once this method has been called the only property which may be
+ * accessed without an exception being thrown is bodyOutputStream, and the
+ * only methods which may be accessed without an exception being thrown are
+ * write(), finish(), and seizePower() (which may be called multiple times
+ * without ill effect so long as all calls are otherwise allowed).
+ *
+ * After a successful call, all data subsequently written to the body of this
+ * response is written directly to the corresponding connection. (Previously-
+ * written data is silently discarded.) No status line or headers are sent
+ * before doing so; if the response handler wishes to write such data, it must
+ * do so manually. Data generation completes only when finish() is called; it
+ * is not enough to simply call close() on bodyOutputStream.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if processAsync() has been called on this
+ * @throws NS_ERROR_UNEXPECTED
+ * if finish() has been called on this
+ */
+ void seizePower();
+
+ /**
* Signals that construction of this response is complete and that it may be
- * sent over the network to the client. This method may only be called after
- * processAsync() has been called. This method is idempotent.
+ * sent over the network to the client, or if seizePower() has been called
+ * signals that all data has been written and that the underlying connection
+ * may be closed. This method may only be called after processAsync() or
+ * seizePower() has been called. This method is idempotent.
*
* @throws NS_ERROR_UNEXPECTED
- * if processAsync() has not already been properly called
+ * if processAsync() or seizePower() has not already been properly called
*/
void finish();
};
View
8 netwerk/test/httpserver/test/test_processasync.js
@@ -299,12 +299,8 @@ function stop_handleAsyncError(ch, cx, status, data)
// Lies! But not really!
do_check_true(ch.requestSucceeded);
- // There's no way server APIs will ever guarantee exactly what data will show
- // up here, but they will guarantee sending a (not necessarily strict) prefix
- // of what was written.
- do_check_true(data.length <= ASYNC_ERROR_BODY.length);
- for (var i = 0, sz = data.length; i < sz; i++)
- do_check_eq(data[i] == ASYNC_ERROR_BODY.charCodeAt(i));
+ do_check_eq(data.length, ASYNC_ERROR_BODY.length);
+ do_check_eq(String.fromCharCode.apply(null, data), ASYNC_ERROR_BODY);
}
test = new Test(PREPATH + "/handleAsyncError",
View
309 netwerk/test/httpserver/test/test_seizepower.js
@@ -0,0 +1,309 @@
+/* -*- 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 httpd.js code.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Jeff Walden <jwalden+code@mit.edu> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Tests that the seizePower API works correctly.
+ */
+
+const PORT = 4444;
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+
+ srv.registerPathHandler("/raw-data", handleRawData);
+ srv.registerPathHandler("/called-too-late", handleTooLate);
+ srv.registerPathHandler("/exceptions", handleExceptions);
+ srv.registerPathHandler("/async-seizure", handleAsyncSeizure);
+ srv.registerPathHandler("/seize-after-async", handleSeizeAfterAsync);
+ srv.registerPathHandler("/thrown-exception", handleThrownException);
+ srv.registerPathHandler("/asap-later-write", handleASAPLaterWrite);
+ srv.registerPathHandler("/asap-later-finish", handleASAPLaterFinish);
+
+ srv.start(PORT);
+
+ runRawTests(tests, testComplete(srv));
+}
+
+
+function checkException(fun, err, msg)
+{
+ try
+ {
+ fun();
+ }
+ catch (e)
+ {
+ if (e !== err && e.result !== err)
+ do_throw(msg);
+ return;
+ }
+ do_throw(msg);
+}
+
+function callASAPLater(fun)
+{
+ gThreadManager.currentThread.dispatch({
+ run: function()
+ {
+ fun();
+ }
+ }, Ci.nsIThreadManager.DISPATCH_NORMAL);
+}
+
+
+/*****************
+ * PATH HANDLERS *
+ *****************/
+
+function handleRawData(request, response)
+{
+ response.seizePower();
+ response.write("Raw data!");
+ response.finish();
+}
+
+function handleTooLate(request, response)
+{
+ response.write("DO NOT WANT");
+ var output = response.bodyOutputStream;
+
+ response.seizePower();
+
+ if (response.bodyOutputStream !== output)
+ response.write("bodyOutputStream changed!");
+ else
+ response.write("too-late passed");
+ response.finish();
+}
+
+function handleExceptions(request, response)
+{
+ response.seizePower();
+ checkException(function() { response.setStatusLine("1.0", 500, "ISE"); },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "setStatusLine should throw not-available after seizePower");
+ checkException(function() { response.setHeader("X-Fail", "FAIL", false); },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "setHeader should throw not-available after seizePower");
+ checkException(function() { response.processAsync(); },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "processAsync should throw not-available after seizePower");
+ var out = response.bodyOutputStream;
+ var data = "exceptions test passed";
+ out.write(data, data.length);
+ response.seizePower(); // idempotency test of seizePower
+ response.finish();
+ response.finish(); // idempotency test of finish after seizePower
+ checkException(function() { response.seizePower(); },
+ Cr.NS_ERROR_UNEXPECTED,
+ "seizePower should throw unexpected after finish");
+}
+
+function handleAsyncSeizure(request, response)
+{
+ response.seizePower();
+ callLater(1, function()
+ {
+ response.write("async seizure passed");
+ response.bodyOutputStream.close();
+ callLater(1, function()
+ {
+ response.finish();
+ });
+ });
+}
+
+function handleSeizeAfterAsync(request, response)
+{
+ response.setStatusLine(request.httpVersion, 200, "async seizure pass");
+ response.processAsync();
+ checkException(function() { response.seizePower(); },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "seizePower should throw not-available after processAsync");
+ callLater(1, function()
+ {
+ response.finish();
+ });
+}
+
+function handleThrownException(request, response)
+{
+ if (request.queryString === "writeBefore")
+ response.write("ignore this");
+ else if (request.queryString === "writeBeforeEmpty")
+ response.write("");
+ else if (request.queryString !== "")
+ throw "query string FAIL";
+ response.seizePower();
+ response.write("preparing to throw...");
+ throw "badness 10000";
+}
+
+function handleASAPLaterWrite(request, response)
+{
+ response.seizePower();
+ response.write("should only ");
+ response.write("see this");
+
+ callASAPLater(function()
+ {
+ response.write("...and not this");
+ callASAPLater(function()
+ {
+ response.write("...or this");
+ response.finish();
+ });
+ });
+
+ throw "opening pitch of the ballgame";
+}
+
+function handleASAPLaterFinish(request, response)
+{
+ response.seizePower();
+ response.write("should only see this");
+
+ callASAPLater(function()
+ {
+ response.finish();
+ });
+
+ throw "out the bum!";
+}
+
+
+/***************
+ * BEGIN TESTS *
+ ***************/
+
+var test, data;
+var tests = [];
+
+data = "GET /raw-data HTTP/1.0\r\n" +
+ "\r\n";
+function checkRawData(data)
+{
+ do_check_eq(data, "Raw data!");
+}
+test = new RawTest("localhost", PORT, data, checkRawData),
+tests.push(test);
+
+data = "GET /called-too-late HTTP/1.0\r\n" +
+ "\r\n";
+function checkTooLate(data)
+{
+ do_check_eq(LineIterator(data).next(), "too-late passed");
+}
+test = new RawTest("localhost", PORT, data, checkTooLate),
+tests.push(test);
+
+data = "GET /exceptions HTTP/1.0\r\n" +
+ "\r\n";
+function checkExceptions(data)
+{
+ do_check_eq("exceptions test passed", data);
+}
+test = new RawTest("localhost", PORT, data, checkExceptions),
+tests.push(test);
+
+data = "GET /async-seizure HTTP/1.0\r\n" +
+ "\r\n";
+function checkAsyncSeizure(data)
+{
+ do_check_eq(data, "async seizure passed");
+}
+test = new RawTest("localhost", PORT, data, checkAsyncSeizure),
+tests.push(test);
+
+data = "GET /seize-after-async HTTP/1.0\r\n" +
+ "\r\n";
+function checkSeizeAfterAsync(data)
+{
+ do_check_eq(LineIterator(data).next(), "HTTP/1.0 200 async seizure pass");
+}
+test = new RawTest("localhost", PORT, data, checkSeizeAfterAsync),
+tests.push(test);
+
+data = "GET /thrown-exception?writeBefore HTTP/1.0\r\n" +
+ "\r\n";
+function checkThrownExceptionWriteBefore(data)
+{
+ do_check_eq(data, "preparing to throw...");
+}
+test = new RawTest("localhost", PORT, data, checkThrownExceptionWriteBefore),
+tests.push(test);
+
+data = "GET /thrown-exception?writeBeforeEmpty HTTP/1.0\r\n" +
+ "\r\n";
+function checkThrownExceptionWriteBefore(data)
+{
+ do_check_eq(data, "preparing to throw...");
+}
+test = new RawTest("localhost", PORT, data, checkThrownExceptionWriteBefore),
+tests.push(test);
+
+data = "GET /thrown-exception HTTP/1.0\r\n" +
+ "\r\n";
+function checkThrownException(data)
+{
+ do_check_eq(data, "preparing to throw...");
+}
+test = new RawTest("localhost", PORT, data, checkThrownException),
+tests.push(test);
+
+data = "GET /asap-later-write HTTP/1.0\r\n" +
+ "\r\n";
+function checkASAPLaterWrite(data)
+{
+ do_check_eq(data, "should only see this");
+}
+test = new RawTest("localhost", PORT, data, checkASAPLaterWrite),
+tests.push(test);
+
+data = "GET /asap-later-finish HTTP/1.0\r\n" +
+ "\r\n";
+function checkASAPLaterFinish(data)
+{
+ do_check_eq(data, "should only see this");
+}
+test = new RawTest("localhost", PORT, data, checkASAPLaterFinish),
+tests.push(test);
Please sign in to comment.
Something went wrong with that request. Please try again.