Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes form submissions with empty file fields throwing exceptions #150

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}
34 changes: 34 additions & 0 deletions example/multipartFormSubmission/app/test/src/ExampleTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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 withFile = requests.post(s"$host/post", data = requests.MultiPart(
requests.MultiItem("somefile", Array[Byte](1,2,3,4,5) , "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
}
}
}
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",
)
}
}
Loading