fix(files_sharing): ensure downloaded file has the correct filename

Single file shares use the share token as source name, so we need to use
the displayname. To do so we need to set the download attribute to the
displayname of the file to download.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/51152/head
Ferdinand Thiessen 2025-02-28 21:50:47 +07:00
parent a4760ef906
commit 4eb2c45c33
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
4 changed files with 172 additions and 109 deletions

@ -105,7 +105,7 @@ describe('Download action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(link.download).toEqual('')
expect(link.download).toBe('foobar.txt')
expect(link.href).toEqual('https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt')
expect(link.click).toHaveBeenCalledTimes(1)
})
@ -123,7 +123,26 @@ describe('Download action execute tests', () => {
// Silent action
expect(exec).toStrictEqual([null])
expect(link.download).toEqual('')
expect(link.download).toEqual('foobar.txt')
expect(link.href).toEqual('https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt')
expect(link.click).toHaveBeenCalledTimes(1)
})
test('Download single file with displayname set', async () => {
const file = new File({
id: 1,
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
owner: 'admin',
mime: 'text/plain',
displayname: 'baz.txt',
permissions: Permission.READ,
})
const exec = await action.execBatch!([file], view, '/')
// Silent action
expect(exec).toStrictEqual([null])
expect(link.download).toEqual('baz.txt')
expect(link.href).toEqual('https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt')
expect(link.click).toHaveBeenCalledTimes(1)
})

@ -9,9 +9,15 @@ import { isDownloadable } from '../utils/permissions'
import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw'
const triggerDownload = function(url: string) {
/**
* Trigger downloading a file.
*
* @param url The url of the asset to download
* @param name Optionally the recommended name of the download (browsers might ignore it)
*/
function triggerDownload(url: string, name?: string) {
const hiddenElement = document.createElement('a')
hiddenElement.download = ''
hiddenElement.download = name ?? ''
hiddenElement.href = url
hiddenElement.click()
}
@ -43,7 +49,7 @@ const downloadNodes = function(nodes: Node[]) {
if (nodes.length === 1) {
if (nodes[0].type === FileType.File) {
return triggerDownload(nodes[0].encodedSource)
return triggerDownload(nodes[0].encodedSource, nodes[0].displayname)
} else {
url = new URL(nodes[0].encodedSource)
url.searchParams.append('accept', 'zip')

@ -4,13 +4,46 @@
*/
// @ts-expect-error The package is currently broken - but works...
import { deleteDownloadsFolderBeforeEach } from 'cypress-delete-downloads-folder'
import { createShare, getShareUrl, setupPublicShare, type ShareContext } from './setup-public-share.ts'
import { getRowForFile, getRowForFileId, triggerActionForFile, triggerActionForFileId } from '../../files/FilesUtils.ts'
import { zipFileContains } from '../../../support/utils/assertions.ts'
import { getRowForFile, triggerActionForFile } from '../../files/FilesUtils.ts'
import { getShareUrl, setupPublicShare } from './setup-public-share.ts'
describe('files_sharing: Public share - downloading files', { testIsolation: true }, () => {
// in general there is no difference except downloading
// as file shares have the source of the share token but a different displayname
describe('file share', () => {
let fileId: number
before(() => {
cy.createRandomUser().then((user) => {
const context: ShareContext = { user }
cy.uploadContent(user, new Blob(['<content>foo</content>']), 'text/plain', '/file.txt')
.then(({ headers }) => { fileId = Number.parseInt(headers['oc-fileid']) })
cy.login(user)
createShare(context, 'file.txt')
.then(() => cy.logout())
.then(() => cy.visit(context.url!))
})
})
it('can download the file', () => {
getRowForFileId(fileId)
.should('be.visible')
getRowForFileId(fileId)
.find('[data-cy-files-list-row-name]')
.should((el) => expect(el.text()).to.match(/file\s*\.txt/)) // extension is sparated so there might be a space between
triggerActionForFileId(fileId, 'download')
// check a file is downloaded with the correct name
const downloadsFolder = Cypress.config('downloadsFolder')
cy.readFile(`${downloadsFolder}/file.txt`, 'utf-8', { timeout: 15000 })
.should('exist')
.and('have.length.gt', 5)
.and('contain', '<content>foo</content>')
})
})
describe('folder share', () => {
before(() => setupPublicShare())
deleteDownloadsFolderBeforeEach()
@ -137,3 +170,4 @@ describe('files_sharing: Public share - downloading files', { testIsolation: tru
})
})
})
})

@ -79,9 +79,13 @@ function checkExpirationDateState(enforced: boolean, hasDefault: boolean) {
cy.get('input[data-cy-files-sharing-expiration-date-input]')
.invoke('val')
.then((val) => {
// eslint-disable-next-line no-unused-expressions
expect(val).to.not.be.undefined
const inputDate = new Date(typeof val === 'number' ? val : String(val))
const expectedDate = new Date()
expectedDate.setDate(expectedDate.getDate() + 2)
expect(new Date(val).toDateString()).to.eq(expectedDate.toDateString())
expect(inputDate.toDateString()).to.eq(expectedDate.toDateString())
})
}