Add WebSocket extension support
authorbashi@chromium.org <bashi@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 10 Feb 2012 06:44:29 +0000 (06:44 +0000)
committerbashi@chromium.org <bashi@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 10 Feb 2012 06:44:29 +0000 (06:44 +0000)
https://bugs.webkit.org/show_bug.cgi?id=78079

Source/WebCore:

This patch introduces WebSocketExtensionDispatcher class, which creates client's
Sec-WebSocket-Extensions header field and parses the server response.
This patch doesn't add any actual extension, so no changes in behavior.

Reviewed by Kent Tamura.

No new tests except for chromium port.

* CMakeLists.txt: Added WebSocketExtensionDispatcher.(cpp|h) and WebSocketExtensionProcessor.h.
* GNUmakefile.list.am: Ditto.
* Target.pri: Ditto.
* WebCore.gypi: Ditto.
* WebCore.vcproj/WebCore.vcproj: Ditto.
* WebCore.xcodeproj/project.pbxproj: Ditto.
* websockets/WebSocketExtensionDispatcher.cpp: Added.
(WebCore):
(ExtensionParser):
(WebCore::ExtensionParser::ExtensionParser):
(WebCore::ExtensionParser::currentToken):
(WebCore::ExtensionParser::finished):
(WebCore::ExtensionParser::parsedSuccessfully):
(WebCore::isTokenCharacter):
(WebCore::isSeparator):
(WebCore::ExtensionParser::skipSpaces):
(WebCore::ExtensionParser::consumeToken):
(WebCore::ExtensionParser::consumeQuotedString):
(WebCore::ExtensionParser::consumeQuotedStringOrToken):
(WebCore::ExtensionParser::consumeCharacter):
(WebCore::WebSocketExtensionDispatcher::reset):
(WebCore::WebSocketExtensionDispatcher::addProcessor):
(WebCore::WebSocketExtensionDispatcher::createHeaderValue):
(WebCore::WebSocketExtensionDispatcher::processHeaderValue):
(WebCore::WebSocketExtensionDispatcher::failureReason):
* websockets/WebSocketExtensionDispatcher.h: Added.
(WebCore):
(WebSocketExtensionDispatcher):
(WebCore::WebSocketExtensionDispatcher::WebSocketExtensionDispatcher):
* websockets/WebSocketExtensionProcessor.h: Added.
(WebCore):
(WebSocketExtensionProcessor):
(WebCore::WebSocketExtensionProcessor::~WebSocketExtensionProcessor):
(WebCore::WebSocketExtensionProcessor::extensionToken):
(WebCore::WebSocketExtensionProcessor::failureReason):
(WebCore::WebSocketExtensionProcessor::WebSocketExtensionProcessor):
* websockets/WebSocketHandshake.cpp:
(WebCore::WebSocketHandshake::clientHandshakeMessage): Adds extension header value if exists.
(WebCore::WebSocketHandshake::clientHandshakeRequest): Ditto.
(WebCore::WebSocketHandshake::reset): Resets WebSocketExtensionDispatcher object.
(WebCore::WebSocketHandshake::serverHandshakeResponse): Removed.
(WebCore::WebSocketHandshake::addExtensionProcessor): Added.
(WebCore::WebSocketHandshake::readHTTPHeaders): Parses and checks every time Sec-WebSocket-Extensions header appears.
(WebCore::WebSocketHandshake::checkResponseHeaders): Removed the check of Sec-WebSocket-Extensions.
* websockets/WebSocketHandshake.h: Removed serverHandshakeResponse().

Source/WebKit/chromium:

Add some tests which check WebSocketExtensions::processHeaderValue()
parses the given response correctly.

Reviewed by Kent Tamura.

* WebKit.gypi:
* tests/WebSocketExtensionDispatcherTest.cpp: Added.
(WebCore):
(MockWebSocketExtensionProcessor):
(WebCore::MockWebSocketExtensionProcessor::MockWebSocketExtensionProcessor):
(WebSocketExtensionDispatcherTest):
(WebCore::WebSocketExtensionDispatcherTest::WebSocketExtensionDispatcherTest):
(WebCore::WebSocketExtensionDispatcherTest::SetUp):
(WebCore::WebSocketExtensionDispatcherTest::TearDown):
(WebCore::WebSocketExtensionDispatcherTest::addMockProcessor):
(WebCore::WebSocketExtensionDispatcherTest::appendResult):
(WebCore::MockWebSocketExtensionProcessor::processResponse):
(WebCore::TEST_F):

LayoutTests:

Reviewed by Kent Tamura.

* http/tests/websocket/tests/hybi/handshake-fail-by-extensions-header-expected.txt: Rebaselined.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@107365 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/websocket/tests/hybi/handshake-fail-by-extensions-header-expected.txt
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/GNUmakefile.list.am
Source/WebCore/Target.pri
Source/WebCore/WebCore.gypi
Source/WebCore/WebCore.vcproj/WebCore.vcproj
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/websockets/WebSocketExtensionDispatcher.cpp [new file with mode: 0644]
Source/WebCore/websockets/WebSocketExtensionDispatcher.h [new file with mode: 0644]
Source/WebCore/websockets/WebSocketExtensionProcessor.h [new file with mode: 0644]
Source/WebCore/websockets/WebSocketHandshake.cpp
Source/WebCore/websockets/WebSocketHandshake.h
Source/WebKit/chromium/ChangeLog
Source/WebKit/chromium/WebKit.gypi
Source/WebKit/chromium/tests/WebSocketExtensionDispatcherTest.cpp [new file with mode: 0644]

index 3538f38..3135c25 100644 (file)
@@ -1,3 +1,12 @@
+2012-02-09  Kenichi Ishibashi  <bashi@chromium.org>
+
+        Add WebSocket extension support
+        https://bugs.webkit.org/show_bug.cgi?id=78079
+
+        Reviewed by Kent Tamura.
+
+        * http/tests/websocket/tests/hybi/handshake-fail-by-extensions-header-expected.txt: Rebaselined.
+
 2012-02-09  Xianzhu Wang  <wangxianzhu@chromium.org>
 
         Avoid compositing invisible fixed positioned elements
index b14697c..adf06b6 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: Error during WebSocket handshake: Sec-WebSocket-Extensions header is invalid
+CONSOLE MESSAGE: Received unexpected Sec-WebSocket-Extensions header
 Test whether WebSocket handshake fails if the server sends Sec-WebSocket-Extensions header.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
index 43a7f21..8c315d6 100644 (file)
@@ -2039,6 +2039,7 @@ IF (ENABLE_WEB_SOCKETS)
         websockets/ThreadableWebSocketChannelClientWrapper.cpp
         websockets/WebSocket.cpp
         websockets/WebSocketChannel.cpp
+        websockets/WebSocketExtensionDispatcher.cpp
         websockets/WebSocketHandshake.cpp
         websockets/WebSocketHandshakeRequest.cpp
         websockets/WebSocketHandshakeResponse.cpp
index f6e0473..c276937 100644 (file)
@@ -1,3 +1,62 @@
+2012-02-09  Kenichi Ishibashi  <bashi@chromium.org>
+
+        Add WebSocket extension support
+        https://bugs.webkit.org/show_bug.cgi?id=78079
+
+        This patch introduces WebSocketExtensionDispatcher class, which creates client's
+        Sec-WebSocket-Extensions header field and parses the server response.
+        This patch doesn't add any actual extension, so no changes in behavior.
+
+        Reviewed by Kent Tamura.
+
+        No new tests except for chromium port.
+
+        * CMakeLists.txt: Added WebSocketExtensionDispatcher.(cpp|h) and WebSocketExtensionProcessor.h.
+        * GNUmakefile.list.am: Ditto.
+        * Target.pri: Ditto.
+        * WebCore.gypi: Ditto.
+        * WebCore.vcproj/WebCore.vcproj: Ditto.
+        * WebCore.xcodeproj/project.pbxproj: Ditto.
+        * websockets/WebSocketExtensionDispatcher.cpp: Added.
+        (WebCore):
+        (ExtensionParser):
+        (WebCore::ExtensionParser::ExtensionParser):
+        (WebCore::ExtensionParser::currentToken):
+        (WebCore::ExtensionParser::finished):
+        (WebCore::ExtensionParser::parsedSuccessfully):
+        (WebCore::isTokenCharacter):
+        (WebCore::isSeparator):
+        (WebCore::ExtensionParser::skipSpaces):
+        (WebCore::ExtensionParser::consumeToken):
+        (WebCore::ExtensionParser::consumeQuotedString):
+        (WebCore::ExtensionParser::consumeQuotedStringOrToken):
+        (WebCore::ExtensionParser::consumeCharacter):
+        (WebCore::WebSocketExtensionDispatcher::reset):
+        (WebCore::WebSocketExtensionDispatcher::addProcessor):
+        (WebCore::WebSocketExtensionDispatcher::createHeaderValue):
+        (WebCore::WebSocketExtensionDispatcher::processHeaderValue):
+        (WebCore::WebSocketExtensionDispatcher::failureReason):
+        * websockets/WebSocketExtensionDispatcher.h: Added.
+        (WebCore):
+        (WebSocketExtensionDispatcher):
+        (WebCore::WebSocketExtensionDispatcher::WebSocketExtensionDispatcher):
+        * websockets/WebSocketExtensionProcessor.h: Added.
+        (WebCore):
+        (WebSocketExtensionProcessor):
+        (WebCore::WebSocketExtensionProcessor::~WebSocketExtensionProcessor):
+        (WebCore::WebSocketExtensionProcessor::extensionToken):
+        (WebCore::WebSocketExtensionProcessor::failureReason):
+        (WebCore::WebSocketExtensionProcessor::WebSocketExtensionProcessor):
+        * websockets/WebSocketHandshake.cpp:
+        (WebCore::WebSocketHandshake::clientHandshakeMessage): Adds extension header value if exists.
+        (WebCore::WebSocketHandshake::clientHandshakeRequest): Ditto.
+        (WebCore::WebSocketHandshake::reset): Resets WebSocketExtensionDispatcher object.
+        (WebCore::WebSocketHandshake::serverHandshakeResponse): Removed.
+        (WebCore::WebSocketHandshake::addExtensionProcessor): Added.
+        (WebCore::WebSocketHandshake::readHTTPHeaders): Parses and checks every time Sec-WebSocket-Extensions header appears.
+        (WebCore::WebSocketHandshake::checkResponseHeaders): Removed the check of Sec-WebSocket-Extensions.
+        * websockets/WebSocketHandshake.h: Removed serverHandshakeResponse().
+
 2012-02-09  Xianzhu Wang  <wangxianzhu@chromium.org>
 
         Avoid compositing invisible fixed positioned elements
index 58d9e9a..8448b3a 100644 (file)
@@ -4307,6 +4307,9 @@ webcore_sources += \
        Source/WebCore/websockets/WebSocketChannel.h \
        Source/WebCore/websockets/WebSocket.cpp \
        Source/WebCore/websockets/WebSocket.h \
+       Source/WebCore/websockets/WebSocketExtensionDispatcher.cpp \
+       Source/WebCore/websockets/WebSocketExtensionDispatcher.h \
+       Source/WebCore/websockets/WebSocketExtensionProcessor.h \
        Source/WebCore/websockets/WebSocketHandshake.cpp \
        Source/WebCore/websockets/WebSocketHandshake.h \
        Source/WebCore/websockets/WebSocketHandshakeRequest.cpp \
index a72a18d..143f78c 100644 (file)
@@ -3658,6 +3658,8 @@ contains(DEFINES, ENABLE_WEB_SOCKETS=1) {
         websockets/WebSocket.h \
         websockets/WebSocketChannel.h \
         websockets/WebSocketChannelClient.h \
+        websockets/WebSocketExtensionDispatcher.h \
+        websockets/WebSocketExtensionProcessor.h \
         websockets/WebSocketHandshake.h \
         websockets/WebSocketHandshakeRequest.h \
         websockets/WebSocketHandshakeResponse.h \
@@ -3666,6 +3668,7 @@ contains(DEFINES, ENABLE_WEB_SOCKETS=1) {
     SOURCES += \
         websockets/WebSocket.cpp \
         websockets/WebSocketChannel.cpp \
+        websockets/WebSocketExtensionDispatcher.cpp \
         websockets/WebSocketHandshake.cpp \
         websockets/WebSocketHandshakeRequest.cpp \
         websockets/WebSocketHandshakeResponse.cpp \
index 90cd8e1..78a85c0 100644 (file)
             'websockets/WebSocketChannel.cpp',
             'websockets/WebSocketChannel.h',
             'websockets/WebSocketChannelClient.h',
+            'websockets/WebSocketExtensionDispatcher.cpp',
+            'websockets/WebSocketExtensionDispatcher.h',
+            'websockets/WebSocketExtensionProcessor.h',
             'websockets/WebSocketHandshake.cpp',
             'websockets/WebSocketHandshake.h',
             'websockets/WebSocketHandshakeRequest.cpp',
index 57a1175..d3ec77a 100755 (executable)
                                >
                        </File>
                        <File
+                               RelativePath="..\websockets\WebSocketExtensionDispatcher.cpp"
+                               >
+                       </File>
+                       <File
+                               RelativePath="..\websockets\WebSocketExtensionDispatcher.h"
+                               >
+                       </File>
+                       <File
+                               RelativePath="..\websockets\WebSocketExtensionProcessor.h"
+                               >
+                       </File>
+                       <File
                                RelativePath="..\websockets\WebSocketHandshake.cpp"
                                >
                        </File>
index 0204222..bcc1e4a 100644 (file)
                4A6E9FC713C17D570046A7F8 /* FontFeatureSettings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A6E9FC513C17D570046A7F8 /* FontFeatureSettings.cpp */; };
                4A6E9FC813C17D570046A7F8 /* FontFeatureSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A6E9FC613C17D570046A7F8 /* FontFeatureSettings.h */; settings = {ATTRIBUTES = (Private, ); }; };
                4A8C96EB0BE69032004EEFF0 /* FrameSelectionMac.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4A8C96EA0BE69032004EEFF0 /* FrameSelectionMac.mm */; };
+               4A957F0614E2412A0049DBFB /* WebSocketExtensionDispatcher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A957F0314E241100049DBFB /* WebSocketExtensionDispatcher.cpp */; };
+               4A957F0714E241300049DBFB /* WebSocketExtensionDispatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A957F0414E241100049DBFB /* WebSocketExtensionDispatcher.h */; };
                4ABDFF0B14DBE385004D117D /* HTMLShadowElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4ABDFF0714DBE312004D117D /* HTMLShadowElement.cpp */; };
                4ABDFF0C14DBE385004D117D /* HTMLShadowElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABDFF0814DBE312004D117D /* HTMLShadowElement.h */; };
                4ACBC0BE12713CBD0094F9B2 /* ClassList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4ACBC0BC12713CBD0094F9B2 /* ClassList.cpp */; };
                4AD01009127E642A0015035F /* HTMLOutputElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AD01006127E642A0015035F /* HTMLOutputElement.h */; };
                4AD0173C127E82860015035F /* JSHTMLOutputElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4AD0173A127E82860015035F /* JSHTMLOutputElement.cpp */; };
                4AD0173D127E82860015035F /* JSHTMLOutputElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AD0173B127E82860015035F /* JSHTMLOutputElement.h */; };
+               4ADE25FA14E3BB4C004C2213 /* WebSocketExtensionProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ADE25F914E3BB4C004C2213 /* WebSocketExtensionProcessor.h */; };
                4AF1AD3E13FD23A400AA9590 /* EventDispatchMediator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4AF1AD3C13FD23A400AA9590 /* EventDispatchMediator.cpp */; };
                4AF1AD3F13FD23A400AA9590 /* EventDispatchMediator.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AF1AD3D13FD23A400AA9590 /* EventDispatchMediator.h */; settings = {ATTRIBUTES = (Private, ); }; };
                4B2708C70AF19EE40065127F /* Pasteboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B2708C50AF19EE40065127F /* Pasteboard.h */; };
                4A6E9FC513C17D570046A7F8 /* FontFeatureSettings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FontFeatureSettings.cpp; sourceTree = "<group>"; };
                4A6E9FC613C17D570046A7F8 /* FontFeatureSettings.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; fileEncoding = 4; path = FontFeatureSettings.h; sourceTree = "<group>"; };
                4A8C96EA0BE69032004EEFF0 /* FrameSelectionMac.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = FrameSelectionMac.mm; path = mac/FrameSelectionMac.mm; sourceTree = "<group>"; };
+               4A957F0314E241100049DBFB /* WebSocketExtensionDispatcher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebSocketExtensionDispatcher.cpp; sourceTree = "<group>"; };
+               4A957F0414E241100049DBFB /* WebSocketExtensionDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebSocketExtensionDispatcher.h; sourceTree = "<group>"; };
                4ABDFF0714DBE312004D117D /* HTMLShadowElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTMLShadowElement.cpp; sourceTree = "<group>"; };
                4ABDFF0814DBE312004D117D /* HTMLShadowElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLShadowElement.h; sourceTree = "<group>"; };
                4ABDFF0914DBE312004D117D /* HTMLShadowElement.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = HTMLShadowElement.idl; sourceTree = "<group>"; };
                4AD01007127E642A0015035F /* HTMLOutputElement.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = HTMLOutputElement.idl; sourceTree = "<group>"; };
                4AD0173A127E82860015035F /* JSHTMLOutputElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSHTMLOutputElement.cpp; sourceTree = "<group>"; };
                4AD0173B127E82860015035F /* JSHTMLOutputElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSHTMLOutputElement.h; sourceTree = "<group>"; };
+               4ADE25F914E3BB4C004C2213 /* WebSocketExtensionProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebSocketExtensionProcessor.h; sourceTree = "<group>"; };
                4AF1AD3C13FD23A400AA9590 /* EventDispatchMediator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EventDispatchMediator.cpp; sourceTree = "<group>"; };
                4AF1AD3D13FD23A400AA9590 /* EventDispatchMediator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EventDispatchMediator.h; sourceTree = "<group>"; };
                4B2708C50AF19EE40065127F /* Pasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pasteboard.h; sourceTree = "<group>"; };
                518A34BD1026C831001B6896 /* websockets */ = {
                        isa = PBXGroup;
                        children = (
+                               4ADE25F914E3BB4C004C2213 /* WebSocketExtensionProcessor.h */,
                                51FB54F4113E364200821176 /* CloseEvent.h */,
                                51FB54F6113E365900821176 /* CloseEvent.idl */,
                                5112247110CFB8C6008099D7 /* ThreadableWebSocketChannel.cpp */,
                                510D4A47103177A20049EA54 /* WebSocketChannel.cpp */,
                                510D4A48103177A20049EA54 /* WebSocketChannel.h */,
                                510D4A49103177A20049EA54 /* WebSocketChannelClient.h */,
+                               4A957F0314E241100049DBFB /* WebSocketExtensionDispatcher.cpp */,
+                               4A957F0414E241100049DBFB /* WebSocketExtensionDispatcher.h */,
                                51ABAE421043AB4A008C5260 /* WebSocketHandshake.cpp */,
                                51ABAE431043AB4A008C5260 /* WebSocketHandshake.h */,
                                7637C540112E7B74003D6CDC /* WebSocketHandshakeRequest.cpp */,
                                10FB084B14E15C7E00A3DB98 /* PublicURLManager.h in Headers */,
                                7A54858014E02D51006AE05A /* InspectorHistory.h in Headers */,
                                7A54881714E432A1006AE05A /* DOMPatchSupport.h in Headers */,
+                               4A957F0714E241300049DBFB /* WebSocketExtensionDispatcher.h in Headers */,
+                               4ADE25FA14E3BB4C004C2213 /* WebSocketExtensionProcessor.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                7A54857F14E02D51006AE05A /* InspectorHistory.cpp in Sources */,
                                CDAA8D0A14D71B2E0061EA60 /* PlatformClockCM.mm in Sources */,
                                7A54881814E432A1006AE05A /* DOMPatchSupport.cpp in Sources */,
+                               4A957F0614E2412A0049DBFB /* WebSocketExtensionDispatcher.cpp in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
diff --git a/Source/WebCore/websockets/WebSocketExtensionDispatcher.cpp b/Source/WebCore/websockets/WebSocketExtensionDispatcher.cpp
new file mode 100644 (file)
index 0000000..d85092a
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2012 Google Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if ENABLE(WEB_SOCKETS)
+
+#include "WebSocketExtensionDispatcher.h"
+
+#include <wtf/ASCIICType.h>
+#include <wtf/text/CString.h>
+#include <wtf/text/StringBuilder.h>
+#include <wtf/text/StringHash.h>
+
+namespace WebCore {
+
+class ExtensionParser {
+public:
+    ExtensionParser(const char* start, const char* end)
+        : m_current(start)
+        , m_end(end)
+    {
+    }
+    bool finished();
+    bool parsedSuccessfully();
+    const String& currentToken() { return m_currentToken; }
+
+    // The following member functions basically follow the grammer defined
+    // in Section 2.2 of RFC 2616.
+    bool consumeToken();
+    bool consumeQuotedString();
+    bool consumeQuotedStringOrToken();
+    bool consumeCharacter(char);
+
+private:
+    void skipSpaces();
+
+    const char* m_current;
+    const char* m_end;
+    String m_currentToken;
+};
+
+bool ExtensionParser::finished()
+{
+    return m_current >= m_end;
+}
+
+bool ExtensionParser::parsedSuccessfully()
+{
+    return m_current == m_end;
+}
+
+static bool isSeparator(char character)
+{
+    static const char* separatorCharacters = "()<>@,;:\\\"/[]?={} \t";
+    const char* p = strchr(separatorCharacters, character);
+    return p && *p;
+}
+
+void ExtensionParser::skipSpaces()
+{
+    while (m_current < m_end && (*m_current == ' ' || *m_current == '\t'))
+        ++m_current;
+}
+
+bool ExtensionParser::consumeToken()
+{
+    skipSpaces();
+    const char* start = m_current;
+    while (m_current < m_end && isASCIIPrintable(*m_current) && !isSeparator(*m_current))
+        ++m_current;
+    if (start < m_current) {
+        m_currentToken = String(start, m_current - start);
+        return true;
+    }
+    return false;
+}
+
+bool ExtensionParser::consumeQuotedString()
+{
+    skipSpaces();
+    if (m_current >= m_end || *m_current != '"')
+        return false;
+
+    Vector<char> buffer;
+    ++m_current;
+    while (m_current < m_end && *m_current != '"') {
+        if (*m_current == '\\' && ++m_current >= m_end)
+            return false;
+        buffer.append(*m_current);
+        ++m_current;
+    }
+    if (m_current >= m_end || *m_current != '"')
+        return false;
+    m_currentToken = String::fromUTF8(buffer.data(), buffer.size());
+    ++m_current;
+    return true;
+}
+
+bool ExtensionParser::consumeQuotedStringOrToken()
+{
+    // This is ok because consumeQuotedString() doesn't update m_current or
+    // makes it same as m_end on failure.
+    return consumeQuotedString() || consumeToken();
+}
+
+bool ExtensionParser::consumeCharacter(char character)
+{
+    skipSpaces();
+    if (m_current < m_end && *m_current == character) {
+        ++m_current;
+        return true;
+    }
+    return false;
+}
+
+void WebSocketExtensionDispatcher::reset()
+{
+    m_processors.clear();
+}
+
+void WebSocketExtensionDispatcher::addProcessor(PassOwnPtr<WebSocketExtensionProcessor> processor)
+{
+    for (size_t i = 0; i < m_processors.size(); ++i) {
+        if (m_processors[i]->extensionToken() == processor->extensionToken())
+            return;
+    }
+    ASSERT(processor->handshakeString().length());
+    ASSERT(!processor->handshakeString().contains('\n'));
+    ASSERT(!processor->handshakeString().contains(static_cast<UChar>('\0')));
+    m_processors.append(processor);
+}
+
+const String WebSocketExtensionDispatcher::createHeaderValue() const
+{
+    size_t numProcessors = m_processors.size();
+    if (!numProcessors)
+        return String();
+
+    StringBuilder builder;
+    builder.append(m_processors[0]->handshakeString());
+    for (size_t i = 1; i < numProcessors; ++i) {
+        builder.append(", ");
+        builder.append(m_processors[i]->handshakeString());
+    }
+    return builder.toString();
+}
+
+bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
+{
+    if (!headerValue.length())
+        return true;
+
+    // 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";
+        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";
+            return false;
+        }
+        String extensionToken = parser.currentToken();
+
+        // Parse extension-parameters if exists.
+        HashMap<String, String> extensionParameters;
+        while (parser.consumeCharacter(';')) {
+            if (!parser.consumeToken()) {
+                m_failureReason = "Sec-WebSocket-Extensions header is invalid";
+                return false;
+            }
+
+            String parameterToken = parser.currentToken();
+            if (parser.consumeCharacter('=')) {
+                if (parser.consumeQuotedStringOrToken())
+                    extensionParameters.add(parameterToken, parser.currentToken());
+                else {
+                    m_failureReason = "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";
+            return false;
+        }
+
+        size_t index;
+        for (index = 0; index < m_processors.size(); ++index) {
+            WebSocketExtensionProcessor* processor = m_processors[index].get();
+            if (extensionToken == processor->extensionToken()) {
+                if (processor->processResponse(extensionParameters))
+                    break;
+                m_failureReason = processor->failureReason();
+                return false;
+            }
+        }
+        // There is no extension which can process the response.
+        if (index == m_processors.size()) {
+            m_failureReason = "Received unexpected extension: " + extensionToken;
+            return false;
+        }
+    }
+    return parser.parsedSuccessfully();
+}
+
+String WebSocketExtensionDispatcher::failureReason() const
+{
+    return m_failureReason;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_SOCKETS)
diff --git a/Source/WebCore/websockets/WebSocketExtensionDispatcher.h b/Source/WebCore/websockets/WebSocketExtensionDispatcher.h
new file mode 100644 (file)
index 0000000..f882bb9
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 Google Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WebSocketExtensionDispatcher_h
+#define WebSocketExtensionDispatcher_h
+
+#if ENABLE(WEB_SOCKETS)
+
+#include "WebSocketExtensionProcessor.h"
+#include <wtf/OwnPtr.h>
+#include <wtf/PassOwnPtr.h>
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class WebSocketExtensionDispatcher {
+public:
+    WebSocketExtensionDispatcher() { }
+
+    void reset();
+    void addProcessor(PassOwnPtr<WebSocketExtensionProcessor>);
+    const String createHeaderValue() const;
+
+    bool processHeaderValue(const String&);
+    String failureReason() const;
+
+private:
+    Vector<OwnPtr<WebSocketExtensionProcessor> > m_processors;
+    String m_failureReason;
+};
+
+}
+
+#endif // ENABLE(WEB_SOCKETS)
+
+#endif // WebSocketExtensionDispatcher_h
diff --git a/Source/WebCore/websockets/WebSocketExtensionProcessor.h b/Source/WebCore/websockets/WebSocketExtensionProcessor.h
new file mode 100644 (file)
index 0000000..ee3d53d
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 Google Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WebSocketExtensionProcessor_h
+#define WebSocketExtensionProcessor_h
+
+#if ENABLE(WEB_SOCKETS)
+
+#include <wtf/HashMap.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class WebSocketExtensionProcessor {
+public:
+    virtual ~WebSocketExtensionProcessor() { }
+
+    String extensionToken() const { return m_extensionToken; }
+
+    // The return value of this method will be a part of the value of
+    // Sec-WebSocket-Extensions.
+    virtual String handshakeString() = 0;
+
+    // This should validate the server's response parameters which are passed
+    // as HashMap<key, value>. This may also do something for the extension.
+    // Note that this method may be called more than once when the server
+    // response contains duplicate extension token that matches extensionToken().
+    virtual bool processResponse(const HashMap<String, String>&) = 0;
+
+    // If procecssResponse() returns false, this should provide the reason.
+    virtual String failureReason() { return "Extension " + m_extensionToken + " failed"; }
+
+protected:
+    WebSocketExtensionProcessor(const String& extensionToken)
+        : m_extensionToken(extensionToken)
+    {
+    }
+
+private:
+    String m_extensionToken;
+};
+
+}
+
+#endif // ENABLE(WEB_SOCKETS)
+
+#endif // WebSocketExtensionProcessor_h
index 8909efa..b261557 100644 (file)
@@ -287,6 +287,9 @@ CString WebSocketHandshake::clientHandshakeMessage() const
     } else {
         fields.append("Sec-WebSocket-Key: " + m_secWebSocketKey);
         fields.append("Sec-WebSocket-Version: 13");
+        const String extensionValue = m_extensionDispatcher.createHeaderValue();
+        if (extensionValue.length())
+            fields.append("Sec-WebSocket-Extensions: " + extensionValue);
     }
 
     // Fields in the handshake are sent by the client in a random order; the
@@ -344,6 +347,9 @@ WebSocketHandshakeRequest WebSocketHandshake::clientHandshakeRequest() const
     } else {
         request.addHeaderField("Sec-WebSocket-Key", m_secWebSocketKey);
         request.addHeaderField("Sec-WebSocket-Version", "13");
+        const String extensionValue = m_extensionDispatcher.createHeaderValue();
+        if (extensionValue.length())
+            request.addHeaderField("Sec-WebSocket-Extensions", extensionValue);
     }
 
     return request;
@@ -352,6 +358,7 @@ WebSocketHandshakeRequest WebSocketHandshake::clientHandshakeRequest() const
 void WebSocketHandshake::reset()
 {
     m_mode = Incomplete;
+    m_extensionDispatcher.reset();
 }
 
 void WebSocketHandshake::clearScriptExecutionContext()
@@ -469,14 +476,14 @@ String WebSocketHandshake::serverWebSocketAccept() const
     return m_response.headerFields().get("sec-websocket-accept");
 }
 
-String WebSocketHandshake::serverWebSocketExtensions() const
+const WebSocketHandshakeResponse& WebSocketHandshake::serverHandshakeResponse() const
 {
-    return m_response.headerFields().get("sec-websocket-extensions");
+    return m_response;
 }
 
-const WebSocketHandshakeResponse& WebSocketHandshake::serverHandshakeResponse() const
+void WebSocketHandshake::addExtensionProcessor(PassOwnPtr<WebSocketExtensionProcessor> processor)
 {
-    return m_response;
+    m_extensionDispatcher.addProcessor(processor);
 }
 
 KURL WebSocketHandshake::httpURLForAuthenticationAndCookies() const
@@ -625,7 +632,15 @@ const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* e
             return 0;
         }
         LOG(Network, "name=%s value=%s", nameStr.string().utf8().data(), valueStr.utf8().data());
-        m_response.addHeaderField(nameStr, valueStr);
+        // Sec-WebSocket-Extensions may be split. We parse and check the
+        // header value every time the header appears.
+        if (equalIgnoringCase("sec-websocket-extensions", nameStr)) {
+            if (!m_extensionDispatcher.processHeaderValue(valueStr)) {
+                m_failureReason = m_extensionDispatcher.failureReason();
+                return 0;
+            }
+        } else
+            m_response.addHeaderField(nameStr, valueStr);
     }
     ASSERT_NOT_REACHED();
     return 0;
@@ -639,7 +654,6 @@ bool WebSocketHandshake::checkResponseHeaders()
     const String& serverUpgrade = this->serverUpgrade();
     const String& serverConnection = this->serverConnection();
     const String& serverWebSocketAccept = this->serverWebSocketAccept();
-    const String& serverWebSocketExtensions = this->serverWebSocketExtensions();
 
     if (serverUpgrade.isNull()) {
         m_failureReason = "Error during WebSocket handshake: 'Upgrade' header is missing";
@@ -692,13 +706,6 @@ bool WebSocketHandshake::checkResponseHeaders()
             m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Accept mismatch";
             return false;
         }
-        if (!serverWebSocketExtensions.isNull()) {
-            // WebSocket protocol extensions are not supported yet.
-            // We do not send Sec-WebSocket-Extensions header in our request, thus
-            // servers should not return this header, either.
-            m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Extensions header is invalid";
-            return false;
-        }
     }
     return true;
 }
index 12c1840..9777fb9 100644 (file)
 
 #include "KURL.h"
 #include "PlatformString.h"
+#include "WebSocketExtensionDispatcher.h"
+#include "WebSocketExtensionProcessor.h"
 #include "WebSocketHandshakeRequest.h"
 #include "WebSocketHandshakeResponse.h"
+#include <wtf/PassOwnPtr.h>
 
 namespace WebCore {
 
@@ -81,10 +84,11 @@ public:
     String serverUpgrade() const;
     String serverConnection() const;
     String serverWebSocketAccept() const; // Only for hybi-10 handshake.
-    String serverWebSocketExtensions() const; // Only for hybi-10 handshake.
 
     const WebSocketHandshakeResponse& serverHandshakeResponse() const;
 
+    void addExtensionProcessor(PassOwnPtr<WebSocketExtensionProcessor>);
+
 private:
     KURL httpURLForAuthenticationAndCookies() const;
 
@@ -116,6 +120,8 @@ private:
     // For hybi-10 handshake.
     String m_secWebSocketKey;
     String m_expectedAccept;
+
+    WebSocketExtensionDispatcher m_extensionDispatcher;
 };
 
 } // namespace WebCore
index 7ad63f3..47935a0 100644 (file)
@@ -1,3 +1,27 @@
+2012-02-09  Kenichi Ishibashi  <bashi@chromium.org>
+
+        Add WebSocket extension support
+        https://bugs.webkit.org/show_bug.cgi?id=78079
+
+        Add some tests which check WebSocketExtensions::processHeaderValue()
+        parses the given response correctly.
+
+        Reviewed by Kent Tamura.
+
+        * WebKit.gypi:
+        * tests/WebSocketExtensionDispatcherTest.cpp: Added.
+        (WebCore):
+        (MockWebSocketExtensionProcessor):
+        (WebCore::MockWebSocketExtensionProcessor::MockWebSocketExtensionProcessor):
+        (WebSocketExtensionDispatcherTest):
+        (WebCore::WebSocketExtensionDispatcherTest::WebSocketExtensionDispatcherTest):
+        (WebCore::WebSocketExtensionDispatcherTest::SetUp):
+        (WebCore::WebSocketExtensionDispatcherTest::TearDown):
+        (WebCore::WebSocketExtensionDispatcherTest::addMockProcessor):
+        (WebCore::WebSocketExtensionDispatcherTest::appendResult):
+        (WebCore::MockWebSocketExtensionProcessor::processResponse):
+        (WebCore::TEST_F):
+
 2012-02-09  W. James MacLean  <wjmaclean@chromium.org>
 
         [chromium] Add support for starting page/scale animations on CC impl thread from WebViewImpl
index c8502f5..35f2b19 100644 (file)
             'tests/WebLayerTest.cpp',
             'tests/WebPageNewSerializerTest.cpp',
             'tests/WebPageSerializerTest.cpp',
+            'tests/WebSocketExtensionDispatcherTest.cpp',
             'tests/WebURLRequestTest.cpp',
             'tests/WebViewTest.cpp',
         ],
diff --git a/Source/WebKit/chromium/tests/WebSocketExtensionDispatcherTest.cpp b/Source/WebKit/chromium/tests/WebSocketExtensionDispatcherTest.cpp
new file mode 100644 (file)
index 0000000..fa0b7d7
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include "WebSocketExtensionDispatcher.h"
+
+#include "WebSocketExtensionProcessor.h"
+
+#include <gtest/gtest.h>
+#include <wtf/text/StringHash.h>
+
+using namespace WebCore;
+
+namespace {
+
+class WebSocketExtensionDispatcherTest;
+
+class MockWebSocketExtensionProcessor : public WebSocketExtensionProcessor {
+public:
+    MockWebSocketExtensionProcessor(const String& name, WebSocketExtensionDispatcherTest* test)
+        : WebSocketExtensionProcessor(name)
+        , m_test(test)
+    {
+    }
+    virtual String handshakeString() OVERRIDE { return extensionToken(); }
+    virtual bool processResponse(const HashMap<String, String>&) OVERRIDE;
+
+private:
+    WebSocketExtensionDispatcherTest* m_test;
+};
+
+class WebSocketExtensionDispatcherTest : public testing::Test {
+public:
+    WebSocketExtensionDispatcherTest() { }
+
+    void SetUp() { }
+
+    void TearDown() { }
+
+    void addMockProcessor(const String& extensionToken)
+    {
+        m_extensions.addProcessor(adoptPtr(new MockWebSocketExtensionProcessor(extensionToken, this)));
+
+    }
+
+    void appendResult(const String& extensionToken, const HashMap<String, String>& parameters)
+    {
+        m_parsedExtensionTokens.append(extensionToken);
+        m_parsedParameters.append(parameters);
+    }
+
+protected:
+    WebSocketExtensionDispatcher m_extensions;
+    Vector<String> m_parsedExtensionTokens;
+    Vector<HashMap<String, String> > m_parsedParameters;
+};
+
+bool MockWebSocketExtensionProcessor::processResponse(const HashMap<String, String>& parameters)
+{
+    m_test->appendResult(extensionToken(), parameters);
+    return true;
+}
+
+TEST_F(WebSocketExtensionDispatcherTest, TestSingle)
+{
+    addMockProcessor("deflate-frame");
+    EXPECT_TRUE(m_extensions.processHeaderValue("deflate-frame"));
+    EXPECT_EQ(1UL, m_parsedExtensionTokens.size());
+    EXPECT_EQ("deflate-frame", m_parsedExtensionTokens[0]);
+    EXPECT_EQ(0, m_parsedParameters[0].size());
+}
+
+TEST_F(WebSocketExtensionDispatcherTest, TestParameters)
+{
+    addMockProcessor("mux");
+    EXPECT_TRUE(m_extensions.processHeaderValue("mux; max-channels=4; flow-control  "));
+    EXPECT_EQ(1UL, m_parsedExtensionTokens.size());
+    EXPECT_EQ("mux", m_parsedExtensionTokens[0]);
+    EXPECT_EQ(2, m_parsedParameters[0].size());
+    HashMap<String, String>::iterator parameter = m_parsedParameters[0].find("max-channels");
+    EXPECT_TRUE(parameter != m_parsedParameters[0].end());
+    EXPECT_EQ("4", parameter->second);
+    parameter = m_parsedParameters[0].find("flow-control");
+    EXPECT_TRUE(parameter != m_parsedParameters[0].end());
+    EXPECT_TRUE(parameter->second.isNull());
+}
+
+TEST_F(WebSocketExtensionDispatcherTest, TestMultiple)
+{
+    struct {
+        String token;
+        HashMap<String, String> parameters;
+    } expected[2];
+    expected[0].token = "mux";
+    expected[0].parameters.add("max-channels", "4");
+    expected[0].parameters.add("flow-control", String());
+    expected[1].token = "deflate-frame";
+
+    addMockProcessor("mux");
+    addMockProcessor("deflate-frame");
+    EXPECT_TRUE(m_extensions.processHeaderValue("mux ;  max-channels =4;flow-control, deflate-frame  "));
+    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;
+        const HashMap<String, String>& parsedParameters = m_parsedParameters[i];
+        EXPECT_EQ(expected[i].parameters.size(), m_parsedParameters[i].size());
+        for (HashMap<String, String>::const_iterator iterator = expectedParameters.begin(); iterator != expectedParameters.end(); ++iterator) {
+            HashMap<String, String>::const_iterator parsed = parsedParameters.find(iterator->first);
+            EXPECT_TRUE(parsed != parsedParameters.end());
+            if (iterator->second.isNull())
+                EXPECT_TRUE(parsed->second.isNull());
+            else
+                EXPECT_EQ(iterator->second, parsed->second);
+        }
+    }
+}
+
+TEST_F(WebSocketExtensionDispatcherTest, TestQuotedString)
+{
+    addMockProcessor("x-foo");
+    EXPECT_TRUE(m_extensions.processHeaderValue("x-foo; param1=\"quoted string\"; param2=\"\\\"quoted\\\" string\\\\\""));
+    EXPECT_EQ(2, m_parsedParameters[0].size());
+    EXPECT_EQ("quoted string", m_parsedParameters[0].get("param1"));
+    EXPECT_EQ("\"quoted\" string\\", m_parsedParameters[0].get("param2"));
+}
+
+TEST_F(WebSocketExtensionDispatcherTest, TestInvalid)
+{
+    const char* inputs[] = {
+        "\"x-foo\"",
+        "x-baz",
+        "x-foo\\",
+        "x-(foo)",
+        "x-foo; ",
+        "x-foo; bar=",
+        "x-foo; bar=x y",
+        "x-foo; bar=\"mismatch quote",
+        "x-foo; bar=\"\\\"",
+        "x-foo; \"bar\"=baz",
+        "x-foo x-bar",
+        "x-foo, x-baz"
+        "x-foo, ",
+    };
+    for (size_t i = 0; i < sizeof(inputs) / sizeof(inputs[0]); ++i) {
+        m_extensions.reset();
+        addMockProcessor("x-foo");
+        addMockProcessor("x-bar");
+        EXPECT_FALSE(m_extensions.processHeaderValue(inputs[i]));
+    }
+}
+
+}