From a971293b20e1d33940c4edcc9804cb46bd9bcf56 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 22 May 2026 14:48:32 +0300 Subject: [PATCH 1/5] wip Signed-off-by: alperozturk96 --- .../ui/activity/RichDocumentsEditorWebView.kt | 30 ++++---- .../owncloud/android/ui/model/DownloadAs.kt | 68 +++++++++++++++++++ 2 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/ui/model/DownloadAs.kt 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..9e3a8c6163c1 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 @@ -28,6 +28,8 @@ import com.owncloud.android.operations.RichDocumentsCreateAssetOperation import com.owncloud.android.ui.asynctasks.PrintAsyncTask import com.owncloud.android.ui.asynctasks.RichDocumentsLoadUrlTask import com.owncloud.android.ui.fragment.OCFileListFragment +import com.owncloud.android.ui.model.DownloadAsV1 +import com.owncloud.android.ui.model.DownloadAsV2 import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.FileStorageUtils import edu.umd.cs.findbugs.annotations.SuppressFBWarnings @@ -163,22 +165,24 @@ 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) + if (json.isNullOrBlank()) return - SLIDESHOW -> showSlideShow(url) + var result = DownloadAsV2.tryDeserialize(json) + if (result == null) { + result = DownloadAsV1.tryDeserialize(json) + } - else -> { - val downloadFileName = downloadJson.optString(FILENAME, fileName) - downloadFile(url, downloadFileName) - } + if (result == null) { + return + } + + val url = result.url.toUri() + when (result.format) { + PRINT -> printFile(url) + SLIDESHOW -> showSlideShow(url) + else -> { + downloadFile(url, result.fileName) } - } catch (e: JSONException) { - Log_OC.e(this, "Failed to parse download json message: $e") } } 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..c524d476751b --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/model/DownloadAs.kt @@ -0,0 +1,68 @@ +/* + * 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.lib.common.utils.Log_OC +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json + +@Serializable +data class DownloadAsV1( + @SerialName("Type") val type: String, + @SerialName("URL") val url: String, + @SerialName("filename") val fileName: String +) { + companion object { + fun tryDeserialize(json: String): DownloadAsResult? { + return try { + val v1 = jsonImpl.decodeFromString(json) + return DownloadAsResult( + format = v1.type, + fileName = v1.fileName, + url = v1.url + ) + } catch (e: SerializationException) { + Log_OC.e("DownloadAsV1", "tryDeserialize: $e") + null + } + } + } +} + +@Serializable +data class DownloadAsV2( + @SerialName("format") val format: String, + @SerialName("name") val fileName: String, + @SerialName("url") val url: String +) { + companion object { + fun tryDeserialize(json: String): DownloadAsResult? { + return try { + val v2 = jsonImpl.decodeFromString(json) + return DownloadAsResult( + format = v2.format, + fileName = v2.fileName, + url = v2.url + ) + } catch (e: SerializationException) { + Log_OC.e("DownloadAsV2", "tryDeserialize: $e") + null + } + } + } +} + +data class DownloadAsResult( + val format: String, + val fileName: String, + val url: String +) + +private val jsonImpl = Json { ignoreUnknownKeys = true } From 132432cf3f515412e19b79ec30fc6ac5b4d51f11 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 22 May 2026 15:02:36 +0300 Subject: [PATCH 2/5] wip Signed-off-by: alperozturk96 --- .../ui/activity/RichDocumentsEditorWebView.kt | 15 +- .../utils/RichDocumentDownloadAsParser.kt | 25 ++++ .../RichDocumentDownloadAsParserTests.kt | 130 ++++++++++++++++++ 3 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt create mode 100644 app/src/test/java/com/owncloud/android/ui/model/RichDocumentDownloadAsParserTests.kt 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 9e3a8c6163c1..18da033cfdd1 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 @@ -28,10 +28,9 @@ import com.owncloud.android.operations.RichDocumentsCreateAssetOperation import com.owncloud.android.ui.asynctasks.PrintAsyncTask import com.owncloud.android.ui.asynctasks.RichDocumentsLoadUrlTask import com.owncloud.android.ui.fragment.OCFileListFragment -import com.owncloud.android.ui.model.DownloadAsV1 -import com.owncloud.android.ui.model.DownloadAsV2 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 @@ -165,17 +164,7 @@ class RichDocumentsEditorWebView : EditorWebView() { @JavascriptInterface fun downloadAs(json: String?) { - if (json.isNullOrBlank()) return - - var result = DownloadAsV2.tryDeserialize(json) - if (result == null) { - result = DownloadAsV1.tryDeserialize(json) - } - - if (result == null) { - return - } - + val result = RichDocumentDownloadAsParser.parse(json) ?: return val url = result.url.toUri() when (result.format) { PRINT -> printFile(url) 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..e5934786899a --- /dev/null +++ b/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt @@ -0,0 +1,25 @@ +/* + * 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.ui.model.DownloadAsResult +import com.owncloud.android.ui.model.DownloadAsV1 +import com.owncloud.android.ui.model.DownloadAsV2 + +object RichDocumentDownloadAsParser { + fun parse(json: String?): DownloadAsResult? { + if (json.isNullOrBlank()) return null + + var result = DownloadAsV2.tryDeserialize(json) + if (result == null) { + result = DownloadAsV1.tryDeserialize(json) + } + + return result + } +} 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..c91fa3d8eb40 --- /dev/null +++ b/app/src/test/java/com/owncloud/android/ui/model/RichDocumentDownloadAsParserTests.kt @@ -0,0 +1,130 @@ +/* + * 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) + } + + @Test + fun `parse v2 lowercase url takes precedence over uppercase URL when both present`() { + val json = """{"format":"pdf","name":"doc.pdf","url":"https://lower.com","URL":"https://upper.com"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("https://lower.com", result!!.url) + } + + @Test + fun `parse v2 falls back to uppercase URL when lowercase url is absent`() { + val json = """{"format":"pdf","name":"doc.pdf","URL":"https://upper.com"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("https://upper.com", result!!.url) + } + + @Test + fun `parse v1 falls back to uppercase URL when lowercase url is absent`() { + val json = """{"Type":"download","URL":"https://upper.com","filename":"doc.pdf"}""" + val result = RichDocumentDownloadAsParser.parse(json) + assertNotNull(result) + assertEquals("https://upper.com", result!!.url) + } +} From 9072d33c4e6271cf3a425be1a84c6d7a68d27aff Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 22 May 2026 15:14:12 +0300 Subject: [PATCH 3/5] wip Signed-off-by: alperozturk96 --- .../ui/activity/RichDocumentsEditorWebView.kt | 5 +- .../owncloud/android/ui/model/DownloadAs.kt | 60 +------------------ .../utils/RichDocumentDownloadAsParser.kt | 51 +++++++++++++--- 3 files changed, 45 insertions(+), 71 deletions(-) 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 18da033cfdd1..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 @@ -168,7 +168,9 @@ class RichDocumentsEditorWebView : EditorWebView() { val url = result.url.toUri() when (result.format) { PRINT -> printFile(url) + SLIDESHOW -> showSlideShow(url) + else -> { downloadFile(url, result.fileName) } @@ -211,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 index c524d476751b..6e330a16358c 100644 --- a/app/src/main/java/com/owncloud/android/ui/model/DownloadAs.kt +++ b/app/src/main/java/com/owncloud/android/ui/model/DownloadAs.kt @@ -7,62 +7,4 @@ package com.owncloud.android.ui.model -import com.owncloud.android.lib.common.utils.Log_OC -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.Json - -@Serializable -data class DownloadAsV1( - @SerialName("Type") val type: String, - @SerialName("URL") val url: String, - @SerialName("filename") val fileName: String -) { - companion object { - fun tryDeserialize(json: String): DownloadAsResult? { - return try { - val v1 = jsonImpl.decodeFromString(json) - return DownloadAsResult( - format = v1.type, - fileName = v1.fileName, - url = v1.url - ) - } catch (e: SerializationException) { - Log_OC.e("DownloadAsV1", "tryDeserialize: $e") - null - } - } - } -} - -@Serializable -data class DownloadAsV2( - @SerialName("format") val format: String, - @SerialName("name") val fileName: String, - @SerialName("url") val url: String -) { - companion object { - fun tryDeserialize(json: String): DownloadAsResult? { - return try { - val v2 = jsonImpl.decodeFromString(json) - return DownloadAsResult( - format = v2.format, - fileName = v2.fileName, - url = v2.url - ) - } catch (e: SerializationException) { - Log_OC.e("DownloadAsV2", "tryDeserialize: $e") - null - } - } - } -} - -data class DownloadAsResult( - val format: String, - val fileName: String, - val url: String -) - -private val jsonImpl = Json { ignoreUnknownKeys = true } +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 index e5934786899a..e07f1802e29c 100644 --- a/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt +++ b/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt @@ -7,19 +7,52 @@ package com.owncloud.android.utils -import com.owncloud.android.ui.model.DownloadAsResult -import com.owncloud.android.ui.model.DownloadAsV1 -import com.owncloud.android.ui.model.DownloadAsV2 +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 { - fun parse(json: String?): DownloadAsResult? { - if (json.isNullOrBlank()) return null - var result = DownloadAsV2.tryDeserialize(json) - if (result == null) { - result = DownloadAsV1.tryDeserialize(json) + 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 } + + 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) + } - return result + 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) } } From cab3b5c0a9e8e238d39acd640452c634aa4e53f8 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 22 May 2026 15:29:10 +0300 Subject: [PATCH 4/5] wip Signed-off-by: alperozturk96 --- .../com/owncloud/android/utils/RichDocumentDownloadAsParser.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt b/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt index e07f1802e29c..1ce992367567 100644 --- a/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt +++ b/app/src/main/java/com/owncloud/android/utils/RichDocumentDownloadAsParser.kt @@ -28,6 +28,7 @@ object RichDocumentDownloadAsParser { private val json = Json { ignoreUnknownKeys = true } + @Suppress("TooGenericExceptionCaught") fun parse(jsonString: String?): DownloadAs? { if (jsonString.isNullOrBlank()) return null From 5c866f411ece9e9c1d2a9a428d5f509c42cdfef0 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 22 May 2026 15:31:25 +0300 Subject: [PATCH 5/5] wip Signed-off-by: alperozturk96 --- .../RichDocumentDownloadAsParserTests.kt | 24 ------------------- 1 file changed, 24 deletions(-) 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 index c91fa3d8eb40..3d186a6d552e 100644 --- a/app/src/test/java/com/owncloud/android/ui/model/RichDocumentDownloadAsParserTests.kt +++ b/app/src/test/java/com/owncloud/android/ui/model/RichDocumentDownloadAsParserTests.kt @@ -103,28 +103,4 @@ class RichDocumentDownloadAsParserTests { assertEquals("slideshow", result!!.format) assertEquals("slides.pdf", result.fileName) } - - @Test - fun `parse v2 lowercase url takes precedence over uppercase URL when both present`() { - val json = """{"format":"pdf","name":"doc.pdf","url":"https://lower.com","URL":"https://upper.com"}""" - val result = RichDocumentDownloadAsParser.parse(json) - assertNotNull(result) - assertEquals("https://lower.com", result!!.url) - } - - @Test - fun `parse v2 falls back to uppercase URL when lowercase url is absent`() { - val json = """{"format":"pdf","name":"doc.pdf","URL":"https://upper.com"}""" - val result = RichDocumentDownloadAsParser.parse(json) - assertNotNull(result) - assertEquals("https://upper.com", result!!.url) - } - - @Test - fun `parse v1 falls back to uppercase URL when lowercase url is absent`() { - val json = """{"Type":"download","URL":"https://upper.com","filename":"doc.pdf"}""" - val result = RichDocumentDownloadAsParser.parse(json) - assertNotNull(result) - assertEquals("https://upper.com", result!!.url) - } }