Adding WebSocket per-frame DEFLATE extension
authorbashi@chromium.org <bashi@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Feb 2012 06:29:33 +0000 (06:29 +0000)
committerbashi@chromium.org <bashi@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Feb 2012 06:29:33 +0000 (06:29 +0000)
https://bugs.webkit.org/show_bug.cgi?id=77522

Source/JavaScriptCore:

Added USE(ZLIB) flag.

Reviewed by Kent Tamura.

* wtf/Platform.h:

Source/WebCore:

Add WebSocketDeflateFramer class which handles deflate-frame extension.
This class encapsulates WebSocketDeflater and WebSocketInflater classes,
which depend on zlib, so that WebSocketChannel is not necessary to aware
zlib dependency.

This is the second patch to land. The previous patch broke Chromium Win
release build. r108600 should fix the build failure. I also added
zlib entry to |export_dependent_settings| of |webcore_prerequisites|
target.

Reviewed by Kent Tamura.

Tests: http/tests/websocket/tests/hybi/compressed-control-frame.html
       http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff.html
       http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter.html
       http/tests/websocket/tests/hybi/deflate-frame-parameter.html

* CMakeLists.txt: Added WebSocketDeflateFramer.(cpp|h)
* GNUmakefile.list.am: Ditto.
* Target.pri: Ditto.
* WebCore.gypi: Ditto.
* WebCore.gyp/WebCore.gyp: Added zlib dependency.
* WebCore.vcproj/WebCore.vcproj: Added WebSocketDeflateFramer.(cpp|h)
* WebCore.xcodeproj/project.pbxproj: Ditto.
* websockets/WebSocket.cpp:
(WebCore::WebSocket::didConnect): Set m_extensions.
* websockets/WebSocketChannel.cpp:
(WebCore::WebSocketChannel::connect): Add deflate-frame extension processor to WebSocketHanshake if deflate can use.
(WebCore::WebSocketChannel::fail): Call m_deflateFramer.didFail().
(WebCore::WebSocketChannel::processFrame): Decompress frames if needed.
(WebCore::WebSocketChannel::sendFrame): Compress frames if possible.
* websockets/WebSocketChannel.h:
* websockets/WebSocketDeflateFramer.cpp: Added.
(WebCore):
(WebSocketExtensionDeflateFrame):
(WebCore::WebSocketExtensionDeflateFrame::create):
(WebCore::WebSocketExtensionDeflateFrame::~WebSocketExtensionDeflateFrame):
(WebCore::WebSocketExtensionDeflateFrame::WebSocketExtensionDeflateFrame):
(WebCore::WebSocketExtensionDeflateFrame::handshakeString):
(WebCore::WebSocketExtensionDeflateFrame::processResponse):
(WebCore::DeflateResultHolder::DeflateResultHolder):
(WebCore::DeflateResultHolder::~DeflateResultHolder):
(WebCore::DeflateResultHolder::fail):
(WebCore::InflateResultHolder::InflateResultHolder):
(WebCore::InflateResultHolder::~InflateResultHolder):
(WebCore::InflateResultHolder::fail):
(WebCore::WebSocketDeflateFramer::WebSocketDeflateFramer):
(WebCore::WebSocketDeflateFramer::createExtensionProcessor):
(WebCore::WebSocketDeflateFramer::canDeflate):
(WebCore::WebSocketDeflateFramer::enableDeflate):
(WebCore::WebSocketDeflateFramer::deflate):
(WebCore::WebSocketDeflateFramer::resetDeflateContext):
(WebCore::WebSocketDeflateFramer::inflate):
(WebCore::WebSocketDeflateFramer::resetInflateContext):
(WebCore::WebSocketDeflateFramer::didFail):
* websockets/WebSocketDeflateFramer.h: Added.
(WebCore):
(DeflateResultHolder):
(WebCore::DeflateResultHolder::succeeded):
(WebCore::DeflateResultHolder::failureReason):
(InflateResultHolder):
(WebCore::InflateResultHolder::succeeded):
(WebCore::InflateResultHolder::failureReason):
(WebSocketDeflateFramer):
(WebCore::WebSocketDeflateFramer::enabled):

LayoutTests:

Added tests for WebSocket deflate-frame extension. Also updated some
tests to follow the change.

Reviewed by Kent Tamura.

* http/tests/websocket/tests/hybi/compressed-control-frame-expected.txt: Added.
* http/tests/websocket/tests/hybi/compressed-control-frame.html: Added.
* http/tests/websocket/tests/hybi/compressed-control-frame_wsh.py: Added.
(web_socket_do_extra_handshake):
(web_socket_transfer_data):
* http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff-expected.txt: Added.
* http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff.html: Added.
* http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter-expected.txt: Added.
* http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter.html: Added.
* http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter_wsh.py: Added.
(web_socket_do_extra_handshake):
(web_socket_transfer_data):
* http/tests/websocket/tests/hybi/deflate-frame-parameter-expected.txt: Added.
* http/tests/websocket/tests/hybi/deflate-frame-parameter.html: Added.
* http/tests/websocket/tests/hybi/deflate-frame_wsh.py: Added.
(_get_deflate_frame_extension_processor):
(web_socket_do_extra_handshake):
(web_socket_transfer_data):
* http/tests/websocket/tests/hybi/handshake-fail-by-extensions-header-expected.txt:
* http/tests/websocket/tests/hybi/send-file-blob_wsh.py:
(_retrieve_frame):
(web_socket_transfer_data):

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

29 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/websocket/tests/hybi/compressed-control-frame-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/compressed-control-frame.html [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/compressed-control-frame_wsh.py [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff.html [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter.html [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter_wsh.py [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-parameter-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-parameter.html [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/deflate-frame_wsh.py [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/hybi/handshake-fail-by-extensions-header-expected.txt
LayoutTests/http/tests/websocket/tests/hybi/send-file-blob_wsh.py
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/wtf/Platform.h
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/GNUmakefile.list.am
Source/WebCore/Target.pri
Source/WebCore/WebCore.gyp/WebCore.gyp
Source/WebCore/WebCore.gypi
Source/WebCore/WebCore.vcproj/WebCore.vcproj
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/websockets/WebSocket.cpp
Source/WebCore/websockets/WebSocketChannel.cpp
Source/WebCore/websockets/WebSocketChannel.h
Source/WebCore/websockets/WebSocketDeflateFramer.cpp [new file with mode: 0644]
Source/WebCore/websockets/WebSocketDeflateFramer.h [new file with mode: 0644]

index 801a1ee..4eac0a5 100644 (file)
@@ -1,3 +1,36 @@
+2012-02-23  Kenichi Ishibashi  <bashi@chromium.org>
+
+        Adding WebSocket per-frame DEFLATE extension
+        https://bugs.webkit.org/show_bug.cgi?id=77522
+
+        Added tests for WebSocket deflate-frame extension. Also updated some
+        tests to follow the change.
+
+        Reviewed by Kent Tamura.
+
+        * http/tests/websocket/tests/hybi/compressed-control-frame-expected.txt: Added.
+        * http/tests/websocket/tests/hybi/compressed-control-frame.html: Added.
+        * http/tests/websocket/tests/hybi/compressed-control-frame_wsh.py: Added.
+        (web_socket_do_extra_handshake):
+        (web_socket_transfer_data):
+        * http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff-expected.txt: Added.
+        * http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff.html: Added.
+        * http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter-expected.txt: Added.
+        * http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter.html: Added.
+        * http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter_wsh.py: Added.
+        (web_socket_do_extra_handshake):
+        (web_socket_transfer_data):
+        * http/tests/websocket/tests/hybi/deflate-frame-parameter-expected.txt: Added.
+        * http/tests/websocket/tests/hybi/deflate-frame-parameter.html: Added.
+        * http/tests/websocket/tests/hybi/deflate-frame_wsh.py: Added.
+        (_get_deflate_frame_extension_processor):
+        (web_socket_do_extra_handshake):
+        (web_socket_transfer_data):
+        * http/tests/websocket/tests/hybi/handshake-fail-by-extensions-header-expected.txt:
+        * http/tests/websocket/tests/hybi/send-file-blob_wsh.py:
+        (_retrieve_frame):
+        (web_socket_transfer_data):
+
 2012-02-23  Erik Arvidsson  <arv@chromium.org>
 
         Rename DOMWindow to Window in the bindings
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/compressed-control-frame-expected.txt b/LayoutTests/http/tests/websocket/tests/hybi/compressed-control-frame-expected.txt
new file mode 100644 (file)
index 0000000..641ec47
--- /dev/null
@@ -0,0 +1,12 @@
+CONSOLE MESSAGE: Received unexpected compressed frame
+Test whether a compressed control frame is rejected
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+onopen() was called.
+onclose() was called.
+PASS closeEvent.wasClean is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/compressed-control-frame.html b/LayoutTests/http/tests/websocket/tests/hybi/compressed-control-frame.html
new file mode 100644 (file)
index 0000000..b2088c7
--- /dev/null
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../../../js-test-resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script type="text/javascript">
+description("Test whether a compressed control frame is rejected");
+
+window.jsTestIsAsync = true;
+if (window.layoutTestController)
+    layoutTestController.overridePreference("WebKitHixie76WebSocketProtocolEnabled", 0);
+
+var ws = new WebSocket("ws://127.0.0.1:8880/websocket/tests/hybi/compressed-control-frame");
+var closeEvent;
+
+ws.onopen = function()
+{
+    debug("onopen() was called.");
+};
+
+ws.onmessage = function(event)
+{
+    var message = event.data;
+    testFailed("onmessage() was called. (message = \"" + message + "\")");
+};
+
+ws.onclose = function(event)
+{
+    debug("onclose() was called.");
+    closeEvent = event;
+    shouldBeFalse("closeEvent.wasClean");
+    finishJSTest();
+};
+
+</script>
+<script src="../../../../js-test-resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/compressed-control-frame_wsh.py b/LayoutTests/http/tests/websocket/tests/hybi/compressed-control-frame_wsh.py
new file mode 100644 (file)
index 0000000..8e12e9c
--- /dev/null
@@ -0,0 +1,48 @@
+# Copyright 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.
+
+
+from mod_pywebsocket import common
+from mod_pywebsocket import stream
+import zlib
+
+
+def web_socket_do_extra_handshake(request):
+    pass
+
+
+def web_socket_transfer_data(request):
+    compress = zlib.compressobj(
+        zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+    compressed_message = compress.compress('close message')
+    compressed_message += compress.flush(zlib.Z_SYNC_FLUSH)
+    compressed_message = compressed_message[:-4]
+    header = stream.create_header(
+        opcode=common.OPCODE_CLOSE, payload_length=len(compressed_message),
+        fin=1, rsv1=1, rsv2=0, rsv3=0, mask=False)
+    request.connection.write(header + compressed_message)
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff-expected.txt b/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff-expected.txt
new file mode 100644 (file)
index 0000000..562fef3
--- /dev/null
@@ -0,0 +1,20 @@
+Test compression enabled/disabled frame receiving.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Sending message: "Hello"
+PASS event.data is 'Hello'
+Sending message: "DisableCompression"
+PASS event.data is 'DisableCompression'
+Sending message: "World"
+PASS event.data is 'World'
+Sending message: "EnableCompression"
+PASS event.data is 'EnableCompression'
+Sending message: "Goodbye"
+PASS event.data is 'Goodbye'
+onclose() was called.
+PASS closeEvent.wasClean is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff.html b/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff.html
new file mode 100644 (file)
index 0000000..532ac57
--- /dev/null
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../../../js-test-resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description("Test compression enabled/disabled frame receiving.");
+
+window.jsTestIsAsync = true;
+if (window.layoutTestController)
+    layoutTestController.overridePreference("WebKitHixie76WebSocketProtocolEnabled", 0);
+
+var closeEvent;
+var ws;
+var messageIndex;
+
+var messages = [
+    "Hello",
+    "DisableCompression", // This disables compression
+    "World",
+    "EnableCompression", // This enables compression
+    "Goodbye"
+];
+
+ws = new WebSocket("ws://localhost:8880/websocket/tests/hybi/deflate-frame");
+
+ws.onopen = function(event)
+{
+    messageIndex = 0;
+    debug("Sending message: \"" + messages[messageIndex] + "\"");
+    ws.send(messages[messageIndex]);
+};
+
+ws.onmessage = function(event)
+{
+    shouldBe("event.data", "'" + messages[messageIndex] + "'");
+    if (messageIndex === messages.length - 1)
+        ws.close();
+    else {
+        messageIndex += 1;
+        debug("Sending message: \"" + messages[messageIndex] + "\"");
+        ws.send(messages[messageIndex]);
+    }
+};
+
+ws.onclose = function(event)
+{
+    debug("onclose() was called.");
+    closeEvent = event;
+    shouldBeTrue("closeEvent.wasClean");
+    finishJSTest();
+};
+
+</script>
+<script src="../../../../js-test-resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter-expected.txt b/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter-expected.txt
new file mode 100644 (file)
index 0000000..83a24d2
--- /dev/null
@@ -0,0 +1,28 @@
+CONSOLE MESSAGE: Received unexpected deflate-frame parameter
+CONSOLE MESSAGE: Received invalid max_window_bits parameter
+CONSOLE MESSAGE: Received invalid max_window_bits parameter
+CONSOLE MESSAGE: Received invalid no_context_takeover parameter
+CONSOLE MESSAGE: Received unexpected deflate-frame parameter
+Test whether WebSocket rejects invalid deflate-frame parameters.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Testing parameter: "x-foo"
+onclose() was called.
+PASS closeEvent.wasClean is false
+Testing parameter: "max_window_bits=7"
+onclose() was called.
+PASS closeEvent.wasClean is false
+Testing parameter: "max_window_bits=16"
+onclose() was called.
+PASS closeEvent.wasClean is false
+Testing parameter: "no_context_takeover=foo"
+onclose() was called.
+PASS closeEvent.wasClean is false
+Testing parameter: "max_window_bits=8; no_context_takeover; x-foo"
+onclose() was called.
+PASS closeEvent.wasClean is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter.html b/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter.html
new file mode 100644 (file)
index 0000000..5a5fbd0
--- /dev/null
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../../../js-test-resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description("Test whether WebSocket rejects invalid deflate-frame parameters.");
+
+window.jsTestIsAsync = true;
+if (window.layoutTestController)
+    layoutTestController.overridePreference("WebKitHixie76WebSocketProtocolEnabled", 0);
+
+var closeEvent;
+
+var testCase = [
+    "x-foo",
+    "max_window_bits=7",
+    "max_window_bits=16",
+    "no_context_takeover=foo",
+    "max_window_bits=8; no_context_takeover; x-foo"
+];
+
+function doTest(index)
+{
+    var parameter = testCase[index];
+    var url = "ws://localhost:8880/websocket/tests/hybi/deflate-frame-invalid-parameter?" + encodeURI(parameter);
+    var ws = new WebSocket(url);
+
+    debug("Testing parameter: \"" + parameter + "\"");
+
+    ws.onmessage = function(event)
+    {
+        var message = event.data;
+        testFailed("onmessage() was called. (message = \"" + message + "\")");
+    };
+
+    ws.onclose = function(event)
+    {
+        debug("onclose() was called.");
+        closeEvent = event;
+        shouldBeFalse("closeEvent.wasClean");
+        if (index === testCase.length - 1)
+            finishJSTest();
+        else
+            doTest(index + 1);
+    };
+}
+
+doTest(0);
+
+</script>
+<script src="../../../../js-test-resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter_wsh.py b/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter_wsh.py
new file mode 100644 (file)
index 0000000..a8cc25c
--- /dev/null
@@ -0,0 +1,56 @@
+# Copyright 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.
+
+
+import urllib
+from mod_pywebsocket import handshake
+from mod_pywebsocket.handshake.hybi import compute_accept
+
+
+def web_socket_do_extra_handshake(request):
+    resources = request.ws_resource.split('?', 1)
+    parameters = None
+    if len(resources) == 2:
+        parameters = urllib.unquote(resources[1])
+
+    message = 'HTTP/1.1 101 Switching Protocols\r\n'
+    message += 'Upgrade: websocket\r\n'
+    message += 'Connection: Upgrade\r\n'
+    message += 'Sec-WebSocket-Accept: %s\r\n' % compute_accept(request.headers_in['Sec-WebSocket-Key'])[0]
+    message += 'Sec-WebSocket-Extensions: x-webkit-deflate-frame'
+    if parameters:
+        message += '; %s\r\n' % parameters
+    else:
+        message += '\r\n'
+    message += '\r\n'
+    request.connection.write(message)
+    raise handshake.AbortedByUserException('Abort the connection') # Prevents pywebsocket from sending its own handshake message.
+
+
+def web_socket_transfer_data(request):
+    pass
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-parameter-expected.txt b/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-parameter-expected.txt
new file mode 100644 (file)
index 0000000..7263fe3
--- /dev/null
@@ -0,0 +1,30 @@
+Test WebSocket deflate-frame extension.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Testing query: "max_window_bits=8"
+PASS ws.extensions.search('x-webkit-deflate-frame') != -1 is true
+PASS ws.extensions.search('max_window_bits=8') != -1 is true
+PASS event.data is firstMessage
+PASS event.data is secondMessage
+onclose() was called.
+PASS closeEvent.wasClean is true
+Testing query: "no_context_takeover"
+PASS ws.extensions.search('x-webkit-deflate-frame') != -1 is true
+PASS ws.extensions.search('no_context_takeover') != -1 is true
+PASS event.data is firstMessage
+PASS event.data is secondMessage
+onclose() was called.
+PASS closeEvent.wasClean is true
+Testing query: "max_window_bits=8&no_context_takeover"
+PASS ws.extensions.search('x-webkit-deflate-frame') != -1 is true
+PASS ws.extensions.search('max_window_bits=8') != -1 is true
+PASS ws.extensions.search('no_context_takeover') != -1 is true
+PASS event.data is firstMessage
+PASS event.data is secondMessage
+onclose() was called.
+PASS closeEvent.wasClean is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-parameter.html b/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame-parameter.html
new file mode 100644 (file)
index 0000000..4b77e47
--- /dev/null
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../../../js-test-resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description("Test WebSocket deflate-frame extension.");
+
+window.jsTestIsAsync = true;
+if (window.layoutTestController)
+    layoutTestController.overridePreference("WebKitHixie76WebSocketProtocolEnabled", 0);
+
+var closeEvent;
+var ws;
+var messageIndex;
+
+var queries = [
+    "max_window_bits=8",
+    "no_context_takeover",
+    "max_window_bits=8&no_context_takeover"
+];
+
+// The first message consists of a lot of 'b' and a few 'a' at the head and
+// the tail, while the second one consists of 'a'.
+var firstMessage = '';
+var secondMessage = '';
+for (var i = 0; i < 16; ++i) {
+    firstMessage += 'a';
+    secondMessage += 'a';
+}
+for (var i = 0; i < 1024; ++i) {
+    firstMessage += 'b';
+    secondMessage += 'a';
+}
+for (var i = 0; i < 16; ++i) {
+    firstMessage += 'a';
+    secondMessage += 'a';
+}
+
+function doTest(queryIndex)
+{
+    var query = queries[queryIndex];
+    debug("Testing query: \"" + query + "\"");
+
+    var url = "ws://localhost:8880/websocket/tests/hybi/deflate-frame?" + query;
+    ws = new WebSocket(url);
+    messageIndex = 0;
+
+    ws.onopen = function(event)
+    {
+        shouldBeTrue("ws.extensions.search('x-webkit-deflate-frame') != -1");
+        parameters = query.split('&');
+        for (var i = 0; i < parameters.length; ++i)
+            shouldBeTrue("ws.extensions.search('" + parameters[i] + "') != -1");
+        ws.send(firstMessage);
+    };
+
+    ws.onmessage = function(event)
+    {
+        if (messageIndex === 0) {
+            shouldBe("event.data", "firstMessage");
+            messageIndex += 1
+            ws.send(secondMessage);
+        } else {
+            shouldBe("event.data", "secondMessage");
+            ws.close();
+        }
+    };
+
+    ws.onclose = function(event)
+    {
+        debug("onclose() was called.");
+        closeEvent = event;
+        shouldBeTrue("closeEvent.wasClean");
+        if (queryIndex === queries.length - 1)
+            finishJSTest();
+        else
+            doTest(queryIndex + 1);
+    };
+}
+
+doTest(0);
+
+</script>
+<script src="../../../../js-test-resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame_wsh.py b/LayoutTests/http/tests/websocket/tests/hybi/deflate-frame_wsh.py
new file mode 100644 (file)
index 0000000..83b7838
--- /dev/null
@@ -0,0 +1,82 @@
+# Copyright 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.
+
+
+import urlparse
+from mod_pywebsocket.extensions import DeflateFrameExtensionProcessor
+from mod_pywebsocket.extensions import ExtensionProcessorInterface
+from mod_pywebsocket.common import ExtensionParameter
+
+
+_GOODBYE_MESSAGE = u'Goodbye'
+_ENABLE_MESSAGE = u'EnableCompression'
+_DISABLE_MESSAGE = u'DisableCompression'
+
+
+def _get_deflate_frame_extension_processor(request):
+    for extension_processor in request.ws_extension_processors:
+        if isinstance(extension_processor, DeflateFrameExtensionProcessor):
+            return extension_processor
+    return None
+
+
+def web_socket_do_extra_handshake(request):
+    processor = _get_deflate_frame_extension_processor(request)
+    if not processor:
+        return
+    r = request.ws_resource.split('?', 1)
+    if len(r) == 1:
+        return
+    parameters = urlparse.parse_qs(r[1], keep_blank_values=True)
+    if 'max_window_bits' in parameters:
+        window_bits = int(parameters['max_window_bits'][0])
+        processor.set_response_window_bits(window_bits)
+    if 'no_context_takeover' in parameters:
+        processor.set_response_no_context_takeover(True)
+
+
+def web_socket_transfer_data(request):
+    processor = _get_deflate_frame_extension_processor(request)
+    while True:
+        line = request.ws_stream.receive_message()
+        if line is None:
+            return
+        if isinstance(line, unicode):
+            if processor:
+                if line == _ENABLE_MESSAGE:
+                    processor.enable_outgoing_compression()
+                elif line == _DISABLE_MESSAGE:
+                    processor.disable_outgoing_compression()
+            request.ws_stream.send_message(line, binary=False)
+            if line == _GOODBYE_MESSAGE:
+                return
+        else:
+            request.ws_stream.send_message(line, binary=True)
+
+
+# vi:sts=4 sw=4 et
index adf06b6..cc99af0 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: Received unexpected Sec-WebSocket-Extensions header
+CONSOLE MESSAGE: Received unexpected extension: x-foo
 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 daa4404..792034c 100644 (file)
@@ -2,6 +2,14 @@ from mod_pywebsocket import common
 from mod_pywebsocket import msgutil
 
 
+def _retrieve_frame(stream):
+    # FIXME: Use better API.
+    frame = stream._receive_frame_as_frame_object()
+    for frame_filter in stream._options.incoming_frame_filters:
+        frame_filter.filter(frame)
+    return frame
+
+
 def web_socket_do_extra_handshake(request):
     pass # Always accept.
 
@@ -10,12 +18,11 @@ def web_socket_transfer_data(request):
     expected_messages = ['Hello, world!']
 
     for test_number, expected_message in enumerate(expected_messages):
-        # FIXME: Use better API.
-        opcode, payload, final, unused_reserved1, unused_reserved2, unused_reserved3 = request.ws_stream._receive_frame()
-        if opcode == common.OPCODE_BINARY and payload == expected_message and final:
+        frame = _retrieve_frame(request.ws_stream)
+        if frame.opcode == common.OPCODE_BINARY and frame.payload == expected_message and frame.fin:
             msgutil.send_message(request, 'PASS: Message #%d.' % test_number)
         else:
-            msgutil.send_message(request, 'FAIL: Message #%d: Received unexpected frame: opcode = %r, payload = %r, final = %r' % (test_number, opcode, payload, final))
+            msgutil.send_message(request, 'FAIL: Message #%d: Received unexpected frame: opcode = %r, payload = %r, final = %r' % (test_number, frame.opcode, frame.payload, frame.fin))
 
 
 def all_distinct_bytes():
index 193b583..7d920f3 100644 (file)
@@ -1,3 +1,14 @@
+2012-02-23  Kenichi Ishibashi  <bashi@chromium.org>
+
+        Adding WebSocket per-frame DEFLATE extension
+        https://bugs.webkit.org/show_bug.cgi?id=77522
+
+        Added USE(ZLIB) flag.
+
+        Reviewed by Kent Tamura.
+
+        * wtf/Platform.h:
+
 2012-02-23  Mark Hahnenberg  <mhahnenberg@apple.com>
 
         Zero out CopiedBlocks on initialization
index ab2522e..dc844b6 100644 (file)
 #define WTF_USE_WTFURL 0
 #endif
 
+#if !PLATFORM(QT) && !PLATFORM(EFL)
+#define WTF_USE_ZLIB 1
+#endif
+
 #endif /* WTF_Platform_h */
index e8d0908..e90aa6b 100644 (file)
@@ -2060,6 +2060,7 @@ IF (ENABLE_WEB_SOCKETS)
         websockets/ThreadableWebSocketChannelClientWrapper.cpp
         websockets/WebSocket.cpp
         websockets/WebSocketChannel.cpp
+        websockets/WebSocketDeflateFramer.cpp
         websockets/WebSocketExtensionDispatcher.cpp
         websockets/WebSocketHandshake.cpp
         websockets/WebSocketHandshakeRequest.cpp
index 1842be0..08579a4 100644 (file)
@@ -1,3 +1,74 @@
+2012-02-23  Kenichi Ishibashi  <bashi@chromium.org>
+
+        Adding WebSocket per-frame DEFLATE extension
+        https://bugs.webkit.org/show_bug.cgi?id=77522
+
+        Add WebSocketDeflateFramer class which handles deflate-frame extension.
+        This class encapsulates WebSocketDeflater and WebSocketInflater classes,
+        which depend on zlib, so that WebSocketChannel is not necessary to aware
+        zlib dependency.
+
+        This is the second patch to land. The previous patch broke Chromium Win
+        release build. r108600 should fix the build failure. I also added
+        zlib entry to |export_dependent_settings| of |webcore_prerequisites|
+        target.
+
+        Reviewed by Kent Tamura.
+
+        Tests: http/tests/websocket/tests/hybi/compressed-control-frame.html
+               http/tests/websocket/tests/hybi/deflate-frame-comp-bit-onoff.html
+               http/tests/websocket/tests/hybi/deflate-frame-invalid-parameter.html
+               http/tests/websocket/tests/hybi/deflate-frame-parameter.html
+
+        * CMakeLists.txt: Added WebSocketDeflateFramer.(cpp|h)
+        * GNUmakefile.list.am: Ditto.
+        * Target.pri: Ditto.
+        * WebCore.gypi: Ditto.
+        * WebCore.gyp/WebCore.gyp: Added zlib dependency.
+        * WebCore.vcproj/WebCore.vcproj: Added WebSocketDeflateFramer.(cpp|h)
+        * WebCore.xcodeproj/project.pbxproj: Ditto.
+        * websockets/WebSocket.cpp:
+        (WebCore::WebSocket::didConnect): Set m_extensions.
+        * websockets/WebSocketChannel.cpp:
+        (WebCore::WebSocketChannel::connect): Add deflate-frame extension processor to WebSocketHanshake if deflate can use.
+        (WebCore::WebSocketChannel::fail): Call m_deflateFramer.didFail().
+        (WebCore::WebSocketChannel::processFrame): Decompress frames if needed.
+        (WebCore::WebSocketChannel::sendFrame): Compress frames if possible.
+        * websockets/WebSocketChannel.h:
+        * websockets/WebSocketDeflateFramer.cpp: Added.
+        (WebCore):
+        (WebSocketExtensionDeflateFrame):
+        (WebCore::WebSocketExtensionDeflateFrame::create):
+        (WebCore::WebSocketExtensionDeflateFrame::~WebSocketExtensionDeflateFrame):
+        (WebCore::WebSocketExtensionDeflateFrame::WebSocketExtensionDeflateFrame):
+        (WebCore::WebSocketExtensionDeflateFrame::handshakeString):
+        (WebCore::WebSocketExtensionDeflateFrame::processResponse):
+        (WebCore::DeflateResultHolder::DeflateResultHolder):
+        (WebCore::DeflateResultHolder::~DeflateResultHolder):
+        (WebCore::DeflateResultHolder::fail):
+        (WebCore::InflateResultHolder::InflateResultHolder):
+        (WebCore::InflateResultHolder::~InflateResultHolder):
+        (WebCore::InflateResultHolder::fail):
+        (WebCore::WebSocketDeflateFramer::WebSocketDeflateFramer):
+        (WebCore::WebSocketDeflateFramer::createExtensionProcessor):
+        (WebCore::WebSocketDeflateFramer::canDeflate):
+        (WebCore::WebSocketDeflateFramer::enableDeflate):
+        (WebCore::WebSocketDeflateFramer::deflate):
+        (WebCore::WebSocketDeflateFramer::resetDeflateContext):
+        (WebCore::WebSocketDeflateFramer::inflate):
+        (WebCore::WebSocketDeflateFramer::resetInflateContext):
+        (WebCore::WebSocketDeflateFramer::didFail):
+        * websockets/WebSocketDeflateFramer.h: Added.
+        (WebCore):
+        (DeflateResultHolder):
+        (WebCore::DeflateResultHolder::succeeded):
+        (WebCore::DeflateResultHolder::failureReason):
+        (InflateResultHolder):
+        (WebCore::InflateResultHolder::succeeded):
+        (WebCore::InflateResultHolder::failureReason):
+        (WebSocketDeflateFramer):
+        (WebCore::WebSocketDeflateFramer::enabled):
+
 2012-02-23  Andy Estes  <aestes@apple.com>
 
         Rename [setS|s]uppressIncrementalRendering to [setS|s]uppressesIncrementalRendering and make it WebPreferences API.
index 0fd66de..8e3ac95 100644 (file)
@@ -4344,6 +4344,8 @@ webcore_sources += \
        Source/WebCore/websockets/WebSocketChannel.h \
        Source/WebCore/websockets/WebSocket.cpp \
        Source/WebCore/websockets/WebSocket.h \
+       Source/WebCore/websockets/WebSocketDeflateFramer.cpp \
+       Source/WebCore/websockets/WebSocketDeflateFramer.h \
        Source/WebCore/websockets/WebSocketDeflater.cpp \
        Source/WebCore/websockets/WebSocketDeflater.h \
        Source/WebCore/websockets/WebSocketExtensionDispatcher.cpp \
index 9cce035..dbbf7af 100644 (file)
@@ -3675,6 +3675,7 @@ contains(DEFINES, ENABLE_WEB_SOCKETS=1) {
         websockets/WebSocket.h \
         websockets/WebSocketChannel.h \
         websockets/WebSocketChannelClient.h \
+        websockets/WebSocketDeflateFramer.h \
         websockets/WebSocketExtensionDispatcher.h \
         websockets/WebSocketExtensionProcessor.h \
         websockets/WebSocketFrame.h \
@@ -3686,6 +3687,7 @@ contains(DEFINES, ENABLE_WEB_SOCKETS=1) {
     SOURCES += \
         websockets/WebSocket.cpp \
         websockets/WebSocketChannel.cpp \
+        websockets/WebSocketDeflateFramer.cpp \
         websockets/WebSocketExtensionDispatcher.cpp \
         websockets/WebSocketHandshake.cpp \
         websockets/WebSocketHandshakeRequest.cpp \
index d319496..42ef505 100644 (file)
         '<(chromium_src_dir)/third_party/ots/ots.gyp:ots',
         '<(chromium_src_dir)/third_party/sqlite/sqlite.gyp:sqlite',
         '<(chromium_src_dir)/third_party/angle/src/build_angle.gyp:translator_glsl',
+        '<(chromium_src_dir)/third_party/zlib/zlib.gyp:zlib',
         '<(chromium_src_dir)/v8/tools/gyp/v8.gyp:v8',
         '<(libjpeg_gyp_path):libjpeg',
       ],
index bb2b54e..a7bc359 100644 (file)
             'websockets/WebSocketChannel.cpp',
             'websockets/WebSocketChannel.h',
             'websockets/WebSocketChannelClient.h',
+            'websockets/WebSocketDeflateFramer.cpp',
+            'websockets/WebSocketDeflateFramer.h',
             'websockets/WebSocketDeflater.cpp',
             'websockets/WebSocketDeflater.h',
             'websockets/WebSocketExtensionDispatcher.cpp',
index 1e7c6f0..d788dd2 100755 (executable)
                                >
                        </File>
                        <File
+                               RelativePath="..\websockets\WebSocketDeflateFramer.cpp"
+                               >
+                       </File>
+                       <File
+                               RelativePath="..\websockets\WebSocketDeflateFramer.h"
+                               >
+                       </File>
+                       <File
                                RelativePath="..\websockets\WebSocketDeflater.cpp"
                                >
                        </File>
index a5c7f6e..6d7448f 100644 (file)
                4A1E719614E101F900626F9D /* JSHTMLShadowElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A1E719414E101F900626F9D /* JSHTMLShadowElement.h */; };
                4A1E71A514E106AC00626F9D /* JSShadowRoot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A1E71A314E106AC00626F9D /* JSShadowRoot.cpp */; };
                4A1E71A614E106AC00626F9D /* JSShadowRoot.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A1E71A414E106AC00626F9D /* JSShadowRoot.h */; };
+               4A29222B14F468BA0021F77E /* WebSocketDeflateFramer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A29222914F468BA0021F77E /* WebSocketDeflateFramer.cpp */; };
+               4A29222C14F468BA0021F77E /* WebSocketDeflateFramer.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A29222A14F468BA0021F77E /* WebSocketDeflateFramer.h */; };
                4A4A234614F1E1440046FBF1 /* WebSocketFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A4A234514F1E1440046FBF1 /* WebSocketFrame.h */; };
                4A6E9FC313C17D1D0046A7F8 /* FontFeatureValue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A6E9FC113C17D1D0046A7F8 /* FontFeatureValue.cpp */; };
                4A6E9FC413C17D1D0046A7F8 /* FontFeatureValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A6E9FC213C17D1D0046A7F8 /* FontFeatureValue.h */; settings = {ATTRIBUTES = (Private, ); }; };
                4A1E719414E101F900626F9D /* JSHTMLShadowElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSHTMLShadowElement.h; sourceTree = "<group>"; };
                4A1E71A314E106AC00626F9D /* JSShadowRoot.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSShadowRoot.cpp; sourceTree = "<group>"; };
                4A1E71A414E106AC00626F9D /* JSShadowRoot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSShadowRoot.h; sourceTree = "<group>"; };
+               4A29222914F468BA0021F77E /* WebSocketDeflateFramer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebSocketDeflateFramer.cpp; sourceTree = "<group>"; };
+               4A29222A14F468BA0021F77E /* WebSocketDeflateFramer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebSocketDeflateFramer.h; sourceTree = "<group>"; };
                4A4A234514F1E1440046FBF1 /* WebSocketFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebSocketFrame.h; sourceTree = "<group>"; };
                4A6E9FC113C17D1D0046A7F8 /* FontFeatureValue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FontFeatureValue.cpp; sourceTree = "<group>"; };
                4A6E9FC213C17D1D0046A7F8 /* FontFeatureValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FontFeatureValue.h; sourceTree = "<group>"; };
                                510D4A47103177A20049EA54 /* WebSocketChannel.cpp */,
                                510D4A48103177A20049EA54 /* WebSocketChannel.h */,
                                510D4A49103177A20049EA54 /* WebSocketChannelClient.h */,
+                               4A29222914F468BA0021F77E /* WebSocketDeflateFramer.cpp */,
+                               4A29222A14F468BA0021F77E /* WebSocketDeflateFramer.h */,
                                4AE02ABB14E8A9D200BC3BA7 /* WebSocketDeflater.cpp */,
                                4AE02ABC14E8A9D200BC3BA7 /* WebSocketDeflater.h */,
                                4A957F0314E241100049DBFB /* WebSocketExtensionDispatcher.cpp */,
                                518A34C21026C831001B6896 /* WebSocket.h in Headers */,
                                510D4A4F103177A20049EA54 /* WebSocketChannel.h in Headers */,
                                510D4A50103177A20049EA54 /* WebSocketChannelClient.h in Headers */,
+                               4A29222C14F468BA0021F77E /* WebSocketDeflateFramer.h in Headers */,
                                4AE02ABE14E8A9D200BC3BA7 /* WebSocketDeflater.h in Headers */,
                                4A957F0714E241300049DBFB /* WebSocketExtensionDispatcher.h in Headers */,
                                4ADE25FA14E3BB4C004C2213 /* WebSocketExtensionProcessor.h in Headers */,
                                1CAF34820A6C405200ABE06E /* WebScriptObject.mm in Sources */,
                                518A34C11026C831001B6896 /* WebSocket.cpp in Sources */,
                                510D4A4E103177A20049EA54 /* WebSocketChannel.cpp in Sources */,
+                               4A29222B14F468BA0021F77E /* WebSocketDeflateFramer.cpp in Sources */,
                                4AE02ABD14E8A9D200BC3BA7 /* WebSocketDeflater.cpp in Sources */,
                                4A957F0614E2412A0049DBFB /* WebSocketExtensionDispatcher.cpp in Sources */,
                                51ABAE441043AB4A008C5260 /* WebSocketHandshake.cpp in Sources */,
index 36dbb6f..7dd854e 100644 (file)
@@ -478,6 +478,7 @@ void WebSocket::didConnect()
     ASSERT(scriptExecutionContext());
     m_state = OPEN;
     m_subprotocol = m_channel->subprotocol();
+    m_extensions = m_channel->extensions();
     dispatchEvent(Event::create(eventNames().openEvent, false, false));
 }
 
index ec0706d..c40b098 100644 (file)
@@ -127,6 +127,8 @@ void WebSocketChannel::connect(const KURL& url, const String& protocol)
     ASSERT(!m_suspended);
     m_handshake = adoptPtr(new WebSocketHandshake(url, protocol, m_document, m_useHixie76Protocol));
     m_handshake->reset();
+    if (!m_useHixie76Protocol && m_deflateFramer.canDeflate())
+        m_handshake->addExtensionProcessor(m_deflateFramer.createExtensionProcessor());
     if (m_identifier)
         InspectorInstrumentation::didCreateWebSocket(m_document, m_identifier, url, m_document->url());
     ref();
@@ -228,6 +230,7 @@ void WebSocketChannel::fail(const String& reason)
         m_shouldDiscardReceivedData = true;
         if (m_buffer)
             skipBuffer(m_bufferSize); // Save memory.
+        m_deflateFramer.didFail();
         m_hasContinuousFrame = false;
         m_continuousFrameData.clear();
     }
@@ -618,6 +621,12 @@ bool WebSocketChannel::processFrame()
     ASSERT(m_buffer < frameEnd);
     ASSERT(frameEnd <= m_buffer + m_bufferSize);
 
+    InflateResultHolder inflateResult = m_deflateFramer.inflate(frame);
+    if (!inflateResult.succeeded()) {
+        fail(inflateResult.failureReason());
+        return false;
+    }
+
     // Validate the frame data.
     if (WebSocketFrame::isReservedOpCode(frame.opCode)) {
         fail("Unrecognized frame opcode: " + String::number(frame.opCode));
@@ -1011,6 +1020,13 @@ bool WebSocketChannel::sendFrame(WebSocketFrame::OpCode opCode, const char* data
 
     ASSERT(!(opCode & ~opCodeMask)); // Checks whether "opCode" fits in the range of opCodes.
     WebSocketFrame frame(opCode, true, false, true, data, dataLength);
+
+    DeflateResultHolder deflateResult = m_deflateFramer.deflate(frame);
+    if (!deflateResult.succeeded()) {
+        fail(deflateResult.failureReason());
+        return false;
+    }
+
     Vector<char> frameData;
     makeFrameData(frame, frameData);
 
index 4bb28c9..6d1febb 100644 (file)
@@ -37,6 +37,7 @@
 #include "SocketStreamHandleClient.h"
 #include "ThreadableWebSocketChannel.h"
 #include "Timer.h"
+#include "WebSocketDeflateFramer.h"
 #include "WebSocketFrame.h"
 #include "WebSocketHandshake.h"
 #include <wtf/Deque.h>
@@ -231,6 +232,8 @@ private:
     OwnPtr<FileReaderLoader> m_blobLoader;
     BlobLoaderStatus m_blobLoaderStatus;
 #endif
+
+    WebSocketDeflateFramer m_deflateFramer;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/websockets/WebSocketDeflateFramer.cpp b/Source/WebCore/websockets/WebSocketDeflateFramer.cpp
new file mode 100644 (file)
index 0000000..0c9d353
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * 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 "WebSocketDeflateFramer.h"
+
+#include <wtf/HashMap.h>
+#include <wtf/text/StringHash.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class WebSocketExtensionDeflateFrame : public WebSocketExtensionProcessor {
+public:
+    static PassOwnPtr<WebSocketExtensionDeflateFrame> create(WebSocketDeflateFramer* framer)
+    {
+        return adoptPtr(new WebSocketExtensionDeflateFrame(framer));
+    }
+    virtual ~WebSocketExtensionDeflateFrame() { }
+
+    virtual String handshakeString() OVERRIDE;
+    virtual bool processResponse(const HashMap<String, String>&) OVERRIDE;
+    virtual String failureReason() OVERRIDE { return m_failureReason; }
+
+private:
+    WebSocketExtensionDeflateFrame(WebSocketDeflateFramer*);
+
+    WebSocketDeflateFramer* m_framer;
+    bool m_responseProcessed;
+    String m_failureReason;
+};
+
+// FXIME: Remove vendor prefix after the specification matured.
+WebSocketExtensionDeflateFrame::WebSocketExtensionDeflateFrame(WebSocketDeflateFramer* framer)
+    : WebSocketExtensionProcessor("x-webkit-deflate-frame")
+    , m_framer(framer)
+    , m_responseProcessed(false)
+{
+    ASSERT(m_framer);
+}
+
+String WebSocketExtensionDeflateFrame::handshakeString()
+{
+    return extensionToken(); // No parameter
+}
+
+bool WebSocketExtensionDeflateFrame::processResponse(const HashMap<String, String>& serverParameters)
+{
+#if USE(ZLIB)
+    if (m_responseProcessed) {
+        m_failureReason = "Received duplicate deflate-frame response";
+        return false;
+    }
+    m_responseProcessed = true;
+
+    int expectedNumParameters = 0;
+    int windowBits = 15;
+    HashMap<String, String>::const_iterator parameter = serverParameters.find("max_window_bits");
+    if (parameter != serverParameters.end()) {
+        windowBits = parameter->second.toInt();
+        if (windowBits < 8 || windowBits > 15) {
+            m_failureReason = "Received invalid max_window_bits parameter";
+            return false;
+        }
+        expectedNumParameters++;
+    }
+
+    WebSocketDeflater::ContextTakeOverMode mode = WebSocketDeflater::TakeOverContext;
+    parameter = serverParameters.find("no_context_takeover");
+    if (parameter != serverParameters.end()) {
+        if (!parameter->second.isNull()) {
+            m_failureReason = "Received invalid no_context_takeover parameter";
+            return false;
+        }
+        mode = WebSocketDeflater::DoNotTakeOverContext;
+        expectedNumParameters++;
+    }
+
+    if (expectedNumParameters != serverParameters.size()) {
+        m_failureReason = "Received unexpected deflate-frame parameter";
+        return false;
+    }
+
+    m_framer->enableDeflate(windowBits, mode);
+    return true;
+#else
+    ASSERT_NOT_REACHED();
+    return false;
+#endif
+}
+
+DeflateResultHolder::DeflateResultHolder(WebSocketDeflateFramer* framer)
+    : m_framer(framer)
+    , m_succeeded(true)
+{
+    ASSERT(m_framer);
+}
+
+DeflateResultHolder::~DeflateResultHolder()
+{
+    m_framer->resetDeflateContext();
+}
+
+void DeflateResultHolder::fail(const String& failureReason)
+{
+    m_succeeded = false;
+    m_failureReason = failureReason;
+}
+
+InflateResultHolder::InflateResultHolder(WebSocketDeflateFramer* framer)
+    : m_framer(framer)
+    , m_succeeded(true)
+{
+    ASSERT(m_framer);
+}
+
+InflateResultHolder::~InflateResultHolder()
+{
+    m_framer->resetInflateContext();
+}
+
+void InflateResultHolder::fail(const String& failureReason)
+{
+    m_succeeded = false;
+    m_failureReason = failureReason;
+}
+
+WebSocketDeflateFramer::WebSocketDeflateFramer()
+    : m_enabled(false)
+{
+}
+
+PassOwnPtr<WebSocketExtensionProcessor> WebSocketDeflateFramer::createExtensionProcessor()
+{
+    return WebSocketExtensionDeflateFrame::create(this);
+}
+
+bool WebSocketDeflateFramer::canDeflate() const
+{
+#if USE(ZLIB)
+    return true;
+#else
+    return false;
+#endif
+}
+
+#if USE(ZLIB)
+void WebSocketDeflateFramer::enableDeflate(int windowBits, WebSocketDeflater::ContextTakeOverMode mode)
+{
+    m_deflater = WebSocketDeflater::create(windowBits, mode);
+    m_inflater = WebSocketInflater::create();
+    if (!m_deflater || !m_inflater) {
+        m_deflater.clear();
+        m_inflater.clear();
+        return;
+    }
+    if (!m_deflater->initialize() || !m_inflater->initialize()) {
+        m_deflater.clear();
+        m_inflater.clear();
+        return;
+    }
+    m_enabled = true;
+}
+#endif
+
+DeflateResultHolder WebSocketDeflateFramer::deflate(WebSocketFrame& frame)
+{
+#if USE(ZLIB)
+    DeflateResultHolder result(this);
+    if (!enabled() || !WebSocketFrame::isNonControlOpCode(frame.opCode) || !frame.payloadLength)
+        return result;
+    if (!m_deflater->addBytes(frame.payload, frame.payloadLength) || !m_deflater->finish()) {
+        result.fail("Failed to compress frame");
+        return result;
+    }
+    frame.compress = true;
+    frame.payload = m_deflater->data();
+    frame.payloadLength = m_deflater->size();
+    return result;
+#else
+    return DeflateResultHolder(this);
+#endif
+}
+
+void WebSocketDeflateFramer::resetDeflateContext()
+{
+#if USE(ZLIB)
+    if (m_deflater)
+        m_deflater->reset();
+#endif
+}
+
+InflateResultHolder WebSocketDeflateFramer::inflate(WebSocketFrame& frame)
+{
+#if USE(ZLIB)
+    InflateResultHolder result(this);
+    if (!frame.compress)
+        return result;
+    if (!enabled() || !WebSocketFrame::isNonControlOpCode(frame.opCode)) {
+        result.fail("Received unexpected compressed frame");
+        return result;
+    }
+    if (!m_inflater->addBytes(frame.payload, frame.payloadLength) || !m_inflater->finish()) {
+        result.fail("Failed to decompress frame");
+        return result;
+    }
+    frame.compress = false;
+    frame.payload = m_inflater->data();
+    frame.payloadLength = m_inflater->size();
+    return result;
+#else
+    return InflateResultHolder(this);
+#endif
+}
+
+void WebSocketDeflateFramer::resetInflateContext()
+{
+#if USE(ZLIB)
+    if (m_inflater)
+        m_inflater->reset();
+#endif
+}
+
+void WebSocketDeflateFramer::didFail()
+{
+    resetDeflateContext();
+    resetInflateContext();
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_SOCKETS)
diff --git a/Source/WebCore/websockets/WebSocketDeflateFramer.h b/Source/WebCore/websockets/WebSocketDeflateFramer.h
new file mode 100644 (file)
index 0000000..8e8ba83
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * 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 WebSocketDeflateFramer_h
+#define WebSocketDeflateFramer_h
+
+#if ENABLE(WEB_SOCKETS)
+
+#if USE(ZLIB)
+#include "WebSocketDeflater.h"
+#endif
+#include "WebSocketExtensionProcessor.h"
+#include "WebSocketFrame.h"
+#include <wtf/OwnPtr.h>
+#include <wtf/PassOwnPtr.h>
+
+namespace WebCore {
+
+class WebSocketDeflateFramer;
+
+class DeflateResultHolder {
+public:
+    explicit DeflateResultHolder(WebSocketDeflateFramer*);
+    ~DeflateResultHolder();
+
+    bool succeeded() const { return m_succeeded; }
+    String failureReason() const { return m_failureReason; }
+
+    void fail(const String& failureReason);
+
+private:
+    WebSocketDeflateFramer* m_framer;
+    bool m_succeeded;
+    String m_failureReason;
+};
+
+class InflateResultHolder {
+public:
+    explicit InflateResultHolder(WebSocketDeflateFramer*);
+    ~InflateResultHolder();
+
+    bool succeeded() const { return m_succeeded; }
+    String failureReason() const { return m_failureReason; }
+
+    void fail(const String& failureReason);
+
+private:
+    WebSocketDeflateFramer* m_framer;
+    bool m_succeeded;
+    String m_failureReason;
+};
+
+class WebSocketDeflateFramer {
+public:
+    WebSocketDeflateFramer();
+
+    PassOwnPtr<WebSocketExtensionProcessor> createExtensionProcessor();
+
+    bool canDeflate() const;
+    bool enabled() const { return m_enabled; }
+
+    DeflateResultHolder deflate(WebSocketFrame&);
+    void resetDeflateContext();
+    InflateResultHolder inflate(WebSocketFrame&);
+    void resetInflateContext();
+
+    void didFail();
+
+#if USE(ZLIB)
+    void enableDeflate(int windowBits, WebSocketDeflater::ContextTakeOverMode);
+#endif
+
+private:
+    bool m_enabled;
+#if USE(ZLIB)
+    OwnPtr<WebSocketDeflater> m_deflater;
+    OwnPtr<WebSocketInflater> m_inflater;
+#endif
+};
+
+}
+
+#endif // ENABLE(WEB_SOCKETS)
+
+#endif // WebSocketDeflateFramer_h