UNPKG

nephele

Version:

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

225 lines 10.4 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 MOVE extends Method { async run(request, response) { const { url, encoding } = this.getRequestData(request, response); if (await this.runPlugins(request, response, 'beginMove', { method: this, url, })) { return; } if (await this.isAdapterRoot(request, response, url)) { throw new ForbiddenError('This collection cannot be moved.'); } await this.checkAuthorization(request, response, 'MOVE'); const contentType = request.accepts('application/xml', 'text/xml'); if (!contentType) { throw new NotAcceptableError('Requested content type is not supported.'); } const destination = this.getRequestDestination(request); const _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, 'preMove', { method: this, resource, destination, overwrite, })) { return; } if (!destination) { throw new BadRequestError('Destination header is required.'); } if (url .toString() .startsWith(destination.toString().replace(/\/?$/, () => '/'))) { throw new BadRequestError("Can't move 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 moved to the destination.'); } 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 MOVE.'); 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, 'beforeMove', { method: this, resource, destination: destResource, exists: destExists, overwrite, })) { return; } const multiStatus = new MultiStatus(); response.set({ 'Cache-Control': 'private, no-cache', Date: new Date().toUTCString(), }); const recursivelyMove = 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 moved 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.'); } const lockPermission = await this.getLockPermission(request, response, resource, response.locals.user); if (lockPermission === 1) { throw new LockedError('The user does not have permission to move a resource from the locked collection.'); } if (lockPermission === 0) { throw new LockedError('The user does not have permission to move the locked 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 move a 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; } } } if (collection) { await resource.copy(destination, response.locals.baseUrl, response.locals.user); let allMoved = true; 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)); const { result } = (await recursivelyMove(child, destinationUrl, false)) || { result: false }; allMoved = allMoved && result; } } catch (e) { if (e instanceof UnauthorizedError) { return; } throw e; } if (allMoved) { await resource.delete(response.locals.user); } } else { await resource.move(destination, response.locals.baseUrl, response.locals.user); } return { existed: destinationExists, result: true }; }, 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 recursivelyMove(resource, destination)) || { existed: false, }; if (multiStatus.statuses.length === 0) { response.status(existed ? 204 : 201); if (!existed) { response.set({ 'Content-Length': 0, 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); } if (await this.runPlugins(request, response, 'afterMove', { method: this, resource, destination: destResource, exists: destExists, overwrite, })) { return; } } } //# sourceMappingURL=MOVE.js.map