Skip to content

Commit

Permalink
Fixes form submissions with empty file fields throwing exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
nk committed Nov 12, 2024
1 parent fae83f8 commit c722ec2
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 5 deletions.
1 change: 1 addition & 0 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def zippedExamples = T {
build.example.websockets2.millSourcePath,
build.example.websockets3.millSourcePath,
build.example.websockets4.millSourcePath,
build.example.multipartFormSubmission.millSourcePath,
)

for (example <- examples) yield {
Expand Down
18 changes: 13 additions & 5 deletions cask/src/cask/model/Params.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package cask.model

import java.io.{ByteArrayOutputStream, InputStream}

import cask.internal.Util
import io.undertow.server.HttpServerExchange
import io.undertow.server.handlers.CookieImpl
import io.undertow.util.HttpString
import scala.util.Try
import scala.collection.JavaConverters.collectionAsScalaIterableConverter

case class QueryParams(value: Map[String, collection.Seq[String]])
case class RemainingPathSegments(value: Seq[String])
Expand Down Expand Up @@ -98,9 +100,13 @@ sealed trait FormEntry{
}
}
object FormEntry{
def fromUndertow(from: io.undertow.server.handlers.form.FormData.FormValue) = {
if (!from.isFile) FormValue(from.getValue, from.getHeaders)
else FormFile(from.getFileName, from.getPath, from.getHeaders)
def fromUndertow(from: io.undertow.server.handlers.form.FormData.FormValue): FormEntry = {
val isOctetStream = Option(from.getHeaders)
.flatMap(headers => Option(headers.get(HttpString.tryFromString("Content-Type"))))
.exists(h => h.asScala.exists(v => v == "application/octet-stream"))
// browsers will set empty file fields to content type: octet-stream
if (isOctetStream || from.isFileItem) FormFile(from.getFileName, Try(from.getFileItem.getFile).toOption, from.getHeaders)
else FormValue(from.getValue, from.getHeaders)
}

}
Expand All @@ -110,7 +116,9 @@ case class FormValue(value: String,
}

case class FormFile(fileName: String,
filePath: java.nio.file.Path,
filePath: Option[java.nio.file.Path],
headers: io.undertow.util.HeaderMap) extends FormEntry{
def valueOrFileName = fileName
}

case class EmptyFormEntry()
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package app

object MultipartFormSubmission extends cask.MainRoutes {

@cask.get("/")
def index() =
cask.model.Response(
"""
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<form action="/post" method="post" enctype="multipart/form-data">
<input type="file" id="somefile" name="somefile">
<button type="submit">Submit</button>
</form>
</body>
</html>
""", 200, Seq(("Content-Type", "text/html")))

@cask.postForm("/post")
def post(somefile: cask.FormFile) =
s"filename: ${somefile.fileName}"

initialize()
}
38 changes: 38 additions & 0 deletions example/multipartFormSubmission/app/test/src/ExampleTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package app
import io.undertow.Undertow

import utest._

object ExampleTests extends TestSuite{
def withServer[T](example: cask.main.Main)(f: String => T): T = {
val server = Undertow.builder
.addHttpListener(8081, "localhost")
.setHandler(example.defaultHandler)
.build
server.start()
val res =
try f("http://localhost:8081")
finally server.stop()
res
}

val tests = Tests{
test("MultipartFormSubmission") - withServer(MultipartFormSubmission){ host =>
val classPath = System.getProperty("java.class.path", ".");
val elements = classPath.split(System.getProperty("path.separator"));
elements.filter(e => e.endsWith("/app/resources")).headOption.map(resourcePath => {
val withFile = requests.post(s"$host/post", data = requests.MultiPart(
requests.MultiItem("somefile", new java.io.File(s"$resourcePath/example.txt"), "example.txt"),
))
withFile.text() ==> s"filename: example.txt"
withFile.statusCode ==> 200

val withoutFile = requests.post(s"$host/post", data = requests.MultiPart(
requests.MultiItem("somefile", Array[Byte]()),
))
withoutFile.text() ==> s"filename: null"
withoutFile.statusCode ==> 200
}).isDefined ==> true
}
}
}
18 changes: 18 additions & 0 deletions example/multipartFormSubmission/package.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package build.example.multipartFormSubmission
import mill._, scalalib._

object app extends Cross[AppModule](build.scalaVersions)
trait AppModule extends CrossScalaModule{

def moduleDeps = Seq(build.cask(crossScalaVersion))

def ivyDeps = Agg[Dep](
)
object test extends ScalaTests with TestModule.Utest{

def ivyDeps = Agg(
ivy"com.lihaoyi::utest::0.8.4",
ivy"com.lihaoyi::requests::0.9.0",
)
}
}

0 comments on commit c722ec2

Please sign in to comment.