Repository: tipsy/javalin
Pull request #1153 (2021-01-25 19:18:52)

[javalinvue] Fix HTML encoding/decoding of params


Patch:
diff --git a/javalin/src/main/java/io/javalin/plugin/rendering/vue/JavalinVue.kt b/javalin/src/main/java/io/javalin/plugin/rendering/vue/JavalinVue.kt
index 606c4c26..1d3b4dd2 100644
--- a/javalin/src/main/java/io/javalin/plugin/rendering/vue/JavalinVue.kt
+++ b/javalin/src/main/java/io/javalin/plugin/rendering/vue/JavalinVue.kt
@@ -66,8 +66,8 @@ object JavalinVue {
 
     internal fun getState(ctx: Context, state: Any?) = "\n<script>\n" + """
         |    Vue.prototype.${"$"}javalin = {
-        |        pathParams: ${JavalinJson.toJson(ctx.pathParamMap().mapKeys { escape(it.key) }.mapValues { escape(it.value) })},
-        |        queryParams: ${JavalinJson.toJson(ctx.queryParamMap().mapKeys { escape(it.key) }.mapValues { it.value.map { escape(it) } })},
+        |        pathParams: `${htmlEscape(JavalinJson.toJson(ctx.pathParamMap()))}`,
+        |        queryParams: `${htmlEscape(JavalinJson.toJson(ctx.queryParamMap()))}`,
         |        state: ${JavalinJson.toJson(state ?: stateFunction(ctx))}
         |    }""".trimMargin() + "\n</script>\n" + decodeParams()
 
@@ -78,7 +78,7 @@ object JavalinVue {
         |        return textArea.value;
         |    }
         |    ["queryParams", "pathParams", "state"].forEach((key) => {
-        |        Vue.prototype.${"$"}javalin[key] = JSON.parse(____decode(JSON.stringify(Vue.prototype.${"$"}javalin[key])));
+        |        Vue.prototype.${"$"}javalin[key] = JSON.parse(____decode(Vue.prototype.${"$"}javalin[key]));
         |    });""".trimMargin() + "\n</script>\n"
 
     private fun String.replaceWebjarsWithCdn() =
@@ -148,7 +148,7 @@ object FileInliner {
 fun Path.readText() = String(Files.readAllBytes(this))
 fun Path.isVueFile() = this.toString().endsWith(".vue")
 
-fun escape(string: String?) = string?.toCharArray()?.map {
+fun htmlEscape(string: String?) = string?.toCharArray()?.map {
     when (it) {
         '<' -> "&lt;"
         '>' -> "&gt;"


Test patch:
diff --git a/javalin/src/test/java/io/javalin/TestJavalinVue.kt b/javalin/src/test/java/io/javalin/TestJavalinVue.kt
index f0c7a8a0..7d01d6de 100644
--- a/javalin/src/test/java/io/javalin/TestJavalinVue.kt
+++ b/javalin/src/test/java/io/javalin/TestJavalinVue.kt
@@ -8,6 +8,7 @@ package io.javalin
 
 import io.javalin.http.Context
 import io.javalin.http.staticfiles.Location
+import io.javalin.plugin.json.JavalinJson
 import io.javalin.plugin.rendering.vue.JavalinVue
 import io.javalin.plugin.rendering.vue.VueComponent
 import io.javalin.testing.TestUtil
@@ -44,13 +45,14 @@ class TestJavalinVue {
 
     @Test
     fun `vue component with state`() = TestUtil.test { app, http ->
+        val q = "&quot;"
         JavalinVue.stateFunction = { ctx -> state }
         app.get("/vue/:my-param", VueComponent("<test-component></test-component>"))
         val res = http.getBody("/vue/test-path-param?qp=test-query-param")
         assertThat(res).contains("""
                 |    Vue.prototype.${"$"}javalin = {
-                |        pathParams: {"my-param":"test-path-param"},
-                |        queryParams: {"qp":["test-query-param"]},
+                |        pathParams: `{${q}my-param${q}:${q}test-path-param${q}}`,
+                |        queryParams: `{${q}qp${q}:[${q}test-query-param${q}]}`,
                 |        state: {"user":{"name":"tipsy","email":"tipsy@tipsy.tipsy"},"role":{"name":"Maintainer"}}
                 |    }""".trimMargin())
         assertThat(res).contains("""Vue.component("test-component", {template: "#test-component"});""")
@@ -61,8 +63,8 @@ class TestJavalinVue {
     fun `vue component without state`() = TestUtil.test { app, http ->
         app.get("/no-state", VueComponent("<test-component></test-component>"))
         val res = http.getBody("/no-state")
-        assertThat(res).contains("""pathParams: {}""")
-        assertThat(res).contains("""queryParams: {}""")
+        assertThat(res).contains("""pathParams: `{}`""")
+        assertThat(res).contains("""queryParams: `{}`""")
         assertThat(res).contains("""state: {}""")
         assertThat(res).contains("<body><test-component></test-component></body>")
     }
@@ -102,6 +104,13 @@ class TestJavalinVue {
         assertThat(http.getBody("/escaped?key=${xss}")).contains("&lt;script&gt;alert(1)&lt;script&gt;")
     }
 
+    @Test
+    fun `quotes are handled correctly`() = TestUtil.test { app, http ->
+        val q = "&quot;"
+        app.get("/escaped", VueComponent("<test-component></test-component>"))
+        assertThat(http.getBody("""/escaped?test=%22cool%22""")).contains("""`{${q}test${q}:[${q}\${q}cool\${q}${q}]}`""")
+    }
+
     @Test
     fun `component shorthand works`() = TestUtil.test { app, http ->
         app.get("/shorthand", VueComponent("test-component"))
diff --git a/javalin/src/test/java/io/javalin/TestWebBrowser.kt b/javalin/src/test/java/io/javalin/TestWebBrowser.kt
index 14c5e24f..2f324138 100644
--- a/javalin/src/test/java/io/javalin/TestWebBrowser.kt
+++ b/javalin/src/test/java/io/javalin/TestWebBrowser.kt
@@ -18,7 +18,6 @@ import org.junit.AfterClass
 import org.junit.Assume
 import org.junit.BeforeClass
 import org.junit.Test
-import org.openqa.selenium.WebDriver
 import org.openqa.selenium.chrome.ChromeDriver
 import org.openqa.selenium.chrome.ChromeOptions
 import java.io.File
@@ -100,12 +99,15 @@ class TestWebBrowser {
 
     @Test
     fun `path params are not html-encoded on the Vue prototype`() = TestUtil.test { app, http ->
+        val q = "&quot;"
         TestJavalinVue.before()
         app.get("/vue/:my-param", VueComponent("<test-component></test-component>"))
         driver.get(http.origin + "/vue/odd&co")
-        assertThat(driver.pageSource).contains("""pathParams: {"my-param":"odd&amp;co"},""")
-        val pathParams = driver.executeScript("""return Vue.prototype.${"$"}javalin.pathParams["my-param"]""") as String
-        assertThat(pathParams).isEqualTo("odd&co")
+        assertThat(driver.pageSource).contains("""pathParams: `{${q}my-param${q}:${q}odd&amp;co${q}}`,""")
+        val pathParam = driver.executeScript("""return Vue.prototype.${"$"}javalin.pathParams["my-param"]""") as String
+        assertThat(pathParam).isEqualTo("odd&co")
+        val nullParam = driver.executeScript("""return Vue.prototype.${"$"}javalin.queryParams["my-param"]""") as String?
+        assertThat(nullParam).isNull()
     }
 
 }