diff --git a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpRequestRendererFactory.scala b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpRequestRendererFactory.scala index 1d9fe5875e..df3a23b9f3 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpRequestRendererFactory.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpRequestRendererFactory.scala @@ -98,23 +98,21 @@ private[http] class HttpRequestRendererFactory(userAgentHeader: Option[headers.` r ~~ `Transfer-Encoding` ~~ ChunkedBytes ~~ CrLf } - def renderContentLength(contentLength: Long): Unit = { - if (method.isEntityAccepted) r ~~ `Content-Length` ~~ contentLength ~~ CrLf - r ~~ CrLf - } + def renderContentLength(contentLength: Long) = + if (method.isEntityAccepted) r ~~ `Content-Length` ~~ contentLength ~~ CrLf else r def completeRequestRendering(): Source[ByteString] = entity match { case x if x.isKnownEmpty ⇒ - renderContentLength(0) + renderContentLength(0) ~~ CrLf Source.singleton(r.get) case HttpEntity.Strict(_, data) ⇒ - renderContentLength(data.length) + renderContentLength(data.length) ~~ CrLf Source.singleton(r.get ++ data) case HttpEntity.Default(_, contentLength, data) ⇒ - renderContentLength(contentLength) + renderContentLength(contentLength) ~~ CrLf renderByteStrings(r, data.transform("checkContentLength", () ⇒ new CheckContentLengthTransformer(contentLength))) diff --git a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala index fa8b575b5c..bbeb1b07c5 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala @@ -136,6 +136,9 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser r ~~ `Transfer-Encoding` ~~ ChunkedBytes ~~ CrLf } + def renderContentLengthHeader(contentLength: Long) = + if (status.allowsEntity) r ~~ `Content-Length` ~~ contentLength ~~ CrLf else r + def byteStrings(entityBytes: ⇒ Source[ByteString]): Source[ByteString] = renderByteStrings(r, entityBytes, skipEntity = noEntity) @@ -144,20 +147,19 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser case HttpEntity.Strict(_, data) ⇒ renderHeaders(headers.toList) renderEntityContentType(r, entity) - r ~~ `Content-Length` ~~ data.length ~~ CrLf ~~ CrLf + renderContentLengthHeader(data.length) ~~ CrLf val entityBytes = if (noEntity) ByteString.empty else data Source.singleton(r.get ++ entityBytes) case HttpEntity.Default(_, contentLength, data) ⇒ renderHeaders(headers.toList) renderEntityContentType(r, entity) - r ~~ `Content-Length` ~~ contentLength ~~ CrLf ~~ CrLf + renderContentLengthHeader(contentLength) ~~ CrLf byteStrings(data.transform("checkContentLength", () ⇒ new CheckContentLengthTransformer(contentLength))) case HttpEntity.CloseDelimited(_, data) ⇒ renderHeaders(headers.toList, alwaysClose = ctx.requestMethod != HttpMethods.HEAD) - renderEntityContentType(r, entity) - r ~~ CrLf + renderEntityContentType(r, entity) ~~ CrLf byteStrings(data) case HttpEntity.Chunked(contentType, chunks) ⇒ @@ -165,8 +167,7 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser completeResponseRendering(HttpEntity.CloseDelimited(contentType, chunks.map(_.data))) else { renderHeaders(headers.toList) - renderEntityContentType(r, entity) - r ~~ CrLf + renderEntityContentType(r, entity) ~~ CrLf byteStrings(chunks.transform("renderChunks", () ⇒ new ChunkTransformer)) } } diff --git a/akka-http-core/src/main/scala/akka/http/engine/rendering/RenderSupport.scala b/akka-http-core/src/main/scala/akka/http/engine/rendering/RenderSupport.scala index 775cd3ebfa..ff9f268a0d 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/rendering/RenderSupport.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/rendering/RenderSupport.scala @@ -40,9 +40,9 @@ private object RenderSupport { } } - def renderEntityContentType(r: Rendering, entity: HttpEntity): Unit = - if (entity.contentType != ContentTypes.NoContentType) - r ~~ headers.`Content-Type` ~~ entity.contentType ~~ CrLf + def renderEntityContentType(r: Rendering, entity: HttpEntity) = + if (entity.contentType != ContentTypes.NoContentType) r ~~ headers.`Content-Type` ~~ entity.contentType ~~ CrLf + else r def renderByteStrings(r: ByteStringRendering, entityBytes: ⇒ Source[ByteString], skipEntity: Boolean = false): Source[ByteString] = { diff --git a/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala b/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala index f9f2020b7d..50cd953837 100644 --- a/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala +++ b/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala @@ -5,6 +5,8 @@ package akka.http.model import java.lang.{ Iterable ⇒ JIterable } +import akka.parboiled2.CharUtils + import scala.concurrent.duration.FiniteDuration import scala.concurrent.{ Future, ExecutionContext } import scala.collection.immutable @@ -129,7 +131,7 @@ final case class HttpRequest(method: HttpMethod = HttpMethods.GET, headers: immutable.Seq[HttpHeader] = Nil, entity: RequestEntity = HttpEntity.Empty, protocol: HttpProtocol = HttpProtocols.`HTTP/1.1`) extends japi.HttpRequest with HttpMessage { - require(!uri.isEmpty, "An HttpRequest must not have an empty Uri") + HttpRequest.verifyUri(uri) require(entity.isKnownEmpty || method.isEntityAccepted, "Requests with this method must have an empty entity") require(protocol != HttpProtocols.`HTTP/1.0` || !entity.isInstanceOf[HttpEntity.Chunked], "HTTP/1.0 requests must not have a chunked entity") @@ -281,6 +283,22 @@ object HttpRequest { else throw new IllegalUriException(s"'Host' header value of request to `$uri` doesn't match request target authority", s"Host header: $hostHeader\nrequest target authority: ${uri.authority}") } + + /** + * Verifies that the given [[Uri]] is non-empty and has either scheme `http`, `https` or no scheme at all. + * If any of these conditions is not met the method throws an [[IllegalArgumentException]]. + */ + def verifyUri(uri: Uri): Unit = + if (uri.isEmpty) throw new IllegalArgumentException("`uri` must not be empty") + else { + def c(i: Int) = CharUtils.toLowerCase(uri.scheme charAt i) + uri.scheme.length match { + case 0 ⇒ // ok + case 4 if c(0) == 'h' && c(1) == 't' && c(2) == 't' && c(3) == 'p' ⇒ // ok + case 5 if c(0) == 'h' && c(1) == 't' && c(2) == 't' && c(3) == 'p' && c(4) == 's' ⇒ // ok + case _ ⇒ throw new IllegalArgumentException("""`uri` must have scheme "http", "https" or no scheme""") + } + } } /** @@ -290,6 +308,10 @@ final case class HttpResponse(status: StatusCode = StatusCodes.OK, headers: immutable.Seq[HttpHeader] = Nil, entity: ResponseEntity = HttpEntity.Empty, protocol: HttpProtocol = HttpProtocols.`HTTP/1.1`) extends japi.HttpResponse with HttpMessage { + require(entity.isKnownEmpty || status.allowsEntity, "Responses with this status code must have an empty entity") + require(protocol == HttpProtocols.`HTTP/1.1` || !entity.isInstanceOf[HttpEntity.Chunked], + "HTTP/1.0 responses must not have a chunked entity") + type Self = HttpResponse def self = this diff --git a/akka-http-core/src/test/scala/akka/http/engine/rendering/ResponseRendererSpec.scala b/akka-http-core/src/test/scala/akka/http/engine/rendering/ResponseRendererSpec.scala index 84cbb078f8..4269310194 100644 --- a/akka-http-core/src/test/scala/akka/http/engine/rendering/ResponseRendererSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/engine/rendering/ResponseRendererSpec.scala @@ -4,7 +4,6 @@ package akka.http.engine.rendering -import akka.http.model.HttpMethods._ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration._ import scala.concurrent.Await @@ -18,7 +17,6 @@ import akka.http.util._ import akka.util.ByteString import akka.stream.scaladsl._ import akka.stream.FlowMaterializer -import akka.stream.impl.SynchronousIterablePublisher import HttpEntity._ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll { @@ -51,7 +49,6 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll |Age: 0 |Server: akka-http/1.0.0 |Date: Thu, 25 Aug 2011 09:10:29 GMT - |Content-Length: 0 | |""" } diff --git a/akka-http-core/src/test/scala/akka/http/engine/server/HttpServerPipelineSpec.scala b/akka-http-core/src/test/scala/akka/http/engine/server/HttpServerPipelineSpec.scala index d16ea0717f..43f2fa82ed 100644 --- a/akka-http-core/src/test/scala/akka/http/engine/server/HttpServerPipelineSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/engine/server/HttpServerPipelineSpec.scala @@ -505,7 +505,6 @@ class HttpServerPipelineSpec extends AkkaSpec with Matchers with BeforeAndAfterA """HTTP/1.1 100 Continue |Server: akka-http/test |Date: XXXX - |Content-Length: 0 | |""".stripMarginWithNewline("\r\n") dataProbe.expectNoMsg(50.millis) @@ -543,7 +542,6 @@ class HttpServerPipelineSpec extends AkkaSpec with Matchers with BeforeAndAfterA """HTTP/1.1 100 Continue |Server: akka-http/test |Date: XXXX - |Content-Length: 0 | |""".stripMarginWithNewline("\r\n") dataProbe.expectNoMsg(50.millis)