diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/DebuggingDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/DebuggingDirectivesExamplesSpec.scala index c90fbd8936..7d0ba017ca 100644 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/DebuggingDirectivesExamplesSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/DebuggingDirectivesExamplesSpec.scala @@ -6,8 +6,8 @@ package docs.http.scaladsl.server package directives import akka.event.Logging -import akka.http.scaladsl.model.{HttpRequest, HttpResponse} -import akka.http.scaladsl.server.directives.{DebuggingDirectives, LogEntry, LoggingMagnet} +import akka.http.scaladsl.model.{ HttpRequest, HttpResponse } +import akka.http.scaladsl.server.directives.{ DebuggingDirectives, LogEntry, LoggingMagnet } class DebuggingDirectivesExamplesSpec extends RoutingSpec { "logRequest-0" in { diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/RangeDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/RangeDirectivesExamplesSpec.scala index 9d5a35e15d..c2933e208c 100644 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/RangeDirectivesExamplesSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/RangeDirectivesExamplesSpec.scala @@ -30,7 +30,6 @@ class RangeDirectivesExamplesSpec extends RoutingSpec { responseAs[String] shouldEqual "DE" } - // we set "akka.http.routing.range-coalescing-threshold = 2" // above to make sure we get two BodyParts Get() ~> addHeader(Range(ByteRange(0, 1), ByteRange(1, 2), ByteRange(6, 7))) ~> route ~> check { diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpResponseParser.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpResponseParser.scala index 004d94bc49..1c65e7a93f 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpResponseParser.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpResponseParser.scala @@ -34,8 +34,7 @@ private[http] class HttpResponseParser(_settings: ParserSettings, _headerParser: if (requestMethodForCurrentResponse.isDefined) { var cursor = parseProtocol(input, offset) if (byteChar(input, cursor) == ' ') { - cursor = parseStatusCode(input, cursor + 1) - cursor = parseReason(input, cursor)() + cursor = parseStatus(input, cursor + 1) parseHeaderLines(input, cursor) } else badProtocol } else { @@ -50,13 +49,13 @@ private[http] class HttpResponseParser(_settings: ParserSettings, _headerParser: def badProtocol = throw new ParsingException("The server-side HTTP version is not supported") - def parseStatusCode(input: ByteString, cursor: Int): Int = { + def parseStatus(input: ByteString, cursor: Int): Int = { def badStatusCode = throw new ParsingException("Illegal response status code") - def intValue(offset: Int): Int = { - val c = byteChar(input, cursor + offset) - if (CharacterClasses.DIGIT(c)) c - '0' else badStatusCode - } - if (byteChar(input, cursor + 3) == ' ') { + def parseStatusCode() = { + def intValue(offset: Int): Int = { + val c = byteChar(input, cursor + offset) + if (CharacterClasses.DIGIT(c)) c - '0' else badStatusCode + } val code = intValue(0) * 100 + intValue(1) * 10 + intValue(2) statusCode = code match { case 200 ⇒ StatusCodes.OK @@ -65,17 +64,22 @@ private[http] class HttpResponseParser(_settings: ParserSettings, _headerParser: case None ⇒ customStatusCodes(code) getOrElse badStatusCode } } - cursor + 4 + } + if (byteChar(input, cursor + 3) == ' ') { + parseStatusCode() + val startIdx = cursor + 4 + @tailrec def skipReason(idx: Int): Int = + if (idx - startIdx <= maxResponseReasonLength) + if (byteChar(input, idx) == '\r' && byteChar(input, idx + 1) == '\n') idx + 2 + else skipReason(idx + 1) + else throw new ParsingException("Response reason phrase exceeds the configured limit of " + + maxResponseReasonLength + " characters") + skipReason(startIdx) + } else if (byteChar(input, cursor + 3) == '\r' && byteChar(input, cursor + 4) == '\n') { + throw new ParsingException("Status code misses trailing space") } else badStatusCode } - @tailrec private def parseReason(input: ByteString, startIx: Int)(cursor: Int = startIx): Int = - if (cursor - startIx <= maxResponseReasonLength) - if (byteChar(input, cursor) == '\r' && byteChar(input, cursor + 1) == '\n') cursor + 2 - else parseReason(input, startIx)(cursor + 1) - else throw new ParsingException("Response reason phrase exceeds the configured limit of " + - maxResponseReasonLength + " characters") - // http://tools.ietf.org/html/rfc7230#section-3.3 def parseEntity(headers: List[HttpHeader], protocol: HttpProtocol, input: ByteString, bodyStart: Int, clh: Option[`Content-Length`], cth: Option[`Content-Type`], teh: Option[`Transfer-Encoding`], diff --git a/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/ResponseParserSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/ResponseParserSpec.scala index 59de26aae0..bd4adfded5 100644 --- a/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/ResponseParserSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/ResponseParserSpec.scala @@ -14,7 +14,6 @@ import org.scalatest.matchers.Matcher import akka.util.ByteString import akka.actor.ActorSystem import akka.stream.scaladsl._ -import akka.stream.scaladsl.FlattenStrategy import akka.stream.ActorMaterializer import akka.http.scaladsl.util.FastFuture._ import akka.http.impl.util._ @@ -79,6 +78,11 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll { closeAfterResponseCompletion shouldEqual Seq(false) } + "a response with a missing reason phrase" in new Test { + "HTTP/1.1 200 \r\nContent-Length: 0\r\n\r\n" should parseTo(HttpResponse(OK)) + closeAfterResponseCompletion shouldEqual Seq(false) + } + "a response funky `Transfer-Encoding` header" in new Test { override def parserSettings: ParserSettings = super.parserSettings.withCustomStatusCodes(ServerOnTheMove) @@ -227,6 +231,11 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll { MessageStartError(400: StatusCode, ErrorInfo("Response reason phrase exceeds the configured limit of 21 characters")))) } + "with a missing reason phrase and no trailing space" in new Test { + Seq("HTTP/1.1 200\r\nContent-Length: 0\r\n\r\n") should generalMultiParseTo(Left(MessageStartError( + 400: StatusCode, ErrorInfo("Status code misses trailing space")))) + } + "with entity length > max-content-length" - { def response(dataElements: ByteString*) = HttpResponse(200, Nil, HttpEntity.Chunked(`application/octet-stream`, Source(dataElements.map(ChunkStreamPart(_)).toVector))) diff --git a/akka-stream-tests/src/test/scala/akka/stream/io/TlsSpec.scala b/akka-stream-tests/src/test/scala/akka/stream/io/TlsSpec.scala index 3d6bcf510d..85cdc41f0d 100644 --- a/akka-stream-tests/src/test/scala/akka/stream/io/TlsSpec.scala +++ b/akka-stream-tests/src/test/scala/akka/stream/io/TlsSpec.scala @@ -381,9 +381,9 @@ class TlsSpec extends AkkaSpec("akka.loglevel=INFO\nakka.actor.debug.receive=off "emit an error if the TLS handshake fails certificate checks" in assertAllStagesStopped { val getError = Flow[SslTlsInbound] - .map[Either[SslTlsInbound, SSLException]](i => Left(i)) - .recover { case e: SSLException => Right(e) } - .collect { case Right(e) => e }.toMat(Sink.head)(Keep.right) + .map[Either[SslTlsInbound, SSLException]](i ⇒ Left(i)) + .recover { case e: SSLException ⇒ Right(e) } + .collect { case Right(e) ⇒ e }.toMat(Sink.head)(Keep.right) val simple = Flow.wrap(getError, Source.lazyEmpty[SslTlsOutbound])(Keep.left) @@ -399,8 +399,8 @@ class TlsSpec extends AkkaSpec("akka.loglevel=INFO\nakka.actor.debug.receive=off val clientErr = simple.join(badClientTls(IgnoreBoth)) .join(Tcp().outgoingConnection(Await.result(server, 1.second).localAddress)).run() - Await.result(serverErr.flatMap(identity), 1.second).getMessage should include ("certificate_unknown") - Await.result(clientErr, 1.second).getMessage should equal ("General SSLEngine problem") + Await.result(serverErr.flatMap(identity), 1.second).getMessage should include("certificate_unknown") + Await.result(clientErr, 1.second).getMessage should equal("General SSLEngine problem") } "reliably cancel subscriptions when TransportIn fails early" in assertAllStagesStopped { diff --git a/akka-stream/src/main/scala/akka/stream/impl/io/SslTlsCipherActor.scala b/akka-stream/src/main/scala/akka/stream/impl/io/SslTlsCipherActor.scala index 7ff5762e40..dd305cebb7 100644 --- a/akka-stream/src/main/scala/akka/stream/impl/io/SslTlsCipherActor.scala +++ b/akka-stream/src/main/scala/akka/stream/impl/io/SslTlsCipherActor.scala @@ -425,10 +425,10 @@ private[akka] class SslTlsCipherActor(settings: ActorMaterializerSettings, sslCo initialPhase(2, bidirectional) - protected def fail(e: Throwable, closeTransport: Boolean=true): Unit = { + protected def fail(e: Throwable, closeTransport: Boolean = true): Unit = { if (tracing) log.debug("fail {} due to: {}", self, e.getMessage) inputBunch.cancel() - if(closeTransport) { + if (closeTransport) { log.debug("closing output") outputBunch.error(TransportOut, e) }