bug 674716 - websockets API close reason codes and messages r=sicking r=biesi sr=bz

--HG--
extra : rebase_source : f7a0eb6b310f29f8e697c26ff5e0ef9d2fde559a
This commit is contained in:
Patrick McManus 2011-08-03 15:15:25 -04:00
parent 2adea969f2
commit c9a6067c84
17 changed files with 427 additions and 93 deletions

View File

@ -58,7 +58,7 @@ class nsString;
* http://dev.w3.org/html5/websockets/ * http://dev.w3.org/html5/websockets/
* *
*/ */
[scriptable, uuid(d50eb158-30a1-4692-8664-fdd479fa24b3)] [scriptable, uuid(8fa080c7-934a-4040-90cb-31055798b35d)]
interface nsIMozWebSocket : nsISupports interface nsIMozWebSocket : nsISupports
{ {
readonly attribute DOMString url; readonly attribute DOMString url;
@ -92,7 +92,8 @@ interface nsIMozWebSocket : nsISupports
* Closes the Web Socket connection or connection attempt, if any. * Closes the Web Socket connection or connection attempt, if any.
* If the connection is already closed, it does nothing. * If the connection is already closed, it does nothing.
*/ */
void close(); [optional_argc] void close([optional] in unsigned short code,
[optional] in DOMString reason);
/** /**
* Initialize the object for use from C++ code with the principal, script * Initialize the object for use from C++ code with the principal, script

View File

@ -390,7 +390,8 @@ nsWebSocketEstablishedConnection::Close()
return NS_OK; return NS_OK;
} }
return mWebSocketChannel->Close(); return mWebSocketChannel->Close(mOwner->mClientReasonCode,
mOwner->mClientReason);
} }
nsresult nsresult
@ -561,9 +562,15 @@ nsWebSocketEstablishedConnection::OnAcknowledge(nsISupports *aContext,
} }
NS_IMETHODIMP NS_IMETHODIMP
nsWebSocketEstablishedConnection::OnServerClose(nsISupports *aContext) nsWebSocketEstablishedConnection::OnServerClose(nsISupports *aContext,
PRUint16 aCode,
const nsACString &aReason)
{ {
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
if (mOwner) {
mOwner->mServerReasonCode = aCode;
CopyUTF8toUTF16(aReason, mOwner->mServerReason);
}
Close(); /* reciprocate! */ Close(); /* reciprocate! */
return NS_OK; return NS_OK;
@ -611,6 +618,8 @@ nsWebSocketEstablishedConnection::GetInterface(const nsIID &aIID,
nsWebSocket::nsWebSocket() : mKeepingAlive(PR_FALSE), nsWebSocket::nsWebSocket() : mKeepingAlive(PR_FALSE),
mCheckMustKeepAlive(PR_TRUE), mCheckMustKeepAlive(PR_TRUE),
mTriggeredCloseEvent(PR_FALSE), mTriggeredCloseEvent(PR_FALSE),
mClientReasonCode(0),
mServerReasonCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
mReadyState(nsIMozWebSocket::CONNECTING), mReadyState(nsIMozWebSocket::CONNECTING),
mOutgoingBufferedAmount(0), mOutgoingBufferedAmount(0),
mScriptLine(0), mScriptLine(0),
@ -806,7 +815,7 @@ nsWebSocket::EstablishConnection()
rv = conn->Init(this); rv = conn->Init(this);
mConnection = conn; mConnection = conn;
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
Close(); Close(0, EmptyString(), 0);
mConnection = nsnull; mConnection = nsnull;
return rv; return rv;
} }
@ -817,14 +826,18 @@ nsWebSocket::EstablishConnection()
class nsWSCloseEvent : public nsRunnable class nsWSCloseEvent : public nsRunnable
{ {
public: public:
nsWSCloseEvent(nsWebSocket *aWebSocket, PRBool aWasClean) nsWSCloseEvent(nsWebSocket *aWebSocket, PRBool aWasClean,
PRUint16 aCode, const nsString &aReason)
: mWebSocket(aWebSocket), : mWebSocket(aWebSocket),
mWasClean(aWasClean) mWasClean(aWasClean),
mCode(aCode),
mReason(aReason)
{} {}
NS_IMETHOD Run() NS_IMETHOD Run()
{ {
nsresult rv = mWebSocket->CreateAndDispatchCloseEvent(mWasClean); nsresult rv = mWebSocket->CreateAndDispatchCloseEvent(mWasClean,
mCode, mReason);
mWebSocket->UpdateMustKeepAlive(); mWebSocket->UpdateMustKeepAlive();
return rv; return rv;
} }
@ -832,6 +845,8 @@ public:
private: private:
nsRefPtr<nsWebSocket> mWebSocket; nsRefPtr<nsWebSocket> mWebSocket;
PRBool mWasClean; PRBool mWasClean;
PRUint16 mCode;
nsString mReason;
}; };
nsresult nsresult
@ -919,7 +934,9 @@ nsWebSocket::CreateAndDispatchMessageEvent(const nsACString& aData)
} }
nsresult nsresult
nsWebSocket::CreateAndDispatchCloseEvent(PRBool aWasClean) nsWebSocket::CreateAndDispatchCloseEvent(PRBool aWasClean,
PRUint16 aCode,
const nsString &aReason)
{ {
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
nsresult rv; nsresult rv;
@ -941,7 +958,7 @@ nsWebSocket::CreateAndDispatchCloseEvent(PRBool aWasClean)
nsCOMPtr<nsIDOMCloseEvent> closeEvent = do_QueryInterface(event); nsCOMPtr<nsIDOMCloseEvent> closeEvent = do_QueryInterface(event);
rv = closeEvent->InitCloseEvent(NS_LITERAL_STRING("close"), rv = closeEvent->InitCloseEvent(NS_LITERAL_STRING("close"),
PR_FALSE, PR_FALSE, PR_FALSE, PR_FALSE,
aWasClean); aWasClean, aCode, aReason);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event); nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
@ -999,7 +1016,10 @@ nsWebSocket::SetReadyState(PRUint16 aNewReadyState)
if (mConnection) { if (mConnection) {
// The close event must be dispatched asynchronously. // The close event must be dispatched asynchronously.
nsCOMPtr<nsIRunnable> event = nsCOMPtr<nsIRunnable> event =
new nsWSCloseEvent(this, mConnection->ClosedCleanly()); new nsWSCloseEvent(this,
mConnection->ClosedCleanly(),
mServerReasonCode,
mServerReason);
mOutgoingBufferedAmount += mConnection->GetOutgoingBufferedAmount(); mOutgoingBufferedAmount += mConnection->GetOutgoingBufferedAmount();
mConnection = nsnull; // this is no longer necessary mConnection = nsnull; // this is no longer necessary
@ -1257,6 +1277,26 @@ NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(error, mOnErrorListener)
NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(message, mOnMessageListener) NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(message, mOnMessageListener)
NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(close, mOnCloseListener) NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(close, mOnCloseListener)
static PRBool
ContainsUnpairedSurrogates(const nsAString& aData)
{
// Check for unpaired surrogates.
PRUint32 i, length = aData.Length();
for (i = 0; i < length; ++i) {
if (NS_IS_LOW_SURROGATE(aData[i])) {
return PR_TRUE;
}
if (NS_IS_HIGH_SURROGATE(aData[i])) {
++i;
if (i == length || !NS_IS_LOW_SURROGATE(aData[i])) {
return PR_TRUE;
}
continue;
}
}
return PR_FALSE;
}
NS_IMETHODIMP NS_IMETHODIMP
nsWebSocket::Send(const nsAString& aData) nsWebSocket::Send(const nsAString& aData)
{ {
@ -1266,20 +1306,8 @@ nsWebSocket::Send(const nsAString& aData)
return NS_ERROR_DOM_INVALID_STATE_ERR; return NS_ERROR_DOM_INVALID_STATE_ERR;
} }
// Check for unpaired surrogates. if (ContainsUnpairedSurrogates(aData))
PRUint32 i, length = aData.Length(); return NS_ERROR_DOM_SYNTAX_ERR;
for (i = 0; i < length; ++i) {
if (NS_IS_LOW_SURROGATE(aData[i])) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
if (NS_IS_HIGH_SURROGATE(aData[i])) {
if (i + 1 == length || !NS_IS_LOW_SURROGATE(aData[i + 1])) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
++i;
continue;
}
}
if (mReadyState == nsIMozWebSocket::CLOSING || if (mReadyState == nsIMozWebSocket::CLOSING ||
mReadyState == nsIMozWebSocket::CLOSED) { mReadyState == nsIMozWebSocket::CLOSED) {
@ -1293,9 +1321,34 @@ nsWebSocket::Send(const nsAString& aData)
} }
NS_IMETHODIMP NS_IMETHODIMP
nsWebSocket::Close() nsWebSocket::Close(PRUint16 code, const nsAString & reason, PRUint8 argc)
{ {
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
// the reason code is optional, but if provided it must be in a specific range
if (argc >= 1) {
if (code != 1000 && (code < 3000 || code > 4999))
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
nsCAutoString utf8Reason;
if (argc >= 2) {
if (ContainsUnpairedSurrogates(reason))
return NS_ERROR_DOM_SYNTAX_ERR;
CopyUTF16toUTF8(reason, utf8Reason);
// The API requires the UTF-8 string to be 123 or less bytes
if (utf8Reason.Length() > 123)
return NS_ERROR_DOM_SYNTAX_ERR;
}
// Format checks for reason and code both passed, they can now be assigned.
if (argc >= 1)
mClientReasonCode = code;
if (argc >= 2)
mClientReason = utf8Reason;
if (mReadyState == nsIMozWebSocket::CLOSING || if (mReadyState == nsIMozWebSocket::CLOSING ||
mReadyState == nsIMozWebSocket::CLOSED) { mReadyState == nsIMozWebSocket::CLOSED) {
return NS_OK; return NS_OK;

View File

@ -109,7 +109,8 @@ protected:
nsresult CreateAndDispatchSimpleEvent(const nsString& aName); nsresult CreateAndDispatchSimpleEvent(const nsString& aName);
nsresult CreateAndDispatchMessageEvent(const nsACString& aData); nsresult CreateAndDispatchMessageEvent(const nsACString& aData);
nsresult CreateAndDispatchCloseEvent(PRBool aWasClean); nsresult CreateAndDispatchCloseEvent(PRBool aWasClean, PRUint16 aCode,
const nsString &aReason);
// called from mConnection accordingly to the situation // called from mConnection accordingly to the situation
void SetReadyState(PRUint16 aNewReadyState); void SetReadyState(PRUint16 aNewReadyState);
@ -136,6 +137,11 @@ protected:
PRPackedBool mCheckMustKeepAlive; PRPackedBool mCheckMustKeepAlive;
PRPackedBool mTriggeredCloseEvent; PRPackedBool mTriggeredCloseEvent;
nsCString mClientReason;
PRUint16 mClientReasonCode;
nsString mServerReason;
PRUint16 mServerReasonCode;
nsCString mAsciiHost; // hostname nsCString mAsciiHost; // hostname
PRUint32 mPort; PRUint32 mPort;
nsCString mResource; // [filepath[?query]] nsCString mResource; // [filepath[?query]]

View File

@ -96,5 +96,25 @@ def web_socket_transfer_data(request):
elif request.ws_protocol == "test-20": elif request.ws_protocol == "test-20":
msgutil.send_message(request, "server data") msgutil.send_message(request, "server data")
msgutil.close_connection(request) msgutil.close_connection(request)
elif request.ws_protocol == "test-34":
request.ws_stream.close_connection(1001, "going away now")
elif request.ws_protocol == "test-35a":
while not request.client_terminated:
msgutil.receive_message(request)
global test35code
test35code = request.ws_close_code
global test35reason
test35reason = request.ws_close_reason
elif request.ws_protocol == "test-35b":
request.ws_stream.close_connection(test35code + 1, test35reason)
elif request.ws_protocol == "test-37b":
while not request.client_terminated:
msgutil.receive_message(request)
global test37code
test37code = request.ws_close_code
global test37reason
test37reason = request.ws_close_reason
elif request.ws_protocol == "test-37c":
request.ws_stream.close_connection(test37code, test37reason)
while not request.client_terminated: while not request.client_terminated:
msgutil.receive_message(request) msgutil.receive_message(request)

View File

@ -56,10 +56,15 @@
* 31. ctor using valid 2 element sub-protocol array with 1 element server * 31. ctor using valid 2 element sub-protocol array with 1 element server
* will reject and one server will accept. * will reject and one server will accept.
* 32. ctor using invalid sub-protocol array that contains duplicate items * 32. ctor using invalid sub-protocol array that contains duplicate items
* 33. default close code test
* 34. test for receiving custom close code and reason
* 35. test for sending custom close code and reason
* 36. negative test for sending out of range close code
* 37. negative test for too long of a close reason
*/ */
var first_test = 1; var first_test = 1;
var last_test = 32; var last_test = 37;
var current_test = first_test; var current_test = first_test;
@ -884,6 +889,173 @@ function test32()
doTest(33); doTest(33);
} }
function test33()
{
var prots=["test33"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 33 open");
ws.close();
};
ws.onclose = function(e)
{
ok(true, "test 33 close");
ok(e.wasClean, "test 33 closed cleanly");
ok(e.code == 1000, "test 33 had normal 1000 error code");
doTest(34);
};
}
function test34()
{
var prots=["test-34"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 34 open");
ws.close();
};
ws.onclose = function(e)
{
ok(true, "test 34 close");
ok(e.wasClean, "test 34 closed cleanly");
ok(e.code == 1001, "test 34 custom server code");
ok(e.reason == "going away now", "test 34 custom server reason");
doTest(35);
};
}
function test35()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-35a");
ws.onopen = function(e)
{
ok(true, "test 35a open");
ws.close(3500, "my code");
};
ws.onclose = function(e)
{
ok(true, "test 35a close");
ok(e.wasClean, "test 35a closed cleanly");
current_test--; // CreateTestWS for 35a incremented this
var wsb = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-35b");
wsb.onopen = function(e)
{
ok(true, "test 35b open");
wsb.close();
};
wsb.onclose = function(e)
{
ok(true, "test 35b close");
ok(e.wasClean, "test 35b closed cleanly");
ok(e.code == 3501, "test 35 custom server code");
ok(e.reason == "my code", "test 35 custom server reason");
doTest(36);
};
}
}
function test36()
{
var prots=["test-36"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 36 open");
try {
ws.close(13200);
ok(false, "testing custom close code out of range");
}
catch (e) {
ok(true, "testing custom close code out of range");
ws.close(3200);
}
};
ws.onclose = function(e)
{
ok(true, "test 36 close");
ok(e.wasClean, "test 36 closed cleanly");
doTest(37);
};
}
function test37()
{
var prots=["test-37"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 37 open");
try {
ws.close(3100,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
ok(false, "testing custom close reason out of range");
}
catch (e) {
ok(true, "testing custom close reason out of range");
ws.close(3100,"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012");
}
};
ws.onclose = function(e)
{
ok(true, "test 37 close");
ok(e.wasClean, "test 37 closed cleanly");
current_test--; // CreateTestWS for 37 incremented this
var wsb = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-37b");
wsb.onopen = function(e)
{
// now test that a rejected close code and reason dont persist
ok(true, "test 37b open");
try {
wsb.close(3101,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
ok(false, "testing custom close reason out of range 37b");
}
catch (e) {
ok(true, "testing custom close reason out of range 37b");
wsb.close();
}
}
wsb.onclose = function(e)
{
ok(true, "test 37b close");
ok(e.wasClean, "test 37b closed cleanly");
current_test--; // CreateTestWS for 37 incremented this
var wsc = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-37c");
wsc.onopen = function(e)
{
ok(true, "test 37c open");
wsc.close();
}
wsc.onclose = function(e)
{
ok(e.code != 3101, "test 37c custom server code not present");
ok(e.reason == "", "test 37c custom server reason not present");
doTest(38);
}
}
}
}
var ranAllTests = false; var ranAllTests = false;
function maybeFinished() function maybeFinished()

View File

@ -56,16 +56,34 @@ nsDOMCloseEvent::GetWasClean(PRBool *aWasClean)
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
nsDOMCloseEvent::GetCode(PRUint16 *aCode)
{
*aCode = mReasonCode;
return NS_OK;
}
NS_IMETHODIMP
nsDOMCloseEvent::GetReason(nsAString & aReason)
{
aReason = mReason;
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsDOMCloseEvent::InitCloseEvent(const nsAString& aType, nsDOMCloseEvent::InitCloseEvent(const nsAString& aType,
PRBool aCanBubble, PRBool aCanBubble,
PRBool aCancelable, PRBool aCancelable,
PRBool aWasClean) PRBool aWasClean,
PRUint16 aReasonCode,
const nsAString &aReason)
{ {
nsresult rv = nsDOMEvent::InitEvent(aType, aCanBubble, aCancelable); nsresult rv = nsDOMEvent::InitEvent(aType, aCanBubble, aCancelable);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
mWasClean = aWasClean; mWasClean = aWasClean;
mReasonCode = aReasonCode;
mReason = aReason;
return NS_OK; return NS_OK;
} }

View File

@ -53,9 +53,9 @@ class nsDOMCloseEvent : public nsDOMEvent,
{ {
public: public:
nsDOMCloseEvent(nsPresContext* aPresContext, nsEvent* aEvent) nsDOMCloseEvent(nsPresContext* aPresContext, nsEvent* aEvent)
: nsDOMEvent(aPresContext, aEvent), mWasClean(PR_FALSE) : nsDOMEvent(aPresContext, aEvent),
{ mWasClean(PR_FALSE),
} mReasonCode(1005) {}
NS_DECL_ISUPPORTS_INHERITED NS_DECL_ISUPPORTS_INHERITED
@ -66,6 +66,8 @@ public:
private: private:
PRBool mWasClean; PRBool mWasClean;
PRUint16 mReasonCode;
nsString mReason;
}; };
#endif // nsDOMCloseEvent_h__ #endif // nsDOMCloseEvent_h__

View File

@ -45,13 +45,17 @@
* For more information on this interface, please see * For more information on this interface, please see
* http://dev.w3.org/html5/websockets/#closeevent * http://dev.w3.org/html5/websockets/#closeevent
*/ */
[scriptable, uuid(a94d4379-eba2-45f4-be3a-6cc2fa1453a8)] [scriptable, uuid(f83d9d6d-6c0c-418c-b12a-438e76d5866b)]
interface nsIDOMCloseEvent : nsIDOMEvent interface nsIDOMCloseEvent : nsIDOMEvent
{ {
readonly attribute boolean wasClean; readonly attribute boolean wasClean;
readonly attribute unsigned short code;
readonly attribute DOMString reason;
void initCloseEvent(in DOMString aType, void initCloseEvent(in DOMString aType,
in boolean aCanBubble, in boolean aCanBubble,
in boolean aCancelable, in boolean aCancelable,
in boolean aWasClean); in boolean aWasClean,
in unsigned short aReasonCode,
in DOMString aReason);
}; };

View File

@ -55,7 +55,7 @@ async protocol PWebSocket
parent: parent:
// Forwarded methods corresponding to methods on nsIWebSocketChannel // Forwarded methods corresponding to methods on nsIWebSocketChannel
AsyncOpen(URI aURI, nsCString aOrigin, nsCString aProtocol, bool aSecure); AsyncOpen(URI aURI, nsCString aOrigin, nsCString aProtocol, bool aSecure);
Close(); Close(PRUint16 code, nsCString reason);
SendMsg(nsCString aMsg); SendMsg(nsCString aMsg);
SendBinaryMsg(nsCString aMsg); SendBinaryMsg(nsCString aMsg);
@ -68,7 +68,7 @@ child:
OnMessageAvailable(nsCString aMsg); OnMessageAvailable(nsCString aMsg);
OnBinaryMessageAvailable(nsCString aMsg); OnBinaryMessageAvailable(nsCString aMsg);
OnAcknowledge(PRUint32 aSize); OnAcknowledge(PRUint32 aSize);
OnServerClose(); OnServerClose(PRUint16 code, nsCString aReason);
__delete__(); __delete__();

View File

@ -179,13 +179,17 @@ public:
NS_DECL_ISUPPORTS NS_DECL_ISUPPORTS
CallOnServerClose(nsIWebSocketListener *aListener, CallOnServerClose(nsIWebSocketListener *aListener,
nsISupports *aContext) nsISupports *aContext,
PRUint16 aCode,
nsCString &aReason)
: mListener(aListener), : mListener(aListener),
mContext(aContext) {} mContext(aContext),
mCode(aCode),
mReason(aReason) {}
NS_SCRIPTABLE NS_IMETHOD Run() NS_SCRIPTABLE NS_IMETHOD Run()
{ {
mListener->OnServerClose(mContext); mListener->OnServerClose(mContext, mCode, mReason);
return NS_OK; return NS_OK;
} }
@ -194,6 +198,8 @@ private:
nsCOMPtr<nsIWebSocketListener> mListener; nsCOMPtr<nsIWebSocketListener> mListener;
nsCOMPtr<nsISupports> mContext; nsCOMPtr<nsISupports> mContext;
PRUint16 mCode;
nsCString mReason;
}; };
NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnServerClose, nsIRunnable) NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnServerClose, nsIRunnable)
@ -502,7 +508,8 @@ WebSocketChannel::WebSocketChannel() :
mTCPClosed(0), mTCPClosed(0),
mMaxMessageSize(16000000), mMaxMessageSize(16000000),
mStopOnClose(NS_OK), mStopOnClose(NS_OK),
mCloseCode(kCloseAbnormal), mServerCloseCode(CLOSE_ABNORMAL),
mScriptCloseCode(0),
mFragmentOpcode(0), mFragmentOpcode(0),
mFragmentAccumulator(0), mFragmentAccumulator(0),
mBuffered(0), mBuffered(0),
@ -869,26 +876,29 @@ WebSocketChannel::ProcessInput(PRUint8 *buffer, PRUint32 count)
LOG(("WebSocketChannel:: close received\n")); LOG(("WebSocketChannel:: close received\n"));
mServerClosed = 1; mServerClosed = 1;
mCloseCode = kCloseNoStatus; mServerCloseCode = CLOSE_NO_STATUS;
if (payloadLength >= 2) { if (payloadLength >= 2) {
memcpy(&mCloseCode, payload, 2); memcpy(&mServerCloseCode, payload, 2);
mCloseCode = PR_ntohs(mCloseCode); mServerCloseCode = PR_ntohs(mServerCloseCode);
LOG(("WebSocketChannel:: close recvd code %u\n", mCloseCode)); LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
PRUint16 msglen = payloadLength - 2; PRUint16 msglen = payloadLength - 2;
if (msglen > 0) { if (msglen > 0) {
nsCString utf8Data((const char *)payload + 2, msglen); mServerCloseReason.SetLength(msglen);
memcpy(mServerCloseReason.BeginWriting(),
(const char *)payload + 2, msglen);
// section 8.1 says to replace received non utf-8 sequences // section 8.1 says to replace received non utf-8 sequences
// (which are non-conformant to send) with u+fffd, // (which are non-conformant to send) with u+fffd,
// but secteam feels that silently rewriting messages is // but secteam feels that silently rewriting messages is
// inappropriate - so we will fail the connection instead. // inappropriate - so we will fail the connection instead.
if (!IsUTF8(utf8Data)) { if (!IsUTF8(mServerCloseReason)) {
LOG(("WebSocketChannel:: close frame invalid utf-8\n")); LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
AbortSession(NS_ERROR_ILLEGAL_VALUE); AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE; return NS_ERROR_ILLEGAL_VALUE;
} }
LOG(("WebSocketChannel:: close msg %s\n", utf8Data.get())); LOG(("WebSocketChannel:: close msg %s\n",
mServerCloseReason.get()));
} }
} }
@ -897,7 +907,9 @@ WebSocketChannel::ProcessInput(PRUint8 *buffer, PRUint32 count)
mCloseTimer = nsnull; mCloseTimer = nsnull;
} }
if (mListener) if (mListener)
NS_DispatchToMainThread(new CallOnServerClose(mListener, mContext)); NS_DispatchToMainThread(
new CallOnServerClose(mListener, mContext,
mServerCloseCode, mServerCloseReason));
if (mClientClosed) if (mClientClosed)
ReleaseSession(); ReleaseSession();
@ -1058,16 +1070,16 @@ PRUint16
WebSocketChannel::ResultToCloseCode(nsresult resultCode) WebSocketChannel::ResultToCloseCode(nsresult resultCode)
{ {
if (NS_SUCCEEDED(resultCode)) if (NS_SUCCEEDED(resultCode))
return kCloseNormal; return CLOSE_NORMAL;
if (resultCode == NS_ERROR_FILE_TOO_BIG) if (resultCode == NS_ERROR_FILE_TOO_BIG)
return kCloseTooLarge; return CLOSE_TOO_LARGE;
if (resultCode == NS_BASE_STREAM_CLOSED || if (resultCode == NS_BASE_STREAM_CLOSED ||
resultCode == NS_ERROR_NET_TIMEOUT || resultCode == NS_ERROR_NET_TIMEOUT ||
resultCode == NS_ERROR_CONNECTION_REFUSED) { resultCode == NS_ERROR_CONNECTION_REFUSED) {
return kCloseAbnormal; return CLOSE_ABNORMAL;
} }
return kCloseProtocolError; return CLOSE_PROTOCOL_ERROR;
} }
void void
@ -1107,16 +1119,34 @@ WebSocketChannel::PrimeNewOutgoingMessage()
LOG(("WebSocketChannel:: PrimeNewOutgoingMessage() found close request\n")); LOG(("WebSocketChannel:: PrimeNewOutgoingMessage() found close request\n"));
mClientClosed = 1; mClientClosed = 1;
mOutHeader[0] = kFinalFragBit | kClose; mOutHeader[0] = kFinalFragBit | kClose;
mOutHeader[1] = 0x02; // payload len = 2 mOutHeader[1] = 0x02; // payload len = 2, maybe more for reason
mOutHeader[1] |= kMaskBit; mOutHeader[1] |= kMaskBit;
// payload is offset 6 including 4 for the mask // payload is offset 6 including 4 for the mask
payload = mOutHeader + 6; payload = mOutHeader + 6;
// The close reason code sits in the first 2 bytes of payload // length is 8 plus any reason information
*((PRUint16 *)payload) = PR_htons(ResultToCloseCode(mStopOnClose));
mHdrOutToSend = 8; mHdrOutToSend = 8;
// The close reason code sits in the first 2 bytes of payload
// If the channel user provided a code and reason during Close()
// and there isn't an internal error, use that.
if (NS_SUCCEEDED(mStopOnClose) && mScriptCloseCode) {
*((PRUint16 *)payload) = PR_htons(mScriptCloseCode);
if (!mScriptCloseReason.IsEmpty()) {
NS_ABORT_IF_FALSE(mScriptCloseReason.Length() <= 123,
"Close Reason Too Long");
mOutHeader[1] += mScriptCloseReason.Length();
mHdrOutToSend += mScriptCloseReason.Length();
memcpy (payload + 2,
mScriptCloseReason.BeginReading(),
mScriptCloseReason.Length());
}
}
else {
*((PRUint16 *)payload) = PR_htons(ResultToCloseCode(mStopOnClose));
}
if (mServerClosed) { if (mServerClosed) {
/* bidi close complete */ /* bidi close complete */
mReleaseOnTransmit = 1; mReleaseOnTransmit = 1;
@ -1984,7 +2014,7 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
} }
NS_IMETHODIMP NS_IMETHODIMP
WebSocketChannel::Close() WebSocketChannel::Close(PRUint16 code, const nsACString & reason)
{ {
LOG(("WebSocketChannel::Close() %p\n", this)); LOG(("WebSocketChannel::Close() %p\n", this));
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
@ -2000,8 +2030,14 @@ WebSocketChannel::Close()
return NS_ERROR_UNEXPECTED; return NS_ERROR_UNEXPECTED;
} }
mRequestedClose = 1; // The API requires the UTF-8 string to be 123 or less bytes
if (reason.Length() > 123)
return NS_ERROR_ILLEGAL_VALUE;
mRequestedClose = 1;
mScriptCloseReason = reason;
mScriptCloseCode = code;
return mSocketThread->Dispatch(new nsPostMessage(this, kFinMessage, -1), return mSocketThread->Dispatch(new nsPostMessage(this, kFinMessage, -1),
nsIEventTarget::DISPATCH_NORMAL); nsIEventTarget::DISPATCH_NORMAL);
} }

View File

@ -99,7 +99,7 @@ public:
const nsACString &aOrigin, const nsACString &aOrigin,
nsIWebSocketListener *aListener, nsIWebSocketListener *aListener,
nsISupports *aContext); nsISupports *aContext);
NS_IMETHOD Close(); NS_IMETHOD Close(PRUint16 aCode, const nsACString & aReason);
NS_IMETHOD SendMsg(const nsACString &aMsg); NS_IMETHOD SendMsg(const nsACString &aMsg);
NS_IMETHOD SendBinaryMsg(const nsACString &aMsg); NS_IMETHOD SendBinaryMsg(const nsACString &aMsg);
NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo); NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo);
@ -123,15 +123,6 @@ public:
const static PRUint8 kMaskBit = 0x80; const static PRUint8 kMaskBit = 0x80;
const static PRUint8 kFinalFragBit = 0x80; const static PRUint8 kFinalFragBit = 0x80;
// section 7.4.1 defines these
const static PRUint16 kCloseNormal = 1000;
const static PRUint16 kCloseGoingAway = 1001;
const static PRUint16 kCloseProtocolError = 1002;
const static PRUint16 kCloseUnsupported = 1003;
const static PRUint16 kCloseTooLarge = 1004;
const static PRUint16 kCloseNoStatus = 1005;
const static PRUint16 kCloseAbnormal = 1006;
protected: protected:
virtual ~WebSocketChannel(); virtual ~WebSocketChannel();
@ -252,7 +243,10 @@ private:
PRInt32 mMaxMessageSize; PRInt32 mMaxMessageSize;
nsresult mStopOnClose; nsresult mStopOnClose;
PRUint16 mCloseCode; PRUint16 mServerCloseCode;
nsCString mServerCloseReason;
PRUint16 mScriptCloseCode;
nsCString mScriptCloseReason;
// These are for the read buffers // These are for the read buffers
PRUint8 *mFramePtr; PRUint8 *mFramePtr;

View File

@ -291,36 +291,44 @@ WebSocketChannelChild::OnAcknowledge(const PRUint32& aSize)
class ServerCloseEvent : public ChannelEvent class ServerCloseEvent : public ChannelEvent
{ {
public: public:
ServerCloseEvent(WebSocketChannelChild* aChild) ServerCloseEvent(WebSocketChannelChild* aChild,
const PRUint16 aCode,
const nsCString &aReason)
: mChild(aChild) : mChild(aChild)
, mCode(aCode)
, mReason(aReason)
{} {}
void Run() void Run()
{ {
mChild->OnServerClose(); mChild->OnServerClose(mCode, mReason);
} }
private: private:
WebSocketChannelChild* mChild; WebSocketChannelChild* mChild;
PRUint16 mCode;
nsCString mReason;
}; };
bool bool
WebSocketChannelChild::RecvOnServerClose() WebSocketChannelChild::RecvOnServerClose(const PRUint16& aCode,
const nsCString& aReason)
{ {
if (mEventQ.ShouldEnqueue()) { if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new ServerCloseEvent(this)); mEventQ.Enqueue(new ServerCloseEvent(this, aCode, aReason));
} else { } else {
OnServerClose(); OnServerClose(aCode, aReason);
} }
return true; return true;
} }
void void
WebSocketChannelChild::OnServerClose() WebSocketChannelChild::OnServerClose(const PRUint16& aCode,
const nsCString& aReason)
{ {
LOG(("WebSocketChannelChild::RecvOnServerClose() %p\n", this)); LOG(("WebSocketChannelChild::RecvOnServerClose() %p\n", this));
if (mListener) { if (mListener) {
AutoEventEnqueuer ensureSerialDispatch(mEventQ);; AutoEventEnqueuer ensureSerialDispatch(mEventQ);;
mListener->OnServerClose(mContext); mListener->OnServerClose(mContext, aCode, aReason);
} }
} }
@ -361,11 +369,11 @@ WebSocketChannelChild::AsyncOpen(nsIURI *aURI,
} }
NS_IMETHODIMP NS_IMETHODIMP
WebSocketChannelChild::Close() WebSocketChannelChild::Close(PRUint16 code, const nsACString & reason)
{ {
LOG(("WebSocketChannelChild::Close() %p\n", this)); LOG(("WebSocketChannelChild::Close() %p\n", this));
if (!mIPCOpen || !SendClose()) if (!mIPCOpen || !SendClose(code, nsCString(reason)))
return NS_ERROR_UNEXPECTED; return NS_ERROR_UNEXPECTED;
return NS_OK; return NS_OK;
} }

View File

@ -64,7 +64,7 @@ class WebSocketChannelChild : public BaseWebSocketChannel,
const nsACString &aOrigin, const nsACString &aOrigin,
nsIWebSocketListener *aListener, nsIWebSocketListener *aListener,
nsISupports *aContext); nsISupports *aContext);
NS_SCRIPTABLE NS_IMETHOD Close(); NS_SCRIPTABLE NS_IMETHOD Close(PRUint16 code, const nsACString & reason);
NS_SCRIPTABLE NS_IMETHOD SendMsg(const nsACString &aMsg); NS_SCRIPTABLE NS_IMETHOD SendMsg(const nsACString &aMsg);
NS_SCRIPTABLE NS_IMETHOD SendBinaryMsg(const nsACString &aMsg); NS_SCRIPTABLE NS_IMETHOD SendBinaryMsg(const nsACString &aMsg);
NS_SCRIPTABLE NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo); NS_SCRIPTABLE NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo);
@ -78,7 +78,7 @@ class WebSocketChannelChild : public BaseWebSocketChannel,
bool RecvOnMessageAvailable(const nsCString& aMsg); bool RecvOnMessageAvailable(const nsCString& aMsg);
bool RecvOnBinaryMessageAvailable(const nsCString& aMsg); bool RecvOnBinaryMessageAvailable(const nsCString& aMsg);
bool RecvOnAcknowledge(const PRUint32& aSize); bool RecvOnAcknowledge(const PRUint32& aSize);
bool RecvOnServerClose(); bool RecvOnServerClose(const PRUint16& aCode, const nsCString &aReason);
bool RecvAsyncOpenFailed(); bool RecvAsyncOpenFailed();
void OnStart(const nsCString& aProtocol); void OnStart(const nsCString& aProtocol);
@ -86,7 +86,7 @@ class WebSocketChannelChild : public BaseWebSocketChannel,
void OnMessageAvailable(const nsCString& aMsg); void OnMessageAvailable(const nsCString& aMsg);
void OnBinaryMessageAvailable(const nsCString& aMsg); void OnBinaryMessageAvailable(const nsCString& aMsg);
void OnAcknowledge(const PRUint32& aSize); void OnAcknowledge(const PRUint32& aSize);
void OnServerClose(); void OnServerClose(const PRUint16& aCode, const nsCString& aReason);
void AsyncOpenFailed(); void AsyncOpenFailed();
ChannelEventQueue mEventQ; ChannelEventQueue mEventQ;

View File

@ -105,11 +105,11 @@ fail:
} }
bool bool
WebSocketChannelParent::RecvClose() WebSocketChannelParent::RecvClose(const PRUint16& code, const nsCString& reason)
{ {
LOG(("WebSocketChannelParent::RecvClose() %p\n", this)); LOG(("WebSocketChannelParent::RecvClose() %p\n", this));
if (mChannel) { if (mChannel) {
nsresult rv = mChannel->Close(); nsresult rv = mChannel->Close(code, reason);
NS_ENSURE_SUCCESS(rv, true); NS_ENSURE_SUCCESS(rv, true);
} }
return true; return true;
@ -203,10 +203,11 @@ WebSocketChannelParent::OnAcknowledge(nsISupports *aContext, PRUint32 aSize)
} }
NS_IMETHODIMP NS_IMETHODIMP
WebSocketChannelParent::OnServerClose(nsISupports *aContext) WebSocketChannelParent::OnServerClose(nsISupports *aContext,
PRUint16 code, const nsACString & reason)
{ {
LOG(("WebSocketChannelParent::OnServerClose() %p\n", this)); LOG(("WebSocketChannelParent::OnServerClose() %p\n", this));
if (!mIPCOpen || !SendOnServerClose()) { if (!mIPCOpen || !SendOnServerClose(code, nsCString(reason))) {
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
return NS_OK; return NS_OK;

View File

@ -67,7 +67,7 @@ class WebSocketChannelParent : public PWebSocketParent,
const nsCString& aOrigin, const nsCString& aOrigin,
const nsCString& aProtocol, const nsCString& aProtocol,
const bool& aSecure); const bool& aSecure);
bool RecvClose(); bool RecvClose(const PRUint16 & code, const nsCString & reason);
bool RecvSendMsg(const nsCString& aMsg); bool RecvSendMsg(const nsCString& aMsg);
bool RecvSendBinaryMsg(const nsCString& aMsg); bool RecvSendBinaryMsg(const nsCString& aMsg);
bool RecvDeleteSelf(); bool RecvDeleteSelf();

View File

@ -44,7 +44,7 @@ interface nsIWebSocketListener;
#include "nsISupports.idl" #include "nsISupports.idl"
[scriptable, uuid(398a2460-a46d-11e0-8264-0800200c9a66)] [scriptable, uuid(783029cf-75b5-439e-8e47-86b390202b4c)]
interface nsIWebSocketChannel : nsISupports interface nsIWebSocketChannel : nsISupports
{ {
/** /**
@ -106,9 +106,22 @@ interface nsIWebSocketChannel : nsISupports
* Close the websocket connection for writing - no more calls to sendMsg * Close the websocket connection for writing - no more calls to sendMsg
* or sendBinaryMsg should be made after calling this. The listener object * or sendBinaryMsg should be made after calling this. The listener object
* may receive more messages if a server close has not yet been received. * may receive more messages if a server close has not yet been received.
*
* @param aCode the websocket closing handshake close code. Set to 0 if
* you are not providing a code.
* @param aReason the websocket closing handshake close reason
*/ */
void close(); void close(in unsigned short aCode, in AUTF8String aReason);
// section 7.4.1 defines these close codes
const unsigned short CLOSE_NORMAL = 1000;
const unsigned short CLOSE_GOING_AWAY = 1001;
const unsigned short CLOSE_PROTOCOL_ERROR = 1002;
const unsigned short CLOSE_UNSUPPORTED = 1003;
const unsigned short CLOSE_TOO_LARGE = 1004;
const unsigned short CLOSE_NO_STATUS = 1005;
const unsigned short CLOSE_ABNORMAL = 1006;
/** /**
* Use to send text message down the connection to WebSocket peer. * Use to send text message down the connection to WebSocket peer.
* *

View File

@ -43,7 +43,7 @@
* nsIWebSocketListener: passed to nsIWebSocketChannel::AsyncOpen. Receives * nsIWebSocketListener: passed to nsIWebSocketChannel::AsyncOpen. Receives
* websocket traffic events as they arrive. * websocket traffic events as they arrive.
*/ */
[scriptable, uuid(b0c27050-31e9-42e5-bc59-499d54b52f99)] [scriptable, uuid(d74c96b2-65b3-4e39-9e39-c577de5d7a73)]
interface nsIWebSocketListener : nsISupports interface nsIWebSocketListener : nsISupports
{ {
/** /**
@ -101,8 +101,14 @@ interface nsIWebSocketListener : nsISupports
* onBinaryMessageAvailable() or onAcknowledge() will be delievered * onBinaryMessageAvailable() or onAcknowledge() will be delievered
* to the listener after onServerClose(), though outgoing messages can still * to the listener after onServerClose(), though outgoing messages can still
* be sent through the nsIWebSocketChannel connection. * be sent through the nsIWebSocketChannel connection.
*
* @param aContext user defined context
* @param aCode the websocket closing handshake close code.
* @param aReason the websocket closing handshake close reason
*/ */
void onServerClose(in nsISupports aContext); void onServerClose(in nsISupports aContext, in unsigned short aCode,
in AUTF8String aReason);
}; };