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 {