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