nephele
Version:
Highly customizable and extensible WebDAV server for Node.js and Express.
256 lines (215 loc) • 6.84 kB
text/typescript
import path from 'node:path';
import type { Request } from 'express';
import type { AuthResponse, Resource } from '../Interfaces/index.js';
import {
ForbiddenError,
LockedError,
MediaTypeNotSupportedError,
NotAcceptableError,
UnauthorizedError,
} from '../Errors/index.js';
import { catchErrors } from '../catchErrors.js';
import { MultiStatus, Status } from '../MultiStatus.js';
import { Method } from './Method.js';
export class DELETE extends Method {
async run(request: Request, response: AuthResponse) {
const { url, encoding } = this.getRequestData(request, response);
if (
await this.runPlugins(request, response, 'beginDelete', {
method: this,
url,
})
) {
return;
}
if (await this.isAdapterRoot(request, response, url)) {
// Can't delete the root of an adapter.
throw new ForbiddenError('This collection cannot be deleted.');
}
await this.checkAuthorization(request, response, 'DELETE');
const contentType = request.accepts('application/xml', 'text/xml');
if (!contentType) {
throw new NotAcceptableError('Requested content type is not supported.');
}
const resource = await response.locals.adapter.getResource(
url,
response.locals.baseUrl,
);
if (
await this.runPlugins(request, response, 'preDelete', {
method: this,
resource,
})
) {
return;
}
let stream = await this.getBodyStream(request, response);
let providedBody = false;
stream.on('data', (data: Buffer) => {
if (data.toString().trim()) {
providedBody = true;
}
});
await new Promise<void>((resolve, _reject) => {
stream.on('end', () => {
resolve();
});
});
if (providedBody) {
response.locals.debug('Provided body to DELETE.');
throw new MediaTypeNotSupportedError(
"This server doesn't understand the body sent in the request.",
);
}
const lockPermission = await this.getLockPermission(
request,
response,
resource,
response.locals.user,
);
// Check that the resource wouldn't be removed from a locked collection.
if (lockPermission === 1) {
throw new LockedError(
'The user does not have permission to remove a resource from the locked collection.',
);
}
if (lockPermission === 0) {
throw new LockedError(
'The user does not have permission to delete the locked resource.',
);
}
await this.checkConditionalHeaders(request, response);
if (
await this.runPlugins(request, response, 'beforeDelete', {
method: this,
resource,
})
) {
return;
}
response.set({
'Cache-Control': 'private, no-cache',
Date: new Date().toUTCString(),
});
if (await resource.isCollection()) {
const multiStatus = new MultiStatus();
await this.recursivelyDelete(resource, request, response, multiStatus);
if (multiStatus.statuses.length === 0) {
// Delete its locks.
const locks = await this.getCurrentResourceLocks(resource);
for (let lock of locks) {
await lock.delete();
}
await resource.delete(response.locals.user);
response.status(204); // No Content
response.end();
} else {
// If there were errors, don't delete the top level resource, respond
// with the errors.
const responseXml = await this.renderXml(multiStatus.render());
response.status(207); // Multi-Status
response.set({
'Content-Type': `${contentType}; charset=utf-8`,
});
this.sendBodyContent(response, responseXml, encoding);
}
} else {
// Delete its locks.
const locks = await this.getCurrentResourceLocks(resource);
for (let lock of locks) {
await lock.delete();
}
await resource.delete(response.locals.user);
response.status(204); // No Content
response.end();
}
await this.runPlugins(request, response, 'afterDelete', {
method: this,
resource,
});
}
async recursivelyDelete(
collection: Resource,
request: Request,
response: AuthResponse,
multiStatus: MultiStatus,
): Promise<boolean> {
try {
const children = await collection.getInternalMembers(
response.locals.user,
);
let allDeleted = true;
for (let child of children) {
const run = catchErrors(
async () => {
if (
await this.isAdapterRoot(
request,
response,
await child.getCanonicalUrl(),
)
) {
// Can't delete the root of another adapter.
throw new ForbiddenError('This collection cannot be deleted.');
}
const lockPermission = await this.getLockPermission(
request,
response,
child,
response.locals.user,
);
if (lockPermission !== 2) {
throw new LockedError('This resource is locked.');
}
if (await child.isCollection()) {
allDeleted =
allDeleted &&
(await this.recursivelyDelete(
child,
request,
response,
multiStatus,
));
}
// Delete its locks.
const locks = await this.getCurrentResourceLocks(child);
for (let lock of locks) {
await lock.delete();
}
await child.delete(response.locals.user);
},
async (code, message, error) => {
if (code === 500 && error) {
response.locals.debug('Unknown Error: %o', error);
}
const url = await child.getCanonicalUrl();
let status = new Status(url, code);
if (message) {
status.description = message;
}
if (error instanceof LockedError) {
status.setBody({ error: [{ 'lock-token-submitted': {} }] });
}
response.locals.errors.push(status);
multiStatus.addStatus(status);
allDeleted = false;
},
);
await run();
}
return allDeleted;
} catch (e: any) {
const url = await collection.getCanonicalUrl();
let error = new Status(url, 500);
if (e instanceof UnauthorizedError) {
error = new Status(url, 401);
}
if (e.message) {
error.description = e.message;
}
response.locals.errors.push(error);
multiStatus.addStatus(error);
return false;
}
}
}