@nephele/adapter-file-system
Version:
File system adapter for the Nephele WebDAV server.
159 lines • 6.46 kB
JavaScript
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