UNPKG

@nephele/adapter-file-system

Version:

File system adapter for the Nephele WebDAV server.

159 lines 6.46 kB
import path from 'node:path'; import fsp from 'node:fs/promises'; import { constants } from 'node:fs'; import { BadGatewayError, MethodNotImplementedError, MethodNotSupportedError, ResourceNotFoundError, } from 'nephele'; import { userReadBit, userWriteBit, userExecuteBit, groupReadBit, groupWriteBit, groupExecuteBit, otherReadBit, otherWriteBit, otherExecuteBit, } from './FileSystemBits.js'; import Resource from './Resource.js'; export default class Adapter { constructor({ root, followLinks = true, properties = 'meta-files', locks = 'meta-files', contentEtagMaxBytes = -1, }) { this.root = root.replace(new RegExp(`${path.sep.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}?$`), () => path.sep); this.followLinks = followLinks; this.properties = properties; this.locks = locks; this.stat = this.followLinks ? fsp.stat : fsp.lstat; this.contentEtagMaxBytes = contentEtagMaxBytes; } urlToRelativePath(url, baseUrl) { if (!decodeURIComponent(url.pathname) .replace(/\/?$/, () => '/') .startsWith(decodeURIComponent(baseUrl.pathname))) { return null; } return path.join(path.sep, ...decodeURIComponent(url.pathname) .substring(decodeURIComponent(baseUrl.pathname).length) .replace(/\/?$/, '') .split('/')); } urlToAbsolutePath(url, baseUrl) { const relativePath = this.urlToRelativePath(url, baseUrl); if (relativePath == null) { return null; } return path.join(this.root, relativePath); } async getUid(user) { return user.uid == null ? -1 : user.uid; } async getGid(user) { return user.gid == null ? -1 : user.gid; } async getGids(user) { return user.gids == null ? [] : user.gids; } async getComplianceClasses(_url, _request, _response) { if (this.locks === 'disallow') { return []; } return ['2']; } async getAllowedMethods(_url, _request, _response) { return []; } async getOptionsResponseCacheControl(_url, _request, _response) { return 'max-age=604800'; } async isAuthorized(url, method, baseUrl, user) { let access = 'u'; if (['GET', 'HEAD', 'COPY', 'OPTIONS', 'PROPFIND'].includes(method)) { access = 'r'; } if (['POST', 'PUT', 'DELETE', 'MOVE', 'MKCOL', 'PROPPATCH'].includes(method)) { access = 'w'; } if (['SEARCH'].includes(method)) { access = 'x'; } if (['LOCK', 'UNLOCK'].includes(method)) { access = 'w'; } if (access === 'u') { return false; } const uid = await this.getUid(user); const gids = await this.getGids(user); const pathname = this.urlToRelativePath(url, baseUrl); const absolutePathname = this.urlToAbsolutePath(url, baseUrl); if (pathname == null || absolutePathname == null) { return false; } const parts = [ this.root, ...pathname.split(path.sep).filter((str) => str !== ''), ]; let exists = true; try { await fsp.access(absolutePathname, access === 'w' ? constants.W_OK : constants.R_OK); } catch (e) { exists = false; } if (uid >= 0) { for (let i = 1; i <= parts.length; i++) { const ipathname = path.join(path.sep, ...parts.slice(0, i)); try { const stats = await this.stat(ipathname); if (access === 'x' || i < parts.length) { if (!(stats.mode & otherExecuteBit || (stats.uid === uid && stats.mode & userExecuteBit) || (gids.includes(stats.gid) && stats.mode & groupExecuteBit))) { return false; } } if (i === parts.length && access === 'r' && exists) { if (!(stats.mode & otherReadBit || (stats.uid === uid && stats.mode & userReadBit) || (gids.includes(stats.gid) && stats.mode & groupReadBit))) { return false; } } if ((i === parts.length && access === 'w') || (!exists && i === parts.length - 1)) { if (!(stats.mode & otherWriteBit || (stats.uid === uid && stats.mode & userWriteBit) || (gids.includes(stats.gid) && stats.mode & groupWriteBit))) { return false; } } } catch (e) { if (exists || (i < parts.length && e.code !== 'ENOENT')) { return false; } } } } return true; } async getResource(url, baseUrl) { const path = this.urlToRelativePath(url, baseUrl); if (path == null) { throw new BadGatewayError('The given path is not managed by this server.'); } const resource = new Resource({ adapter: this, baseUrl, path }); if (!(await resource.exists())) { throw new ResourceNotFoundError('Resource not found.'); } return resource; } async newResource(url, baseUrl) { const path = this.urlToRelativePath(url, baseUrl); if (path == null) { throw new BadGatewayError('The given path is not managed by this server.'); } return new Resource({ adapter: this, baseUrl, path, collection: false }); } async newCollection(url, baseUrl) { const path = this.urlToRelativePath(url, baseUrl); if (path == null) { throw new BadGatewayError('The given path is not managed by this server.'); } return new Resource({ adapter: this, baseUrl, path, collection: true }); } getMethod(method) { if (method === 'POST' || method === 'PATCH') { throw new MethodNotSupportedError('Method not supported.'); } throw new MethodNotImplementedError('Method not implemented.'); } } //# sourceMappingURL=Adapter.js.map