diff options
Diffstat (limited to 'apps/files_versions/src/utils')
-rw-r--r-- | apps/files_versions/src/utils/davClient.js | 47 | ||||
-rw-r--r-- | apps/files_versions/src/utils/davRequest.js | 24 | ||||
-rw-r--r-- | apps/files_versions/src/utils/logger.js | 20 | ||||
-rw-r--r-- | apps/files_versions/src/utils/versions.js | 139 | ||||
-rw-r--r-- | apps/files_versions/src/utils/versions.ts | 133 |
5 files changed, 161 insertions, 202 deletions
diff --git a/apps/files_versions/src/utils/davClient.js b/apps/files_versions/src/utils/davClient.js index e4bfeb10411..029373e9193 100644 --- a/apps/files_versions/src/utils/davClient.js +++ b/apps/files_versions/src/utils/davClient.js @@ -1,34 +1,29 @@ /** - * @copyright 2022 Louis Chemineau <mlouis@chmn.me> - * - * @author Louis Chemineau <mlouis@chmn.me> - * - * @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: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { createClient, getPatcher } from 'webdav' +import { createClient } from 'webdav' import { generateRemoteUrl } from '@nextcloud/router' -import axios from '@nextcloud/axios' +import { getRequestToken, onRequestTokenUpdate } from '@nextcloud/auth' +// init webdav client const rootPath = 'dav' +const remote = generateRemoteUrl(rootPath) +const client = createClient(remote) -// force our axios -const patcher = getPatcher() -patcher.patch('request', axios) +// set CSRF token header +const setHeaders = (token) => { + client.setHeaders({ + // Add this so the server knows it is an request from the browser + 'X-Requested-With': 'XMLHttpRequest', + // Inject user auth + requesttoken: token ?? '', + }) +} -// init webdav client on default dav endpoint -const remote = generateRemoteUrl(rootPath) -export default createClient(remote) +// refresh headers when request token changes +onRequestTokenUpdate(setHeaders) +setHeaders(getRequestToken()) + +export default client diff --git a/apps/files_versions/src/utils/davRequest.js b/apps/files_versions/src/utils/davRequest.js index fb2126d98bf..1dcf620564a 100644 --- a/apps/files_versions/src/utils/davRequest.js +++ b/apps/files_versions/src/utils/davRequest.js @@ -1,23 +1,6 @@ /** - * @copyright Copyright (c) 2019 Louis Chmn <louis@chmn.me> - * - * @author Louis Chmn <louis@chmn.me> - * - * @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: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ export default `<?xml version="1.0"?> @@ -29,6 +12,9 @@ export default `<?xml version="1.0"?> <d:getcontentlength /> <d:getcontenttype /> <d:getlastmodified /> + <d:getetag /> <nc:version-label /> + <nc:version-author /> + <nc:has-preview /> </d:prop> </d:propfind>` diff --git a/apps/files_versions/src/utils/logger.js b/apps/files_versions/src/utils/logger.js index 4f0356764d9..f84cb969244 100644 --- a/apps/files_versions/src/utils/logger.js +++ b/apps/files_versions/src/utils/logger.js @@ -1,22 +1,6 @@ /** - * @copyright 2022 Louis Chemineau <mlouis@chmn.me> - * - * @author Louis Chemineau <mlouis@chmn.me> - * - * @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: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import { getLoggerBuilder } from '@nextcloud/logger' diff --git a/apps/files_versions/src/utils/versions.js b/apps/files_versions/src/utils/versions.js deleted file mode 100644 index 1a5dde10824..00000000000 --- a/apps/files_versions/src/utils/versions.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @copyright 2022 Louis Chemineau <mlouis@chmn.me> - * - * @author Louis Chemineau <mlouis@chmn.me> - * - * @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/>. - */ - -import { getCurrentUser } from '@nextcloud/auth' -import client from '../utils/davClient.js' -import davRequest from '../utils/davRequest.js' -import logger from '../utils/logger.js' -import { joinPaths } from '@nextcloud/paths' -import { generateUrl } from '@nextcloud/router' -import moment from '@nextcloud/moment' - -/** - * @typedef {object} Version - * @property {string} fileId - The id of the file associated to the version. - * @property {string} label - 'Current version' or '' - * @property {string} fileName - File name relative to the version DAV endpoint - * @property {string} mimeType - Empty for the current version, else the actual mime type of the version - * @property {string} size - Human readable size - * @property {string} type - 'file' - * @property {number} mtime - Version creation date as a timestamp - * @property {string} preview - Preview URL of the version - * @property {string} url - Download URL of the version - * @property {string|null} fileVersion - The version id, null for the current version - */ - -/** - * @param fileInfo - * @return {Promise<Version[]>} - */ -export async function fetchVersions(fileInfo) { - const path = `/versions/${getCurrentUser()?.uid}/versions/${fileInfo.id}` - - try { - /** @type {import('webdav').ResponseDataDetailed<import('webdav').FileStat[]>} */ - const response = await client.getDirectoryContents(path, { - data: davRequest, - details: true, - }) - return response.data - // Filter out root - .filter(({ mime }) => mime !== '') - .map(version => formatVersion(version, fileInfo)) - } catch (exception) { - logger.error('Could not fetch version', { exception }) - throw exception - } -} - -/** - * Restore the given version - * - * @param {Version} version - */ -export async function restoreVersion(version) { - try { - logger.debug('Restoring version', { url: version.url }) - await client.moveFile( - `/versions/${getCurrentUser()?.uid}/versions/${version.fileId}/${version.fileVersion}`, - `/versions/${getCurrentUser()?.uid}/restore/target` - ) - } catch (exception) { - logger.error('Could not restore version', { exception }) - throw exception - } -} - -/** - * Format version - * - * @param {object} version - raw version received from the versions DAV endpoint - * @param {object} fileInfo - file properties received from the files DAV endpoint - * @return {Version} - */ -function formatVersion(version, fileInfo) { - return { - fileId: fileInfo.id, - label: version.props['version-label'], - fileName: version.filename, - mimeType: version.mime, - size: version.size, - type: version.type, - mtime: moment(version.lastmod).unix() * 1000, - preview: generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', { - file: joinPaths(fileInfo.path, fileInfo.name), - fileVersion: version.basename, - }), - url: joinPaths('/remote.php/dav', version.filename), - fileVersion: version.basename, - } -} - -/** - * @param {Version} version - * @param {string} newLabel - */ -export async function setVersionLabel(version, newLabel) { - return await client.customRequest( - version.fileName, - { - method: 'PROPPATCH', - data: `<?xml version="1.0"?> - <d:propertyupdate xmlns:d="DAV:" - xmlns:oc="http://owncloud.org/ns" - xmlns:nc="http://nextcloud.org/ns" - xmlns:ocs="http://open-collaboration-services.org/ns"> - <d:set> - <d:prop> - <nc:version-label>${newLabel}</nc:version-label> - </d:prop> - </d:set> - </d:propertyupdate>`, - } - ) -} - -/** - * @param {Version} version - */ -export async function deleteVersion(version) { - await client.deleteFile(version.fileName) -} diff --git a/apps/files_versions/src/utils/versions.ts b/apps/files_versions/src/utils/versions.ts new file mode 100644 index 00000000000..6d5933f0bd9 --- /dev/null +++ b/apps/files_versions/src/utils/versions.ts @@ -0,0 +1,133 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable jsdoc/require-param */ +/* eslint-disable jsdoc/require-jsdoc */ +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import type { FileStat, ResponseDataDetailed } from 'webdav' + +import { generateRemoteUrl, generateUrl } from '@nextcloud/router' +import { getCurrentUser } from '@nextcloud/auth' +import { joinPaths, encodePath } from '@nextcloud/paths' +import moment from '@nextcloud/moment' + +import client from '../utils/davClient.js' +import davRequest from '../utils/davRequest.js' +import logger from '../utils/logger.js' + +export interface Version { + fileId: string, // The id of the file associated to the version. + label: string, // 'Current version' or '' + author: string|null, // UID for the author of the version + filename: string, // File name relative to the version DAV endpoint + basename: string, // A base name generated from the mtime + mime: string, // Empty for the current version, else the actual mime type of the version + etag: string, // Empty for the current version, else the actual mime type of the version + size: string, // Human readable size + type: string, // 'file' + mtime: number, // Version creation date as a timestamp + permissions: string, // Only readable: 'R' + previewUrl: string, // Preview URL of the version + url: string, // Download URL of the version + source: string, // The WebDAV endpoint of the ressource + fileVersion: string|null, // The version id, null for the current version +} + +export async function fetchVersions(fileInfo: any): Promise<Version[]> { + const path = `/versions/${getCurrentUser()?.uid}/versions/${fileInfo.id}` + + try { + const response = await client.getDirectoryContents(path, { + data: davRequest, + details: true, + }) as ResponseDataDetailed<FileStat[]> + + return response.data + // Filter out root + .filter(({ mime }) => mime !== '') + .map(version => formatVersion(version, fileInfo)) + } catch (exception) { + logger.error('Could not fetch version', { exception }) + throw exception + } +} + +/** + * Restore the given version + */ +export async function restoreVersion(version: Version) { + try { + logger.debug('Restoring version', { url: version.url }) + await client.moveFile( + `/versions/${getCurrentUser()?.uid}/versions/${version.fileId}/${version.fileVersion}`, + `/versions/${getCurrentUser()?.uid}/restore/target`, + ) + } catch (exception) { + logger.error('Could not restore version', { exception }) + throw exception + } +} + +/** + * Format version + */ +function formatVersion(version: any, fileInfo: any): Version { + const mtime = moment(version.lastmod).unix() * 1000 + let previewUrl = '' + + if (mtime === fileInfo.mtime) { // Version is the current one + previewUrl = generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0&forceIcon=1&mimeFallback=1', { + fileId: fileInfo.id, + fileEtag: fileInfo.etag, + }) + } else { + previewUrl = generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}&mimeFallback=1', { + file: joinPaths(fileInfo.path, fileInfo.name), + fileVersion: version.basename, + }) + } + + return { + fileId: fileInfo.id, + // If version-label is defined make sure it is a string (prevent issue if the label is a number an PHP returns a number then) + label: version.props['version-label'] && String(version.props['version-label']), + author: version.props['version-author'] ?? null, + filename: version.filename, + basename: moment(mtime).format('LLL'), + mime: version.mime, + etag: `${version.props.getetag}`, + size: version.size, + type: version.type, + mtime, + permissions: 'R', + previewUrl, + url: joinPaths('/remote.php/dav', version.filename), + source: generateRemoteUrl('dav') + encodePath(version.filename), + fileVersion: version.basename, + } +} + +export async function setVersionLabel(version: Version, newLabel: string) { + return await client.customRequest( + version.filename, + { + method: 'PROPPATCH', + data: `<?xml version="1.0"?> + <d:propertyupdate xmlns:d="DAV:" + xmlns:oc="http://owncloud.org/ns" + xmlns:nc="http://nextcloud.org/ns" + xmlns:ocs="http://open-collaboration-services.org/ns"> + <d:set> + <d:prop> + <nc:version-label>${newLabel}</nc:version-label> + </d:prop> + </d:set> + </d:propertyupdate>`, + }, + ) +} + +export async function deleteVersion(version: Version) { + await client.deleteFile(version.filename) +} |