Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions packages/web-client/src/webdav/getFileUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ export const GetFileUrlFactory = (
resource: Resource,
{
disposition = 'attachment',
isUrlSigningEnabled = false,
isUrlSigningEnabled = true,
signUrlTimeout = 86400,
version = null,
doHeadRequest = false,
username = '',
...opts
}: {
disposition?: 'inline' | 'attachment'
/** @deprecated no need to specify, the server always supports this */
isUrlSigningEnabled?: boolean
/** @deprecated this has no effect */
signUrlTimeout?: number
version?: string
doHeadRequest?: boolean
Expand All @@ -34,23 +36,19 @@ export const GetFileUrlFactory = (
const inlineDisposition = disposition === 'inline'
let { downloadURL } = resource

let signed = true
if (!downloadURL && !inlineDisposition) {
// compute unsigned url
downloadURL = version
? dav.getFileUrl(urlJoin('meta', resource.fileId, 'v', version))
: dav.getFileUrl(resource.webDavPath)

if (username && doHeadRequest) {
await axiosClient.head(downloadURL)
}
if (username) {
if (doHeadRequest) {
await axiosClient.head(downloadURL)
}

// sign url
if (isUrlSigningEnabled && username) {
const ocsClient = ocs(baseUrl, axiosClient)
downloadURL = await ocsClient.signUrl(downloadURL, username)
} else {
signed = false
}
}

Expand All @@ -64,7 +62,7 @@ export const GetFileUrlFactory = (
// const combinedQuery = [queryStr, signedQuery].filter(Boolean).join('&')
// downloadURL = [url, combinedQuery].filter(Boolean).join('?')

if (!signed || inlineDisposition) {
if (inlineDisposition) {
const response = await getFileContentsFactory.getFileContents(space, resource, {
responseType: 'blob',
...opts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,19 @@ import {
import { useIsFilesAppActive } from '../helpers'
import { isProjectSpaceResource, isPublicSpaceResource, Resource } from '@opencloud-eu/web-client'
import { computed, unref } from 'vue'
import { useLoadingService } from '../../loadingService'
import { useRouter } from '../../router'

import { FileAction, FileActionOptions } from '../types'
import { useGettext } from 'vue3-gettext'
import { useArchiverService } from '../../archiverService'
import { formatFileSize } from '../../../helpers/filesize'
import { useAuthStore, useMessages } from '../../piniaStores'
import { useMessages } from '../../piniaStores'

export const useFileActionsDownloadArchive = () => {
const { showErrorMessage } = useMessages()
const router = useRouter()
const loadingService = useLoadingService()
const archiverService = useArchiverService()
const { $ngettext, $gettext, current } = useGettext()
const authStore = useAuthStore()
const isFilesAppActive = useIsFilesAppActive()

const handler = ({ space, resources }: FileActionOptions) => {
Expand All @@ -37,8 +34,7 @@ export const useFileActionsDownloadArchive = () => {
fileIds: resources.map((resource) => resource.fileId),
...(space &&
isPublicSpaceResource(space) && {
publicToken: space.id,
publicLinkPassword: authStore.publicLinkPassword
publicToken: space.id
})
})
.catch((e) => {
Expand Down Expand Up @@ -73,8 +69,8 @@ export const useFileActionsDownloadArchive = () => {
{
name: 'download-archive',
icon: 'inbox-archive',
handler: async (args) => {
await loadingService.addTask(() => handler(args))
handler: (args) => {
handler(args)
},
label: () => $gettext('Download'),
disabledTooltip: ({ resources }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { FileResource, SpaceResource } from '@opencloud-eu/web-client'
import { useClientService } from '../clientService'
import { ListFilesOptions } from '@opencloud-eu/web-client/webdav'
import { WebDAV } from '@opencloud-eu/web-client/webdav'
import { useCapabilityStore, useUserStore } from '../piniaStores'
import { useUserStore } from '../piniaStores'

interface AppFileHandlingOptions {
clientService: ClientService
Expand Down Expand Up @@ -39,7 +39,6 @@ export function useAppFileHandling({
clientService
}: AppFileHandlingOptions): AppFileHandlingResult {
clientService = clientService || useClientService()
const capabilityStore = useCapabilityStore()
const userStore = useUserStore()

const getUrlForResource = (
Expand All @@ -48,7 +47,6 @@ export function useAppFileHandling({
options?: UrlForResourceOptions
) => {
return clientService.webdav.getFileUrl(space, resource, {
isUrlSigningEnabled: capabilityStore.supportUrlSigning,
username: userStore.user?.onPremisesSamAccountName,
...options
})
Expand Down
5 changes: 1 addition & 4 deletions packages/web-pkg/src/composables/download/useDownloadFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useClientService } from '../clientService'
import { triggerDownloadWithFilename } from '../../../src/helpers'
import { useGettext } from 'vue3-gettext'
import { ClientService } from '../../services'
import { useCapabilityStore, useMessages, useUserStore } from '../piniaStores'
import { useMessages, useUserStore } from '../piniaStores'
import { Resource, SpaceResource } from '@opencloud-eu/web-client'

export interface DownloadFileOptions {
Expand All @@ -13,15 +13,12 @@ export const useDownloadFile = (options?: DownloadFileOptions) => {
const { showErrorMessage } = useMessages()
const clientService = options?.clientService || useClientService()
const { $gettext } = useGettext()
const capabilityStore = useCapabilityStore()
const userStore = useUserStore()

const downloadFile = async (space: SpaceResource, file: Resource, version: string = null) => {
try {
const url = await clientService.webdav.getFileUrl(space, file, {
version,
doHeadRequest: true,
isUrlSigningEnabled: capabilityStore.supportUrlSigning,
username: userStore.user?.onPremisesSamAccountName
})
triggerDownloadWithFilename(url, file.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const useCapabilityStore = defineStore('capabilities', () => {
isInitialized.value = true
}

/** @deprecated the server always supports this */
const supportUrlSigning = computed(() => unref(capabilities).core['support-url-signing'])
const supportSSE = computed(() => unref(capabilities).core['support-sse'])
const personalDataExport = computed(() => unref(capabilities).graph['personal-data-export'])
Expand Down
2 changes: 1 addition & 1 deletion packages/web-pkg/src/helpers/download.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const triggerDownloadWithFilename = (url: string, name: string) => {
export const triggerDownloadWithFilename = (url: string, name = '') => {
const a = document.createElement('a')
a.style.display = 'none'
document.body.appendChild(a)
Expand Down
39 changes: 4 additions & 35 deletions packages/web-pkg/src/services/archiver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RuntimeError } from '../errors'
import { HttpError, urlJoin } from '@opencloud-eu/web-client'
import { urlJoin } from '@opencloud-eu/web-client'
import { ClientService } from '../services'
import { triggerDownloadWithFilename } from '../helpers/download'
import { Ref, ref, computed, unref } from 'vue'
Expand All @@ -8,12 +8,9 @@ import { UserStore } from '../composables'
import { compareVersions } from '../utils'

interface TriggerDownloadOptions {
dir?: string
files?: string[]
fileIds?: string[]
downloadSecret?: string
publicToken?: string
publicLinkPassword?: string
}

export class ArchiverService {
Expand Down Expand Up @@ -60,36 +57,13 @@ export class ArchiverService {

const url = options.publicToken
? downloadUrl
: await this.clientService.ocsUserContext.signUrl(
: await this.clientService.ocs.signUrl(
downloadUrl,
this.userStore.user?.onPremisesSamAccountName
)

try {
// use fetch because we can't reliably retrieve large data streams with axios
const response = await fetch(url, {
headers: {
...this.clientService.getRequestHeaders({ useAuth: !options.publicLinkPassword }),
...(!!options.publicLinkPassword && {
Authorization:
'Basic ' +
Buffer.from(['public', options.publicLinkPassword].join(':')).toString('base64')
})
}
})

if (!response.ok) {
throw new HttpError('', response)
}

const blob = await response.blob()
const objectUrl = URL.createObjectURL(blob)
const fileName = this.getFileNameFromResponseHeaders(response.headers)
triggerDownloadWithFilename(objectUrl, fileName)
return url
} catch (e) {
throw new HttpError('archive could not be fetched', e.response)
}
triggerDownloadWithFilename(url)
return url
}

private buildDownloadUrl(options: TriggerDownloadOptions): string {
Expand All @@ -112,9 +86,4 @@ export class ArchiverService {
}
return urlJoin(this.serverUrl, capability.archiver_url)
}

private getFileNameFromResponseHeaders(headers: Headers) {
const fileName = headers.get('content-disposition')?.split('"')[1]
return decodeURI(fileName)
}
}
15 changes: 1 addition & 14 deletions packages/web-pkg/tests/unit/services/archiver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,19 @@ import { unref, ref, Ref } from 'vue'
import { ArchiverCapability } from '@opencloud-eu/web-client/ocs'
import { createTestingPinia } from '@opencloud-eu/web-test-helpers'
import { useUserStore } from '../../../src/composables/piniaStores'
import { Mock } from 'vitest'

const serverUrl = 'https://demo.opencloud.eu'
const getArchiverServiceInstance = (capabilities: Ref<ArchiverCapability[]>) => {
createTestingPinia()
const userStore = useUserStore()

const clientServiceMock = mockDeep<ClientService>()
clientServiceMock.ocsUserContext.signUrl.mockImplementation((url) => Promise.resolve(url))
clientServiceMock.ocs.signUrl.mockImplementation((url) => Promise.resolve(url))

return new ArchiverService(clientServiceMock, userStore, serverUrl, capabilities)
}

describe('archiver', () => {
beforeEach(() => {
global.window.fetch = vi.fn(() =>
Promise.resolve({
blob: () => Promise.resolve({ data: new ArrayBuffer(8) }),
headers: { get: () => 'filename="download.tar"' },
ok: true
})
) as Mock
})

describe('availability', () => {
it('is unavailable if no version given via capabilities', () => {
const capabilities = ref([mock<ArchiverCapability>({ version: undefined })])
Expand Down Expand Up @@ -68,10 +57,8 @@ describe('archiver', () => {
})
it('returns a download url for a valid archive download trigger', async () => {
const archiverService = getArchiverServiceInstance(capabilities)
window.URL.createObjectURL = vi.fn(() => '')
const fileId = 'asdf'
const url = await archiverService.triggerDownload({ fileIds: [fileId] })
expect(window.URL.createObjectURL).toHaveBeenCalled()
expect(url.startsWith(archiverUrl)).toBeTruthy()
expect(url.indexOf(`id=${fileId}`)).toBeGreaterThan(-1)
})
Expand Down
6 changes: 0 additions & 6 deletions tests/e2e/support/objects/account/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,6 @@ export const downloadGdprExport = async (args: { page: Page }): Promise<string>

const [download] = await Promise.all([
page.waitForEvent('download'),
page.waitForResponse(
(resp) =>
resp.url().endsWith('.personal_data_export.json') &&
resp.status() === 200 &&
resp.request().method() === 'HEAD'
),
page.locator(downloadExportButton).click()
])
await page.locator(requestExportButton).waitFor()
Expand Down
11 changes: 2 additions & 9 deletions tests/e2e/support/objects/app-files/resource/actions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Download, Locator, Page, Response, expect } from '@playwright/test'
import { Download, Locator, Page, expect } from '@playwright/test'
import util from 'util'
import path from 'path'
import { waitForResources } from './utils'
Expand Down Expand Up @@ -1263,21 +1263,14 @@ export interface downloadResourceVersionArgs {
export const downloadResourceVersion = async (args: downloadResourceVersionArgs) => {
const { page, files, folder } = args
const fileName = files.map((file) => path.basename(file.name))
const downloads: Response[] = []
await clickResource({ page, path: folder })
await sidebar.open({ page, resource: fileName[0] })
await sidebar.openPanel({ page, name: 'versions' })
const [download] = await Promise.all([
page.waitForResponse(
(resp) =>
resp.url().includes('/v/') && resp.status() === 200 && resp.request().method() === 'HEAD'
),
await Promise.all([
page.waitForEvent('download'),
page.locator('//*[@data-testid="file-versions-download-button"]').first().click()
])
await sidebar.close({ page: page })
downloads.push(download)
return downloads
}

export interface deleteResourceTrashbinArgs {
Expand Down
7 changes: 3 additions & 4 deletions tests/e2e/support/objects/app-files/resource/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Download, Locator, Page, Response } from '@playwright/test'
import { Download, Locator, Page } from '@playwright/test'
import * as po from './actions'
import { Space } from '../../../types'
import { showShareIndicator } from './utils'
Expand Down Expand Up @@ -114,11 +114,10 @@ export class Resource {
await this.#page.goto(startUrl)
}

async downloadVersion(args: Omit<po.downloadResourceVersionArgs, 'page'>): Promise<Response[]> {
async downloadVersion(args: Omit<po.downloadResourceVersionArgs, 'page'>) {
const startUrl = this.#page.url()
const downloads = await po.downloadResourceVersion({ ...args, page: this.#page })
await po.downloadResourceVersion({ ...args, page: this.#page })
await this.#page.goto(startUrl)
return downloads
}

async deleteTrashBin(args: Omit<po.deleteResourceTrashbinArgs, 'page'>): Promise<string> {
Expand Down