=htc prevent response entity rendering for status codes not allowing response entities
This commit is contained in:
parent
b5a66eb458
commit
142929165c
6 changed files with 38 additions and 22 deletions
|
|
@ -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)))
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
||||
|"""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue