mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 00:32:29 -04:00
fix(files_sharing): fix parsing of remote shares
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
This commit is contained in:
parent
f626476b11
commit
a0a36563b6
5 changed files with 61 additions and 23 deletions
|
|
@ -51,7 +51,8 @@ export const canDownload = (nodes: Node[]) => {
|
|||
}
|
||||
|
||||
export const canCopy = (nodes: Node[]) => {
|
||||
// For now the only restriction is that a shared file
|
||||
// cannot be copied if the download is disabled
|
||||
// a shared file cannot be copied if the download is disabled
|
||||
// it can be copied if the user has at least read permissions
|
||||
return canDownload(nodes)
|
||||
&& !nodes.some(node => node.permissions === Permission.NONE)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
class="files-list__row-mtime"
|
||||
data-cy-files-list-row-mtime
|
||||
@click="openDetailsIfAvailable">
|
||||
<NcDateTime :timestamp="source.mtime" :ignore-seconds="true" />
|
||||
<NcDateTime v-if="source.mtime" :timestamp="source.mtime" :ignore-seconds="true" />
|
||||
</td>
|
||||
|
||||
<!-- View columns -->
|
||||
|
|
@ -177,8 +177,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
size() {
|
||||
const size = parseInt(this.source.size, 10) || 0
|
||||
if (typeof size !== 'number' || size < 0) {
|
||||
const size = parseInt(this.source.size, 10)
|
||||
if (typeof size !== 'number' || isNaN(size) || size < 0) {
|
||||
return this.t('files', 'Pending')
|
||||
}
|
||||
return formatFileSize(size, true)
|
||||
|
|
@ -186,8 +186,8 @@ export default defineComponent({
|
|||
sizeOpacity() {
|
||||
const maxOpacitySize = 10 * 1024 * 1024
|
||||
|
||||
const size = parseInt(this.source.size, 10) || 0
|
||||
if (!size || size < 0) {
|
||||
const size = parseInt(this.source.size, 10)
|
||||
if (!size || isNaN(size) || size < 0) {
|
||||
return {}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,14 +17,18 @@ import { getCurrentUser } from '@nextcloud/auth'
|
|||
|
||||
import './sharingStatusAction.scss'
|
||||
|
||||
const generateAvatarSvg = (userId: string) => {
|
||||
const avatarUrl = generateUrl('/avatar/{userId}/32', { userId })
|
||||
const generateAvatarSvg = (userId: string, isGuest = false) => {
|
||||
const avatarUrl = generateUrl(isGuest ? '/avatar/guest/{userId}/32' : '/avatar/{userId}/32?guestFallback=true', { userId })
|
||||
return `<svg width="32" height="32" viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg" class="sharing-status__avatar">
|
||||
<image href="${avatarUrl}" height="32" width="32" />
|
||||
</svg>`
|
||||
}
|
||||
|
||||
const isExternal = (node: Node) => {
|
||||
return node.attributes.remote_id !== undefined
|
||||
}
|
||||
|
||||
export const action = new FileAction({
|
||||
id: 'sharing-status',
|
||||
displayName(nodes: Node[]) {
|
||||
|
|
@ -33,7 +37,7 @@ export const action = new FileAction({
|
|||
const ownerId = node?.attributes?.['owner-id']
|
||||
|
||||
if (shareTypes.length > 0
|
||||
|| (ownerId && ownerId !== getCurrentUser()?.uid)) {
|
||||
|| (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
|
||||
return t('files_sharing', 'Shared')
|
||||
}
|
||||
|
||||
|
|
@ -46,11 +50,11 @@ export const action = new FileAction({
|
|||
const ownerDisplayName = node?.attributes?.['owner-display-name']
|
||||
|
||||
// Mixed share types
|
||||
if (Array.isArray(node.attributes?.['share-types'])) {
|
||||
if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) {
|
||||
return t('files_sharing', 'Shared multiple times with different people')
|
||||
}
|
||||
|
||||
if (ownerId && ownerId !== getCurrentUser()?.uid) {
|
||||
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
|
||||
return t('files_sharing', 'Shared by {ownerDisplayName}', { ownerDisplayName })
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +66,7 @@ export const action = new FileAction({
|
|||
const shareTypes = Object.values(node?.attributes?.['share-types'] || {}).flat() as number[]
|
||||
|
||||
// Mixed share types
|
||||
if (Array.isArray(node.attributes?.['share-types'])) {
|
||||
if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) {
|
||||
return AccountPlusSvg
|
||||
}
|
||||
|
||||
|
|
@ -84,8 +88,8 @@ export const action = new FileAction({
|
|||
}
|
||||
|
||||
const ownerId = node?.attributes?.['owner-id']
|
||||
if (ownerId && ownerId !== getCurrentUser()?.uid) {
|
||||
return generateAvatarSvg(ownerId)
|
||||
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
|
||||
return generateAvatarSvg(ownerId, isExternal(node))
|
||||
}
|
||||
|
||||
return AccountPlusSvg
|
||||
|
|
@ -107,7 +111,7 @@ export const action = new FileAction({
|
|||
}
|
||||
|
||||
// If the node is shared by someone else
|
||||
if (ownerId && ownerId !== getCurrentUser()?.uid) {
|
||||
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -336,12 +336,27 @@ describe('SharingService share to Node mapping', () => {
|
|||
expect(folder.attributes.favorite).toBe(1)
|
||||
})
|
||||
|
||||
test('Empty', async () => {
|
||||
jest.spyOn(logger, 'error').mockImplementationOnce(() => {})
|
||||
jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({
|
||||
data: {
|
||||
ocs: {
|
||||
data: [],
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const shares = await getContents(false, true, false, false)
|
||||
expect(shares.contents).toHaveLength(0)
|
||||
expect(logger.error).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
test('Error', async () => {
|
||||
jest.spyOn(logger, 'error').mockImplementationOnce(() => {})
|
||||
jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({
|
||||
data: {
|
||||
ocs: {
|
||||
data: [{}],
|
||||
data: [null],
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import type { AxiosPromise } from 'axios'
|
||||
import type { OCSResponse } from '@nextcloud/typings/ocs'
|
||||
|
||||
import { Folder, File, type ContentsWithRoot } from '@nextcloud/files'
|
||||
import { Folder, File, type ContentsWithRoot, Permission } from '@nextcloud/files'
|
||||
import { generateOcsUrl, generateRemoteUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
|
@ -19,16 +19,34 @@ 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) {
|
||||
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.mimetype ? 'file' : 'folder'
|
||||
|
||||
// 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
|
||||
const fileid = ocsEntry.file_source || ocsEntry.id
|
||||
|
||||
// Generate path and strip double slashes
|
||||
const path = ocsEntry?.path || ocsEntry.file_target
|
||||
const path = ocsEntry?.path || ocsEntry.file_target || ocsEntry.name
|
||||
const source = generateRemoteUrl(`dav/${rootPath}/${path}`.replaceAll(/\/\//gm, '/'))
|
||||
|
||||
// Prefer share time if more recent than item mtime
|
||||
|
|
@ -41,7 +59,7 @@ const ocsEntryToNode = function(ocsEntry: any): Folder | File | null {
|
|||
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,
|
||||
|
|
@ -150,7 +168,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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue