Skip to content
Permalink
Browse files

Updates to ContentType to offer greater flexibility with extras

Catching of exceptions for content-type parsing failure
  • Loading branch information...
darkfrog26 committed Jul 3, 2019
1 parent b08624d commit 6116cea9f5f27967c33efd81a08e7e3696f3cc86
@@ -3,24 +3,27 @@ package io.youi.net
import io.circe.Decoder.Result
import io.circe.{Decoder, DecodingFailure, Encoder, HCursor, Json}

case class ContentType(`type`: String, subType: String, charSet: Option[String] = None, boundary: Option[String] = None) {
case class ContentType(`type`: String,
subType: String,
extras: Map[String, String] = Map.empty) {
def this(mimeType: String) = {
this(mimeType.substring(0, mimeType.indexOf('/')), mimeType.substring(mimeType.indexOf('/') + 1))
}

lazy val mimeType: String = s"${`type`}/$subType"
lazy val boundary: String = extras("boundary")
lazy val charSet: String = extras("charset")
lazy val start: String = extras("start")
lazy val outputString: String = {
val b = new StringBuilder(mimeType)
charSet.foreach { s =>
b.append(s"; charset=$s")
}
boundary.foreach { s =>
b.append(s"; boundary=$s")
extras.foreach {
case (key, value) => b.append(s"; $key=$value")
}
b.toString()
}

def withCharSet(charSet: String): ContentType = copy(charSet = Some(charSet))
def withExtra(key: String, value: String): ContentType = copy(extras = extras + (key -> value))
def withCharSet(charSet: String): ContentType = withExtra("charset", charSet)

def is(contentType: ContentType): Boolean = contentType.mimeType == mimeType

@@ -1812,11 +1815,7 @@ object ContentType {
}
val name = block.substring(0, divider)
val value = block.substring(divider + 1)
name match {
case "boundary" => contentType = contentType.copy(boundary = Some(value))
case "charset" => contentType = contentType.copy(charSet = Some(value))
case _ => throw new RuntimeException(s"Unable to parse content type: [$contentTypeString]")
}
contentType = contentType.withExtra(name, value)
}
contentType
}
@@ -0,0 +1,17 @@
package specs

import io.youi.net.ContentType
import org.scalatest.Matchers
import org.scalatest.wordspec.AnyWordSpec

class ContentTypeSpec extends AnyWordSpec with Matchers {
"ContentType" should {
"parse a massive type" in {
val s = """multipart/related;start="<rootpart*1faa50c8-1aec-4659-ba8b-372a789b1945@example.jaxws.sun.com>";type="application/xop+xml";boundary="uuid:1faa50c8-1aec-4659-ba8b-372a789b1945";start-info="text/xml""""
val ct = ContentType.parse(s)
ct.`type` should be("multipart")
ct.subType should be("related")
ct.outputString should be("""multipart/related; start="<rootpart*1faa50c8-1aec-4659-ba8b-372a789b1945@example.jaxws.sun.com>"; type="application/xop+xml"; boundary="uuid:1faa50c8-1aec-4659-ba8b-372a789b1945"; start-info="text/xml"""")
}
}
}
@@ -157,28 +157,41 @@ object UndertowServerImplementation extends ServerImplementationCreator {
}

if (exchange.getRequestContentLength > 0L) {
Headers.`Content-Type`.value(headers).getOrElse(ContentType.`text/plain`) match {
case ContentType.`multipart/form-data` => {
val formData = exchange.getAttachment(FormDataParser.FORM_DATA)
val data = formData.asScala.toList.map { key =>
val entries: List[FormDataEntry] = formData.get(key).asScala.map { entry =>
val headers = parseHeaders(entry.getHeaders)
if (entry.isFileItem) {
val path = entry.getFileItem.getFile
FileEntry(entry.getFileName, path.toFile, headers)
} else {
StringEntry(entry.getValue, headers)
try {
Headers.`Content-Type`.value(headers).getOrElse(ContentType.`text/plain`) match {
case ContentType.`multipart/form-data` => {
val formData = exchange.getAttachment(FormDataParser.FORM_DATA)
val data = formData.asScala.toList.map { key =>
val entries: List[FormDataEntry] = formData.get(key).asScala.map { entry =>
val headers = parseHeaders(entry.getHeaders)
if (entry.isFileItem) {
val path = entry.getFileItem.getFile
FileEntry(entry.getFileName, path.toFile, headers)
} else {
StringEntry(entry.getValue, headers)
}
}.toList
FormData(key, entries)
}
handle(Some(FormDataContent(data)))
}
case ct => {
val runnable = new Runnable {
override def run(): Unit = {
val cis = new ChannelInputStream(exchange.getRequestChannel)
val data = IO.stream(cis, new StringBuilder).toString
handle(Some(StringContent(data, ct)))
}
}.toList
FormData(key, entries)
}
if (exchange.isInIoThread) {
exchange.dispatch(runnable)
} else {
runnable.run()
}
}
handle(Some(FormDataContent(data)))
}
case ct => {
val cis = new ChannelInputStream(exchange.getRequestChannel)
val data = IO.stream(cis, new StringBuilder).toString
handle(Some(StringContent(data, ct)))
}
} catch {
case t: Throwable => scribe.error(t)
}
} else {
handle(None)
@@ -292,6 +305,7 @@ object UndertowServerImplementation extends ServerImplementationCreator {
case StringContent(s, _, _) => {
exchange.getResponseSender.send(s, new IoCallback {
override def onComplete(exchange: HttpServerExchange, sender: Sender): Unit = {
exchange.endExchange()
sender.close()
}

0 comments on commit 6116cea

Please sign in to comment.
You can’t perform that action at this time.