UNPKG

@sync-in/server

Version:

The secure, open-source platform for file storage, sharing, collaboration, and sync

259 lines (258 loc) 12.2 kB
/* * 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