bug 674527 - websockets sub-protocol array implementation and tests r=sicking sr=bz

This commit is contained in:
Patrick McManus 2011-08-03 12:42:02 -04:00
parent 2bc2762f54
commit b363c5480c
5 changed files with 263 additions and 62 deletions

View File

@ -43,6 +43,13 @@ interface nsIDOMEventListener;
interface nsIPrincipal;
interface nsIScriptContext;
interface nsPIDOMWindow;
interface nsIDOMDOMStringList;
%{C++
#include "nsTArray.h"
class nsString;
%}
[ref] native nsStringTArrayRef(nsTArray<nsString>);
/**
* The nsIMozWebSocket interface enables Web applications to maintain
@ -51,7 +58,7 @@ interface nsPIDOMWindow;
* http://dev.w3.org/html5/websockets/
*
*/
[scriptable, uuid(662691db-2b99-4461-801b-fbb72d99a4b9)]
[scriptable, uuid(d50eb158-30a1-4692-8664-fdd479fa24b3)]
interface nsIMozWebSocket : nsISupports
{
readonly attribute DOMString url;
@ -98,13 +105,16 @@ interface nsIMozWebSocket : nsISupports
* @param ownerWindow The associated window for the request. May be null.
* @param url The url for opening the socket. This must not be empty, and
* must have an absolute url, using either the ws or wss schemes.
* @param protocol Specifies a sub-protocol that the server must support for
* the connection to be successful. If empty, no protocol is
* specified.
* @param protocol Specifies array of sub-protocols acceptable to the client.
* If the length of the array is at least one, the server
* must select one of the listed sub-protocols for the
* connection to be successful. If empty, no sub-protocol is
* specified. The server selected sub-protocol can be read
* from the protocol attribute after connection.
*/
[noscript] void init(in nsIPrincipal principal,
in nsIScriptContext scriptContext,
in nsPIDOMWindow ownerWindow,
in DOMString url,
in DOMString protocol);
in nsStringTArrayRef protocol);
};

View File

@ -77,6 +77,7 @@
#include "nsILoadGroup.h"
#include "nsIRequest.h"
#include "mozilla/Preferences.h"
#include "nsDOMLists.h"
using namespace mozilla;
@ -295,9 +296,10 @@ nsWebSocketEstablishedConnection::Init(nsWebSocket *aOwner)
NS_ENSURE_SUCCESS(rv, rv);
}
if (!mOwner->mProtocol.IsEmpty())
rv = mWebSocketChannel->SetProtocol(mOwner->mProtocol);
NS_ENSURE_SUCCESS(rv, rv);
if (!mOwner->mRequestedProtocolList.IsEmpty()) {
rv = mWebSocketChannel->SetProtocol(mOwner->mRequestedProtocolList);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCString utf8Origin;
CopyUTF16toUTF8(mOwner->mUTF16Origin, utf8Origin);
@ -503,8 +505,8 @@ nsWebSocketEstablishedConnection::OnStart(nsISupports *aContext)
if (!mOwner)
return NS_OK;
if (!mOwner->mProtocol.IsEmpty())
mWebSocketChannel->GetProtocol(mOwner->mProtocol);
if (!mOwner->mRequestedProtocolList.IsEmpty())
mWebSocketChannel->GetProtocol(mOwner->mEstablishedProtocol);
mStatus = CONN_CONNECTED_AND_READY;
mOwner->SetReadyState(nsIMozWebSocket::OPEN);
@ -676,8 +678,9 @@ NS_IMPL_RELEASE_INHERITED(nsWebSocket, nsDOMEventTargetWrapperCache)
/**
* This Initialize method is called from XPConnect via nsIJSNativeInitializer.
* It is used for constructing our nsWebSocket from JavaScript. It expects a URL
* string parameter and an optional protocol parameter. It also initializes the
* principal, the script context and the window owner.
* string parameter and an optional protocol parameter which may be a string or
* an array of strings. It also initializes the principal, the script context and
* the window owner.
*/
NS_IMETHODIMP
nsWebSocket::Initialize(nsISupports* aOwner,
@ -687,7 +690,7 @@ nsWebSocket::Initialize(nsISupports* aOwner,
jsval* aArgv)
{
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
nsAutoString urlParam, protocolParam;
nsAutoString urlParam;
if (!PrefEnabled()) {
return NS_ERROR_DOM_SECURITY_ERR;
@ -714,24 +717,6 @@ nsWebSocket::Initialize(nsISupports* aOwner,
urlParam.Assign(chars, length);
deleteProtector.clear();
if (aArgc == 2) {
jsstr = JS_ValueToString(aContext, aArgv[1]);
if (!jsstr) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
deleteProtector.set(jsstr);
chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
if (!chars) {
return NS_ERROR_OUT_OF_MEMORY;
}
protocolParam.Assign(chars, length);
if (protocolParam.IsEmpty()) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
}
nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aOwner);
NS_ENSURE_STATE(ownerWindow);
@ -745,7 +730,62 @@ nsWebSocket::Initialize(nsISupports* aOwner,
nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
NS_ENSURE_STATE(principal);
return Init(principal, scriptContext, ownerWindow, urlParam, protocolParam);
nsTArray<nsString> protocolArray;
if (aArgc == 2) {
JSObject *jsobj;
if (JSVAL_IS_OBJECT(aArgv[1]) &&
(jsobj = JSVAL_TO_OBJECT(aArgv[1])) &&
JS_IsArrayObject(aContext, jsobj)) {
jsuint len;
JS_GetArrayLength(aContext, jsobj, &len);
for (PRUint32 index = 0; index < len; ++index) {
jsval value;
if (!JS_GetElement(aContext, jsobj, index, &value))
return NS_ERROR_DOM_SYNTAX_ERR;
jsstr = JS_ValueToString(aContext, value);
if (!jsstr)
return NS_ERROR_DOM_SYNTAX_ERR;
deleteProtector.set(jsstr);
chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
if (!chars)
return NS_ERROR_OUT_OF_MEMORY;
nsDependentString protocolElement(chars, length);
if (protocolElement.IsEmpty())
return NS_ERROR_DOM_SYNTAX_ERR;
if (protocolArray.Contains(protocolElement))
return NS_ERROR_DOM_SYNTAX_ERR;
if (protocolElement.FindChar(',') != -1) /* interferes w/list */
return NS_ERROR_DOM_SYNTAX_ERR;
protocolArray.AppendElement(protocolElement);
deleteProtector.clear();
}
} else {
jsstr = JS_ValueToString(aContext, aArgv[1]);
if (!jsstr)
return NS_ERROR_DOM_SYNTAX_ERR;
deleteProtector.set(jsstr);
chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
if (!chars)
return NS_ERROR_OUT_OF_MEMORY;
nsDependentString protocolElement(chars, length);
if (protocolElement.IsEmpty())
return NS_ERROR_DOM_SYNTAX_ERR;
if (protocolElement.FindChar(',') != -1) /* interferes w/list */
return NS_ERROR_DOM_SYNTAX_ERR;
protocolArray.AppendElement(protocolElement);
}
}
return Init(principal, scriptContext, ownerWindow, urlParam, protocolArray);
}
//-----------------------------------------------------------------------------
@ -1058,26 +1098,6 @@ nsWebSocket::ParseURL(const nsString& aURL)
return NS_OK;
}
nsresult
nsWebSocket::SetProtocol(const nsString& aProtocol)
{
if (aProtocol.IsEmpty()) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
PRUint32 length = aProtocol.Length();
PRUint32 i;
for (i = 0; i < length; ++i) {
if (aProtocol[i] < static_cast<PRUnichar>(0x0021) ||
aProtocol[i] > static_cast<PRUnichar>(0x007E)) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
}
CopyUTF16toUTF8(aProtocol, mProtocol);
return NS_OK;
}
//-----------------------------------------------------------------------------
// Methods that keep alive the WebSocket object when:
// 1. the object has registered event listeners that can be triggered
@ -1196,7 +1216,7 @@ nsWebSocket::GetUrl(nsAString& aURL)
NS_IMETHODIMP
nsWebSocket::GetProtocol(nsAString& aProtocol)
{
CopyUTF8toUTF16(mProtocol, aProtocol);
CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
return NS_OK;
}
@ -1306,7 +1326,7 @@ nsWebSocket::Init(nsIPrincipal* aPrincipal,
nsIScriptContext* aScriptContext,
nsPIDOMWindow* aOwnerWindow,
const nsAString& aURL,
const nsAString& aProtocol)
nsTArray<nsString> & protocolArray)
{
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
nsresult rv;
@ -1364,10 +1384,17 @@ nsWebSocket::Init(nsIPrincipal* aPrincipal,
return NS_ERROR_DOM_SECURITY_ERR;
}
// sets the protocol
if (!aProtocol.IsEmpty()) {
rv = SetProtocol(PromiseFlatString(aProtocol));
NS_ENSURE_SUCCESS(rv, rv);
// Assign the sub protocol list and scan it for illegal values
for (PRUint32 index = 0; index < protocolArray.Length(); ++index) {
for (PRUint32 i = 0; i < protocolArray[index].Length(); ++i) {
if (protocolArray[index][i] < static_cast<PRUnichar>(0x0021) ||
protocolArray[index][i] > static_cast<PRUnichar>(0x007E))
return NS_ERROR_DOM_SYNTAX_ERR;
}
if (!mRequestedProtocolList.IsEmpty())
mRequestedProtocolList.Append(NS_LITERAL_CSTRING(", "));
AppendUTF16toUTF8(protocolArray[index], mRequestedProtocolList);
}
// the constructor should throw a SYNTAX_ERROR only if it fails to parse the

View File

@ -50,6 +50,7 @@
#include "nsIDOMEventListener.h"
#include "nsDOMEventTargetWrapperCache.h"
#include "nsAutoPtr.h"
#include "nsIDOMDOMStringList.h"
#define DEFAULT_WS_SCHEME_PORT 80
#define DEFAULT_WSS_SCHEME_PORT 443
@ -104,7 +105,6 @@ public:
protected:
nsresult ParseURL(const nsString& aURL);
nsresult SetProtocol(const nsString& aProtocol);
nsresult EstablishConnection();
nsresult CreateAndDispatchSimpleEvent(const nsString& aName);
@ -142,7 +142,8 @@ protected:
nsString mUTF16Origin;
nsCOMPtr<nsIURI> mURI;
nsCString mProtocol;
nsCString mRequestedProtocolList;
nsCString mEstablishedProtocol;
PRUint16 mReadyState;

View File

@ -7,7 +7,10 @@ import sys
def web_socket_do_extra_handshake(request):
# must set request.ws_protocol to the selected version from ws_requested_protocols
request.ws_protocol = request.ws_requested_protocols[0]
for x in request.ws_requested_protocols:
if x != "test-does-not-exist":
request.ws_protocol = x
break
if request.ws_protocol == "test-2.1":
time.sleep(5)

View File

@ -45,10 +45,21 @@
* 22. server takes too long to establish the ws connection;
* 23. see bug 664692 - feature detection should detect MozWebSocket but not
* WebSocket on window object;
* 24. server rejects sub-protocol string
* 25. ctor with valid empty sub-protocol array
* 26. ctor with invalid sub-protocol array containing 1 empty element
* 27. ctor with invalid sub-protocol array containing an empty element in list
* 28. ctor using valid 1 element sub-protocol array
* 29. ctor using all valid 5 element sub-protocol array
* 30. ctor using valid 1 element sub-protocol array with element server will
* reject
* 31. ctor using valid 2 element sub-protocol array with 1 element server
* will reject and one server will accept.
* 32. ctor using invalid sub-protocol array that contains duplicate items
*/
var first_test = 1;
var last_test = 23;
var last_test = 32;
var current_test = first_test;
@ -309,6 +320,7 @@ function test8()
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-8");
ws.onopen = function()
{
ok(ws.protocol == "test-8", "test-8 subprotocol selection");
ws.close();
}
ws.onclose = function(e)
@ -635,11 +647,159 @@ function test22()
function test23()
{
current_test++;
is(false, "WebSocket" in window, "WebSocket shouldn't be available on window object");
is(true, "MozWebSocket" in window, "MozWebSocket should be available on window object");
doTest(24);
}
function test24()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-does-not-exist");
ws.onopen = shouldNotOpen;
ws.onclose = function(e)
{
shouldCloseNotCleanly(e);
doTest(25);
};
ws.onerror = function()
{
}
}
function test25()
{
var prots=[];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 25 protocol array open");
ws.close();
};
ws.onclose = function(e)
{
ok(ws.protocol == "", "test25 subprotocol selection");
ok(true, "test 25 protocol array close");
doTest(26);
};
}
function test26()
{
var prots=[""];
try {
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ok(false, "testing empty element sub protocol array");
}
catch (e) {
ok(true, "testing empty sub element protocol array");
}
doTest(27);
}
function test27()
{
var prots=["test27", ""];
try {
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ok(false, "testing empty element mixed sub protocol array");
}
catch (e) {
ok(true, "testing empty element mixed sub protocol array");
}
doTest(28);
}
function test28()
{
var prots=["test28"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 28 protocol array open");
ws.close();
};
ws.onclose = function(e)
{
ok(ws.protocol == "test28", "test28 subprotocol selection");
ok(true, "test 28 protocol array close");
doTest(29);
};
}
function test29()
{
var prots=["test29a", "test29b"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 29 protocol array open");
ws.close();
};
ws.onclose = function(e)
{
ok(true, "test 29 protocol array close");
doTest(30);
};
}
function test30()
{
var prots=["test-does-not-exist"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = shouldNotOpen;
ws.onclose = function(e)
{
shouldCloseNotCleanly(e);
doTest(31);
};
ws.onerror = function()
{
}
}
function test31()
{
var prots=["test-does-not-exist", "test31"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 31 protocol array open");
ws.close();
};
ws.onclose = function(e)
{
ok(ws.protocol == "test31", "test31 subprotocol selection");
ok(true, "test 31 protocol array close");
doTest(32);
};
}
function test32()
{
var prots=["test32","test32"];
try {
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ok(false, "testing duplicated element sub protocol array");
}
catch (e) {
ok(true, "testing duplicated sub element protocol array");
}
doTest(33);
}
function finishWSTest()
{
for (i = 0; i < all_ws.length; ++i) {