From 74175b13782524d510cf5dee77eca93dec6cdeb3 Mon Sep 17 00:00:00 2001
From: Vamshi Maskuri <117595548+varshith257@users.noreply.github.com>
Date: Sat, 28 Sep 2024 15:34:53 +0530
Subject: [PATCH] fix CLRF
---
.../test/scala/zio/http/ConformanceSpec.scala | 23 +++++++------
.../src/main/scala/zio/http/Header.scala | 13 --------
.../src/main/scala/zio/http/Routes.scala | 32 ++++++++++++++++++-
3 files changed, 42 insertions(+), 26 deletions(-)
diff --git a/zio-http/jvm/src/test/scala/zio/http/ConformanceSpec.scala b/zio-http/jvm/src/test/scala/zio/http/ConformanceSpec.scala
index dee6d3b563..5ebff52895 100644
--- a/zio-http/jvm/src/test/scala/zio/http/ConformanceSpec.scala
+++ b/zio-http/jvm/src/test/scala/zio/http/ConformanceSpec.scala
@@ -19,9 +19,8 @@ object ConformanceSpec extends ZIOSpecDefault {
* Stock, presented at the 19th ACM Asia Conference on Computer and
* Communications Security (ASIA CCS) 2024.
*
- * Paper URL: https://doi.org/10.1145/3634737.3637678
- * GitHub Project: https://github.com/cispa/http-conformance
- *
+ * Paper URL: https://doi.org/10.1145/3634737.3637678 GitHub Project:
+ * https://github.com/cispa/http-conformance
*/
val validUrl = URL.decode("http://example.com").toOption.getOrElse(URL.root)
@@ -1160,15 +1159,15 @@ object ConformanceSpec extends ZIOSpecDefault {
.addHeader(Header.XFrameOptions.SameOrigin),
),
// need test fail assertion something like this
- // request.headers.get(Header.IfModifiedSince.name) match {
- // case Some(_) =>
- // Response.status(Status.NotModified).addHeader(Header.ContentLength(14)).copy(body = Body.empty)
- // case None =>
- // Response
- // .status(Status.Ok)
- // .addHeader(Header.ContentLength(14))
- // .copy(body = Body.fromString("
ABC
"))
- // }
+ // request.headers.get(Header.IfModifiedSince.name) match {
+ // case Some(_) =>
+ // Response.status(Status.NotModified).addHeader(Header.ContentLength(14)).copy(body = Body.empty)
+ // case None =>
+ // Response
+ // .status(Status.Ok)
+ // .addHeader(Header.ContentLength(14))
+ // .copy(body = Body.fromString("ABC
"))
+ // }
)
for {
response <- app.runZIO(Request.get("/test"))
diff --git a/zio-http/shared/src/main/scala/zio/http/Header.scala b/zio-http/shared/src/main/scala/zio/http/Header.scala
index 3905203c11..fca8e9d09f 100644
--- a/zio-http/shared/src/main/scala/zio/http/Header.scala
+++ b/zio-http/shared/src/main/scala/zio/http/Header.scala
@@ -50,19 +50,6 @@ sealed trait Header {
object Header {
- def validateHeaders(headers: Headers): ZIO[Any, Response, Unit] = {
- val invalidHeaderChars = Set('\r', '\n', '\u0000')
- val hasInvalidChar = headers.toList.exists { header =>
- header.renderedValue.exists(invalidHeaderChars.contains)
- }
-
- if (hasInvalidChar) {
- ZIO.fail(Response.status(Status.BadRequest))
- } else {
- ZIO.unit
- }
- }
-
sealed trait HeaderType {
type HeaderValue <: Header
diff --git a/zio-http/shared/src/main/scala/zio/http/Routes.scala b/zio-http/shared/src/main/scala/zio/http/Routes.scala
index a2c5183e0e..6abd96932e 100644
--- a/zio-http/shared/src/main/scala/zio/http/Routes.scala
+++ b/zio-http/shared/src/main/scala/zio/http/Routes.scala
@@ -248,7 +248,7 @@ final case class Routes[-Env, +Err](routes: Chunk[zio.http.Route[Env, Err]]) { s
val tree = self.tree
Handler
.fromFunctionHandler[Request] { req =>
- Header.validateHeaders(req.headers) *> {
+ validateHostHeader(req).flatMap { _ =>
val chunk = tree.get(req.method, req.path)
val allowedMethods = tree.getAllMethods(req.path)
@@ -321,6 +321,36 @@ final case class Routes[-Env, +Err](routes: Chunk[zio.http.Route[Env, Err]]) { s
// }
// }
+ private def validateHostHeader(req: Request): Handler[Any, Response, Request, Response] = {
+ // val invalidHostChars = Set('\r', '\n', '\u0000')
+ val validHostRegex = """^[a-zA-Z0-9.-]+$""".r // RFC-1123 valid host pattern
+
+ // Extract all the host headers
+ val hostHeaders = req.headers.collect {
+ case h if h.headerName.equalsIgnoreCase("Host") => h.renderedValue
+ }
+
+ // Case 1: No Host header
+ if (hostHeaders.isEmpty) {
+ Handler.status(Status.BadRequest)
+ }
+ // Case 2: Multiple Host headers
+ else if (hostHeaders.length > 1) {
+ Handler.status(Status.BadRequest)
+ }
+ // Case 3: Host header contains invalid characters
+ // else if (hostHeaders.exists(value => value.exists(invalidHostChars.contains))) {
+ // Handler.status(Status.BadRequest)
+ // }
+ else if (!validHostRegex.matches(hostHeaders.head)) {
+ Handler.status(Status.BadRequest)
+ }
+ // Host header is valid
+ else {
+ Handler.ok
+ }
+ }
+
}
object Routes extends RoutesCompanionVersionSpecific {