nephele
Version:
Highly customizable and extensible WebDAV server for Node.js and Express.
225 lines • 10.4 kB
JavaScript
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