Commit afb40ae5 authored by bashi@chromium.org's avatar bashi@chromium.org

[WebSocket] Add extension attribute support

https://bugs.webkit.org/show_bug.cgi?id=78557

Source/WebCore:

Implement WebSocket "extensions" attribute that holds a list of
extension the server accepted. No change in behavior at this time
because we don't send any extension on handshake.

Reviewed by Kent Tamura.

No new tests. http/tests/websocket/tests/hybi/extensions.html checks the value of this attribute.

* websockets/ThreadableWebSocketChannel.h: Add extensions().
(ThreadableWebSocketChannel):
* websockets/ThreadableWebSocketChannelClientWrapper.cpp:
(WebCore::ThreadableWebSocketChannelClientWrapper::extensions): Added.
(WebCore):
(WebCore::ThreadableWebSocketChannelClientWrapper::setExtensions): Added.
* websockets/ThreadableWebSocketChannelClientWrapper.h:
(ThreadableWebSocketChannelClientWrapper):
* websockets/WebSocket.cpp: Added m_extensions member variable.
(WebCore::WebSocket::WebSocket):
(WebCore::WebSocket::extensions): Returns m_extensions.
* websockets/WebSocket.h:
* websockets/WebSocketChannel.cpp:
(WebCore::WebSocketChannel::extensions): Added.
(WebCore):
* websockets/WebSocketChannel.h:
(WebSocketChannel):
* websockets/WebSocketExtensionDispatcher.cpp:
(WebCore::WebSocketExtensionDispatcher::fail): Added.
(WebCore::WebSocketExtensionDispatcher::processHeaderValue): Stores accepted extensions.
(WebCore::WebSocketExtensionDispatcher::acceptedExtensions): Added.
(WebCore):
(WebCore::WebSocketExtensionDispatcher::acceptedExtensions): Added.
* websockets/WebSocketExtensionDispatcher.h:
(WebSocketExtensionDispatcher):
* websockets/WebSocketHandshake.cpp:
(WebCore::WebSocketHandshake::acceptedExtensions): Added.
(WebCore):
* websockets/WebSocketHandshake.h:
* websockets/WorkerThreadableWebSocketChannel.cpp:
(WebCore::WorkerThreadableWebSocketChannel::extensions): Added.
(WebCore):
(WebCore::workerContextDidConnect): Calls ThreadableWebSocketChannelClientWrapper::setExtensions().
(WebCore::WorkerThreadableWebSocketChannel::Peer::didConnect): Passes extensions as an argument.
* websockets/WorkerThreadableWebSocketChannel.h:
(WorkerThreadableWebSocketChannel):

Source/WebKit/chromium:

Add WebSocketExtensionDispatcher::acceptedExtensions() checks.

Reviewed by Kent Tamura.

* tests/WebSocketExtensionDispatcherTest.cpp:
(WebCore::TEST_F):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@107769 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 852fdb1d
2012-02-14 Kenichi Ishibashi <bashi@chromium.org>
[WebSocket] Add extension attribute support
https://bugs.webkit.org/show_bug.cgi?id=78557
Implement WebSocket "extensions" attribute that holds a list of
extension the server accepted. No change in behavior at this time
because we don't send any extension on handshake.
Reviewed by Kent Tamura.
No new tests. http/tests/websocket/tests/hybi/extensions.html checks the value of this attribute.
* websockets/ThreadableWebSocketChannel.h: Add extensions().
(ThreadableWebSocketChannel):
* websockets/ThreadableWebSocketChannelClientWrapper.cpp:
(WebCore::ThreadableWebSocketChannelClientWrapper::extensions): Added.
(WebCore):
(WebCore::ThreadableWebSocketChannelClientWrapper::setExtensions): Added.
* websockets/ThreadableWebSocketChannelClientWrapper.h:
(ThreadableWebSocketChannelClientWrapper):
* websockets/WebSocket.cpp: Added m_extensions member variable.
(WebCore::WebSocket::WebSocket):
(WebCore::WebSocket::extensions): Returns m_extensions.
* websockets/WebSocket.h:
* websockets/WebSocketChannel.cpp:
(WebCore::WebSocketChannel::extensions): Added.
(WebCore):
* websockets/WebSocketChannel.h:
(WebSocketChannel):
* websockets/WebSocketExtensionDispatcher.cpp:
(WebCore::WebSocketExtensionDispatcher::fail): Added.
(WebCore::WebSocketExtensionDispatcher::processHeaderValue): Stores accepted extensions.
(WebCore::WebSocketExtensionDispatcher::acceptedExtensions): Added.
(WebCore):
(WebCore::WebSocketExtensionDispatcher::acceptedExtensions): Added.
* websockets/WebSocketExtensionDispatcher.h:
(WebSocketExtensionDispatcher):
* websockets/WebSocketHandshake.cpp:
(WebCore::WebSocketHandshake::acceptedExtensions): Added.
(WebCore):
* websockets/WebSocketHandshake.h:
* websockets/WorkerThreadableWebSocketChannel.cpp:
(WebCore::WorkerThreadableWebSocketChannel::extensions): Added.
(WebCore):
(WebCore::workerContextDidConnect): Calls ThreadableWebSocketChannelClientWrapper::setExtensions().
(WebCore::WorkerThreadableWebSocketChannel::Peer::didConnect): Passes extensions as an argument.
* websockets/WorkerThreadableWebSocketChannel.h:
(WorkerThreadableWebSocketChannel):
2012-02-14 Kentaro Hara <haraken@chromium.org>
Rename [JSGenerateToJS] to [JSGenerateToJSObject]
......@@ -53,6 +53,7 @@ public:
virtual bool useHixie76Protocol() = 0;
virtual void connect(const KURL&, const String& protocol) = 0;
virtual String subprotocol() = 0; // Will be available after didConnect() callback is invoked.
virtual String extensions() = 0; // Will be available after didConnect() callback is invoked.
virtual bool send(const String& message) = 0;
virtual bool send(const ArrayBuffer&) = 0;
virtual bool send(const Blob&) = 0;
......
......@@ -95,6 +95,21 @@ void ThreadableWebSocketChannelClientWrapper::setSubprotocol(const String& subpr
memcpy(m_subprotocol.data(), subprotocol.characters(), sizeof(UChar) * length);
}
String ThreadableWebSocketChannelClientWrapper::extensions() const
{
if (m_extensions.isEmpty())
return String("");
return String(m_extensions);
}
void ThreadableWebSocketChannelClientWrapper::setExtensions(const String& extensions)
{
unsigned length = extensions.length();
m_extensions.resize(length);
if (length)
memcpy(m_extensions.data(), extensions.characters(), sizeof(UChar) * length);
}
bool ThreadableWebSocketChannelClientWrapper::sendRequestResult() const
{
return m_sendRequestResult;
......
......@@ -59,9 +59,11 @@ public:
bool useHixie76Protocol() const;
void setUseHixie76Protocol(bool);
// Subprotocol is cached too. Will be available when didConnect() callback is invoked.
// Subprotocol and extensions are cached too. Will be available when didConnect() callback is invoked.
String subprotocol() const;
void setSubprotocol(const String&);
String extensions() const;
void setExtensions(const String&);
bool sendRequestResult() const;
void setSendRequestResult(bool);
......@@ -95,7 +97,9 @@ protected:
WebSocketChannelClient* m_client;
bool m_syncMethodDone;
bool m_useHixie76Protocol;
Vector<UChar> m_subprotocol; // ThreadSafeRefCounted must not have a String member variable.
// ThreadSafeRefCounted must not have String member variables.
Vector<UChar> m_subprotocol;
Vector<UChar> m_extensions;
bool m_sendRequestResult;
unsigned long m_bufferedAmount;
bool m_suspended;
......
......@@ -155,6 +155,7 @@ WebSocket::WebSocket(ScriptExecutionContext* context)
, m_binaryType(BinaryTypeBlob)
, m_useHixie76Protocol(true)
, m_subprotocol("")
, m_extensions("")
{
}
......@@ -387,8 +388,7 @@ String WebSocket::extensions() const
{
if (m_useHixie76Protocol)
return String();
// WebSocket protocol extension is not supported yet.
return "";
return m_extensions;
}
String WebSocket::binaryType() const
......
......@@ -136,6 +136,7 @@ private:
BinaryType m_binaryType;
bool m_useHixie76Protocol;
String m_subprotocol;
String m_extensions;
};
} // namespace WebCore
......
......@@ -153,6 +153,17 @@ String WebSocketChannel::subprotocol()
return serverProtocol;
}
String WebSocketChannel::extensions()
{
LOG(Network, "WebSocketChannel %p extensions", this);
if (!m_handshake || m_handshake->mode() != WebSocketHandshake::Connected)
return "";
String extensions = m_handshake->acceptedExtensions();
if (extensions.isNull())
return "";
return extensions;
}
bool WebSocketChannel::send(const String& message)
{
LOG(Network, "WebSocketChannel %p send %s", this, message.utf8().data());
......
......@@ -68,6 +68,7 @@ public:
virtual bool useHixie76Protocol() OVERRIDE;
virtual void connect(const KURL&, const String& protocol) OVERRIDE;
virtual String subprotocol() OVERRIDE;
virtual String extensions() OVERRIDE;
virtual bool send(const String& message) OVERRIDE;
virtual bool send(const ArrayBuffer&) OVERRIDE;
virtual bool send(const Blob&) OVERRIDE;
......
......@@ -35,8 +35,8 @@
#include "WebSocketExtensionDispatcher.h"
#include <wtf/ASCIICType.h>
#include <wtf/HashMap.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringHash.h>
namespace WebCore {
......@@ -173,6 +173,28 @@ const String WebSocketExtensionDispatcher::createHeaderValue() const
return builder.toString();
}
void WebSocketExtensionDispatcher::appendAcceptedExtension(const String& extensionToken, HashMap<String, String>& extensionParameters)
{
if (!m_acceptedExtensionsBuilder.isEmpty())
m_acceptedExtensionsBuilder.append(", ");
m_acceptedExtensionsBuilder.append(extensionToken);
// FIXME: Should use ListHashSet to keep the order of the parameters.
for (HashMap<String, String>::const_iterator iterator = extensionParameters.begin(); iterator != extensionParameters.end(); ++iterator) {
m_acceptedExtensionsBuilder.append("; ");
m_acceptedExtensionsBuilder.append(iterator->first);
if (!iterator->second.isNull()) {
m_acceptedExtensionsBuilder.append("=");
m_acceptedExtensionsBuilder.append(iterator->second);
}
}
}
void WebSocketExtensionDispatcher::fail(const String& reason)
{
m_failureReason = reason;
m_acceptedExtensionsBuilder.clear();
}
bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
{
if (!headerValue.length())
......@@ -180,17 +202,16 @@ bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
// If we don't send Sec-WebSocket-Extensions header, the server should not return the header.
if (!m_processors.size()) {
m_failureReason = "Received unexpected Sec-WebSocket-Extensions header";
fail("Received unexpected Sec-WebSocket-Extensions header");
return false;
}
const CString headerValueData = headerValue.utf8();
ExtensionParser parser(headerValueData.data(), headerValueData.data() + headerValueData.length());
while (!parser.finished()) {
// Parse extension-token.
if (!parser.consumeToken()) {
m_failureReason = "Sec-WebSocket-Extensions header is invalid";
fail("Sec-WebSocket-Extensions header is invalid");
return false;
}
String extensionToken = parser.currentToken();
......@@ -199,7 +220,7 @@ bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
HashMap<String, String> extensionParameters;
while (parser.consumeCharacter(';')) {
if (!parser.consumeToken()) {
m_failureReason = "Sec-WebSocket-Extensions header is invalid";
fail("Sec-WebSocket-Extensions header is invalid");
return false;
}
......@@ -208,14 +229,14 @@ bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
if (parser.consumeQuotedStringOrToken())
extensionParameters.add(parameterToken, parser.currentToken());
else {
m_failureReason = "Sec-WebSocket-Extensions header is invalid";
fail("Sec-WebSocket-Extensions header is invalid");
return false;
}
} else
extensionParameters.add(parameterToken, String());
}
if (!parser.finished() && !parser.consumeCharacter(',')) {
m_failureReason = "Sec-WebSocket-Extensions header is invalid";
fail("Sec-WebSocket-Extensions header is invalid");
return false;
}
......@@ -223,21 +244,30 @@ bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
for (index = 0; index < m_processors.size(); ++index) {
WebSocketExtensionProcessor* processor = m_processors[index].get();
if (extensionToken == processor->extensionToken()) {
if (processor->processResponse(extensionParameters))
if (processor->processResponse(extensionParameters)) {
appendAcceptedExtension(extensionToken, extensionParameters);
break;
m_failureReason = processor->failureReason();
}
fail(processor->failureReason());
return false;
}
}
// There is no extension which can process the response.
if (index == m_processors.size()) {
m_failureReason = "Received unexpected extension: " + extensionToken;
fail("Received unexpected extension: " + extensionToken);
return false;
}
}
return parser.parsedSuccessfully();
}
String WebSocketExtensionDispatcher::acceptedExtensions() const
{
if (m_acceptedExtensionsBuilder.isEmpty())
return String();
return m_acceptedExtensionsBuilder.toStringPreserveCapacity();
}
String WebSocketExtensionDispatcher::failureReason() const
{
return m_failureReason;
......
......@@ -37,6 +37,7 @@
#include <wtf/OwnPtr.h>
#include <wtf/PassOwnPtr.h>
#include <wtf/Vector.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
......@@ -50,10 +51,15 @@ public:
const String createHeaderValue() const;
bool processHeaderValue(const String&);
String acceptedExtensions() const;
String failureReason() const;
private:
void appendAcceptedExtension(const String& extensionToken, HashMap<String, String>& extensionParameters);
void fail(const String& reason);
Vector<OwnPtr<WebSocketExtensionProcessor> > m_processors;
StringBuilder m_acceptedExtensionsBuilder;
String m_failureReason;
};
......
......@@ -476,6 +476,11 @@ String WebSocketHandshake::serverWebSocketAccept() const
return m_response.headerFields().get("sec-websocket-accept");
}
String WebSocketHandshake::acceptedExtensions() const
{
return m_extensionDispatcher.acceptedExtensions();
}
const WebSocketHandshakeResponse& WebSocketHandshake::serverHandshakeResponse() const
{
return m_response;
......
......@@ -84,6 +84,7 @@ public:
String serverUpgrade() const;
String serverConnection() const;
String serverWebSocketAccept() const; // Only for hybi-10 handshake.
String acceptedExtensions() const;
const WebSocketHandshakeResponse& serverHandshakeResponse() const;
......
......@@ -82,6 +82,12 @@ String WorkerThreadableWebSocketChannel::subprotocol()
return m_workerClientWrapper->subprotocol();
}
String WorkerThreadableWebSocketChannel::extensions()
{
ASSERT(m_workerClientWrapper);
return m_workerClientWrapper->extensions();
}
bool WorkerThreadableWebSocketChannel::send(const String& message)
{
if (!m_bridge)
......@@ -262,17 +268,18 @@ void WorkerThreadableWebSocketChannel::Peer::resume()
m_mainWebSocketChannel->resume();
}
static void workerContextDidConnect(ScriptExecutionContext* context, PassRefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, const String& subprotocol)
static void workerContextDidConnect(ScriptExecutionContext* context, PassRefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, const String& subprotocol, const String& extensions)
{
ASSERT_UNUSED(context, context->isWorkerContext());
workerClientWrapper->setSubprotocol(subprotocol);
workerClientWrapper->setExtensions(extensions);
workerClientWrapper->didConnect();
}
void WorkerThreadableWebSocketChannel::Peer::didConnect()
{
ASSERT(isMainThread());
m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidConnect, m_workerClientWrapper, m_mainWebSocketChannel->subprotocol()), m_taskMode);
m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidConnect, m_workerClientWrapper, m_mainWebSocketChannel->subprotocol(), m_mainWebSocketChannel->extensions()), m_taskMode);
}
static void workerContextDidReceiveMessage(ScriptExecutionContext* context, PassRefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, const String& message)
......
......@@ -64,6 +64,7 @@ public:
virtual bool useHixie76Protocol() OVERRIDE;
virtual void connect(const KURL&, const String& protocol) OVERRIDE;
virtual String subprotocol() OVERRIDE;
virtual String extensions() OVERRIDE;
virtual bool send(const String& message) OVERRIDE;
virtual bool send(const ArrayBuffer&) OVERRIDE;
virtual bool send(const Blob&) OVERRIDE;
......
2012-02-14 Kenichi Ishibashi <bashi@chromium.org>
[WebSocket] Add extension attribute support
https://bugs.webkit.org/show_bug.cgi?id=78557
Add WebSocketExtensionDispatcher::acceptedExtensions() checks.
Reviewed by Kent Tamura.
* tests/WebSocketExtensionDispatcherTest.cpp:
(WebCore::TEST_F):
2012-02-14 Kenichi Ishibashi <bashi@chromium.org>
[WebSocket] Add deflater/inflater classes.
......@@ -90,6 +90,7 @@ TEST_F(WebSocketExtensionDispatcherTest, TestSingle)
EXPECT_TRUE(m_extensions.processHeaderValue("deflate-frame"));
EXPECT_EQ(1UL, m_parsedExtensionTokens.size());
EXPECT_EQ("deflate-frame", m_parsedExtensionTokens[0]);
EXPECT_EQ("deflate-frame", m_extensions.acceptedExtensions());
EXPECT_EQ(0, m_parsedParameters[0].size());
}
......@@ -122,6 +123,8 @@ TEST_F(WebSocketExtensionDispatcherTest, TestMultiple)
addMockProcessor("mux");
addMockProcessor("deflate-frame");
EXPECT_TRUE(m_extensions.processHeaderValue("mux ; max-channels =4;flow-control, deflate-frame "));
EXPECT_TRUE(m_extensions.acceptedExtensions().find("mux") != notFound);
EXPECT_TRUE(m_extensions.acceptedExtensions().find("deflate-frame") != notFound);
for (size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); ++i) {
EXPECT_EQ(expected[i].token, m_parsedExtensionTokens[i]);
const HashMap<String, String>& expectedParameters = expected[i].parameters;
......@@ -169,6 +172,7 @@ TEST_F(WebSocketExtensionDispatcherTest, TestInvalid)
addMockProcessor("x-foo");
addMockProcessor("x-bar");
EXPECT_FALSE(m_extensions.processHeaderValue(inputs[i]));
EXPECT_TRUE(m_extensions.acceptedExtensions().isNull());
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment