зеркало из https://github.com/mozilla/pjs.git
Bug 484027 - Add a method providing minimally controlled arbitrary write 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
This commit is contained in:
Родитель
612169811b
Коммит
7d6ee6ce79
|
@ -3332,6 +3332,13 @@ function Response(connection)
|
||||||
* to this may be made.
|
* to this may be made.
|
||||||
*/
|
*/
|
||||||
this._finished = false;
|
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 =
|
Response.prototype =
|
||||||
{
|
{
|
||||||
|
@ -3351,7 +3358,7 @@ Response.prototype =
|
||||||
null);
|
null);
|
||||||
this._bodyOutputStream = pipe.outputStream;
|
this._bodyOutputStream = pipe.outputStream;
|
||||||
this._bodyInputStream = pipe.inputStream;
|
this._bodyInputStream = pipe.inputStream;
|
||||||
if (this._processAsync)
|
if (this._processAsync || this._powerSeized)
|
||||||
this._startAsyncProcessor();
|
this._startAsyncProcessor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3375,7 +3382,7 @@ Response.prototype =
|
||||||
//
|
//
|
||||||
setStatusLine: function(httpVersion, code, description)
|
setStatusLine: function(httpVersion, code, description)
|
||||||
{
|
{
|
||||||
if (!this._headers || this._finished)
|
if (!this._headers || this._finished || this._powerSeized)
|
||||||
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
||||||
this._ensureAlive();
|
this._ensureAlive();
|
||||||
|
|
||||||
|
@ -3420,7 +3427,7 @@ Response.prototype =
|
||||||
//
|
//
|
||||||
setHeader: function(name, value, merge)
|
setHeader: function(name, value, merge)
|
||||||
{
|
{
|
||||||
if (!this._headers || this._finished)
|
if (!this._headers || this._finished || this._powerSeized)
|
||||||
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
||||||
this._ensureAlive();
|
this._ensureAlive();
|
||||||
|
|
||||||
|
@ -3434,8 +3441,11 @@ Response.prototype =
|
||||||
{
|
{
|
||||||
if (this._finished)
|
if (this._finished)
|
||||||
throw Cr.NS_ERROR_UNEXPECTED;
|
throw Cr.NS_ERROR_UNEXPECTED;
|
||||||
|
if (this._powerSeized)
|
||||||
|
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
||||||
if (this._processAsync)
|
if (this._processAsync)
|
||||||
return;
|
return;
|
||||||
|
this._ensureAlive();
|
||||||
|
|
||||||
dumpn("*** processing connection " + this._connection.number + " async");
|
dumpn("*** processing connection " + this._connection.number + " async");
|
||||||
this._processAsync = true;
|
this._processAsync = true;
|
||||||
|
@ -3457,23 +3467,60 @@ Response.prototype =
|
||||||
this._startAsyncProcessor();
|
this._startAsyncProcessor();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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
|
// see nsIHttpResponse.finish
|
||||||
//
|
//
|
||||||
finish: function()
|
finish: function()
|
||||||
{
|
{
|
||||||
if (!this._processAsync)
|
if (!this._processAsync && !this._powerSeized)
|
||||||
throw Cr.NS_ERROR_UNEXPECTED;
|
throw Cr.NS_ERROR_UNEXPECTED;
|
||||||
if (this._finished)
|
if (this._finished)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dumpn("*** finishing async connection " + this._connection.number);
|
dumpn("*** finishing connection " + this._connection.number);
|
||||||
this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
|
this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
|
||||||
if (this._bodyOutputStream)
|
if (this._bodyOutputStream)
|
||||||
this._bodyOutputStream.close();
|
this._bodyOutputStream.close();
|
||||||
this._finished = true;
|
this._finished = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// POST-CONSTRUCTION API (not exposed externally)
|
// POST-CONSTRUCTION API (not exposed externally)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3532,8 +3579,9 @@ Response.prototype =
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether this response may be abandoned in favor of a newly
|
* Determines whether this response may be abandoned in favor of a newly
|
||||||
* constructed response, as determined by whether any of this response's data
|
* constructed response. A response may be abandoned only if it is not being
|
||||||
* has been written to the network.
|
* sent asynchronously and if raw control over it has not been taken from the
|
||||||
|
* server.
|
||||||
*
|
*
|
||||||
* @returns boolean
|
* @returns boolean
|
||||||
* true iff no data has been written to the network
|
* true iff no data has been written to the network
|
||||||
|
@ -3541,7 +3589,7 @@ Response.prototype =
|
||||||
partiallySent: function()
|
partiallySent: function()
|
||||||
{
|
{
|
||||||
dumpn("*** partiallySent()");
|
dumpn("*** partiallySent()");
|
||||||
return this._headers === null;
|
return this._processAsync || this._powerSeized;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3551,8 +3599,12 @@ Response.prototype =
|
||||||
complete: function()
|
complete: function()
|
||||||
{
|
{
|
||||||
dumpn("*** complete()");
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
|
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
|
* 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
|
* 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)
|
* cannot handle the error in the usual way (giving an HTTP error page in
|
||||||
* because data may already have been sent, we stop processing this response
|
* response) because data may already have been sent (or because the response
|
||||||
* and abruptly close the connection.
|
* 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
|
* @param e : Error
|
||||||
* the exception which precipitated this abort, or null if no such exception
|
* the exception which precipitated this abort, or null if no such exception
|
||||||
|
@ -3579,11 +3633,34 @@ Response.prototype =
|
||||||
dumpn("*** abort(<" + e + ">)");
|
dumpn("*** abort(<" + e + ">)");
|
||||||
|
|
||||||
// This response will be ended by the processor if one was created.
|
// This response will be ended by the processor if one was created.
|
||||||
var processor = this._asyncCopier;
|
var copier = this._asyncCopier;
|
||||||
if (processor)
|
if (copier)
|
||||||
processor.cancel(Cr.NS_BINDING_ABORTED);
|
{
|
||||||
|
// 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
|
else
|
||||||
|
{
|
||||||
this.end();
|
this.end();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3616,6 +3693,7 @@ Response.prototype =
|
||||||
dumpn("*** _sendHeaders()");
|
dumpn("*** _sendHeaders()");
|
||||||
|
|
||||||
NS_ASSERT(this._headers);
|
NS_ASSERT(this._headers);
|
||||||
|
NS_ASSERT(!this._powerSeized);
|
||||||
|
|
||||||
// request-line
|
// request-line
|
||||||
var statusLine = "HTTP/" + this.httpVersion + " " +
|
var statusLine = "HTTP/" + this.httpVersion + " " +
|
||||||
|
@ -3709,8 +3787,13 @@ Response.prototype =
|
||||||
|
|
||||||
// Send headers if they haven't been sent already.
|
// Send headers if they haven't been sent already.
|
||||||
if (this._headers)
|
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 response = this;
|
||||||
var connection = this._connection;
|
var connection = this._connection;
|
||||||
|
@ -3732,15 +3815,19 @@ Response.prototype =
|
||||||
|
|
||||||
onStopRequest: function(request, cx, statusCode)
|
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: " +
|
dumpn("*** terminating copy observer without ending the response");
|
||||||
statusCode);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!Components.isSuccessCode(statusCode))
|
||||||
|
dumpn("*** WARNING: non-success statusCode in onStopRequest");
|
||||||
|
|
||||||
response.end();
|
response.end();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
QueryInterface: function(aIID)
|
QueryInterface: function(aIID)
|
||||||
|
@ -3784,8 +3871,9 @@ function notImplemented()
|
||||||
* @param input : nsIAsyncInputStream
|
* @param input : nsIAsyncInputStream
|
||||||
* the stream from which data is to be read
|
* the stream from which data is to be read
|
||||||
* @param output : nsIOutputStream
|
* @param output : nsIOutputStream
|
||||||
|
* the stream to which data is to be copied
|
||||||
* @param observer : nsIRequestObserver
|
* @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
|
* @param context : nsISupports
|
||||||
* context passed to observer when notified of start/stop
|
* context passed to observer when notified of start/stop
|
||||||
* @throws NS_ERROR_NULL_POINTER
|
* @throws NS_ERROR_NULL_POINTER
|
||||||
|
@ -3847,7 +3935,10 @@ WriteThroughCopier.prototype =
|
||||||
dumpn("*** cancel(" + status.toString(16) + ")");
|
dumpn("*** cancel(" + status.toString(16) + ")");
|
||||||
|
|
||||||
if (this._completed)
|
if (this._completed)
|
||||||
|
{
|
||||||
|
dumpn("*** ignoring cancel on already-canceled copier...");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._completed = true;
|
this._completed = true;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
@ -3890,13 +3981,16 @@ WriteThroughCopier.prototype =
|
||||||
* Receives a more-data-in-input notification and writes the corresponding
|
* Receives a more-data-in-input notification and writes the corresponding
|
||||||
* data to the output.
|
* data to the output.
|
||||||
*/
|
*/
|
||||||
onInputStreamReady: function()
|
onInputStreamReady: function(input)
|
||||||
{
|
{
|
||||||
dumpn("*** onInputStreamReady");
|
dumpn("*** onInputStreamReady");
|
||||||
if (this._completed)
|
if (this._completed)
|
||||||
|
{
|
||||||
|
dumpn("*** ignoring stream-ready callback on a canceled copier...");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var input = new BinaryInputStream(this._input);
|
input = new BinaryInputStream(input);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var avail = input.available();
|
var avail = input.available();
|
||||||
|
@ -3931,6 +4025,19 @@ WriteThroughCopier.prototype =
|
||||||
{
|
{
|
||||||
dumpn("*** _waitForData");
|
dumpn("*** _waitForData");
|
||||||
this._input.asyncWait(this, 0, 1, gThreadManager.mainThread);
|
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -365,9 +365,17 @@ interface nsIHttpRequestHandler : nsISupports
|
||||||
* Processes the HTTP request represented by metadata and initializes the
|
* Processes the HTTP request represented by metadata and initializes the
|
||||||
* passed-in response to reflect the correct HTTP response.
|
* passed-in response to reflect the correct HTTP response.
|
||||||
*
|
*
|
||||||
* Note that in some uses of nsIHttpRequestHandler, this method is required to
|
* If this method throws an exception, externally observable behavior depends
|
||||||
* not throw an exception; in the general case, however, this method may throw
|
* upon whether is being processed asynchronously and the connection has had
|
||||||
* an exception (causing an HTTP 500 response to occur).
|
* 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
|
* @param metadata
|
||||||
* data representing an HTTP request
|
* data representing an HTTP request
|
||||||
|
@ -504,7 +512,8 @@ interface nsIHttpResponse : nsISupports
|
||||||
* than 999, or description contains invalid characters
|
* than 999, or description contains invalid characters
|
||||||
* @throws NS_ERROR_NOT_AVAILABLE
|
* @throws NS_ERROR_NOT_AVAILABLE
|
||||||
* if this response is being processed asynchronously and data has been
|
* 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,
|
void setStatusLine(in string httpVersion,
|
||||||
in unsigned short statusCode,
|
in unsigned short statusCode,
|
||||||
|
@ -530,23 +539,29 @@ interface nsIHttpResponse : nsISupports
|
||||||
* if name or value is not a valid header component
|
* if name or value is not a valid header component
|
||||||
* @throws NS_ERROR_NOT_AVAILABLE
|
* @throws NS_ERROR_NOT_AVAILABLE
|
||||||
* if this response is being processed asynchronously and data has been
|
* 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);
|
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
|
* A stream to which data appearing in the body of this response (or in the
|
||||||
* written. After this response has been designated as being processed
|
* totality of the response if seizePower() is called) should be written.
|
||||||
* asynchronously, subsequent writes will be synchronously written to the
|
* After this response has been designated as being processed asynchronously,
|
||||||
* underlying transport. However, immediate write-through visible to the HTTP
|
* or after seizePower() has been called on this, subsequent writes will no
|
||||||
* client cannot be guaranteed, as intermediate buffers both in the server
|
* longer be buffered and will be written to the underlying transport without
|
||||||
* socket and in the client may delay written data; be prepared for potential
|
* delaying until the entire response is constructed. Write-through may or
|
||||||
* delays.
|
* 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
|
* @note
|
||||||
* As writes to the underlying transport are synchronous, care must be taken
|
* Although in the asynchronous cases writes to the underlying transport
|
||||||
* not to block on these writes; it is even possible for deadlock to occur
|
* are not buffered, care must still be taken not to block for too long on
|
||||||
* in the case that the server and the client reside in the same process.
|
* 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
|
* @throws NS_ERROR_NOT_AVAILABLE
|
||||||
* if accessed after this response is fully constructed
|
* if accessed after this response is fully constructed
|
||||||
*/
|
*/
|
||||||
|
@ -578,16 +593,43 @@ interface nsIHttpResponse : nsISupports
|
||||||
* @throws NS_ERROR_UNEXPECTED
|
* @throws NS_ERROR_UNEXPECTED
|
||||||
* if not initially called within a nsIHttpRequestHandler.handle call or if
|
* if not initially called within a nsIHttpRequestHandler.handle call or if
|
||||||
* called after this response has been finished
|
* called after this response has been finished
|
||||||
|
* @throws NS_ERROR_NOT_AVAILABLE
|
||||||
|
* if seizePower() has been called on this
|
||||||
*/
|
*/
|
||||||
void processAsync();
|
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
|
* 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
|
* sent over the network to the client, or if seizePower() has been called
|
||||||
* processAsync() has been called. This method is idempotent.
|
* 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
|
* @throws NS_ERROR_UNEXPECTED
|
||||||
* if processAsync() has not already been properly called
|
* if processAsync() or seizePower() has not already been properly called
|
||||||
*/
|
*/
|
||||||
void finish();
|
void finish();
|
||||||
};
|
};
|
||||||
|
|
|
@ -299,12 +299,8 @@ function stop_handleAsyncError(ch, cx, status, data)
|
||||||
// Lies! But not really!
|
// Lies! But not really!
|
||||||
do_check_true(ch.requestSucceeded);
|
do_check_true(ch.requestSucceeded);
|
||||||
|
|
||||||
// There's no way server APIs will ever guarantee exactly what data will show
|
do_check_eq(data.length, ASYNC_ERROR_BODY.length);
|
||||||
// up here, but they will guarantee sending a (not necessarily strict) prefix
|
do_check_eq(String.fromCharCode.apply(null, data), ASYNC_ERROR_BODY);
|
||||||
// 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test = new Test(PREPATH + "/handleAsyncError",
|
test = new Test(PREPATH + "/handleAsyncError",
|
||||||
|
|
|
@ -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);
|
Загрузка…
Ссылка в новой задаче