diff options
Diffstat (limited to 'apps/files_sharing/src/services/SharingService.ts')
-rw-r--r-- | apps/files_sharing/src/services/SharingService.ts | 125 |
1 files changed, 88 insertions, 37 deletions
diff --git a/apps/files_sharing/src/services/SharingService.ts b/apps/files_sharing/src/services/SharingService.ts index 41861c5d3ea..41c20f9aa73 100644 --- a/apps/files_sharing/src/services/SharingService.ts +++ b/apps/files_sharing/src/services/SharingService.ts @@ -1,76 +1,107 @@ /** - * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author John Molakvoæ <skjnldsv@protonmail.com> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ -/* eslint-disable camelcase, n/no-extraneous-import */ -import type { AxiosPromise } from 'axios' +// TODO: Fix this instead of disabling ESLint!!! +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import type { AxiosPromise } from '@nextcloud/axios' +import type { ContentsWithRoot } from '@nextcloud/files' import type { OCSResponse } from '@nextcloud/typings/ocs' +import type { ShareAttribute } from '../sharing' -import { Folder, File, type ContentsWithRoot } from '@nextcloud/files' -import { generateOcsUrl, generateRemoteUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' +import { Folder, File, Permission, davRemoteURL, davRootPath } from '@nextcloud/files' +import { generateOcsUrl } from '@nextcloud/router' import axios from '@nextcloud/axios' import logger from './logger' -export const rootPath = `/files/${getCurrentUser()?.uid}` - const headers = { 'Content-Type': 'application/json', } -const ocsEntryToNode = function(ocsEntry: any): Folder | File | null { +const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | null> { try { + // Federated share handling + if (ocsEntry?.remote_id !== undefined) { + if (!ocsEntry.mimetype) { + const mime = (await import('mime')).default + // This won't catch files without an extension, but this is the best we can do + ocsEntry.mimetype = mime.getType(ocsEntry.name) + } + ocsEntry.item_type = ocsEntry.type || (ocsEntry.mimetype ? 'file' : 'folder') + + // different naming for remote shares + ocsEntry.item_mtime = ocsEntry.mtime + ocsEntry.file_target = ocsEntry.file_target || ocsEntry.mountpoint + + if (ocsEntry.file_target.includes('TemporaryMountPointName')) { + ocsEntry.file_target = ocsEntry.name + } + + // If the share is not accepted yet we don't know which permissions it will have + if (!ocsEntry.accepted) { + // Need to set permissions to NONE for federated shares + ocsEntry.item_permissions = Permission.NONE + ocsEntry.permissions = Permission.NONE + } + + ocsEntry.uid_owner = ocsEntry.owner + // TODO: have the real display name stored somewhere + ocsEntry.displayname_owner = ocsEntry.owner + } + const isFolder = ocsEntry?.item_type === 'folder' const hasPreview = ocsEntry?.has_preview === true const Node = isFolder ? Folder : File - const fileid = ocsEntry.file_source + // If this is an external share that is not yet accepted, + // we don't have an id. We can fallback to the row id temporarily + // local shares (this server) use `file_source`, but remote shares (federated) use `file_id` + const fileid = ocsEntry.file_source || ocsEntry.file_id || ocsEntry.id // Generate path and strip double slashes - const path = ocsEntry?.path || ocsEntry.file_target - const source = generateRemoteUrl(`dav/${rootPath}/${path}`.replaceAll(/\/\//gm, '/')) + const path = ocsEntry.path || ocsEntry.file_target || ocsEntry.name + const source = `${davRemoteURL}${davRootPath}/${path.replace(/^\/+/, '')}` + let mtime = ocsEntry.item_mtime ? new Date((ocsEntry.item_mtime) * 1000) : undefined // Prefer share time if more recent than item mtime - let mtime = ocsEntry?.item_mtime ? new Date((ocsEntry.item_mtime) * 1000) : undefined if (ocsEntry?.stime > (ocsEntry?.item_mtime || 0)) { mtime = new Date((ocsEntry.stime) * 1000) } + let sharees: { sharee: object } | undefined + if ('share_with' in ocsEntry) { + sharees = { + sharee: { + id: ocsEntry.share_with, + 'display-name': ocsEntry.share_with_displayname || ocsEntry.share_with, + type: ocsEntry.share_type, + }, + } + } + return new Node({ id: fileid, source, owner: ocsEntry?.uid_owner, - mime: ocsEntry?.mimetype, + mime: ocsEntry?.mimetype || 'application/octet-stream', mtime, size: ocsEntry?.item_size, permissions: ocsEntry?.item_permissions || ocsEntry?.permissions, - root: rootPath, + root: davRootPath, attributes: { ...ocsEntry, 'has-preview': hasPreview, + 'hide-download': ocsEntry?.hide_download === 1, // Also check the sharingStatusAction.ts code 'owner-id': ocsEntry?.uid_owner, 'owner-display-name': ocsEntry?.displayname_owner, 'share-types': ocsEntry?.share_type, - favorite: ocsEntry?.tags?.includes(window.OC.TAG_FAVORITE) ? 1 : 0, + 'share-attributes': ocsEntry?.attributes || '[]', + sharees, + favorite: ocsEntry?.tags?.includes((window.OC as { TAG_FAVORITE: string }).TAG_FAVORITE) ? 1 : 0, }, }) } catch (error) { @@ -79,12 +110,12 @@ const ocsEntryToNode = function(ocsEntry: any): Folder | File | null { } } -const getShares = function(shared_with_me = false): AxiosPromise<OCSResponse<any>> { +const getShares = function(shareWithMe = false): AxiosPromise<OCSResponse<any>> { const url = generateOcsUrl('apps/files_sharing/api/v1/shares') return axios.get(url, { headers, params: { - shared_with_me, + shared_with_me: shareWithMe, include_tags: true, }, }) @@ -139,8 +170,28 @@ const getDeletedShares = function(): AxiosPromise<OCSResponse<any>> { } /** + * Check if a file request is enabled + * @param attributes the share attributes json-encoded array + */ +export const isFileRequest = (attributes = '[]'): boolean => { + const isFileRequest = (attribute) => { + return attribute.scope === 'fileRequest' && attribute.key === 'enabled' && attribute.value === true + } + + try { + const attributesArray = JSON.parse(attributes) as Array<ShareAttribute> + return attributesArray.some(isFileRequest) + } catch (error) { + logger.error('Error while parsing share attributes', { error }) + return false + } +} + +/** * Group an array of objects (here Nodes) by a key * and return an array of arrays of them. + * @param nodes Nodes to group + * @param key The attribute to group by */ const groupBy = function(nodes: (Folder | File)[], key: string) { return Object.values(nodes.reduce(function(acc, curr) { @@ -167,7 +218,7 @@ export const getContents = async (sharedWithYou = true, sharedWithOthers = true, const responses = await Promise.all(promises) const data = responses.map((response) => response.data.ocs.data).flat() - let contents = data.map(ocsEntryToNode) + let contents = (await Promise.all(data.map(ocsEntryToNode))) .filter((node) => node !== null) as (Folder | File)[] if (filterTypes.length > 0) { @@ -185,7 +236,7 @@ export const getContents = async (sharedWithYou = true, sharedWithOthers = true, return { folder: new Folder({ id: 0, - source: generateRemoteUrl('dav' + rootPath), + source: `${davRemoteURL}${davRootPath}`, owner: getCurrentUser()?.uid || null, }), contents, |