Skip to content

Commit ac67a4a

Browse files
Merge pull request #17083 from nextcloud/fix/rich-documents-file-download
fix(rich documents): download as
2 parents 9b760aa + 635d57a commit ac67a4a

4 files changed

Lines changed: 183 additions & 16 deletions

File tree

app/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.kt

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.owncloud.android.ui.asynctasks.RichDocumentsLoadUrlTask
3030
import com.owncloud.android.ui.fragment.OCFileListFragment
3131
import com.owncloud.android.utils.DisplayUtils
3232
import com.owncloud.android.utils.FileStorageUtils
33+
import com.owncloud.android.utils.RichDocumentDownloadAsParser
3334
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
3435
import org.json.JSONException
3536
import org.json.JSONObject
@@ -163,22 +164,16 @@ class RichDocumentsEditorWebView : EditorWebView() {
163164

164165
@JavascriptInterface
165166
fun downloadAs(json: String?) {
166-
try {
167-
json ?: return
168-
val downloadJson = JSONObject(json)
169-
val url = downloadJson.getString(URL).toUri()
170-
when (downloadJson.getString(TYPE)) {
171-
PRINT -> printFile(url)
167+
val result = RichDocumentDownloadAsParser.parse(json) ?: return
168+
val url = result.url.toUri()
169+
when (result.format) {
170+
PRINT -> printFile(url)
172171

173-
SLIDESHOW -> showSlideShow(url)
172+
SLIDESHOW -> showSlideShow(url)
174173

175-
else -> {
176-
val downloadFileName = downloadJson.optString(FILENAME, fileName)
177-
downloadFile(url, downloadFileName)
178-
}
174+
else -> {
175+
downloadFile(url, result.filename)
179176
}
180-
} catch (e: JSONException) {
181-
Log_OC.e(this, "Failed to parse download json message: $e")
182177
}
183178
}
184179

@@ -218,12 +213,9 @@ class RichDocumentsEditorWebView : EditorWebView() {
218213
}
219214

220215
companion object {
221-
private const val URL = "URL"
222216
private const val HYPERLINK = "Url"
223-
private const val TYPE = "Type"
224217
private const val PRINT = "print"
225218
private const val SLIDESHOW = "slideshow"
226219
private const val NEW_NAME = "NewName"
227-
private const val FILENAME = "filename"
228220
}
229221
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.owncloud.android.ui.model
9+
10+
data class DownloadAs(val format: String, val filename: String, val url: String)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.owncloud.android.utils
9+
10+
import com.owncloud.android.lib.common.utils.Log_OC
11+
import com.owncloud.android.ui.model.DownloadAs
12+
import kotlinx.serialization.json.Json
13+
import kotlinx.serialization.json.JsonObject
14+
import kotlinx.serialization.json.contentOrNull
15+
import kotlinx.serialization.json.jsonObject
16+
import kotlinx.serialization.json.jsonPrimitive
17+
18+
object RichDocumentDownloadAsParser {
19+
20+
private const val TAG = "RichDocumentDownloadAsParser"
21+
private const val URL_UPPERCASE = "URL"
22+
private const val URL_LOWERCASE = "url"
23+
24+
private const val FORMAT = "format"
25+
private const val NAME = "name"
26+
private const val TYPE = "Type"
27+
private const val FILENAME = "filename"
28+
29+
private val json = Json { ignoreUnknownKeys = true }
30+
31+
@Suppress("TooGenericExceptionCaught")
32+
fun parse(jsonString: String?): DownloadAs? {
33+
if (jsonString.isNullOrBlank()) return null
34+
35+
return try {
36+
val obj = json.parseToJsonElement(jsonString).jsonObject
37+
val url = obj[URL_LOWERCASE]?.jsonPrimitive?.contentOrNull
38+
?: obj[URL_UPPERCASE]?.jsonPrimitive?.contentOrNull
39+
tryParseV2(obj, url) ?: tryParseV1(obj, url)
40+
} catch (e: Exception) {
41+
Log_OC.e(TAG, "parse failed: $e")
42+
null
43+
}
44+
}
45+
46+
private fun tryParseV2(obj: JsonObject, url: String?): DownloadAs? {
47+
val format = obj[FORMAT]?.jsonPrimitive?.contentOrNull
48+
val name = obj[NAME]?.jsonPrimitive?.contentOrNull
49+
if (format == null || url == null) return null
50+
return DownloadAs(format = format, filename = name ?: "", url = url)
51+
}
52+
53+
private fun tryParseV1(obj: JsonObject, url: String?): DownloadAs? {
54+
val type = obj[TYPE]?.jsonPrimitive?.contentOrNull
55+
val filename = obj[FILENAME]?.jsonPrimitive?.contentOrNull
56+
if (type == null || url == null) return null
57+
return DownloadAs(format = type, filename = filename ?: "", url = url)
58+
}
59+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.owncloud.android.ui.model
9+
10+
import com.owncloud.android.utils.RichDocumentDownloadAsParser
11+
import org.junit.Assert.assertEquals
12+
import org.junit.Assert.assertNotNull
13+
import org.junit.Assert.assertNull
14+
import org.junit.Test
15+
16+
class RichDocumentDownloadAsParserTests {
17+
18+
@Test
19+
fun `parse returns null for null-like blank input`() {
20+
assertNull(RichDocumentDownloadAsParser.parse(""))
21+
assertNull(RichDocumentDownloadAsParser.parse(" "))
22+
}
23+
24+
@Test
25+
fun `parse returns null for malformed json`() {
26+
assertNull(RichDocumentDownloadAsParser.parse("not json at all"))
27+
assertNull(RichDocumentDownloadAsParser.parse("{invalid}"))
28+
}
29+
30+
@Test
31+
fun `parse returns null when required v2 fields are missing`() {
32+
assertNull(RichDocumentDownloadAsParser.parse("""{"format":"pdf"}"""))
33+
assertNull(RichDocumentDownloadAsParser.parse("""{"format":"pdf","name":"file.pdf"}"""))
34+
}
35+
36+
@Test
37+
fun `parse returns null when required v1 fields are missing`() {
38+
assertNull(RichDocumentDownloadAsParser.parse("""{"Type":"print"}"""))
39+
assertNull(RichDocumentDownloadAsParser.parse("""{"Type":"print","filename":"file.pdf"}"""))
40+
}
41+
42+
@Test
43+
fun `parse v2 with lowercase url succeeds`() {
44+
val json = """{"format":"pdf","name":"document.pdf","url":"https://example.com/file.pdf"}"""
45+
val result = RichDocumentDownloadAsParser.parse(json)
46+
assertNotNull(result)
47+
assertEquals("pdf", result!!.format)
48+
assertEquals("document.pdf", result.filename)
49+
assertEquals("https://example.com/file.pdf", result.url)
50+
}
51+
52+
@Test
53+
fun `parse v2 with uppercase URL succeeds`() {
54+
val json = """{"format":"pdf","name":"document.pdf","URL":"https://example.com/file.pdf"}"""
55+
val result = RichDocumentDownloadAsParser.parse(json)
56+
assertNotNull(result)
57+
assertEquals("pdf", result!!.format)
58+
assertEquals("document.pdf", result.filename)
59+
assertEquals("https://example.com/file.pdf", result.url)
60+
}
61+
62+
@Test
63+
fun `parse v2 with format print succeeds`() {
64+
val json = """{"format":"print","name":"document.pdf","url":"https://example.com/file.pdf"}"""
65+
val result = RichDocumentDownloadAsParser.parse(json)
66+
assertNotNull(result)
67+
assertEquals("print", result!!.format)
68+
}
69+
70+
@Test
71+
fun `parse v2 with format slideshow succeeds`() {
72+
val json = """{"format":"slideshow","name":"slides.pdf","url":"https://example.com/slides.pdf"}"""
73+
val result = RichDocumentDownloadAsParser.parse(json)
74+
assertNotNull(result)
75+
assertEquals("slideshow", result!!.format)
76+
}
77+
78+
@Test
79+
fun `parse v1 with uppercase URL succeeds`() {
80+
val json = """{"Type":"print","URL":"https://example.com/file.pdf","filename":"file.pdf"}"""
81+
val result = RichDocumentDownloadAsParser.parse(json)
82+
assertNotNull(result)
83+
assertEquals("print", result!!.format)
84+
assertEquals("file.pdf", result.filename)
85+
assertEquals("https://example.com/file.pdf", result.url)
86+
}
87+
88+
@Test
89+
fun `parse v1 with lowercase url succeeds`() {
90+
val json = """{"Type":"print","url":"https://example.com/file.pdf","filename":"file.pdf"}"""
91+
val result = RichDocumentDownloadAsParser.parse(json)
92+
assertNotNull(result)
93+
assertEquals("print", result!!.format)
94+
assertEquals("file.pdf", result.filename)
95+
assertEquals("https://example.com/file.pdf", result.url)
96+
}
97+
98+
@Test
99+
fun `parse v1 with slideshow type succeeds`() {
100+
val json = """{"Type":"slideshow","URL":"https://example.com/slides.pdf","filename":"slides.pdf"}"""
101+
val result = RichDocumentDownloadAsParser.parse(json)
102+
assertNotNull(result)
103+
assertEquals("slideshow", result!!.format)
104+
assertEquals("slides.pdf", result.filename)
105+
}
106+
}

0 commit comments

Comments
 (0)