[Android] Remote access: fix path traversal guard, streaming upload, and request handling in subtitle upload

Nicolas Pomepuy git at videolan.org
Wed Sep 9 06:43:37 UTC 2026


vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Thu Sep  3 14:47:24 2026 +0200| [7dd999225c2389a1011e4d16fa99721a88b351bc] | committer: Duncan McNamara

Remote access: fix path traversal guard, streaming upload, and request handling in subtitle upload

> https://code.videolan.org/videolan/vlc-android/commit/7dd999225c2389a1011e4d16fa99721a88b351bc
---

 .../vlc/remoteaccessserver/RemoteAccessRouting.kt    | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
index 3a53728f97..a4080f9aa3 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
@@ -258,7 +258,7 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
             call.respond(HttpStatusCode.Forbidden)
             return at post
         }
-        var fileName: String
+        var uploaded = false
         val multipartData = call.receiveMultipart()
 
         multipartData.forEachPart { part ->
@@ -266,14 +266,18 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
                 is PartData.FileItem -> {
                     val uploadDir = File("${AndroidDevices.MediaFolders.EXTERNAL_PUBLIC_DOWNLOAD_DIRECTORY_URI.path}/subtitles")
                     uploadDir.mkdirs()
-                    fileName = part.originalFileName ?: "subtitle.srt"
+                    val fileName = part.originalFileName?.let { File(it).name } ?: "subtitle.srt"
                     val file = File(uploadDir, fileName)
-                    if (file.canonicalFile.parent?.startsWith(uploadDir.absolutePath) != true) {
+                    if (!file.canonicalFile.canonicalPath.startsWith(uploadDir.canonicalPath + File.separator)) {
                         call.respond(HttpStatusCode.Unauthorized)
                         throw (IllegalStateException("${file.canonicalFile.parent} is not a valid path"))
                     }
-                    val fileBytes = part.streamProvider().readBytes()
-                    file.writeBytes(fileBytes)
+                    part.streamProvider().use { input ->
+                        file.outputStream().use { output ->
+                            input.copyTo(output)
+                        }
+                    }
+                    uploaded = true
 
                     val service = RemoteAccessServer.getInstance(appContext).service
                     if (service?.isPlaying == true) {
@@ -287,7 +291,11 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
                 else -> {}
             }
         }
-        call.respond(HttpStatusCode.OK)
+        if (uploaded) {
+            call.respond(HttpStatusCode.OK)
+        } else {
+            call.respond(HttpStatusCode.BadRequest)
+        }
     }
     // Download a log file
     get("/download-logfile") {



More information about the Android mailing list