From 4cbbb7dbad3a01e4a73dec4dc74af173c5018953 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Tue, 6 Oct 2015 14:37:06 +0200 Subject: [PATCH] =htc fix WS masking for empty frames on client side --- .../akka/http/impl/engine/ws/Masking.scala | 9 ++-- .../http/impl/engine/ws/MessageSpec.scala | 45 ++++++++++++++++++- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/ws/Masking.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/ws/Masking.scala index c4be386eb2..a4f805cee9 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/ws/Masking.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/ws/Masking.scala @@ -51,12 +51,9 @@ private[http] object Masking { def onPush(part: FrameEvent, ctx: Context[FrameEvent]): SyncDirective = part match { case start @ FrameStart(header, data) ⇒ - if (header.length == 0) ctx.push(part) - else { - val mask = extractMask(header) - become(new Running(mask)) - current.onPush(start.copy(header = setNewMask(header, mask)), ctx) - } + val mask = extractMask(header) + become(new Running(mask)) + current.onPush(start.copy(header = setNewMask(header, mask)), ctx) case _: FrameData ⇒ ctx.fail(new IllegalStateException("unexpected FrameData (need FrameStart first)")) } diff --git a/akka-http-core/src/test/scala/akka/http/impl/engine/ws/MessageSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/engine/ws/MessageSpec.scala index cfeecee154..3914fdf6fb 100644 --- a/akka-http-core/src/test/scala/akka/http/impl/engine/ws/MessageSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/impl/engine/ws/MessageSpec.scala @@ -134,6 +134,13 @@ class MessageSpec extends FreeSpec with Matchers with WithMaterializerSpec { sub.expectNext(ByteString("def", "ASCII")) sub.expectComplete() } + "unmask masked input on the server side for empty frame" in new ServerTestSetup { + val mask = Random.nextInt() + val header = frameHeader(Opcode.Binary, 0, fin = true, mask = Some(mask)) + + pushInput(header) + expectBinaryMessage(BinaryMessage.Strict(ByteString.empty)) + } } "for text messages" - { "empty message" in new ClientTestSetup { @@ -207,6 +214,13 @@ class MessageSpec extends FreeSpec with Matchers with WithMaterializerSpec { sub.expectNext(ByteString("cdef€", "UTF-8")) sub.expectComplete() } + "unmask masked input on the server side for empty frame" in new ServerTestSetup { + val mask = Random.nextInt() + val header = frameHeader(Opcode.Text, 0, fin = true, mask = Some(mask)) + + pushInput(header) + expectTextMessage(TextMessage.Strict("")) + } } } "render frames from messages" - { @@ -265,6 +279,10 @@ class MessageSpec extends FreeSpec with Matchers with WithMaterializerSpec { sub.sendComplete() expectFrameOnNetwork(Opcode.Continuation, ByteString.empty, fin = true) } + "and mask input on the client side for empty frame" in new ClientTestSetup { + pushMessage(BinaryMessage(ByteString.empty)) + expectMaskedFrameOnNetwork(Opcode.Binary, ByteString.empty, fin = true) + } } "for text messages" - { "for a short strict message" in new ServerTestSetup { @@ -347,6 +365,10 @@ class MessageSpec extends FreeSpec with Matchers with WithMaterializerSpec { sub.sendComplete() expectFrameOnNetwork(Opcode.Continuation, ByteString.empty, fin = true) } + "and mask input on the client side for empty frame" in new ClientTestSetup { + pushMessage(TextMessage("")) + expectMaskedFrameOnNetwork(Opcode.Text, ByteString.empty, fin = true) + } } } "supply automatic low-level websocket behavior" - { @@ -440,7 +462,7 @@ class MessageSpec extends FreeSpec with Matchers with WithMaterializerSpec { } "after receiving close frame without close code" in new ServerTestSetup { netInSub.expectRequest() - pushInput(frameHeader(Opcode.Close, 0, fin = true)) + pushInput(frameHeader(Opcode.Close, 0, fin = true, mask = Some(Random.nextInt()))) messageIn.expectComplete() messageOutSub.sendComplete() @@ -479,7 +501,7 @@ class MessageSpec extends FreeSpec with Matchers with WithMaterializerSpec { netOutSub.request(10) messageInSub.request(10) - pushInput(frameHeader(Protocol.Opcode.Binary, 0, fin = false)) + pushInput(frameHeader(Protocol.Opcode.Binary, 0, fin = false, mask = Some(Random.nextInt()))) val dataSource = expectBinaryMessage().dataStream val inSubscriber = TestSubscriber.manualProbe[ByteString]() dataSource.runWith(Sink(inSubscriber)) @@ -742,10 +764,23 @@ class MessageSpec extends FreeSpec with Matchers with WithMaterializerSpec { pushInput(input) expectProtocolErrorOnNetwork() } + "unmasked input on the server side for empty frame" in new ServerTestSetup { + val input = frameHeader(Opcode.Binary, 0, fin = true) + + pushInput(input) + expectProtocolErrorOnNetwork() + } "masked input on the client side" in new ClientTestSetup { val mask = Random.nextInt() val input = frameHeader(Opcode.Binary, 6, fin = true, mask = Some(mask)) ++ maskedASCII("abcdef", mask)._1 + pushInput(input) + expectProtocolErrorOnNetwork() + } + "masked input on the client side for empty frame" in new ClientTestSetup { + val mask = Random.nextInt() + val input = frameHeader(Opcode.Binary, 0, fin = true, mask = Some(mask)) + pushInput(input) expectProtocolErrorOnNetwork() } @@ -813,9 +848,15 @@ class MessageSpec extends FreeSpec with Matchers with WithMaterializerSpec { def expectBinaryMessage(): BinaryMessage = expectMessage().asInstanceOf[BinaryMessage] + def expectBinaryMessage(message: BinaryMessage): Unit = + expectBinaryMessage() shouldEqual message + def expectTextMessage(): TextMessage = expectMessage().asInstanceOf[TextMessage] + def expectTextMessage(message: TextMessage): Unit = + expectTextMessage() shouldEqual message + var inBuffer = ByteString.empty @tailrec final def expectNetworkData(bytes: Int): ByteString = if (inBuffer.size >= bytes) {