diff --git a/app/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.kt b/app/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.kt index 18dc8a2b477a..94fbc32338eb 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.kt @@ -30,6 +30,7 @@ import com.owncloud.android.ui.asynctasks.RichDocumentsLoadUrlTask import com.owncloud.android.ui.fragment.OCFileListFragment import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.FileStorageUtils +import com.owncloud.android.utils.RichDocumentDownloadAsParser import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.json.JSONException import org.json.JSONObject @@ -163,22 +164,16 @@ class RichDocumentsEditorWebView : EditorWebView() { @JavascriptInterface fun downloadAs(json: String?) { - try { - json ?: return - val downloadJson = JSONObject(json) - val url = downloadJson.getString(URL).toUri() - when (downloadJson.getString(TYPE)) { - PRINT -> printFile(url) + val result = RichDocumentDownloadAsParser.parse(json) ?: return + val url = result.url.toUri() + when (result.format) { + PRINT -> printFile(url) - SLIDESHOW -> showSlideShow(url) + SLIDESHOW -> showSlideShow(url) - else -> { - val downloadFileName = downloadJson.optString(FILENAME, fileName) - downloadFile(url, downloadFileName) - } + else -> { + downloadFile(url, result.fileName) } - } catch (e: JSONException) { - Log_OC.e(this, "Failed to parse download json message: $e") } } @@ -218,12 +213,9 @@ class RichDocumentsEditorWebView : EditorWebView() { } companion object { - private const val URL = "URL" private const val HYPERLINK = "Url" - private const val TYPE = "Type" private const val PRINT = "print" private const val SLIDESHOW = "slideshow" private const val NEW_NAME = "NewName" - private const val FILENAME = "filename" } } diff --git a/app/src/main/java/com/owncloud/android/ui/model/DownloadAs.kt b/app/src/main/java/com/owncloud/android/ui/model/DownloadAs.kt new file mode 100644 index 000000000000..6e330a16358c --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/model/DownloadAs.kt @@ -0,0 +1,10 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.model + +data class DownloadAs(val format: String, val fileName: String, val url: String) diff --git a/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt b/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt new file mode 100644 index 000000000000..1ce992367567 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt @@ -0,0 +1,59 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.utils + +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.model.DownloadAs +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +object RichDocumentDownloadAsParser { + + private const val TAG = "RichDocumentDownloadAsParser" + private const val URL_UPPERCASE = "URL" + private const val URL_LOWERCASE = "url" + + private const val FORMAT = "format" + private const val NAME = "name" + private const val TYPE = "Type" + private const val FILENAME = "filename" + + private val json = Json { ignoreUnknownKeys = true } + + @Suppress("TooGenericExceptionCaught") + fun parse(jsonString: String?): DownloadAs? { + if (jsonString.isNullOrBlank()) return null + + return try { + val obj = json.parseToJsonElement(jsonString).jsonObject + val url = obj[URL_LOWERCASE]?.jsonPrimitive?.contentOrNull + ?: obj[URL_UPPERCASE]?.jsonPrimitive?.contentOrNull + tryParseV2(obj, url) ?: tryParseV1(obj, url) + } catch (e: Exception) { + Log_OC.e(TAG, "parse failed: $e") + null + } + } + + private fun tryParseV2(obj: JsonObject, url: String?): DownloadAs? { + val format = obj[FORMAT]?.jsonPrimitive?.contentOrNull + val name = obj[NAME]?.jsonPrimitive?.contentOrNull + if (format == null || url == null) return null + return DownloadAs(format = format, fileName = name ?: "", url = url) + } + + private fun tryParseV1(obj: JsonObject, url: String?): DownloadAs? { + val type = obj[TYPE]?.jsonPrimitive?.contentOrNull + val filename = obj[FILENAME]?.jsonPrimitive?.contentOrNull + if (type == null || url == null) return null + return DownloadAs(format = type, fileName = filename ?: "", url = url) + } +} diff --git a/app/src/test/java/com/owncloud/android/ui/model/RichDocumentDownloadAsParserTests.kt b/app/src/test/java/com/owncloud/android/ui/model/RichDocumentDownloadAsParserTests.kt new file mode 100644 index 000000000000..3d186a6d552e --- /dev/null +++ b/app/src/test/java/com/owncloud/android/ui/model/RichDocumentDownloadAsParserTests.kt @@ -0,0 +1,106 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.model + +import com.owncloud.android.utils.RichDocumentDownloadAsParser +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Test + +class RichDocumentDownloadAsParserTests { + + @Test + fun `parse returns null for null-like blank input`() { + assertNull(RichDocumentDownloadAsParser.parse("")) + assertNull(RichDocumentDownloadAsParser.parse(" ")) + } + + @Test + fun `parse returns null for malformed json`() { + assertNull(RichDocumentDownloadAsParser.parse("not json at all")) + assertNull(RichDocumentDownloadAsParser.parse("{invalid}")) + } + + @Test + fun `parse returns null when required v2 fields are missing`() { + assertNull(RichDocumentDownloadAsParser.parse("""{"format":"pdf"}""")) + assertNull(RichDocumentDownloadAsParser.parse("""{"format":"pdf","name":"file.pdf"}""")) + } + + @Test + fun `parse returns null when required v1 fields are missing`() { + assertNull(RichDocumentDownloadAsParser.parse("""{"Type":"print"}""")) + assertNull(RichDocumentDownloadAsParser.parse("""{"Type":"print","filename":"file.pdf"}""")) + } + + @Test + fun `parse v2 with lowercase url succeeds`() { + val json = """{"format":"pdf","name":"document.pdf","url":"https://example.com/file.pdf"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("pdf", result!!.format) + assertEquals("document.pdf", result.fileName) + assertEquals("https://example.com/file.pdf", result.url) + } + + @Test + fun `parse v2 with uppercase URL succeeds`() { + val json = """{"format":"pdf","name":"document.pdf","URL":"https://example.com/file.pdf"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("pdf", result!!.format) + assertEquals("document.pdf", result.fileName) + assertEquals("https://example.com/file.pdf", result.url) + } + + @Test + fun `parse v2 with format print succeeds`() { + val json = """{"format":"print","name":"document.pdf","url":"https://example.com/file.pdf"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("print", result!!.format) + } + + @Test + fun `parse v2 with format slideshow succeeds`() { + val json = """{"format":"slideshow","name":"slides.pdf","url":"https://example.com/slides.pdf"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("slideshow", result!!.format) + } + + @Test + fun `parse v1 with uppercase URL succeeds`() { + val json = """{"Type":"print","URL":"https://example.com/file.pdf","filename":"file.pdf"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("print", result!!.format) + assertEquals("file.pdf", result.fileName) + assertEquals("https://example.com/file.pdf", result.url) + } + + @Test + fun `parse v1 with lowercase url succeeds`() { + val json = """{"Type":"print","url":"https://example.com/file.pdf","filename":"file.pdf"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("print", result!!.format) + assertEquals("file.pdf", result.fileName) + assertEquals("https://example.com/file.pdf", result.url) + } + + @Test + fun `parse v1 with slideshow type succeeds`() { + val json = """{"Type":"slideshow","URL":"https://example.com/slides.pdf","filename":"slides.pdf"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("slideshow", result!!.format) + assertEquals("slides.pdf", result.fileName) + } +}