UNPKG

nephele

Version:

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

277 lines 13.3 kB
import { BadRequestError, ForbiddenError, NotAcceptableError, PropertyNotFoundError, UnauthorizedError, } from '../Errors/index.js'; import { MultiStatus, Status, PropStatStatus } from '../MultiStatus.js'; import { Method } from './Method.js'; export class PROPFIND extends Method { async run(request, response) { const { url, encoding } = this.getRequestData(request, response); if (await this.runPlugins(request, response, 'beginPropfind', { method: this, url, })) { return; } await this.checkAuthorization(request, response, 'PROPFIND'); const contentType = request.accepts('application/xml', 'text/xml'); if (!contentType) { throw new NotAcceptableError('Requested content type is not supported.'); } const depth = request.get('Depth') || 'infinity'; 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, 'prePropfind', { method: this, resource, depth, })) { return; } if (!['0', '1', 'infinity'].includes(depth)) { throw new BadRequestError('Depth header must be one of "0", "1", or "infinity".'); } const xmlBody = await this.getBodyXML(request, response); const { output: xml, prefixes } = xmlBody ? await this.parseXml(xmlBody) : { output: null, prefixes: {} }; let requestedProps = []; let allprop = true; let propname = false; if (xml != null) { if (!('propfind' in xml)) { throw new BadRequestError('PROPFIND methods requires a propfind element.'); } if ('propname' in xml.propfind) { propname = true; } if (!('allprop' in xml.propfind)) { allprop = false; } else if ('include' in xml.propfind) { for (let include of xml.propfind.include) { requestedProps = [ ...requestedProps, ...Object.keys(include).filter((name) => name !== '$'), ]; } } if ('prop' in xml.propfind) { for (let prop of xml.propfind.prop) { requestedProps = [ ...requestedProps, ...Object.keys(prop).filter((name) => name !== '$'), ]; } } } await this.checkConditionalHeaders(request, response); if (await this.runPlugins(request, response, 'beforePropfind', { method: this, resource, depth, })) { return; } const multiStatus = new MultiStatus(); if (propname) { response.locals.debug(`Requested prop names.`); } else if (allprop) { response.locals.debug(`Requested all props.${requestedProps.length ? ` Includes: ${requestedProps.join(', ')}` : ''}`); } else { response.locals.debug(`Requested props: ${requestedProps.join(', ')}`); } response.locals.debug(`Requested depth: ${depth}`); let level = 0; const addResourceProps = async (curResource, skipRootCheck = false) => { const url = await curResource.getCanonicalUrl(); response.locals.debug(`Retrieving props for ${await curResource.getCanonicalPath()}`); try { if (!skipRootCheck && (await this.isAdapterRoot(request, response, url))) { const absoluteUrl = new URL(url.toString().replace(/\/?$/, () => '/')); const adapter = await this.getAdapter(request, response, decodeURIComponent(absoluteUrl.pathname.substring(request.baseUrl.length))); curResource = await adapter.getResource(absoluteUrl, absoluteUrl); } } catch (e) { const error = new Status(url, 500); error.description = 'An internal server error occurred.'; response.locals.errors.push(error); multiStatus.addStatus(error); return; } if (!(await curResource.adapter.isAuthorized(url, 'PROPFIND', curResource.baseUrl, response.locals.user))) { const error = new Status(url, 401); error.description = 'The user is not authorized to get properties for this resource.'; response.locals.errors.push(error); multiStatus.addStatus(error); return; } const status = new Status(url, 207); const props = await curResource.getProperties(); try { const supportsLocks = (await curResource.adapter.getComplianceClasses(url, request, response)).includes('2'); if (propname) { const propnames = await props.listByUser(response.locals.user); const propStatStatus = new PropStatStatus(200); const propObj = {}; for (let name of propnames) { propObj[name] = {}; } if (supportsLocks) { propObj.lockdiscovery = {}; } propStatStatus.setProp(propObj); status.addPropStatStatus(propStatStatus); } else { let propObj = {}; const forbiddenProps = []; const unauthorizedProps = []; const notFoundProps = []; const errorProps = []; if (allprop) { propObj = await props.getAllByUser(response.locals.user); for (let name in propObj) { if (propObj[name] instanceof ForbiddenError) { forbiddenProps.push(name); delete propObj[name]; } else if (propObj[name] instanceof UnauthorizedError) { unauthorizedProps.push(name); delete propObj[name]; } else if (propObj[name] instanceof PropertyNotFoundError) { notFoundProps.push(name); delete propObj[name]; } else if (propObj[name] instanceof Error) { errorProps.push(name); delete propObj[name]; } } } for (let name of requestedProps) { if (name in propObj) { continue; } if (name === 'lockdiscovery') { if (!supportsLocks) { notFoundProps.push(name); } continue; } try { const value = await props.getByUser(name, response.locals.user); propObj[name] = value; } catch (e) { if (e instanceof ForbiddenError) { forbiddenProps.push(name); } else if (e instanceof UnauthorizedError) { unauthorizedProps.push(name); } else if (e instanceof PropertyNotFoundError) { notFoundProps.push(name); } else { errorProps.push(name); } } } if (supportsLocks && (allprop || requestedProps.includes('lockdiscovery'))) { const currentLocks = await this.getLocks(request, response, curResource); propObj.lockdiscovery = await this.formatLocks(currentLocks.all); } if (Object.keys(propObj).length) { const propStatStatus = new PropStatStatus(200); propStatStatus.setProp(propObj); status.addPropStatStatus(propStatStatus); } if (forbiddenProps.length) { const propStatStatus = new PropStatStatus(403); propStatStatus.description = `The user does not have access to the ${forbiddenProps .map((name) => name.replace('%%', '')) .join(', ')} propert${forbiddenProps.length === 1 ? 'y' : 'ies'}.`; propStatStatus.setProp(Object.fromEntries(forbiddenProps.map((name) => [name, {}]))); response.locals.errors.push(propStatStatus); status.addPropStatStatus(propStatStatus); } if (unauthorizedProps.length) { const propStatStatus = new PropStatStatus(401); propStatStatus.description = `The user is not authorized to retrieve the ${unauthorizedProps .map((name) => name.replace('%%', '')) .join(', ')} propert${unauthorizedProps.length === 1 ? 'y' : 'ies'}.`; propStatStatus.setProp(Object.fromEntries(unauthorizedProps.map((name) => [name, {}]))); response.locals.errors.push(propStatStatus); status.addPropStatStatus(propStatStatus); } if (notFoundProps.length) { const propStatStatus = new PropStatStatus(404); propStatStatus.description = `The ${notFoundProps .map((name) => name.replace('%%', '')) .join(', ')} propert${notFoundProps.length === 1 ? 'y was' : 'ies were'} not found.`; propStatStatus.setProp(Object.fromEntries(notFoundProps.map((name) => [name, {}]))); response.locals.errors.push(propStatStatus); status.addPropStatStatus(propStatStatus); } if (errorProps.length) { const propStatStatus = new PropStatStatus(500); propStatStatus.description = `An error occurred while trying to retrieve the ${errorProps .map((name) => name.replace('%%', '')) .join(', ')} propert${errorProps.length === 1 ? 'y' : 'ies'}.`; propStatStatus.setProp(Object.fromEntries(errorProps.map((name) => [name, {}]))); response.locals.errors.push(propStatStatus); status.addPropStatStatus(propStatStatus); } } } catch (e) { const propStatStatus = new PropStatStatus(500); propStatStatus.description = 'An internal server error occurred.'; response.locals.errors.push(propStatStatus); status.addPropStatStatus(propStatStatus); } multiStatus.addStatus(status); if (depth === '0' || (level === 1 && depth === '1')) { return; } if (await curResource.isCollection()) { let children = []; try { children = await curResource.getInternalMembers(response.locals.user); } catch (e) { if (!(e instanceof UnauthorizedError)) { throw e; } } level++; for (let child of children) { await addResourceProps(child); } level--; } }; await addResourceProps(resource, true); const responseXml = await this.renderXml(multiStatus.render(), prefixes); response.status(207); response.set({ 'Content-Type': `${contentType}; charset=utf-8`, }); this.sendBodyContent(response, responseXml, encoding); await this.runPlugins(request, response, 'afterPropfind', { method: this, resource, depth, }); } } //# sourceMappingURL=PROPFIND.js.map