Repository: javalin/javalin
Pull request #2405 (2025-05-16 01:54:21)

[Static Files] access files with special characters using encoded path
Create static files with special characters and access using the encoded path.
Workflow result:
https://github.com/hupender/javalin/actions/runs/15058856533

tipsy: Thanks, nice tests !

Patch:
diff --git a/javalin/src/main/java/io/javalin/jetty/JettyResourceHandler.kt b/javalin/src/main/java/io/javalin/jetty/JettyResourceHandler.kt
index 714e4298..384845af 100644
--- a/javalin/src/main/java/io/javalin/jetty/JettyResourceHandler.kt
+++ b/javalin/src/main/java/io/javalin/jetty/JettyResourceHandler.kt
@@ -23,6 +23,7 @@ import org.eclipse.jetty.server.handler.ResourceHandler
 import org.eclipse.jetty.util.URIUtil
 import org.eclipse.jetty.util.resource.EmptyResource
 import org.eclipse.jetty.util.resource.Resource
+import java.net.URLDecoder
 import java.nio.file.AccessDeniedException
 import java.nio.file.Files
 import kotlin.io.path.Path
@@ -53,7 +54,7 @@ class JettyResourceHandler(val pvt: PrivateConfig) : JavalinResourceHandler {
     override fun handle(ctx: Context): Boolean {
         nonSkippedHandlers(ctx.jettyReq()).forEach { handler ->
             try {
-                val target = ctx.target
+                val target = URLDecoder.decode(ctx.target, "UTF-8")
                 val fileOrWelcomeFile = fileOrWelcomeFile(handler, target)
                 if (fileOrWelcomeFile != null) {
                     handler.config.headers.forEach { ctx.header(it.key, it.value) } // set user headers


Test patch:
diff --git a/javalin/src/test/java/io/javalin/staticfiles/TestStaticFiles.kt b/javalin/src/test/java/io/javalin/staticfiles/TestStaticFiles.kt
index 204be8b3..3a099e21 100644
--- a/javalin/src/test/java/io/javalin/staticfiles/TestStaticFiles.kt
+++ b/javalin/src/test/java/io/javalin/staticfiles/TestStaticFiles.kt
@@ -20,6 +20,7 @@ import io.javalin.testing.TestDependency
 import io.javalin.testing.TestUtil
 import io.javalin.testing.TestUtil.TestLogsKey
 import io.javalin.testing.httpCode
+import io.javalin.util.JavalinLogger
 import jakarta.servlet.DispatcherType
 import jakarta.servlet.Filter
 import jakarta.servlet.FilterChain
@@ -34,6 +35,7 @@ import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.io.TempDir
 import java.io.File
 import java.io.IOException
+import java.net.URLEncoder
 import java.nio.file.Files
 import java.nio.file.Paths
 import java.util.*
@@ -391,4 +393,47 @@ class TestStaticFiles {
         assertThat(http.get("/file.javalin").headers.getFirst(Header.CONTENT_TYPE)).contains("application/x-javalin")
         assertThat(http.getBody("/file.javalin")).contains("TESTFILE.javalin")
     }
+
+    @Test
+    fun `access files with non encoded names with encoded url`()  = TestUtil.test(externalStaticResourceApp) { _, http ->
+        val dirName = File("src/test/external")
+        val symbols = listOf(
+            '"', '<', '>', '#', '{', '}', '\\', '^', '~', '[', ']',
+            '%', '|', '\'', ':', '/', '?', '&', '=', '@', '+', '$', ',', ' '
+        )
+        val illegalWindowsChars = listOf('<', '>', ':', '"', '/', '\\', '|', '?', '*')
+
+        symbols.map { it ->
+            if (System.getProperty("os.name").contains("windows", ignoreCase = true) && it in illegalWindowsChars) {
+                JavalinLogger.info("Skipped symbol as windows don't allow file creation with $it.")
+            } else {
+                val tempFile = File.createTempFile("test$it", ".txt", dirName)
+                var path = tempFile.name
+                path = URLEncoder.encode(path, "UTF-8")
+                assertThat(http.get("/$path").httpCode()).isEqualTo(OK)
+                tempFile.deleteOnExit()
+            }
+        }
+    }
+
+    @Test
+    fun `access files with encoded names with encoded url`()  = TestUtil.test(externalStaticResourceApp) { _, http ->
+        val dirName = File("src/test/external")
+        val symbols = listOf(
+            "\"", "<", ">", "#", "{", "}", "\\", "^", "~", "[", "]",
+            "%", "|", "'", ":", "/", "?", "&", "=", "@", "+", "$", ",", " "
+        )
+        symbols.map { it ->
+            val encodedName = URLEncoder.encode(it, "UTF-8")
+            try {
+                val tempFile = File.createTempFile("test$encodedName", ".txt", dirName)
+                val path = tempFile.name
+                assertThat(http.get("/$path").httpCode()).isEqualTo(NOT_FOUND)
+                tempFile.deleteOnExit()
+            } catch (e: IOException) {
+                JavalinLogger.warn("Unable to create file with symbol $it.")
+            }
+        }
+    }
+
 }