@sync-in/server
Version:
The secure, open-source platform for file storage, sharing, collaboration, and sync
259 lines (258 loc) • 12.2 kB
JavaScript
/*
* Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
* This file is part of Sync-in | The open source file sync and share solution
* See the LICENSE file for licensing details
*/ "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "WebDAVProtocolGuard", {
enumerable: true,
get: function() {
return WebDAVProtocolGuard;
}
});
const _common = require("@nestjs/common");
const _functions = require("../../../common/functions");
const _shared = require("../../../common/shared");
const _applicationsconstants = require("../../applications.constants");
const _cache = require("../../files/constants/cache");
const _user = require("../../users/constants/user");
const _webdav = require("../constants/webdav");
const _ifheader = require("../utils/if-header");
const _webdav1 = require("../utils/webdav");
const _xml = require("../utils/xml");
function _ts_decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
let WebDAVProtocolGuard = class WebDAVProtocolGuard {
async canActivate(ctx) {
const req = ctx.switchToHttp().getRequest();
const res = ctx.switchToHttp().getResponse();
this.checkUserPermission(req);
this.setDAVContext(req);
switch(req.method){
case _applicationsconstants.HTTP_METHOD.OPTIONS:
// hook to stop propagation and return headers on all webdav routes
res.headers(_webdav.OPTIONS_HEADERS);
throw new _common.HttpException(null, _common.HttpStatus.OK);
case _applicationsconstants.HTTP_METHOD.PROPFIND:
return this.propfindMethod(req);
case _applicationsconstants.HTTP_METHOD.LOCK:
return this.lockMethod(req);
case _applicationsconstants.HTTP_METHOD.UNLOCK:
return this.unlockMethod(req);
case _applicationsconstants.HTTP_METHOD.PUT:
return this.putMethod(req);
case _applicationsconstants.HTTP_METHOD.DELETE:
return this.deleteMethod(req);
case _applicationsconstants.HTTP_METHOD.PROPPATCH:
return this.proppatchMethod(req);
case _applicationsconstants.HTTP_METHOD.MKCOL:
return this.mkcolMethod(req);
case _applicationsconstants.HTTP_METHOD.COPY:
return this.copyMoveMethod(req);
case _applicationsconstants.HTTP_METHOD.MOVE:
return this.copyMoveMethod(req, true);
default:
return true;
}
}
checkUserPermission(req) {
if (req.method !== _applicationsconstants.HTTP_METHOD.OPTIONS && !req.user.havePermission(_user.USER_PERMISSION.WEBDAV)) {
this.logger.warn(`does not have permission : ${_user.USER_PERMISSION.WEBDAV}`);
throw new _common.HttpException('Missing permission', _common.HttpStatus.FORBIDDEN);
}
}
setDAVContext(req) {
req.dav = {
url: (0, _shared.decodeUrl)(req.originalUrl),
depth: _webdav.DEPTH.RESOURCE
};
}
parseBody(req) {
let body = null;
let valid;
try {
valid = (0, _xml.xmlIsValid)(req.body.toString());
} catch (e) {
valid = {
err: {
code: e.code,
msg: `Invalid body content: ${e.message}`,
line: 0,
col: 0
}
};
}
if (valid === true) {
body = (0, _xml.xmlParse)(req.body);
} else if (valid.err.code === undefined && _webdav.ALLOW_EMPTY_BODY_METHODS.indexOf(req.method) > -1) {
body = null;
} else {
throw new _common.HttpException(`${valid.err.code} : ${valid.err.msg}`, _common.HttpStatus.BAD_REQUEST);
}
req.dav.httpVersion = `${req.protocol.toUpperCase()}/${req.raw['httpVersion']}`;
req.dav.body = body;
return true;
}
parseIfHeader(req) {
// stores if headers, examine it later
if (req.headers[_webdav.HEADER.IF]) {
this.logger.verbose(`If header before : ${JSON.stringify(req.headers[_webdav.HEADER.IF])}`);
const ifHeaders = (0, _ifheader.parseIfHeader)(req.headers[_webdav.HEADER.IF]);
this.logger.verbose(`If header after : ${JSON.stringify(ifHeaders)}`);
if (ifHeaders.length) req.dav.ifHeaders = ifHeaders;
}
}
propfindMethod(req) {
req.dav.depth = req.headers[_webdav.HEADER.DEPTH]?.toLowerCase();
if (!req.dav.depth || req.dav.depth !== _webdav.DEPTH.MEMBERS && req.dav.depth !== _webdav.DEPTH.RESOURCE) {
if (!req.dav.depth) {
this.logger.warn('Missing propfind depth, default value 1 was set');
} else if (req.dav.depth === _webdav.DEPTH.INFINITY) {
this.logger.warn('Infinite depth is disabled for security reasons, default value 1 was set');
} else {
this.logger.warn(`Invalid propfind depth header : ${req.dav.depth}, default value 1 was set`);
}
req.dav.depth = _webdav.DEPTH.MEMBERS;
}
if (this.parseBody(req)) {
if (req.dav.body) {
for(const propType in req.dav.body.propfind){
if (Object.values(_webdav.PROPSTAT).indexOf(propType) > -1) {
req.dav.propfindMode = propType;
break;
}
}
if (!req.dav.propfindMode) {
this.logger.warn(`Invalid propfind mode : ${JSON.stringify(Object.keys(req.dav.body.propfind))}`);
throw new _common.HttpException('Invalid propfind mode', _common.HttpStatus.BAD_REQUEST);
}
} else {
// propfind request allow empty body
req.dav.body = _webdav1.PROPFIND_ALL_PROP;
req.dav.propfindMode = _webdav.PROPSTAT.ALLPROP;
}
}
this.parseIfHeader(req);
return true;
}
proppatchMethod(req) {
req.dav.depth = (req.headers[_webdav.HEADER.DEPTH] || _webdav.DEPTH.RESOURCE).toLowerCase();
this.parseBody(req);
if (!req.dav.body || Object.keys(req.dav.body).indexOf(_webdav.PROPPATCH_PROP_UPDATE) === -1) {
this.logger.debug(`'${_webdav.PROPPATCH_PROP_UPDATE}' is missing : ${JSON.stringify(req.dav.body)}`);
throw new _common.HttpException(`'${_webdav.PROPPATCH_PROP_UPDATE}' is missing`, _common.HttpStatus.BAD_REQUEST);
}
this.parseIfHeader(req);
return true;
}
lockMethod(req) {
req.dav.lock = {};
if (req.headers[_webdav.HEADER.TIMEOUT]) {
// timeout: 'Infinite, Second-4100000000' | 'Second-4100000000' | 'Infinite'
const timeout = req.headers[_webdav.HEADER.TIMEOUT];
if (timeout.toLowerCase() === 'infinite') {
req.dav.lock.timeout = _cache.CACHE_LOCK_DEFAULT_TTL;
} else {
try {
const timeoutSplit = timeout.split('-');
const seconds = parseInt(timeoutSplit[timeoutSplit.length - 1], 10);
req.dav.lock.timeout = seconds > _cache.CACHE_LOCK_DEFAULT_TTL ? _cache.CACHE_LOCK_DEFAULT_TTL : seconds;
} catch (e) {
this.logger.warn(`${this.lockMethod.name} - unable to set timeout, use the default value : ${e}`);
req.dav.lock.timeout = _cache.CACHE_LOCK_DEFAULT_TTL;
}
}
}
this.parseBody(req);
if (req.dav.body) {
if (!req.dav.body.lockinfo) {
this.logger.warn(`Missing lockinfo : ${JSON.stringify(req.dav.body)}`);
throw new _common.HttpException('Missing lockinfo', _common.HttpStatus.BAD_REQUEST);
}
try {
req.dav.lock.lockscope = Object.keys(req.dav.body.lockinfo.lockscope)[0];
} catch (e) {
this.logger.warn(`${this.parseBody.name} - invalid or undefined lockscope : ${JSON.stringify(req.dav.body.lockinfo)} : ${e}`);
throw new _common.HttpException('Invalid or undefined lockscope', _common.HttpStatus.BAD_REQUEST);
}
if (Object.values(_webdav.LOCK_SCOPE).indexOf(req.dav.lock.lockscope) === -1) {
this.logger.warn(`${this.parseBody.name} - invalid or undefined lockscope : ${JSON.stringify(req.dav.body.lockinfo)}`);
throw new _common.HttpException('Invalid or undefined lockscope', _common.HttpStatus.BAD_REQUEST);
}
if (req.dav.body.lockinfo.owner) {
const owner = typeof req.dav.body?.lockinfo?.owner === 'string' ? req.dav.body.lockinfo.owner : req.dav.body?.lockinfo?.owner?.href;
if (typeof owner === 'string') {
req.dav.lock.owner = owner.trim();
}
}
const depth = (req.headers[_webdav.HEADER.DEPTH] || '').toLowerCase();
req.dav.depth = [
_webdav.DEPTH.INFINITY,
_webdav.DEPTH.RESOURCE
].indexOf(depth) === -1 ? _webdav.DEPTH.INFINITY : depth;
} else {
// must ignore depth header on lock refresh request
req.dav.depth = null;
}
this.parseIfHeader(req);
return true;
}
unlockMethod(req) {
if (!req.headers[_webdav.HEADER.LOCK_TOKEN]) {
throw new _common.HttpException('Missing lock token', _common.HttpStatus.BAD_REQUEST);
}
req.dav.lock = {
token: req.headers[_webdav.HEADER.LOCK_TOKEN].replace('<', '').replace('>', '').trim()
};
this.parseIfHeader(req);
return true;
}
putMethod(req) {
req.dav.depth = _webdav.DEPTH.RESOURCE;
this.parseIfHeader(req);
return true;
}
deleteMethod(req) {
this.parseIfHeader(req);
return true;
}
mkcolMethod(req) {
if (req.headers['content-length'] && req.headers['content-length'] !== '0') {
throw new _common.HttpException('no body content required', _common.HttpStatus.UNSUPPORTED_MEDIA_TYPE);
}
req.dav.depth = _webdav.DEPTH.RESOURCE;
this.parseIfHeader(req);
return true;
}
copyMoveMethod(req, isMove = false) {
if (!req.headers[_webdav.HEADER.DESTINATION]) {
throw new _common.HttpException(`Missing ${_webdav.HEADER.DESTINATION} header`, _common.HttpStatus.BAD_REQUEST);
}
const destination = (0, _shared.decodeUrl)((0, _functions.urlToPath)(req.headers[_webdav.HEADER.DESTINATION]));
if (!_webdav.REGEX_BASE_PATH.test(destination)) {
this.logger.warn(`The destination does not match the webdav base path : ${destination}`);
throw new _common.HttpException('The destination does not match', _common.HttpStatus.BAD_REQUEST);
}
req.dav.depth = (req.headers[_webdav.HEADER.DEPTH] || _webdav.DEPTH.INFINITY).toLowerCase();
req.dav.copyMove = {
overwrite: (req.headers[_webdav.HEADER.OVERWRITE] || 't').toLowerCase() === 't',
destination: destination,
isMove: isMove
};
this.parseIfHeader(req);
return true;
}
constructor(){
this.logger = new _common.Logger(WebDAVProtocolGuard.name);
}
};
WebDAVProtocolGuard = _ts_decorate([
(0, _common.Injectable)()
], WebDAVProtocolGuard);
//# sourceMappingURL=webdav-protocol.guard.js.map