aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-09-16 16:35:01 +0200
committerFerdinand Thiessen <opensource@fthiessen.de>2024-09-28 13:18:29 +0200
commit0f6760c810e370023728d93a31f69c79dc5c3e3d (patch)
treeec8ac8201ef131b1f1727b33060c731b915414b0 /apps/files/src
parent2f66bd5b754f072a3cfeda759d71a479e4538350 (diff)
downloadnextcloud-server-0f6760c810e370023728d93a31f69c79dc5c3e3d.tar.gz
nextcloud-server-0f6760c810e370023728d93a31f69c79dc5c3e3d.zip
feat(files): Make the files download action use WebDAV zip download
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps/files/src')
-rw-r--r--apps/files/src/actions/downloadAction.spec.ts4
-rw-r--r--apps/files/src/actions/downloadAction.ts96
2 files changed, 58 insertions, 42 deletions
diff --git a/apps/files/src/actions/downloadAction.spec.ts b/apps/files/src/actions/downloadAction.spec.ts
index 2c24625e90e..2d42de5b8b1 100644
--- a/apps/files/src/actions/downloadAction.spec.ts
+++ b/apps/files/src/actions/downloadAction.spec.ts
@@ -141,7 +141,7 @@ describe('Download action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(link.download).toEqual('')
- expect(link.href.startsWith('/index.php/apps/files/ajax/download.php?dir=%2F&files=%5B%22FooBar%22%5D&downloadStartSecret=')).toBe(true)
+ expect(link.href).toMatch('https://cloud.domain.com/remote.php/dav/files/admin/FooBar/?accept=zip')
expect(link.click).toHaveBeenCalledTimes(1)
})
@@ -166,7 +166,7 @@ describe('Download action execute tests', () => {
// Silent action
expect(exec).toStrictEqual([null, null])
expect(link.download).toEqual('')
- expect(link.href.startsWith('/index.php/apps/files/ajax/download.php?dir=%2FDir&files=%5B%22foo.txt%22%2C%22bar.txt%22%5D&downloadStartSecret=')).toBe(true)
+ expect(link.href).toMatch('https://cloud.domain.com/remote.php/dav/files/admin/Dir/?accept=zip&files=%5B%22foo.txt%22%2C%22bar.txt%22%5D')
expect(link.click).toHaveBeenCalledTimes(1)
})
})
diff --git a/apps/files/src/actions/downloadAction.ts b/apps/files/src/actions/downloadAction.ts
index 97d1cc773d4..19e0b3502fa 100644
--- a/apps/files/src/actions/downloadAction.ts
+++ b/apps/files/src/actions/downloadAction.ts
@@ -2,11 +2,8 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import { FileAction, Node, FileType, View, DefaultType } from '@nextcloud/files'
+import { FileAction, Node, FileType, DefaultType } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
-import { generateUrl } from '@nextcloud/router'
-import { getSharingToken, isPublicShare } from '@nextcloud/sharing/public'
-import { basename } from 'path'
import { isDownloadable } from '../utils/permissions'
import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw'
@@ -18,25 +15,57 @@ const triggerDownload = function(url: string) {
hiddenElement.click()
}
-const downloadNodes = function(dir: string, nodes: Node[]) {
- const secret = Math.random().toString(36).substring(2)
- let url: string
- if (isPublicShare()) {
- url = generateUrl('/s/{token}/download/{filename}?path={dir}&files={files}&downloadStartSecret={secret}', {
- dir,
- secret,
- files: JSON.stringify(nodes.map(node => node.basename)),
- token: getSharingToken(),
- filename: `${basename(dir)}.zip}`,
- })
+/**
+ * Find the longest common path prefix of both input paths
+ * @param first The first path
+ * @param second The second path
+ */
+function longestCommonPath(first: string, second: string): string {
+ const firstSegments = first.split('/').filter(Boolean)
+ const secondSegments = second.split('/').filter(Boolean)
+ let base = ''
+ for (const [index, segment] of firstSegments.entries()) {
+ if (index >= second.length) {
+ break
+ }
+ if (segment !== secondSegments[index]) {
+ break
+ }
+ const sep = base === '' ? '' : '/'
+ base = `${base}${sep}${segment}`
+ }
+ return base
+}
+
+const downloadNodes = function(nodes: Node[]) {
+ let url: URL
+
+ if (nodes.length === 1) {
+ if (nodes[0].type === FileType.File) {
+ return triggerDownload(nodes[0].encodedSource)
+ } else {
+ url = new URL(nodes[0].encodedSource)
+ url.searchParams.append('accept', 'zip')
+ }
} else {
- url = generateUrl('/apps/files/ajax/download.php?dir={dir}&files={files}&downloadStartSecret={secret}', {
- dir,
- secret,
- files: JSON.stringify(nodes.map(node => node.basename)),
- })
+ url = new URL(nodes[0].source)
+ let base = url.pathname
+ for (const node of nodes.slice(1)) {
+ base = longestCommonPath(base, (new URL(node.source).pathname))
+ }
+ url.pathname = base
+
+ // The URL contains the path encoded so we need to decode as the query.append will re-encode it
+ const filenames = nodes.map((node) => decodeURI(node.encodedSource.slice(url.href.length + 1)))
+ url.searchParams.append('accept', 'zip')
+ url.searchParams.append('files', JSON.stringify(filenames))
}
- triggerDownload(url)
+
+ if (url.pathname.at(-1) !== '/') {
+ url.pathname = `${url.pathname}/`
+ }
+
+ return triggerDownload(url.href)
}
export const action = new FileAction({
@@ -51,34 +80,21 @@ export const action = new FileAction({
return false
}
- // We can download direct dav files. But if we have
- // some folders, we need to use the /apps/files/ajax/download.php
- // endpoint, which only supports user root folder.
- if (nodes.some(node => node.type === FileType.Folder)
- && nodes.some(node => !node.root?.startsWith('/files'))) {
+ // We can only download dav files and folders.
+ if (nodes.some(node => !node.isDavRessource)) {
return false
}
return nodes.every(isDownloadable)
},
- async exec(node: Node, view: View, dir: string) {
- if (node.type === FileType.Folder) {
- downloadNodes(dir, [node])
- return null
- }
-
- triggerDownload(node.encodedSource)
+ async exec(node: Node) {
+ downloadNodes([node])
return null
},
- async execBatch(nodes: Node[], view: View, dir: string) {
- if (nodes.length === 1) {
- this.exec(nodes[0], view, dir)
- return [null]
- }
-
- downloadNodes(dir, nodes)
+ async execBatch(nodes: Node[]) {
+ downloadNodes(nodes)
return new Array(nodes.length).fill(null)
},