diff options
-rw-r--r-- | apps/files/ajax/download.php | 55 | ||||
-rw-r--r-- | apps/files/appinfo/routes.php | 340 | ||||
-rw-r--r-- | apps/files/src/actions/downloadAction.spec.ts | 4 | ||||
-rw-r--r-- | apps/files/src/actions/downloadAction.ts | 96 | ||||
-rw-r--r-- | build/integration/features/bootstrap/Download.php | 3 | ||||
-rw-r--r-- | build/integration/files_features/download.feature | 56 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/download-files.cy.ts | 4 | ||||
-rw-r--r-- | tests/lib/UrlGeneratorTest.php | 14 |
8 files changed, 260 insertions, 312 deletions
diff --git a/apps/files/ajax/download.php b/apps/files/ajax/download.php deleted file mode 100644 index fc434f79e2c..00000000000 --- a/apps/files/ajax/download.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php - -/** - * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2016 ownCloud, Inc. - * SPDX-License-Identifier: AGPL-3.0-only - */ -// Check if we are a user -OC_Util::checkLoggedIn(); -\OC::$server->getSession()->close(); - -$files = isset($_GET['files']) ? (string)$_GET['files'] : ''; -$dir = isset($_GET['dir']) ? (string)$_GET['dir'] : ''; - -$files_list = json_decode($files); -// in case we get only a single file -if (!is_array($files_list)) { - $files_list = [$files]; -} - -/** - * @psalm-taint-escape cookie - */ -function cleanCookieInput(string $value): string { - if (strlen($value) > 32) { - return ''; - } - if (preg_match('!^[a-zA-Z0-9]+$!', $_GET['downloadStartSecret']) !== 1) { - return ''; - } - return $value; -} - -/** - * this sets a cookie to be able to recognize the start of the download - * the content must not be longer than 32 characters and must only contain - * alphanumeric characters - */ -if (isset($_GET['downloadStartSecret'])) { - $value = cleanCookieInput($_GET['downloadStartSecret']); - if ($value !== '') { - setcookie('ocDownloadStarted', $value, time() + 20, '/'); - } -} - -$server_params = [ 'head' => \OC::$server->getRequest()->getMethod() === 'HEAD' ]; - -/** - * Http range requests support - */ -if (isset($_SERVER['HTTP_RANGE'])) { - $server_params['range'] = \OC::$server->getRequest()->getHeader('Range'); -} - -OC_Files::get($dir, $files_list, $server_params); diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php index 487f6335d45..a67ec7cbc14 100644 --- a/apps/files/appinfo/routes.php +++ b/apps/files/appinfo/routes.php @@ -9,181 +9,169 @@ declare(strict_types=1); */ namespace OCA\Files\AppInfo; -use OCA\Files\Controller\OpenLocalEditorController; - -// Legacy routes above -/** @var \OC\Route\Router $this */ -$this->create('files_ajax_download', 'apps/files/ajax/download.php') - ->actionInclude('files/ajax/download.php'); - -/** @var Application $application */ -$application = \OC::$server->get(Application::class); -$application->registerRoutes( - $this, - [ - 'routes' => [ - [ - 'name' => 'view#index', - 'url' => '/', - 'verb' => 'GET', - ], - [ - 'name' => 'View#showFile', - 'url' => '/f/{fileid}', - 'verb' => 'GET', - 'root' => '', - ], - [ - 'name' => 'Api#getThumbnail', - 'url' => '/api/v1/thumbnail/{x}/{y}/{file}', - 'verb' => 'GET', - 'requirements' => ['file' => '.+'] - ], - [ - 'name' => 'Api#updateFileTags', - 'url' => '/api/v1/files/{path}', - 'verb' => 'POST', - 'requirements' => ['path' => '.+'], - ], - [ - 'name' => 'Api#getRecentFiles', - 'url' => '/api/v1/recent/', - 'verb' => 'GET' - ], - [ - 'name' => 'Api#getStorageStats', - 'url' => '/api/v1/stats', - 'verb' => 'GET' - ], - [ - 'name' => 'Api#setViewConfig', - 'url' => '/api/v1/views/{view}/{key}', - 'verb' => 'PUT' - ], - [ - 'name' => 'Api#setViewConfig', - 'url' => '/api/v1/views', - 'verb' => 'PUT' - ], - [ - 'name' => 'Api#getViewConfigs', - 'url' => '/api/v1/views', - 'verb' => 'GET' - ], - [ - 'name' => 'Api#setConfig', - 'url' => '/api/v1/config/{key}', - 'verb' => 'PUT' - ], - [ - 'name' => 'Api#getConfigs', - 'url' => '/api/v1/configs', - 'verb' => 'GET' - ], - [ - 'name' => 'Api#showHiddenFiles', - 'url' => '/api/v1/showhidden', - 'verb' => 'POST' - ], - [ - 'name' => 'Api#cropImagePreviews', - 'url' => '/api/v1/cropimagepreviews', - 'verb' => 'POST' - ], - [ - 'name' => 'Api#showGridView', - 'url' => '/api/v1/showgridview', - 'verb' => 'POST' - ], - [ - 'name' => 'Api#getGridView', - 'url' => '/api/v1/showgridview', - 'verb' => 'GET' - ], - [ - 'name' => 'DirectEditingView#edit', - 'url' => '/directEditing/{token}', - 'verb' => 'GET' - ], - [ - 'name' => 'Api#serviceWorker', - 'url' => '/preview-service-worker.js', - 'verb' => 'GET' - ], - [ - 'name' => 'view#indexView', - 'url' => '/{view}', - 'verb' => 'GET', - ], - [ - 'name' => 'view#indexViewFileid', - 'url' => '/{view}/{fileid}', - 'verb' => 'GET', - ], - ], - 'ocs' => [ - [ - 'name' => 'DirectEditing#info', - 'url' => '/api/v1/directEditing', - 'verb' => 'GET' - ], - [ - 'name' => 'DirectEditing#templates', - 'url' => '/api/v1/directEditing/templates/{editorId}/{creatorId}', - 'verb' => 'GET' - ], - [ - 'name' => 'DirectEditing#open', - 'url' => '/api/v1/directEditing/open', - 'verb' => 'POST' - ], - [ - 'name' => 'DirectEditing#create', - 'url' => '/api/v1/directEditing/create', - 'verb' => 'POST' - ], - [ - 'name' => 'Template#list', - 'url' => '/api/v1/templates', - 'verb' => 'GET' - ], - [ - 'name' => 'Template#create', - 'url' => '/api/v1/templates/create', - 'verb' => 'POST' - ], - [ - 'name' => 'Template#path', - 'url' => '/api/v1/templates/path', - 'verb' => 'POST' - ], - [ - 'name' => 'TransferOwnership#transfer', - 'url' => '/api/v1/transferownership', - 'verb' => 'POST', - ], - [ - 'name' => 'TransferOwnership#accept', - 'url' => '/api/v1/transferownership/{id}', - 'verb' => 'POST', - ], - [ - 'name' => 'TransferOwnership#reject', - 'url' => '/api/v1/transferownership/{id}', - 'verb' => 'DELETE', - ], - [ - /** @see OpenLocalEditorController::create() */ - 'name' => 'OpenLocalEditor#create', - 'url' => '/api/v1/openlocaleditor', - 'verb' => 'POST', - ], - [ - /** @see OpenLocalEditorController::validate() */ - 'name' => 'OpenLocalEditor#validate', - 'url' => '/api/v1/openlocaleditor/{token}', - 'verb' => 'POST', - ], +return [ + 'routes' => [ + [ + 'name' => 'view#index', + 'url' => '/', + 'verb' => 'GET', + ], + [ + 'name' => 'View#showFile', + 'url' => '/f/{fileid}', + 'verb' => 'GET', + 'root' => '', + ], + [ + 'name' => 'Api#getThumbnail', + 'url' => '/api/v1/thumbnail/{x}/{y}/{file}', + 'verb' => 'GET', + 'requirements' => ['file' => '.+'] + ], + [ + 'name' => 'Api#updateFileTags', + 'url' => '/api/v1/files/{path}', + 'verb' => 'POST', + 'requirements' => ['path' => '.+'], + ], + [ + 'name' => 'Api#getRecentFiles', + 'url' => '/api/v1/recent/', + 'verb' => 'GET' + ], + [ + 'name' => 'Api#getStorageStats', + 'url' => '/api/v1/stats', + 'verb' => 'GET' + ], + [ + 'name' => 'Api#setViewConfig', + 'url' => '/api/v1/views/{view}/{key}', + 'verb' => 'PUT' + ], + [ + 'name' => 'Api#setViewConfig', + 'url' => '/api/v1/views', + 'verb' => 'PUT' + ], + [ + 'name' => 'Api#getViewConfigs', + 'url' => '/api/v1/views', + 'verb' => 'GET' + ], + [ + 'name' => 'Api#setConfig', + 'url' => '/api/v1/config/{key}', + 'verb' => 'PUT' + ], + [ + 'name' => 'Api#getConfigs', + 'url' => '/api/v1/configs', + 'verb' => 'GET' + ], + [ + 'name' => 'Api#showHiddenFiles', + 'url' => '/api/v1/showhidden', + 'verb' => 'POST' + ], + [ + 'name' => 'Api#cropImagePreviews', + 'url' => '/api/v1/cropimagepreviews', + 'verb' => 'POST' + ], + [ + 'name' => 'Api#showGridView', + 'url' => '/api/v1/showgridview', + 'verb' => 'POST' + ], + [ + 'name' => 'Api#getGridView', + 'url' => '/api/v1/showgridview', + 'verb' => 'GET' + ], + [ + 'name' => 'DirectEditingView#edit', + 'url' => '/directEditing/{token}', + 'verb' => 'GET' + ], + [ + 'name' => 'Api#serviceWorker', + 'url' => '/preview-service-worker.js', + 'verb' => 'GET' + ], + [ + 'name' => 'view#indexView', + 'url' => '/{view}', + 'verb' => 'GET', + ], + [ + 'name' => 'view#indexViewFileid', + 'url' => '/{view}/{fileid}', + 'verb' => 'GET', + ], + ], + 'ocs' => [ + [ + 'name' => 'DirectEditing#info', + 'url' => '/api/v1/directEditing', + 'verb' => 'GET' + ], + [ + 'name' => 'DirectEditing#templates', + 'url' => '/api/v1/directEditing/templates/{editorId}/{creatorId}', + 'verb' => 'GET' + ], + [ + 'name' => 'DirectEditing#open', + 'url' => '/api/v1/directEditing/open', + 'verb' => 'POST' + ], + [ + 'name' => 'DirectEditing#create', + 'url' => '/api/v1/directEditing/create', + 'verb' => 'POST' + ], + [ + 'name' => 'Template#list', + 'url' => '/api/v1/templates', + 'verb' => 'GET' + ], + [ + 'name' => 'Template#create', + 'url' => '/api/v1/templates/create', + 'verb' => 'POST' + ], + [ + 'name' => 'Template#path', + 'url' => '/api/v1/templates/path', + 'verb' => 'POST' + ], + [ + 'name' => 'TransferOwnership#transfer', + 'url' => '/api/v1/transferownership', + 'verb' => 'POST', + ], + [ + 'name' => 'TransferOwnership#accept', + 'url' => '/api/v1/transferownership/{id}', + 'verb' => 'POST', + ], + [ + 'name' => 'TransferOwnership#reject', + 'url' => '/api/v1/transferownership/{id}', + 'verb' => 'DELETE', + ], + [ + /** @see OpenLocalEditorController::create() */ + 'name' => 'OpenLocalEditor#create', + 'url' => '/api/v1/openlocaleditor', + 'verb' => 'POST', + ], + [ + /** @see OpenLocalEditorController::validate() */ + 'name' => 'OpenLocalEditor#validate', + 'url' => '/api/v1/openlocaleditor/{token}', + 'verb' => 'POST', ], ] -); +]; 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) }, diff --git a/build/integration/features/bootstrap/Download.php b/build/integration/features/bootstrap/Download.php index 1434e182e7d..bef89d2ddb6 100644 --- a/build/integration/features/bootstrap/Download.php +++ b/build/integration/features/bootstrap/Download.php @@ -21,8 +21,9 @@ trait Download { * @When user :user downloads zip file for entries :entries in folder :folder */ public function userDownloadsZipFileForEntriesInFolder($user, $entries, $folder) { + $folder = trim($folder, '/'); $this->asAn($user); - $this->sendingToDirectUrl('GET', '/index.php/apps/files/ajax/download.php?dir=' . $folder . '&files=[' . $entries . ']'); + $this->sendingToDirectUrl('GET', "/remote.php/dav/files/$user/$folder?accept=zip&files=[" . $entries . ']'); $this->theHTTPStatusCodeShouldBe('200'); } diff --git a/build/integration/files_features/download.feature b/build/integration/files_features/download.feature index 176963c2610..f9d4e7e95b9 100644 --- a/build/integration/files_features/download.feature +++ b/build/integration/files_features/download.feature @@ -2,60 +2,60 @@ # SPDX-License-Identifier: AGPL-3.0-or-later Feature: download - Scenario: downloading 2 small files returns a zip32 + Scenario: downloading 2 small files Given using new dav path And user "user0" exists And User "user0" copies file "/welcome.txt" to "/welcome2.txt" When user "user0" downloads zip file for entries '"welcome.txt","welcome2.txt"' in folder "/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a file named "welcome.txt" with the contents of "/welcome.txt" from "user0" data And the downloaded zip file contains a file named "welcome2.txt" with the contents of "/welcome2.txt" from "user0" data - Scenario: downloading a small file and a directory returns a zip32 + Scenario: downloading a small file and a directory Given using new dav path And user "user0" exists And user "user0" created a folder "/emptySubFolder" When user "user0" downloads zip file for entries '"welcome.txt","emptySubFolder"' in folder "/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a file named "welcome.txt" with the contents of "/welcome.txt" from "user0" data And the downloaded zip file contains a folder named "emptySubFolder/" - Scenario: downloading a small file and 2 nested directories returns a zip32 + Scenario: downloading a small file and 2 nested directories Given using new dav path And user "user0" exists And user "user0" created a folder "/subFolder" And user "user0" created a folder "/subFolder/emptySubSubFolder" When user "user0" downloads zip file for entries '"welcome.txt","subFolder"' in folder "/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a file named "welcome.txt" with the contents of "/welcome.txt" from "user0" data And the downloaded zip file contains a folder named "subFolder/" And the downloaded zip file contains a folder named "subFolder/emptySubSubFolder/" - Scenario: downloading dir with 2 small files returns a zip32 + Scenario: downloading dir with 2 small files Given using new dav path And user "user0" exists And user "user0" created a folder "/sparseFolder" And User "user0" copies file "/welcome.txt" to "/sparseFolder/welcome.txt" And User "user0" copies file "/welcome.txt" to "/sparseFolder/welcome2.txt" When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a folder named "sparseFolder/" And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/sparseFolder/welcome.txt" from "user0" data And the downloaded zip file contains a file named "sparseFolder/welcome2.txt" with the contents of "/sparseFolder/welcome2.txt" from "user0" data - Scenario: downloading dir with a small file and a directory returns a zip32 + Scenario: downloading dir with a small file and a directory Given using new dav path And user "user0" exists And user "user0" created a folder "/sparseFolder" And User "user0" copies file "/welcome.txt" to "/sparseFolder/welcome.txt" And user "user0" created a folder "/sparseFolder/emptySubFolder" When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a folder named "sparseFolder/" And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/sparseFolder/welcome.txt" from "user0" data And the downloaded zip file contains a folder named "sparseFolder/emptySubFolder/" - Scenario: downloading dir with a small file and 2 nested directories returns a zip32 + Scenario: downloading dir with a small file and 2 nested directories Given using new dav path And user "user0" exists And user "user0" created a folder "/sparseFolder" @@ -63,35 +63,35 @@ Feature: download And user "user0" created a folder "/sparseFolder/subFolder" And user "user0" created a folder "/sparseFolder/subFolder/emptySubSubFolder" When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a folder named "sparseFolder/" And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/sparseFolder/welcome.txt" from "user0" data And the downloaded zip file contains a folder named "sparseFolder/subFolder/" And the downloaded zip file contains a folder named "sparseFolder/subFolder/emptySubSubFolder/" - Scenario: downloading (from folder) 2 small files returns a zip32 + Scenario: downloading (from folder) 2 small files Given using new dav path And user "user0" exists And user "user0" created a folder "/baseFolder" And User "user0" copies file "/welcome.txt" to "/baseFolder/welcome.txt" And User "user0" copies file "/welcome.txt" to "/baseFolder/welcome2.txt" When user "user0" downloads zip file for entries '"welcome.txt","welcome2.txt"' in folder "/baseFolder/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a file named "welcome.txt" with the contents of "/baseFolder/welcome.txt" from "user0" data And the downloaded zip file contains a file named "welcome2.txt" with the contents of "/baseFolder/welcome2.txt" from "user0" data - Scenario: downloading (from folder) a small file and a directory returns a zip32 + Scenario: downloading (from folder) a small file and a directory Given using new dav path And user "user0" exists And user "user0" created a folder "/baseFolder" And User "user0" copies file "/welcome.txt" to "/baseFolder/welcome.txt" And user "user0" created a folder "/baseFolder/emptySubFolder" When user "user0" downloads zip file for entries '"welcome.txt","emptySubFolder"' in folder "/baseFolder/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a file named "welcome.txt" with the contents of "/baseFolder/welcome.txt" from "user0" data And the downloaded zip file contains a folder named "emptySubFolder/" - Scenario: downloading (from folder) a small file and 2 nested directories returns a zip32 + Scenario: downloading (from folder) a small file and 2 nested directories Given using new dav path And user "user0" exists And user "user0" created a folder "/baseFolder" @@ -99,12 +99,12 @@ Feature: download And user "user0" created a folder "/baseFolder/subFolder" And user "user0" created a folder "/baseFolder/subFolder/emptySubSubFolder" When user "user0" downloads zip file for entries '"welcome.txt","subFolder"' in folder "/baseFolder/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a file named "welcome.txt" with the contents of "/baseFolder/welcome.txt" from "user0" data And the downloaded zip file contains a folder named "subFolder/" And the downloaded zip file contains a folder named "subFolder/emptySubSubFolder/" - Scenario: downloading (from folder) dir with 2 small files returns a zip32 + Scenario: downloading (from folder) dir with 2 small files Given using new dav path And user "user0" exists And user "user0" created a folder "/baseFolder" @@ -112,12 +112,12 @@ Feature: download And User "user0" copies file "/welcome.txt" to "/baseFolder/sparseFolder/welcome.txt" And User "user0" copies file "/welcome.txt" to "/baseFolder/sparseFolder/welcome2.txt" When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/baseFolder/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a folder named "sparseFolder/" And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/baseFolder/sparseFolder/welcome.txt" from "user0" data And the downloaded zip file contains a file named "sparseFolder/welcome2.txt" with the contents of "/baseFolder/sparseFolder/welcome2.txt" from "user0" data - Scenario: downloading (from folder) dir with a small file and a directory returns a zip32 + Scenario: downloading (from folder) dir with a small file and a directory Given using new dav path And user "user0" exists And user "user0" created a folder "/baseFolder" @@ -125,12 +125,12 @@ Feature: download And User "user0" copies file "/welcome.txt" to "/baseFolder/sparseFolder/welcome.txt" And user "user0" created a folder "/baseFolder/sparseFolder/emptySubFolder" When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/baseFolder/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a folder named "sparseFolder/" And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/baseFolder/sparseFolder/welcome.txt" from "user0" data And the downloaded zip file contains a folder named "sparseFolder/emptySubFolder/" - Scenario: downloading (from folder) dir with a small file and 2 nested directories returns a zip32 + Scenario: downloading (from folder) dir with a small file and 2 nested directories Given using new dav path And user "user0" exists And user "user0" created a folder "/baseFolder" @@ -139,14 +139,14 @@ Feature: download And user "user0" created a folder "/baseFolder/sparseFolder/subFolder" And user "user0" created a folder "/baseFolder/sparseFolder/subFolder/emptySubSubFolder" When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/baseFolder/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a folder named "sparseFolder/" And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/baseFolder/sparseFolder/welcome.txt" from "user0" data And the downloaded zip file contains a folder named "sparseFolder/subFolder/" And the downloaded zip file contains a folder named "sparseFolder/subFolder/emptySubSubFolder/" @large - Scenario: downloading small file and dir with 65524 small files and 9 nested directories returns a zip32 + Scenario: downloading small file and dir with 65524 small files and 9 nested directories Given using new dav path And user "user0" exists And user "user0" created a folder "/crowdedFolder" @@ -174,7 +174,7 @@ Feature: download And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder" And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder" When user "user0" downloads zip file for entries '"welcome.txt","crowdedFolder"' in folder "/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a file named "welcome.txt" with the contents of "/welcome.txt" from "user0" data And the downloaded zip file contains a folder named "crowdedFolder/" And the downloaded zip file contains a folder named "crowdedFolder/subFolder1/" @@ -183,7 +183,7 @@ Feature: download And the downloaded zip file contains a folder named "crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder/" @large - Scenario: downloading dir with 65525 small files and 9 nested directories returns a zip32 + Scenario: downloading dir with 65525 small files and 9 nested directories Given using new dav path And user "user0" exists And user "user0" created a folder "/crowdedFolder" @@ -211,7 +211,7 @@ Feature: download And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder" And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder" When user "user0" downloads zip file for entries '"crowdedFolder"' in folder "/" - Then the downloaded zip file is a zip32 file + Then the downloaded file is a zip file And the downloaded zip file contains a folder named "crowdedFolder/" And the downloaded zip file contains a folder named "crowdedFolder/subFolder1/" And the downloaded zip file contains a file named "crowdedFolder/subFolder1/test.txt-0" with the contents of "/crowdedFolder/subFolder1/test.txt-0" from "user0" data diff --git a/cypress/e2e/files_sharing/public-share/download-files.cy.ts b/cypress/e2e/files_sharing/public-share/download-files.cy.ts index 4e37d1b38ae..a21361bd8b9 100644 --- a/cypress/e2e/files_sharing/public-share/download-files.cy.ts +++ b/cypress/e2e/files_sharing/public-share/download-files.cy.ts @@ -11,8 +11,6 @@ import { getShareUrl, setupPublicShare } from './setup-public-share.ts' describe('files_sharing: Public share - downloading files', { testIsolation: true }, () => { - const shareName = 'shared' - before(() => setupPublicShare()) deleteDownloadsFolderBeforeEach() @@ -40,7 +38,7 @@ describe('files_sharing: Public share - downloading files', { testIsolation: tru // check a file is downloaded const downloadsFolder = Cypress.config('downloadsFolder') - cy.readFile(`${downloadsFolder}/${shareName}.zip`, null, { timeout: 15000 }) + cy.readFile(`${downloadsFolder}/download.zip`, null, { timeout: 15000 }) .should('exist') .and('have.length.gt', 30) // Check all files are included diff --git a/tests/lib/UrlGeneratorTest.php b/tests/lib/UrlGeneratorTest.php index 0f5e2984ce9..ed7b797d809 100644 --- a/tests/lib/UrlGeneratorTest.php +++ b/tests/lib/UrlGeneratorTest.php @@ -107,25 +107,25 @@ class UrlGeneratorTest extends \Test\TestCase { $this->assertEquals($expected, $result); } - public function provideRoutes() { + public static function provideRoutes() { return [ ['core.Preview.getPreview', 'http://localhost/nextcloud/index.php/core/preview.png'], ['cloud_federation_api.requesthandlercontroller.addShare', 'http://localhost/nextcloud/index.php/ocm/shares'], ]; } - public function provideDocRootAppUrlParts() { + public static function provideDocRootAppUrlParts() { return [ - ['files', 'ajax/download.php', [], '/index.php/apps/files/ajax/download.php'], - ['files', 'ajax/download.php', ['trut' => 'trat', 'dut' => 'dat'], '/index.php/apps/files/ajax/download.php?trut=trat&dut=dat'], + ['files_external', 'ajax/oauth2.php', [], '/index.php/apps/files_external/ajax/oauth2.php'], + ['files_external', 'ajax/oauth2.php', ['trut' => 'trat', 'dut' => 'dat'], '/index.php/apps/files_external/ajax/oauth2.php?trut=trat&dut=dat'], ['', 'index.php', ['trut' => 'trat', 'dut' => 'dat'], '/index.php?trut=trat&dut=dat'], ]; } - public function provideSubDirAppUrlParts() { + public static function provideSubDirAppUrlParts() { return [ - ['files', 'ajax/download.php', [], '/nextcloud/index.php/apps/files/ajax/download.php'], - ['files', 'ajax/download.php', ['trut' => 'trat', 'dut' => 'dat'], '/nextcloud/index.php/apps/files/ajax/download.php?trut=trat&dut=dat'], + ['files_external', 'ajax/oauth2.php', [], '/nextcloud/index.php/apps/files_external/ajax/oauth2.php'], + ['files_external', 'ajax/oauth2.php', ['trut' => 'trat', 'dut' => 'dat'], '/nextcloud/index.php/apps/files_external/ajax/oauth2.php?trut=trat&dut=dat'], ['', 'index.php', ['trut' => 'trat', 'dut' => 'dat'], '/nextcloud/index.php?trut=trat&dut=dat'], ]; } |