UNPKG

nephele

Version:

Highly customizable and extensible WebDAV server for Node.js and Express.

215 lines 9.88 kB
import { BadRequestError, ForbiddenError, LockedError, MediaTypeNotSupportedError, NotAcceptableError, PreconditionFailedError, ResourceNotFoundError, UnauthorizedError, } from '../Errors/index.js'; import { catchErrors } from '../catchErrors.js'; import { MultiStatus, Status } from '../MultiStatus.js'; import { Method } from './Method.js'; import { DELETE } from './DELETE.js'; export class COPY extends Method { async run(request, response) { const { url, encoding } = this.getRequestData(request, response); if (await this.runPlugins(request, response, 'beginCopy', { method: this, url, })) { return; } if (await this.isAdapterRoot(request, response, url)) { throw new ForbiddenError('This collection cannot be copied.'); } await this.checkAuthorization(request, response, 'COPY'); const contentType = request.accepts('application/xml', 'text/xml'); if (!contentType) { throw new NotAcceptableError('Requested content type is not supported.'); } let destination = this.getRequestDestination(request); const depth = request.get('Depth') || 'infinity'; const overwrite = request.get('Overwrite'); const resource = await response.locals.adapter.getResource(url, response.locals.baseUrl); if ((await resource.isCollection()) && !url.toString().endsWith('/')) { response.set({ 'Content-Location': `${url}/`, }); } if (await this.runPlugins(request, response, 'preCopy', { method: this, resource, destination, depth, overwrite, })) { return; } if (!destination) { throw new BadRequestError('Destination header is required.'); } if (url .toString() .startsWith(destination.toString().replace(/\/?$/, () => '/'))) { throw new BadRequestError("Can't copy a resource to its own ancestor."); } if (!(await this.pathsHaveSameAdapter(response, decodeURIComponent(request.path), decodeURIComponent(destination.pathname.substring(request.baseUrl.length))))) { throw new ForbiddenError('This resource cannot be copied to the destination.'); } if (await resource.isCollection()) { destination = new URL(destination.toString().replace(/\/?$/, () => '/')); } if (!['0', 'infinity'].includes(depth)) { throw new BadRequestError('Depth header must be one of "0", or "infinity".'); } let stream = await this.getBodyStream(request, response); let providedBody = false; stream.on('data', (data) => { if (data.toString().trim()) { providedBody = true; } }); await new Promise((resolve, _reject) => { stream.on('end', () => { resolve(); }); }); if (providedBody) { response.locals.debug('Provided body to COPY.'); throw new MediaTypeNotSupportedError("This server doesn't understand the body sent in the request."); } await this.checkConditionalHeaders(request, response); let destResource; let destExists = true; try { destResource = await response.locals.adapter.getResource(destination, response.locals.baseUrl); } catch (e) { if (e instanceof ResourceNotFoundError) { destResource = await response.locals.adapter.newResource(destination, response.locals.baseUrl); destExists = false; } else { throw e; } } if (await this.runPlugins(request, response, 'beforeCopy', { method: this, resource, destination: destResource, exists: destExists, depth, overwrite, })) { return; } response.set({ 'Cache-Control': 'private, no-cache', Date: new Date().toUTCString(), }); const multiStatus = new MultiStatus(); const recursivelyCopy = async (resource, destination, topLevel = true) => { const run = catchErrors(async () => { if (!(await this.pathsHaveSameAdapter(response, decodeURIComponent((await resource.getCanonicalUrl()).pathname.substring(request.baseUrl.length)), decodeURIComponent(destination.pathname.substring(request.baseUrl.length)).replace(/\/?$/, () => '/')))) { throw new ForbiddenError('This resource cannot be copied to the destination.'); } let destinationResource; let destinationExists = true; try { destinationResource = await response.locals.adapter.getResource(destination, response.locals.baseUrl); } catch (e) { if (e instanceof ResourceNotFoundError) { destinationResource = await response.locals.adapter.newResource(destination, response.locals.baseUrl); destinationExists = false; } else { throw e; } } if (overwrite === 'F' && destinationExists) { throw new PreconditionFailedError('A resource exists at the destination.'); } const collection = await resource.isCollection(); if (!(await response.locals.adapter.isAuthorized(destination, collection ? 'MKCOL' : 'PUT', response.locals.baseUrl, response.locals.user))) { throw new UnauthorizedError('The user is not authorized to modify the destination resource.'); } if (topLevel) { const lockPermission = await this.getLockPermission(request, response, destinationResource, response.locals.user); if (lockPermission === 1) { throw new LockedError('The user does not have permission to add a new resource to the locked collection.'); } if (lockPermission === 0) { throw new LockedError('The user does not have permission to modify the locked resource.'); } } if (destinationExists) { if (topLevel && (await destinationResource.isCollection())) { const del = new DELETE(this.opts); const childrenDeleted = await del.recursivelyDelete(destinationResource, request, response, multiStatus); if (childrenDeleted) { await destinationResource.delete(response.locals.user); } } else { const lockPermission = await this.getLockPermission(request, response, destinationResource, response.locals.user); if (lockPermission !== 2) { return; } } } await resource.copy(destination, response.locals.baseUrl, response.locals.user); if (depth === 'infinity' && collection) { try { const children = await resource.getInternalMembers(response.locals.user); for (let child of children) { const name = await child.getCanonicalName(); const destinationUrl = new URL(destination.toString().replace(/\/?$/, () => '/') + encodeURIComponent(name) + ((await child.isCollection()) ? '/' : ''), `${destination.protocol}//${destination.host}`); await recursivelyCopy(child, destinationUrl, false); } } catch (e) { if (e instanceof UnauthorizedError) { return; } throw e; } } return destinationExists; }, async (code, message, error) => { if (code === 500 && error) { response.locals.debug('Unknown Error: %o', error); } let status = new Status(destination, code); if (message) { status.description = message; } response.locals.errors.push(status); multiStatus.addStatus(status); }); return await run(); }; const existed = await recursivelyCopy(resource, destination); if (multiStatus.statuses.length === 0) { response.status(existed ? 204 : 201); if (!existed) { response.set({ Location: destination.toString(), }); } response.end(); } else { const responseXml = await this.renderXml(multiStatus.render()); response.status(207); response.set({ 'Content-Type': `${contentType}; charset=utf-8`, }); this.sendBodyContent(response, responseXml, encoding); } await this.runPlugins(request, response, 'afterCopy', { method: this, resource, destination: destResource, exists: destExists, depth, overwrite, }); } } //# sourceMappingURL=COPY.js.map