Repository: tipsy/javalin
Pull request #1350 (2021-08-17 15:20:04)

GH-1342 Support content-type through a dedicated ContentType enum


Patch:
diff --git a/javalin/src/main/java/io/javalin/core/PathParser.kt b/javalin/src/main/java/io/javalin/core/PathParser.kt
index e1528e86..91035dd6 100644
--- a/javalin/src/main/java/io/javalin/core/PathParser.kt
+++ b/javalin/src/main/java/io/javalin/core/PathParser.kt
@@ -1,8 +1,12 @@
 package io.javalin.core
 
-import io.javalin.core.routing.*
+import io.javalin.core.routing.ParameterNamesNotUniqueException
+import io.javalin.core.routing.PathSegment
+import io.javalin.core.routing.constructRegexList
+import io.javalin.core.routing.convertSegment
+import io.javalin.core.routing.pathParamNames
+import io.javalin.core.routing.values
 import io.javalin.http.util.ContextUtil
-import java.lang.IllegalArgumentException
 
 class PathParser(private val rawPath: String, ignoreTrailingSlashes: Boolean) {
 
@@ -12,8 +16,8 @@ class PathParser(private val rawPath: String, ignoreTrailingSlashes: Boolean) {
         }
     }
 
-    private val matchEverySubPath: Boolean = rawPath.endsWith("**")
-    private val path: String = rawPath.removeSuffix("**")
+    private val matchPathAndEverySubPath: Boolean = rawPath.endsWith(">*") || rawPath.endsWith("}*")
+    private val path: String = if (matchPathAndEverySubPath) rawPath.removeSuffix("*") else rawPath
 
     val segments: List<PathSegment> = path.split("/")
         .filter { it.isNotEmpty() }
@@ -33,9 +37,9 @@ class PathParser(private val rawPath: String, ignoreTrailingSlashes: Boolean) {
         else -> ""
     }
 
-    private val matchRegex = constructRegexList(matchEverySubPath, segments, regexSuffix) { it.asRegexString() }
+    private val matchRegex = constructRegexList(matchPathAndEverySubPath, segments, regexSuffix) { it.asRegexString() }
     private val pathParamRegex =
-        constructRegexList(matchEverySubPath, segments, regexSuffix) { it.asGroupedRegexString() }
+        constructRegexList(matchPathAndEverySubPath, segments, regexSuffix) { it.asGroupedRegexString() }
 
     fun matches(url: String): Boolean = matchRegex.any { url matches it }
 
diff --git a/javalin/src/main/java/io/javalin/http/ContentType.kt b/javalin/src/main/java/io/javalin/http/ContentType.kt
new file mode 100644
index 00000000..84e53c97
--- /dev/null
+++ b/javalin/src/main/java/io/javalin/http/ContentType.kt
@@ -0,0 +1,95 @@
+package io.javalin.http
+
+enum class ContentType(
+    val mimeType: String,
+    val isHumanReadable: Boolean,
+    vararg val extensions: String,
+) {
+
+    // Fallback list of basic mime types used by Maven repository
+    // ~ https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
+    // Extra types
+    // ~ http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
+
+    /* Text */
+
+    TEXT_PLAIN("text/plain", true, "txt"),
+    TEXT_CSS("text/css", true, "css"),
+    TEXT_CSV("text/csv", false, "csv"),
+    TEXT_HTML("text/html", true, "html", "htm"),
+    TEXT_MARKDOWN("text/markdown", true, "md"),
+    TEXT_PROPERTIES("text/x-java-properties", true, "properties"),
+    TEXT_XML("text/xml", true, "xml"),
+
+    /* Image */
+
+    IMAGE_ICO("image/vnd.microsoft.icon", true, "ico"),
+    IMAGE_JPEG("image/jpeg", true, "jpeg"),
+    IMAGE_JPG("image/jpg", true, "jpg"),
+    IMAGE_PNG("image/png", true, "png"),
+    IMAGE_TIFF("image/tiff", true, "tiff", "tif"),
+
+    /* Font */
+
+    FONT_OTF("font/otf", false, "otf"),
+    FONT_TTF("font/ttf", false, "ttf"),
+
+    /* Application */
+
+    APPLICATION_OCTET_STREAM("application/octet-stream", false, "bin"),
+    APPLICATION_BZ("application/x-bzip", false, "bz"),
+    APPLICATION_BZ2("application/x-bzip2", false, "bz2"),
+    APPLICATION_CDN("application/cdn", true, "cdn"),
+    APPLICATION_GZ("application/gzip", false, "gz"),
+    APPLICATION_JS("application/javascript", true, "js"),
+    APPLICATION_JSON("application/json", true, "json"),
+    APPLICATION_MPKG("application/vnd.apple.installer+xml", false, "mpkg"),
+    APPLICATION_JAR("application/java-archive", false, "jar"),
+    APPLICATION_PDF("application/pdf", false, "pdf"),
+    APPLICATION_POM("application/xml", true, "pom"),
+    APPLICATION_RAR("application/vnd.rar", false, "rar"),
+    APPLICATION_SH("application/x-sh", true, "sh"),
+    APPLICATION_TAR("application/x-tar", false, "tar"),
+    APPLICATION_XHTML("application/xhtml+xml", true, "xhtml"),
+    APPLICATION_YAML("application/yaml", true, "yaml", "yml"),
+    APPLICATION_ZIP("application/zip", false, "zip"),
+    APPLICATION_7Z("application/x-7z-compressed", false, "7z"),
+
+    /* Other */
+
+    MULTIPART_FORM_DATA("multipart/form-data", false, "multipart/form-data");
+
+    override fun toString(): String =
+        mimeType
+
+    companion object {
+
+        /* Compile time common constants - useful for annotations & as raw string values */
+
+        const val PLAIN = "text/plain"
+        const val HTML = "text/html"
+        const val XML = "text/xml"
+        const val OCTET_STREAM = "application/octet-stream"
+        const val JAVASCRIPT = "application/javascript"
+        const val JSON = "application/json"
+        const val FORM_DATA = "multipart/form-data"
+
+        @JvmStatic
+        fun getContentType(mimeType: String): ContentType? =
+            values().find { it.mimeType.equals(mimeType, ignoreCase = true) }
+
+        @JvmStatic
+        fun getContentTypeByExtension(extension: String): ContentType? =
+            values().firstOrNull { type ->
+                type.extensions.any {
+                    extension.equals(it, ignoreCase = true)
+                }
+            }
+
+        @JvmStatic
+        fun getMimeTypeByExtension(extensions: String): String? =
+            getContentTypeByExtension(extensions)?.mimeType
+
+    }
+
+}
diff --git a/javalin/src/main/java/io/javalin/http/Context.kt b/javalin/src/main/java/io/javalin/http/Context.kt
index 0a71afce..93eec5e8 100644
--- a/javalin/src/main/java/io/javalin/http/Context.kt
+++ b/javalin/src/main/java/io/javalin/http/Context.kt
@@ -380,6 +380,10 @@ open class Context(@JvmField val req: HttpServletRequest, @JvmField val res: Htt
         return this
     }
 
+    /** Sets response content type to specified [ContentType] value. */
+    fun contentType(contentType: ContentType): Context =
+        contentType(contentType.mimeType)
+
     /** Sets response header by name and value. */
     fun header(name: String, value: String): Context {
         res.setHeader(name, value)


Test patch:
diff --git a/javalin/src/test/java/io/javalin/TestHttpVerbs.kt b/javalin/src/test/java/io/javalin/TestHttpVerbs.kt
deleted file mode 100644
index 8342457f..00000000
--- a/javalin/src/test/java/io/javalin/TestHttpVerbs.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Javalin - https://javalin.io
- * Copyright 2017 David Åse
- * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
- *
- */
-
-package io.javalin
-
-import com.mashape.unirest.http.HttpMethod
-import io.javalin.testing.TestUtil
-import io.javalin.testing.TestUtil.okHandler
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Test
-
-class TestHttpVerbs {
-
-    @Test
-    fun `basic hello world works`() = TestUtil.test { app, http ->
-        app.get("/hello") { ctx -> ctx.result("Hello World") }
-        assertThat(http.getBody("/hello")).isEqualTo("Hello World")
-    }
-
-    @Test
-    fun `all mapped verbs return 200`() = TestUtil.test { app, http ->
-        app.get("/mapped", okHandler)
-        app.post("/mapped", okHandler)
-        app.put("/mapped", okHandler)
-        app.delete("/mapped", okHandler)
-        app.patch("/mapped", okHandler)
-        app.head("/mapped", okHandler)
-        app.options("/mapped", okHandler)
-        for (httpMethod in HttpMethod.values()) {
-            assertThat(http.call(httpMethod, "/mapped").status).isEqualTo(200)
-        }
-    }
-
-    @Test
-    fun `all unmapped verbs return 404`() = TestUtil.test { _, http ->
-        for (httpMethod in HttpMethod.values()) {
-            assertThat(http.call(httpMethod, "/unmapped").status).isEqualTo(404)
-        }
-    }
-
-    @Test
-    fun `HEAD returns 200 if GET is mapped`() = TestUtil.test { app, http ->
-        app.get("/mapped", okHandler)
-        assertThat(http.call(HttpMethod.HEAD, "/mapped").status).isEqualTo(200)
-    }
-
-    @Test
-    fun `filers are executed in order`() = TestUtil.test { app, http ->
-        app.before { ctx -> ctx.result("1") }
-        app.before { ctx -> ctx.result(ctx.resultString()!! + "2") }
-        app.before { ctx -> ctx.result(ctx.resultString()!! + "3") }
-        app.before { ctx -> ctx.result(ctx.resultString()!! + "4") }
-        app.get("/hello") { ctx -> ctx.result(ctx.resultString()!! + "Hello") }
-        app.after { ctx -> ctx.result(ctx.resultString()!! + "5") }
-        assertThat(http.getBody("/hello")).isEqualTo("1234Hello5")
-    }
-
-}
diff --git a/javalin/src/test/java/io/javalin/TestRouting.kt b/javalin/src/test/java/io/javalin/TestRouting.kt
index 836a60f2..d890ff23 100644
--- a/javalin/src/test/java/io/javalin/TestRouting.kt
+++ b/javalin/src/test/java/io/javalin/TestRouting.kt
@@ -7,12 +7,13 @@
 
 package io.javalin
 
+import com.mashape.unirest.http.HttpMethod
+import io.javalin.apibuilder.ApiBuilder.after
 import io.javalin.apibuilder.ApiBuilder.get
 import io.javalin.apibuilder.ApiBuilder.path
 import io.javalin.core.routing.MissingBracketsException
 import io.javalin.core.routing.ParameterNamesNotUniqueException
 import io.javalin.core.routing.WildcardBracketAdjacentException
-import io.javalin.testing.HttpUtil
 import io.javalin.testing.TestUtil
 import okhttp3.OkHttpClient
 import okhttp3.Request
@@ -27,6 +28,56 @@ class TestRouting {
     private val okHttp = OkHttpClient().newBuilder().build()
     fun OkHttpClient.getBody(path: String) = this.newCall(Request.Builder().url(path).get().build()).execute().body!!.string()
 
+    @Test
+    fun `basic hello world works`() = TestUtil.test { app, http ->
+        app.get("/hello") { ctx -> ctx.result("Hello World") }
+        assertThat(http.getBody("/hello")).isEqualTo("Hello World")
+    }
+
+    @Test
+    fun `all mapped verbs return 200`() = TestUtil.test { app, http ->
+        app.get("/mapped", TestUtil.okHandler)
+        app.post("/mapped", TestUtil.okHandler)
+        app.put("/mapped", TestUtil.okHandler)
+        app.delete("/mapped", TestUtil.okHandler)
+        app.patch("/mapped", TestUtil.okHandler)
+        app.head("/mapped", TestUtil.okHandler)
+        app.options("/mapped", TestUtil.okHandler)
+        for (httpMethod in HttpMethod.values()) {
+            assertThat(http.call(httpMethod, "/mapped").status).isEqualTo(200)
+        }
+    }
+
+    @Test
+    fun `all unmapped verbs return 404`() = TestUtil.test { _, http ->
+        for (httpMethod in HttpMethod.values()) {
+            assertThat(http.call(httpMethod, "/unmapped").status).isEqualTo(404)
+        }
+    }
+
+    @Test
+    fun `HEAD returns 200 if GET is mapped`() = TestUtil.test { app, http ->
+        app.get("/mapped", TestUtil.okHandler)
+        assertThat(http.call(HttpMethod.HEAD, "/mapped").status).isEqualTo(200)
+    }
+
+    @Test
+    fun `urls are case sensitive`() = TestUtil.test { app, http ->
+        app.get("/My-Url") { ctx -> ctx.result("OK") }
+        assertThat(http.get("/My-Url").status).isEqualTo(200)
+        assertThat(http.get("/MY-URL").status).isEqualTo(404)
+        assertThat(http.get("/my-url").status).isEqualTo(404)
+    }
+
+    @Test
+    fun `filers are executed in order`() = TestUtil.test { app, http ->
+        app.before { ctx -> ctx.result("1") }
+        app.before { ctx -> ctx.result(ctx.resultString() + "2") }
+        app.get("/hello") { ctx -> ctx.result(ctx.resultString() + "Hello") }
+        app.after { ctx -> ctx.result(ctx.resultString() + "3") }
+        assertThat(http.getBody("/hello")).isEqualTo("12Hello3")
+    }
+
     @Test
     fun `colon in path throws exception`() {
         assertThatExceptionOfType(IllegalArgumentException::class.java)
@@ -37,13 +88,15 @@ class TestRouting {
     @Test
     fun `wildcard first works`() = TestUtil.test { app, http ->
         app.get("/*/test") { it.result("!") }
-        assertThat(http.getBody("/en/test")).isEqualTo("!")
+        assertThat(http.getBody("/tast/test")).isEqualTo("!")
     }
 
     @Test
-    fun `can add multiple path params in same path segment`() = TestUtil.test { app, http ->
-        app.get("/{p1}AND{p2}") { it.result(it.pathParamMap.toString()) }
-        assertThat(http.getBody("/param1ANDparam2")).isEqualTo("{p1=param1, p2=param2}")
+    fun `wildcard last works`() = TestUtil.test { app, http ->
+        app.get("/test/*") { it.result("!") }
+        assertThat(http.getBody("/test")).isEqualTo("Not found")
+        assertThat(http.getBody("/test/1")).isEqualTo("!")
+        assertThat(http.getBody("/test/tast")).isEqualTo("!")
     }
 
     @Test
@@ -53,18 +106,15 @@ class TestRouting {
     }
 
     @Test
-    fun `wildcard end works`() = TestUtil.test { app, http ->
-        app.get("/test/*") { it.result("!") }
-        assertThat(http.getBody("/test/en")).isEqualTo("!")
+    fun `path params work`() = TestUtil.test { app, http ->
+        app.get("/{p1}") { it.result(it.pathParamMap.toString()) }
+        assertThat(http.getBody("/param1")).isEqualTo("{p1=param1}")
     }
 
     @Test
-    fun `case sensitive urls work`() = TestUtil.test { app, http ->
-        app.get("/My-Url") { ctx -> ctx.result("OK") }
-        assertThat(http.get("/MY-URL").status).isEqualTo(404)
-        val response = http.get("/My-Url")
-        assertThat(response.status).isEqualTo(200)
-        assertThat(response.body).isEqualTo("OK")
+    fun `can add multiple path params in same path segment`() = TestUtil.test { app, http ->
+        app.get("/{p1}AND{p2}") { it.result(it.pathParamMap.toString()) }
+        assertThat(http.getBody("/param1ANDparam2")).isEqualTo("{p1=param1, p2=param2}")
     }
 
     @Test
@@ -109,9 +159,10 @@ class TestRouting {
     }
 
     @Test
-    fun `double star does not consume text`() = TestUtil.test { app, http ->
-        app.get("/{name}**") { ctx -> ctx.result(ctx.pathParam("name")) }
+    fun `percentage operator does not consume text`() = TestUtil.test { app, http ->
+        app.get("/{name}*") { ctx -> ctx.result(ctx.pathParam("name")) }
         assertThat(http.getBody("/text")).isEqualTo("text")
+        assertThat(http.getBody("/text/two")).isEqualTo("text")
     }
 
     @Test(expected = WildcardBracketAdjacentException::class)
@@ -120,25 +171,19 @@ class TestRouting {
     }
 
     @Test
-    fun `wildcards and path-params are compatible without glue`() = TestUtil.test { app, http ->
-        app.get("/hi-{name*}") { ctx -> ctx.result(ctx.pathParam("name*")) }
-        assertThat(http.getBody("/hi-world")).isEqualTo("world")
-    }
-
-    @Test
-    fun `splat path-params work`() = TestUtil.test { app, http ->
+    fun `angle-bracket path-params can accept slashes`() = TestUtil.test { app, http ->
         app.get("/<name>") { ctx -> ctx.result(ctx.pathParam("name")) }
         assertThat(http.getBody("/hi/with/slashes")).isEqualTo("hi/with/slashes")
     }
 
     @Test
-    fun `splat path-params can be combined with regular content`() = TestUtil.test { app, http ->
+    fun `angle-bracket path-params can be combined with regular content`() = TestUtil.test { app, http ->
         app.get("/hi/<name>") { ctx -> ctx.result(ctx.pathParam("name")) }
         assertThat(http.getBody("/hi/with/slashes")).isEqualTo("with/slashes")
     }
 
     @Test
-    fun `splat path-params can be combined with wildcards`() = TestUtil.test { app, http ->
+    fun `angle-bracket path-params can be combined with wildcards`() = TestUtil.test { app, http ->
         app.get("/hi-<name>-*") { ctx -> ctx.result(ctx.pathParam("name")) }
         assertThat(http.get("/hi-world").status).isEqualTo(404)
         val response = http.get("/hi-world/hi-not-included")
@@ -159,7 +204,9 @@ class TestRouting {
     fun `automatic slash prefixing works`() = TestUtil.test { app, http ->
         app.routes {
             path("test") {
-                path("{id}") { get { ctx -> ctx.result(ctx.pathParam("id")) } }
+                path("{id}") {
+                    get { ctx -> ctx.result(ctx.pathParam("id")) }
+                }
                 get { ctx -> ctx.result("test") }
             }
         }
@@ -168,25 +215,36 @@ class TestRouting {
     }
 
     @Test
-    fun `non sub-path wildcard works for paths`() = TestUtil.test { app, http ->
-        app.get("/p") { it.result("GET") }
-        app.get("/p/test") { it.result("GET") }
-        assertThat(http.getBody("/p")).isEqualTo("GET")
-        assertThat(http.getBody("/p/test")).isEqualTo("GET")
-        app.after("/p**") { it.result((it.resultString() ?: "") + "AFTER") }
-        assertThat(http.getBody("/p")).isEqualTo("GETAFTER")
-        assertThat(http.getBody("/p/test")).isEqualTo("GETAFTER")
+    fun `non sub-path star wildcard works for plain paths`() = TestUtil.test { app, http ->
+        app.get("/p") { it.result("1") }.also { assertThat(http.getBody("/p")).isEqualTo("1") }
+        app.get("/p-test") { it.result("2") }.also { assertThat(http.getBody("/p-test")).isEqualTo("2") }
+        app.after("/p*") { it.result("${it.resultString()}AFTER") }.also {
+            assertThat(http.getBody("/p")).isEqualTo("1AFTER")
+            assertThat(http.getBody("/p-test")).isEqualTo("2AFTER")
+        }
     }
 
     @Test
     fun `non sub-path wildcard works for path-params`() = TestUtil.test { app, http ->
-        app.get("/{pp}") { it.result(it.resultString() + it.pathParam("pp")) }
-        app.get("/{pp}/test") { it.result(it.resultString() + it.pathParam("pp")) }
-        assertThat(http.getBody("/123")).isEqualTo("null123")
-        assertThat(http.getBody("/123/test")).isEqualTo("null123")
-        app.before("/{pp}**") { it.result("BEFORE") }
-        assertThat(http.getBody("/123")).isEqualTo("BEFORE123")
-        assertThat(http.getBody("/123/test")).isEqualTo("BEFORE123")
+        app.get("/{pp}-test") { it.result("2") }.also { assertThat(http.getBody("/p-test")).isEqualTo("2") }
+        app.get("/{pp}") { it.result("1") }.also { assertThat(http.getBody("/p")).isEqualTo("1") }
+        app.after("/{pp}*") { it.result("${it.resultString()}AFTER") }.also {
+            assertThat(http.getBody("/p")).isEqualTo("1AFTER")
+            assertThat(http.getBody("/p-test")).isEqualTo("2AFTER")
+        }
+    }
+
+    @Test
+    fun `sub-path wildcard works for path-params`() = TestUtil.test { app, http ->
+        app.routes {
+            after("/partners/{pp}*") { it.result("${it.resultString()} - after") }
+            path("/partners/{pp}") {
+                get { it.result("root") }
+                get("/api") { it.result("api") }
+            }
+        }
+        assertThat(http.getBody("/partners/microsoft")).isEqualTo("root - after")
+        assertThat(http.getBody("/partners/microsoft/api")).isEqualTo("api - after")
     }
 
     @Test
@@ -211,53 +269,4 @@ class TestRouting {
         }
     }
 
-    @Test
-    fun `proposal works as expected`() = TestUtil.test { app, http ->
-        app.get("/a-*") { ctx -> ctx.result("A") }
-        app.get("/b-**") { ctx -> ctx.result("B") }
-        app.get("/c-<param>") { ctx -> ctx.result("C" + ctx.pathParam("param")) }
-        app.get("/d-{param}") { ctx -> ctx.result("D" + ctx.pathParam("param")) }
-        app.get("/e-<param>-end") { ctx -> ctx.result("E" + ctx.pathParam("param")) }
-        app.get("/f-{param}-end") { ctx -> ctx.result("F" + ctx.pathParam("param")) }
-        app.get("/g-***") { ctx -> ctx.result("G") }
-
-        proposalAssertions200(http)
-        nonMatchingAssertions(http)
-    }
-
-    private fun proposalAssertions200(http: HttpUtil) {
-        listOf(
-            "/a-" to "A", // this one is unexpected?
-            "/a-wildcard" to "A",
-            "/a-/other" to "A", // this one is unexpected?
-            "/b-" to "B",
-            "/b-/other" to "B",
-            "/c-hi" to "Chi",
-            "/c-with/slashes" to "Cwith/slashes",
-            "/d-hi" to "Dhi",
-            "/e-hi-end" to "Ehi",
-            "/e-with/slashes-end" to "Ewith/slashes",
-            "/f-hi-end" to "Fhi",
-            "/g-" to "G",
-            "/g-wildcard" to "G",
-            "/g-/other" to "G",
-        ).forEach { (path, body) ->
-            val response = http.get(path)
-            assertThat(response.status).`as`("$path - status").isEqualTo(200)
-            assertThat(response.body).`as`("$path - body").isEqualTo(body)
-        }
-    }
-
-    private fun nonMatchingAssertions(http: HttpUtil) {
-        listOf(
-            "/b-aa",
-            "/c-",
-            "/d-",
-            "/e-",
-            "/f-",
-        ).forEach {
-            val response = http.get(it)
-            assertThat(response.status).`as`("$it - 404").isEqualTo(404)
-        }
-    }
 }
diff --git a/javalin/src/test/java/io/javalin/http/ContentTypeTest.kt b/javalin/src/test/java/io/javalin/http/ContentTypeTest.kt
new file mode 100644
index 00000000..02711861
--- /dev/null
+++ b/javalin/src/test/java/io/javalin/http/ContentTypeTest.kt
@@ -0,0 +1,30 @@
+package io.javalin.http
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class ContentTypeTest {
+
+    @Test
+    fun `fetching content type by mime type should get the right type`() {
+        ContentType.values().forEach {
+            assertThat(ContentType.getContentType(it.mimeType)).isEqualTo(it)
+        }
+    }
+
+    @Test
+    fun `fetching content type by its extension should get the right type`() {
+        ContentType.values().forEach { type ->
+            type.extensions.forEach { extension ->
+                assertThat(ContentType.getContentTypeByExtension(extension)).isEqualTo(type)
+                assertThat(ContentType.getMimeTypeByExtension(extension)).isEqualTo(type.mimeType)
+            }
+        }
+    }
+
+    @Test
+    fun `string representation should return mime type`() {
+        assertThat(ContentType.TEXT_PLAIN.toString()).isEqualTo(ContentType.TEXT_PLAIN.mimeType)
+    }
+
+}