=htc prevent response entity rendering for status codes not allowing response entities

This commit is contained in:
Mathias 2014-11-20 13:59:23 +01:00
parent b5a66eb458
commit 142929165c
6 changed files with 38 additions and 22 deletions

View file

@ -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)))

View file

@ -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))
}
}

View file

@ -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] = {

View file

@ -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

View file

@ -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
|
|"""
}

View file

@ -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)