http4s / http4s

A minimal, idiomatic Scala interface for HTTP

Home Page:https://http4s.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`EntityLimiter` with Multipart Error

LaurenceWarne opened this issue · comments

Hi, I found that when I send a multipart request with a greater length than allowed by EntityLimiter I receive org.http4s.InvalidMessageBodyFailure: Invalid message body: Invalid multipart body rather what I would expect: org.http4s.server.middleware.EntityLimiter$EntityTooLarge:

//> using lib "org.http4s::http4s-server:0.23.23"
//> using lib "org.http4s::http4s-dsl:0.23.23"
//> using lib "org.http4s::http4s-client:0.23.23"

import cats.effect._
import cats.effect.unsafe.IORuntime
import cats.syntax.all._
import fs2.Stream
import org.http4s._
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.implicits._
import org.http4s.server.middleware.EntityLimiter
import org.http4s.multipart._
import org.typelevel.ci._
import org.typelevel.log4cats.LoggerFactory
import org.typelevel.log4cats.slf4j.Slf4jFactory

implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global
implicit val loggerFactory: LoggerFactory[IO] = Slf4jFactory.create[IO]
println(org.http4s.BuildInfo.version)

val service = HttpRoutes.of[IO] {
  case r @ POST -> Root / "reverse" => r.as[String].flatMap(s => Ok(s.reverse))
  case req @ POST -> Root / "mp" => {
    req.as[Multipart[IO]].flatMap { m =>
      Ok(s"""Multipart Data\nParts:${m.parts.length}""".stripMargin)
    }
  }
}

val postRequest = Request[IO](Method.POST, uri"/reverse")
val mpRequest = Request[IO](Method.POST, uri"/mp")
val client = Client.fromHttpApp(service.orNotFound)

// Entity Limiter

val limiterService = EntityLimiter.httpApp(service.orNotFound, limit = 16L)
val limiterClient = Client.fromHttpApp(limiterService)
val smallRequest = postRequest.withEntity("*" * 15)
val bigRequest = postRequest.withEntity("*" * 16)

val mpBody = Multiparts
  .forSync[IO]
  .flatMap(
    _.multipart(
      Vector(
        Part.formData("foo", "bar")
      )
    )
  )
  .unsafeRunSync()
val mpBigRequest = mpRequest.withEntity(mpBody).withHeaders(mpBody.headers)

println(limiterClient.status(smallRequest).unsafeRunSync())
// 200 OK

println(limiterClient.status(bigRequest).attempt.unsafeRunSync())
// Left(org.http4s.server.middleware.EntityLimiter$EntityTooLarge)

println(
  limiterClient
    .status(mpBigRequest)
    .attempt
    .unsafeRunSync()
)
// Left(org.http4s.InvalidMessageBodyFailure: Invalid message body: Invalid multipart body)

(scala-cli - output commented)

I found I get a similar result with 1.0.0-M40.

Thanks!

Seems like the problem is all kinds of errors are handled here. To fix we should replace handleError with recover and only match exceptions that are relevant to multipart decoding.

.handleError {
case e: InvalidMessageBodyFailure => Left(e)
case e => Left(InvalidMessageBodyFailure("Invalid multipart body", Some(e)))
}

Seems like the problem is all kinds of errors are handled here. To fix we should replace handleError with recover and only match exceptions that are relevant to multipart decoding.

Looks to have done the trick, thanks for the pointer. I've opened a PR here: #7265 🙂